mirror of
https://github.com/d4rken-org/capod.git
synced 2026-09-14 18:26:11 -04:00
Compare commits
183
Commits
v4.0.2-rc1
...
v5.1.1-rc0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
70d9432b2b | ||
|
|
9d06839a1c | ||
|
|
97f294e5f7 | ||
|
|
a40599c1dc | ||
|
|
175aef9e2f | ||
|
|
36ae35918a | ||
|
|
60ad1943b6 | ||
|
|
5e727e907d | ||
|
|
5360f54ea8 | ||
|
|
486efe6e65 | ||
|
|
c2ff1f111e | ||
|
|
26b72726a7 | ||
|
|
f35fe968c6 | ||
|
|
498f040a57 | ||
|
|
59b3106c3b | ||
|
|
89e8b83be4 | ||
|
|
f0531796dd | ||
|
|
a701fdfba7 | ||
|
|
118eaa8d08 | ||
|
|
016914ec05 | ||
|
|
0aadec0b75 | ||
|
|
e6cf507937 | ||
|
|
f27d84fec3 | ||
|
|
7632ba9e0a | ||
|
|
fd20ef0500 | ||
|
|
250d0ab154 | ||
|
|
79886e0bfb | ||
|
|
710d443074 | ||
|
|
86b685f28b | ||
|
|
bba9155573 | ||
|
|
c19b0b35b6 | ||
|
|
ec087e4b07 | ||
|
|
e7c280cb52 | ||
|
|
8a3a6cade5 | ||
|
|
77486fcd48 | ||
|
|
f4ec4630e4 | ||
|
|
384d81be18 | ||
|
|
07b3b95270 | ||
|
|
5b187c6d5b | ||
|
|
5ee094da3c | ||
|
|
77ea31216f | ||
|
|
74e48e79eb | ||
|
|
f141ce3f2a | ||
|
|
14da81d231 | ||
|
|
789c23d24c | ||
|
|
4ba59636c0 | ||
|
|
e5821de4dd | ||
|
|
59b4c404aa | ||
|
|
0d566a5aec | ||
|
|
35c06d9982 | ||
|
|
3ad732ff09 | ||
|
|
32ff7e5bdd | ||
|
|
fd6a9667e2 | ||
|
|
c5d9bed5f0 | ||
|
|
90af17bf7d | ||
|
|
6f90687b72 | ||
|
|
f17b6ca99f | ||
|
|
397b32b425 | ||
|
|
4b80ac7336 | ||
|
|
5d5e6670d4 | ||
|
|
da64406638 | ||
|
|
77b8992355 | ||
|
|
f3b540987d | ||
|
|
1a0bfbb3f4 | ||
|
|
02fd83440b | ||
|
|
7f82119001 | ||
|
|
8229163f70 | ||
|
|
6d313c077d | ||
|
|
60970a2a0c | ||
|
|
c28502fc8a | ||
|
|
2f3cae2b2c | ||
|
|
566a6cd120 | ||
|
|
7d08a44a19 | ||
|
|
dae12e588f | ||
|
|
6d5c5d29cc | ||
|
|
bac970fa61 | ||
|
|
6e8439e2f4 | ||
|
|
df0fcede6f | ||
|
|
d4a355c356 | ||
|
|
200e32f67a | ||
|
|
c0060ce5ca | ||
|
|
34c23751b3 | ||
|
|
04c23ecf63 | ||
|
|
b1aee5ee83 | ||
|
|
2a8416d146 | ||
|
|
1f7bc7cfd8 | ||
|
|
46fcf5a4cb | ||
|
|
a4e7820e6f | ||
|
|
e6e66c3831 | ||
|
|
71ba531025 | ||
|
|
6825abaa41 | ||
|
|
a4e1a3ba2e | ||
|
|
1cd8ae5ace | ||
|
|
c1601c105c | ||
|
|
b8f342f096 | ||
|
|
a6f292f737 | ||
|
|
a37b9b0213 | ||
|
|
65a74e9bc1 | ||
|
|
609423b27d | ||
|
|
067eb2e9e0 | ||
|
|
29df90628f | ||
|
|
70d9c047dc | ||
|
|
7f2b6976ce | ||
|
|
37e2593d8a | ||
|
|
30163a3f69 | ||
|
|
ed6c939284 | ||
|
|
a51b24f284 | ||
|
|
09c0692205 | ||
|
|
4174072dbf | ||
|
|
aeaf92d7e6 | ||
|
|
a650405c2d | ||
|
|
7883dcbafb | ||
|
|
300e6b8c49 | ||
|
|
33b6fbd0e0 | ||
|
|
f2bf39bda1 | ||
|
|
87d366bcb7 | ||
|
|
df5732569f | ||
|
|
56e01cfde9 | ||
|
|
ed37e142ad | ||
|
|
cb915d6263 | ||
|
|
41245d928c | ||
|
|
ccd95a0981 | ||
|
|
2d38db5cba | ||
|
|
175f8e36b5 | ||
|
|
a102b03261 | ||
|
|
57fc4d8e7b | ||
|
|
f67336d4fe | ||
|
|
a511c57b7d | ||
|
|
66e3ab1670 | ||
|
|
7e73ac80e9 | ||
|
|
1ae9ae587e | ||
|
|
4631aa8bb2 | ||
|
|
33c8fe76a6 | ||
|
|
e356344ba6 | ||
|
|
e014ad8045 | ||
|
|
9d446938bd | ||
|
|
fea1092a95 | ||
|
|
adfedc1cce | ||
|
|
c609b03148 | ||
|
|
927a198d8e | ||
|
|
1f5f406b33 | ||
|
|
4a475419e8 | ||
|
|
7c37854076 | ||
|
|
30d7efeec2 | ||
|
|
942e03af36 | ||
|
|
24a84a49f1 | ||
|
|
e3693628c2 | ||
|
|
94156b6200 | ||
|
|
5ae15d828d | ||
|
|
360067b07e | ||
|
|
e91243e577 | ||
|
|
19d61ca5cb | ||
|
|
7b403defab | ||
|
|
ceb4108306 | ||
|
|
fe4ac306cf | ||
|
|
618229b96b | ||
|
|
1931e60b07 | ||
|
|
7bdc9ca212 | ||
|
|
04ea3d0cd8 | ||
|
|
fef6c97d2d | ||
|
|
dc2e318374 | ||
|
|
71638c13ab | ||
|
|
bd5c6bf858 | ||
|
|
70e5b99e1d | ||
|
|
f65c6e4a29 | ||
|
|
c860ecc430 | ||
|
|
a8593eecd3 | ||
|
|
94e28fb0b5 | ||
|
|
4808e04146 | ||
|
|
aebf43c355 | ||
|
|
c1cd99f701 | ||
|
|
3fa01bb510 | ||
|
|
e84f8762b4 | ||
|
|
ac21261d4d | ||
|
|
c19ee88ae6 | ||
|
|
f751cdb37e | ||
|
|
28cd3b1340 | ||
|
|
3e2852e622 | ||
|
|
b919d8d19b | ||
|
|
9d6ec2b6bb | ||
|
|
9a85b23d91 | ||
|
|
217d9be177 | ||
|
|
53447f3fb9 |
+2
-1
@@ -1 +1,2 @@
|
||||
worktrees/
|
||||
worktrees/
|
||||
scheduled_tasks.lock
|
||||
+4
-8
@@ -4,10 +4,7 @@ Android app that detects and monitors AirPods via Bluetooth LE. Displays battery
|
||||
|
||||
## Project Structure
|
||||
|
||||
| Module | Description |
|
||||
|--------|-------------|
|
||||
| `app/` | Main Android app (FOSS and Google Play flavors) |
|
||||
| `app-common/` | Shared code between phone and Wear OS apps |
|
||||
Single Gradle module `app/` with multiple source sets (`main`, `foss`, `gplay`, `debug`, `test`, `testFoss`, `testGplay`, `screenshotTest`). A previous `app-common/` module was merged into `app/`.
|
||||
|
||||
## Build Flavors
|
||||
|
||||
@@ -20,10 +17,10 @@ Quick build check: `./gradlew assembleFossDebug`
|
||||
|
||||
| Path | Contains |
|
||||
|------|----------|
|
||||
| `app/src/main/java/` | Main app source (activities, fragments, services) |
|
||||
| `app-common/src/main/java/` | Shared logic (monitor, bluetooth, models) |
|
||||
| `app/src/main/java/` | Main app source (Compose screens, services, receivers, monitor, bluetooth, models) |
|
||||
| `app/src/foss/java/`, `app/src/gplay/java/` | Flavor-specific code (e.g. upgrade/billing) |
|
||||
| `app/src/main/res/` | Layouts, drawables, strings |
|
||||
| `app-common/src/test/` | Unit tests |
|
||||
| `app/src/test/`, `app/src/testFoss/`, `app/src/testGplay/` | Unit tests (shared + flavor-specific) |
|
||||
| `app/build.gradle.kts` | App build config, dependencies, flavors |
|
||||
| `app/src/debug/java/.../screenshots/` | Play Store screenshot content composables |
|
||||
| `fastlane/` | Screenshot generation scripts, Play Store metadata |
|
||||
@@ -31,7 +28,6 @@ Quick build check: `./gradlew assembleFossDebug`
|
||||
## Development Tips
|
||||
|
||||
- Use `assembleFossDebug` as the fastest build variant for iteration
|
||||
- Shared code goes in `app-common/`, app-specific code in `app/`
|
||||
- Follow existing patterns — the codebase uses MVVM + Hilt + Coroutines
|
||||
- Always use string resources for user-facing text (see localization rules)
|
||||
- Check `git log --oneline -20` for commit message style before committing
|
||||
|
||||
@@ -2,16 +2,22 @@
|
||||
description: Architecture overview, module structure, key components, data flow, and dependencies
|
||||
globs:
|
||||
- "app/**/*.kt"
|
||||
- "app-common/**/*.kt"
|
||||
- "**/*.gradle.kts"
|
||||
---
|
||||
|
||||
# Architecture
|
||||
|
||||
## Multi-Module Structure
|
||||
## Single-Module Structure
|
||||
|
||||
- **app/**: Main Android application with FOSS and Google Play flavors
|
||||
- **app-common/**: Shared code between main app and Wear OS app
|
||||
One Gradle module: `app/`. Source sets:
|
||||
|
||||
- `main` — shared code (Compose UI, services, monitor, bluetooth, AAP protocol, widgets)
|
||||
- `foss` / `gplay` — flavor-specific code (e.g. upgrade/billing implementations)
|
||||
- `debug` — debug-only code including screenshot content composables
|
||||
- `test` / `testFoss` / `testGplay` — unit tests
|
||||
- `screenshotTest` — Compose Preview Screenshot tests for Play Store assets
|
||||
|
||||
A previous `app-common/` module was merged into `app/` (commit `be8f4919`).
|
||||
|
||||
## Core Patterns
|
||||
|
||||
@@ -22,18 +28,62 @@ globs:
|
||||
|
||||
## Key Components
|
||||
|
||||
### PodMonitor System
|
||||
### Device Monitoring
|
||||
|
||||
- `PodMonitor`: Core service that detects and tracks AirPods via Bluetooth LE
|
||||
- `MonitorControl`: Manages MonitorService lifecycle
|
||||
- `MonitorService`: Foreground service that continuously scans for AirPods
|
||||
- `BluetoothEventReceiver`: Handles system Bluetooth events
|
||||
`monitor/core/` is split into two data-source siblings that `DeviceMonitor` merges:
|
||||
|
||||
- `monitor/core/ble/BlePodMonitor` — passive BLE scanning; reads Apple advertisement beacons (battery, case state, in-ear, etc.). Works for any pod in range; no pairing required
|
||||
- `monitor/core/aap/` — AAP connection lifecycle layer on top of `AapConnectionManager`:
|
||||
- `AapLifecycleManager` — starts/stops the AAP subsystem
|
||||
- `AapAutoConnect` — auto-opens AAP sessions for bonded/known devices
|
||||
- `AapKeyPersister`, `AapLearnedSettingsPersister` — persist session keys and learned pod settings across app restarts
|
||||
- `StemConfigSender`, `StemPressReaction`, `AncGestureResolver` — push config and react to stem/HID events
|
||||
- `monitor/core/cache/DeviceStateCache` — persisted last-known state so profiles still show data when a device is out of range
|
||||
- `DeviceMonitor` — singleton that `combine`s `BlePodMonitor.devices + AapConnectionManager.allStates + DeviceStateCache + profiles` into unified `PodDevice` objects. ViewModels observe `DeviceMonitor.devices`; they do **not** reach into `BlePodMonitor` or the AAP layer directly
|
||||
- `MonitorControl` / `MonitorService` — foreground service lifecycle holding the scan awake
|
||||
- `BluetoothEventReceiver`, `BootCompletedReceiver` — system triggers that wake the service
|
||||
|
||||
**BLE vs AAP — what each path gives you:**
|
||||
|
||||
| | BLE (advertisements) | AAP (L2CAP session) |
|
||||
|---|---|---|
|
||||
| Direction | Read-only, passive | Bidirectional commands + events |
|
||||
| Prerequisite | Bluetooth on | Bonded + `BLUETOOTH_CONNECT` + active L2CAP socket |
|
||||
| Data | Battery, case open, in-ear, pod model | Settings, ANC mode control, press controls, stem events, device info |
|
||||
| Availability | Any pod in range | Only your own paired pods |
|
||||
|
||||
### Reaction System
|
||||
|
||||
- `ReactionSettingsFragment`: Configuration for popup notifications
|
||||
- `ReactionsCard`: Compose UI for reaction settings, embedded in the device settings screen
|
||||
- `PopUpWindow`: Displays AirPods status when case is opened
|
||||
- `PopUpPodViewFactory`: Creates UI components for different pod models
|
||||
- `PopUpContent`: Compose pod rendering — model-specific UI branches inline, no factory class
|
||||
|
||||
### Widget System (Glance)
|
||||
|
||||
- `BatteryGlanceWidget`, `AncGlanceWidget`: Jetpack Glance-based home-screen widgets
|
||||
- `WidgetConfigurationActivity`: Configuration UI launched on widget placement
|
||||
- Lives under `app/src/main/java/eu/darken/capod/main/ui/widget/`
|
||||
|
||||
### Upgrade / Pro Features
|
||||
|
||||
- `UpgradeRepo` interface with two flavor implementations:
|
||||
- `UpgradeRepoGplay` — billing-client backed, includes grace-period handling for interrupted purchases
|
||||
- `UpgradeControlFoss` — cache/sponsor-backed; users are `isPro = false` until they call `upgrade()`, after which the pro flag is persisted via DataStore
|
||||
- FOSS is **not** "always pro" — it's opt-in via a local sponsor flow
|
||||
|
||||
### AAP (Apple Accessory Protocol) Stack
|
||||
|
||||
Three-layer structure under `pods/core/apple/aap/`:
|
||||
|
||||
- **`protocol/`** — pure data: `AapMessage`, `AapCommand`, `AapSetting`, `AapDeviceProfile`, `AapDeviceInfo`, `StemPressEvent`, `KeyExchangeResult`. Plus `DefaultAapDeviceProfile` and `Model.Features` capturing per-model capability
|
||||
- **`engine/`** — session state machine for one connection:
|
||||
- `AapConnection` — the L2CAP socket wrapper
|
||||
- `AapSessionEngine` — drives the session lifecycle; tested in `AapSessionEngineTest`
|
||||
- `AapInboundInterpreter` / `AapOutboundController` — decode incoming messages, encode outgoing
|
||||
- `AapSettingsCoordinator`, `AapAncController`, `HidTracker`, `AapDeviceInfoDiagnostics` — feature-specific coordinators that sit on top of the session
|
||||
- **`AapConnectionManager`** (singleton) — owns all open AAP sessions keyed by `BluetoothAddress`, uses `L2capSocketFactory` to create sockets. Consumers don't touch `AapConnection` directly — they call `sendCommand(...)` and observe `allStates`
|
||||
|
||||
The monitor-layer glue (`monitor/core/aap/`) described above wires this stack into the foreground service and persists its learned state.
|
||||
|
||||
### Common Utilities
|
||||
|
||||
@@ -54,31 +104,20 @@ globs:
|
||||
|
||||
## Data Flow
|
||||
|
||||
The app follows a unidirectional data flow:
|
||||
|
||||
1. `BluetoothEventReceiver` detects Bluetooth events
|
||||
2. `MonitorService` scans for AirPods beacon data
|
||||
3. `PodMonitor` processes and stores device information
|
||||
4. ViewModels observe monitor data via repositories
|
||||
5. UI components react to ViewModel state changes
|
||||
6. `ReactionSystem` triggers popups and notifications
|
||||
|
||||
## Bluetooth LE Implementation
|
||||
|
||||
The app uses Android's Bluetooth LE APIs to scan for Apple device advertisements. The core scanning logic is in `MonitorService` which runs as a foreground service.
|
||||
|
||||
## Multi-Platform Considerations
|
||||
|
||||
Code shared between phone and Wear OS apps is placed in `app-common`. When modifying shared functionality, ensure compatibility across both platforms.
|
||||
1. `BluetoothEventReceiver` / `BootCompletedReceiver` wake `MonitorService` (foreground)
|
||||
2. `MonitorService` keeps `BlePodMonitor` scanning (passive advertisements) and `AapLifecycleManager` running (active L2CAP sessions via `AapConnectionManager`)
|
||||
3. `DeviceMonitor` merges BLE + AAP + cached state + profiles into `PodDevice` objects
|
||||
4. ViewModels (`OverviewViewModel`, `DeviceSettingsViewModel`, `PressControlsViewModel`, widget view models) observe `DeviceMonitor.devices`; settings/command changes are sent back through `AapConnectionManager.sendCommand(...)`
|
||||
5. Reaction triggers (case-open popup, auto-play, notifications) and widget state updates react to the merged flow
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
- **Unit Tests**: Located in `app-common/src/test/` for shared logic
|
||||
- **Test Flavors**: Separate test configurations for FOSS and Google Play variants
|
||||
- **Unit Tests**: `app/src/test/` (shared), `app/src/testFoss/`, `app/src/testGplay/` (flavor-specific — e.g. `UpgradeRepoGplayTest`, `FossUpgradeSerializationTest`)
|
||||
- **Screenshot Tests**: `app/src/screenshotTest/` — Compose Preview Screenshot Testing, powers the Play Store screenshot pipeline
|
||||
|
||||
## Key Dependencies
|
||||
|
||||
- **Hilt**: Dependency injection framework
|
||||
- **AndroidX Navigation**: Fragment navigation with SafeArgs
|
||||
- **Navigation**: Navigation3 (`addNavigation3()`) drives current Compose screen routing. Some legacy `androidx.navigation` helpers still exist (`NavDirectionsExtensions`, `ViewModel3`) — don't assume SafeArgs is fully gone
|
||||
- **kotlinx.serialization**: JSON serialization for configuration and caching
|
||||
- **Material Design**: UI components following Material Design guidelines
|
||||
- **Material Design 3**: Compose Material3 UI components
|
||||
|
||||
@@ -21,6 +21,7 @@ Use the existing commit history as reference. Common prefixes:
|
||||
- **fix**: Bug fixes (e.g., `fix: Handle display cutouts in landscape mode`)
|
||||
- **feat**: New features
|
||||
- **refactor**: Code restructuring without behavior change
|
||||
- **ui**: Visual/layout-only tweaks that aren't a full feature or refactor (e.g., `ui(overview): ...`)
|
||||
- **chore**: Maintenance, dependency updates, build config
|
||||
- **docs**: Documentation changes
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ globs:
|
||||
When adding new user-facing strings:
|
||||
|
||||
- **Always use string resources**: Never hardcode user-facing text in layouts or code
|
||||
- **Follow naming conventions**: Use descriptive, hierarchical naming (e.g., `profiles_name_default`, `settings_bluetooth_enabled`)
|
||||
- **Follow naming conventions**: Use descriptive, hierarchical naming (e.g., `profiles_name_default`, `settings_monitor_mode_label`)
|
||||
- **Provide context**: String names should indicate usage and location
|
||||
- **Consider pluralization**: Use Android plural resources (`<plurals>`) when quantities vary
|
||||
|
||||
@@ -17,5 +17,7 @@ When adding new user-facing strings:
|
||||
|
||||
- `profiles_create_title` (screen title)
|
||||
- `profiles_name_label` (form field label)
|
||||
- `profiles_delete_confirmation` (dialog message)
|
||||
- `error_network_unavailable` (error message)
|
||||
- `profiles_name_default` (default value)
|
||||
- `troubleshooter_ble_result_failure_title` (status/error title)
|
||||
|
||||
Common prefixes currently in use: `device_`, `settings_`, `support_`, `profiles_`, `press_`, `general_`, `pods_`, `upgrade_`, `widget_`, `debug_`, `permission_`, `troubleshooter_`, `overview_`, `anc_`, `onboarding_`. There is no `error_*` prefix — error labels live under the relevant feature (e.g. `general_error_label`, `troubleshooter_*_failure_*`).
|
||||
|
||||
@@ -40,6 +40,18 @@ PR titles appear in auto-generated changelogs and are read by users. Use **ELI5,
|
||||
|
||||
## PR Description Format
|
||||
|
||||
PRs are reviewed in **GitHub's web UI**, which already shows the file tree, the diff, and the tests. Don't duplicate any
|
||||
of it. The description should answer questions the diff can't — not restate it.
|
||||
|
||||
Only these sections, in this order:
|
||||
|
||||
1. `## What changed`
|
||||
2. `## Technical Context`
|
||||
3. `## Review checklist` *(optional)*
|
||||
|
||||
No `Scope`, `Files changed`, `Tests`, or `Review guidance` sub-sections — GitHub shows the files and tests, and review
|
||||
notes belong in the checklist. Fold anything critical into a Technical Context bullet.
|
||||
|
||||
### What changed
|
||||
|
||||
User-friendly explanation of what this PR does. Describe the problem that was fixed or the feature that was added from the user's perspective. No internal class or method names.
|
||||
@@ -53,9 +65,18 @@ Explain what's hard to extract from the diff alone. Focus on:
|
||||
- **Why** this approach was chosen (and alternatives considered/rejected)
|
||||
- **Root cause** for bug fixes (the diff shows the fix, not what caused it)
|
||||
- **Non-obvious side effects** or behavioral changes not apparent from reading the code
|
||||
- **Review guidance** — what's tricky or deserves close attention
|
||||
|
||||
Keep it scannable with bullet points. Don't restate what's visible in the diff (file names, class renames, line-level changes).
|
||||
Format rules:
|
||||
|
||||
- **One bullet per point.** No prose paragraphs, no nested sub-headers like `**Bug 1** / **Bug 2**` — if a PR fixes
|
||||
multiple bugs, one bullet per bug is enough.
|
||||
- **Don't restate the diff.** File paths, class renames, test names, and line-level changes are all visible in the web
|
||||
UI.
|
||||
|
||||
### Review checklist (optional)
|
||||
|
||||
For PRs with multiple non-trivial review points, add a `## Review checklist` section with `- [ ]` tasks the reviewer can
|
||||
tick off as they verify. Skip it for small PRs — a single tricky thing can stay as a Technical Context bullet.
|
||||
|
||||
### Example
|
||||
|
||||
|
||||
@@ -19,16 +19,17 @@ ScreenshotContent.kt (mock data + composables)
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `app/src/debug/java/.../screenshots/ScreenshotContent.kt` | Mock data composables for each screen (8 screens) |
|
||||
| `app/src/screenshotTest/kotlin/.../screenshots/PlayStoreScreenshots.kt` | `@PreviewTest` functions that wire content to locale annotations |
|
||||
| `app/src/debug/java/.../screenshots/ScreenshotContent.kt` | Mock data composables for each screen (7 screens) |
|
||||
| `app/src/screenshotTest/kotlin/.../screenshots/PlayStoreScreenshots.kt` | `@PreviewTest` functions (currently: `DashboardLight`, `DashboardDark`, `CasePopUp`, `DeviceProfiles`, `AddProfile`, `DeviceSettingsReactions`, `WidgetConfiguration`) |
|
||||
| `app/src/screenshotTest/kotlin/.../screenshots/PlayStoreLocales.kt` | Multi-preview annotations (auto-generated by batch script) |
|
||||
| `fastlane/generate_screenshots.sh` | Batched generation across 68 locales |
|
||||
| `fastlane/generate_screenshots.sh` | Batched generation; locale list (`ALL_LOCALES`) and `BATCH_SIZE` are defined inside the script |
|
||||
| `fastlane/copy_screenshots.sh` | Copies rendered PNGs into fastlane structure |
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
# Full run — all 68 locales, ~12 batches, ~7 minutes
|
||||
# Full run — iterates over ALL_LOCALES in batches of BATCH_SIZE.
|
||||
# The script prints "Locales: N | Batch size: B | Batches: ceil(N/B)" at startup.
|
||||
./fastlane/generate_screenshots.sh
|
||||
|
||||
# Smoke test — 6 locales (en, de, ja, ar, zh-CN, pt-BR), single batch
|
||||
@@ -37,7 +38,7 @@ ScreenshotContent.kt (mock data + composables)
|
||||
# Copy into fastlane directories (run after generate)
|
||||
./fastlane/copy_screenshots.sh
|
||||
|
||||
# Clean copy (removes old screenshots first)
|
||||
# Clean copy (removes old screenshots first) — REQUIRED when screens are removed or renamed
|
||||
./fastlane/copy_screenshots.sh --clean
|
||||
```
|
||||
|
||||
@@ -49,6 +50,12 @@ ScreenshotContent.kt (mock data + composables)
|
||||
4. Update the expected count in `generate_screenshots.sh` (composables per locale)
|
||||
5. Run the full pipeline: `generate_screenshots.sh` then `copy_screenshots.sh`
|
||||
|
||||
## Removing or Renaming a Screenshot
|
||||
|
||||
1. Remove the `@PreviewTest` entry and its `SCREEN_MAP` mapping
|
||||
2. Run `generate_screenshots.sh`
|
||||
3. Run `copy_screenshots.sh --clean` — **`--clean` is required** here; without it, old files (e.g. a renamed `8_reaction_settings.png`) stay in `fastlane/metadata/android/*/images/phoneScreenshots/` and get uploaded to Play Store
|
||||
|
||||
## After UI Changes
|
||||
|
||||
When modifying a screen that appears in screenshots (check `ScreenshotContent.kt`), regenerate:
|
||||
@@ -60,7 +67,7 @@ When modifying a screen that appears in screenshots (check `ScreenshotContent.kt
|
||||
|
||||
## Technical Notes
|
||||
|
||||
- Batch size defaults to 2 locales (16 renders) to avoid layoutlib memory leak (~10MB/image)
|
||||
- Batch size defaults to 2 locales; renders per batch = `BATCH_SIZE × screen count` (currently 2 × 7 = 14). Small batches avoid layoutlib memory leaks (~10MB/image)
|
||||
- Gradle daemon is stopped between batches to release memory
|
||||
- `PlayStoreLocales.kt` is temporarily rewritten per batch and restored via trap
|
||||
- Device spec: 1080x2400px @ 428 DPI (Pixel-class phone)
|
||||
|
||||
+12
-11
@@ -1,16 +1,17 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"mcp__ide__getDiagnostics",
|
||||
"WebSearch"
|
||||
],
|
||||
"deny": []
|
||||
"allow": [
|
||||
"mcp__ide__getDiagnostics",
|
||||
"WebSearch"
|
||||
],
|
||||
"deny": []
|
||||
},
|
||||
"enabledPlugins": {
|
||||
"android-translation@claude-code-cafe": true,
|
||||
"debugbadger@claude-code-cafe": true,
|
||||
"jvm-tools@claude-code-cafe": true,
|
||||
"frontend-design@claude-plugins-official": true,
|
||||
"kotlin-lsp@claude-plugins-official": true
|
||||
"android-translation@claude-code-cafe": true,
|
||||
"debugbadger@claude-code-cafe": true,
|
||||
"jvm-tools@claude-code-cafe": true,
|
||||
"frontend-design@claude-plugins-official": true,
|
||||
"kotlin-lsp@claude-plugins-official": true,
|
||||
"support-investigator@claude-code-cafe": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,9 @@ on:
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
lint-vital:
|
||||
name: Lint vitals
|
||||
@@ -19,6 +22,8 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Setup project and build environment
|
||||
uses: ./.github/actions/common-setup
|
||||
|
||||
@@ -37,6 +42,8 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Setup project and build environment
|
||||
uses: ./.github/actions/common-setup
|
||||
|
||||
@@ -54,6 +61,8 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Setup project and build environment
|
||||
uses: ./.github/actions/common-setup
|
||||
|
||||
@@ -66,5 +75,7 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Validate metadata lengths
|
||||
run: bash fastlane/check_metadata_length.sh
|
||||
@@ -8,10 +8,15 @@ on:
|
||||
branches:
|
||||
- main
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
validation:
|
||||
name: "Validation"
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: gradle/actions/wrapper-validation@0723195856401067f7a2779048b490ace7a47d7c #v5.0.2
|
||||
|
||||
@@ -11,6 +11,9 @@ on:
|
||||
type: boolean
|
||||
default: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
release-github:
|
||||
name: Create GitHub release
|
||||
@@ -33,6 +36,7 @@ jobs:
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup project and build environment
|
||||
uses: ./.github/actions/common-setup
|
||||
@@ -108,6 +112,7 @@ jobs:
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup project and build environment
|
||||
uses: ./.github/actions/common-setup
|
||||
|
||||
+3
-1
@@ -16,4 +16,6 @@
|
||||
/fastlane/README.md
|
||||
.kotlin
|
||||
# Screenshot test reference images (ephemeral, regenerated on demand)
|
||||
app/src/screenshotTest*/reference/
|
||||
app/src/screenshotTest*/reference/
|
||||
.codex
|
||||
protocol-research/
|
||||
@@ -1,4 +1,4 @@
|
||||
<img src="https://github.com/d4rken-org/capod/raw/main/.assets/banner.png" width="400">
|
||||
<img src="https://github.com/d4rken-org/capod/raw/main/.assets/banner.png" width="400" alt="CAPod banner">
|
||||
|
||||
# Companion App for AirPods (CAPod)
|
||||
|
||||
@@ -34,11 +34,20 @@ Currently supported models:
|
||||
* AirPods Pro 2. Generation (USB-C)
|
||||
* AirPods Pro 3. Generation
|
||||
* AirPods Max
|
||||
* AirPods Max (USB-C)
|
||||
* AirPods Max 2. Generation
|
||||
* Power Beats Pro
|
||||
* Power Beats Pro 2
|
||||
* Power Beats 3
|
||||
* Power Beats 4
|
||||
* Beats Solo 3
|
||||
* Beats Solo Pro
|
||||
* Beats Solo 4
|
||||
* Beats Solo Buds
|
||||
* Beats Studio 3
|
||||
* Beats Studio Buds
|
||||
* Beats Studio Buds+
|
||||
* Beats Studio Pro
|
||||
* Beats X
|
||||
* Beats Flex
|
||||
* Beats Fit Pro
|
||||
@@ -87,6 +96,7 @@ Currently supported models:
|
||||
check it out.
|
||||
* [@kavishdevar](https://github.com/kavishdevar/librepods) and
|
||||
his [LibrePods project](https://github.com/kavishdevar/librepods) for sharing a lot of cool stuff.
|
||||
* [Crowdin](https://crowdin.com/) for supporting open-source projects.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<application>
|
||||
<activity
|
||||
android:name="eu.darken.capod.debug.l2cap.L2capPocActivity"
|
||||
android:exported="true"
|
||||
android:label="L2CAP PoC">
|
||||
<intent-filter>
|
||||
<action android:name="eu.darken.capod.L2CAP_POC" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,256 @@
|
||||
package eu.darken.capod.debug.l2cap
|
||||
|
||||
import eu.darken.capod.common.bluetooth.l2cap.L2capSocketFactory
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.bluetooth.BluetoothAdapter
|
||||
import android.bluetooth.BluetoothDevice
|
||||
import android.bluetooth.BluetoothManager
|
||||
import android.bluetooth.BluetoothSocket
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
||||
class L2capPocActivity : ComponentActivity() {
|
||||
|
||||
companion object {
|
||||
private const val TAG = "L2capPoc"
|
||||
private const val PSM = 0x1001
|
||||
|
||||
private val HANDSHAKE = byteArrayOf(
|
||||
0x00, 0x00, 0x04, 0x00, 0x01, 0x00, 0x02, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||
)
|
||||
private val ANC_OFF = byteArrayOf(
|
||||
0x04, 0x00, 0x04, 0x00, 0x09, 0x00, 0x0d, 0x01, 0x00, 0x00, 0x00
|
||||
)
|
||||
private val ANC_ON = byteArrayOf(
|
||||
0x04, 0x00, 0x04, 0x00, 0x09, 0x00, 0x0d, 0x02, 0x00, 0x00, 0x00
|
||||
)
|
||||
private val TRANSPARENCY = byteArrayOf(
|
||||
0x04, 0x00, 0x04, 0x00, 0x09, 0x00, 0x0d, 0x03, 0x00, 0x00, 0x00
|
||||
)
|
||||
}
|
||||
|
||||
private val permissionLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.RequestPermission()
|
||||
) { granted ->
|
||||
if (granted) Log.d(TAG, "BLUETOOTH_CONNECT granted")
|
||||
else Log.w(TAG, "BLUETOOTH_CONNECT denied")
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S &&
|
||||
checkSelfPermission(Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
permissionLauncher.launch(Manifest.permission.BLUETOOTH_CONNECT)
|
||||
}
|
||||
|
||||
setContent {
|
||||
MaterialTheme {
|
||||
Surface(modifier = Modifier.fillMaxSize()) {
|
||||
PocScreen()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@SuppressLint("MissingPermission")
|
||||
@Composable
|
||||
private fun PocScreen() {
|
||||
val btManager = remember { getSystemService(BluetoothManager::class.java) }
|
||||
val adapter: BluetoothAdapter? = remember { btManager?.adapter }
|
||||
val logEntries = remember { mutableStateListOf<String>() }
|
||||
val logListState = rememberLazyListState()
|
||||
var selectedDevice by remember { mutableStateOf<BluetoothDevice?>(null) }
|
||||
var socket by remember { mutableStateOf<BluetoothSocket?>(null) }
|
||||
var outputStream by remember { mutableStateOf<OutputStream?>(null) }
|
||||
var connected by remember { mutableStateOf(false) }
|
||||
val scope = rememberCoroutineScope()
|
||||
var readerJob by remember { mutableStateOf<Job?>(null) }
|
||||
|
||||
val timeFmt = remember { SimpleDateFormat("HH:mm:ss.SSS", Locale.US) }
|
||||
|
||||
fun log(msg: String) {
|
||||
val line = "${timeFmt.format(Date())} $msg"
|
||||
Log.d(TAG, msg)
|
||||
logEntries.add(line)
|
||||
}
|
||||
|
||||
fun ByteArray.hex(): String = joinToString(" ") { "%02X".format(it) }
|
||||
|
||||
LaunchedEffect(logEntries.size) {
|
||||
if (logEntries.isNotEmpty()) logListState.animateScrollToItem(logEntries.size - 1)
|
||||
}
|
||||
|
||||
fun startReader(input: InputStream) {
|
||||
readerJob = scope.launch(Dispatchers.IO) {
|
||||
val buf = ByteArray(1024)
|
||||
try {
|
||||
while (isActive) {
|
||||
val len = input.read(buf)
|
||||
if (len == -1) {
|
||||
launch(Dispatchers.Main) { log("<<< Stream closed by remote") }
|
||||
break
|
||||
}
|
||||
val data = buf.copyOf(len)
|
||||
launch(Dispatchers.Main) { log("<<< [${data.size}] ${data.hex()}") }
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
launch(Dispatchers.Main) { log("<<< Read error: ${e.message}") }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column(modifier = Modifier.fillMaxSize().padding(16.dp)) {
|
||||
Text("L2CAP PoC — AAP Connection", style = MaterialTheme.typography.titleMedium)
|
||||
|
||||
if (adapter == null) {
|
||||
Text("No Bluetooth adapter found", color = MaterialTheme.colorScheme.error)
|
||||
return@Column
|
||||
}
|
||||
|
||||
// Bonded devices
|
||||
if (!connected) {
|
||||
Text("Bonded Devices:", style = MaterialTheme.typography.labelLarge, modifier = Modifier.padding(top = 8.dp))
|
||||
val bonded = remember(adapter) {
|
||||
try { adapter.bondedDevices?.toList() ?: emptyList() }
|
||||
catch (_: SecurityException) { emptyList() }
|
||||
}
|
||||
bonded.forEach { device ->
|
||||
val name = try { device.name ?: "Unknown" } catch (_: SecurityException) { "Unknown" }
|
||||
val isSelected = selectedDevice == device
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth().padding(vertical = 2.dp).clickable { selectedDevice = device },
|
||||
colors = if (isSelected) CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.primaryContainer) else CardDefaults.cardColors()
|
||||
) {
|
||||
Row(modifier = Modifier.padding(8.dp)) {
|
||||
Text("$name ${device.address}", style = MaterialTheme.typography.bodySmall)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Connection controls
|
||||
FlowRow(modifier = Modifier.padding(vertical = 8.dp), horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Button(
|
||||
onClick = {
|
||||
val dev = selectedDevice ?: return@Button
|
||||
log("=== Connecting to ${dev.address} ===")
|
||||
scope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
val sock = L2capSocketFactory().createSocket(dev, PSM)
|
||||
withContext(Dispatchers.Main) { log("Socket created, connecting...") }
|
||||
sock.connect()
|
||||
withContext(Dispatchers.Main) {
|
||||
log("Connected!")
|
||||
socket = sock
|
||||
outputStream = sock.outputStream
|
||||
connected = true
|
||||
}
|
||||
startReader(sock.inputStream)
|
||||
|
||||
withContext(Dispatchers.Main) { log(">>> Handshake [${HANDSHAKE.size}] ${HANDSHAKE.hex()}") }
|
||||
sock.outputStream.write(HANDSHAKE)
|
||||
sock.outputStream.flush()
|
||||
} catch (e: Exception) {
|
||||
withContext(Dispatchers.Main) { log("Failed: ${e::class.simpleName}: ${e.message}") }
|
||||
}
|
||||
}
|
||||
},
|
||||
enabled = selectedDevice != null && !connected
|
||||
) { Text("Connect") }
|
||||
|
||||
Button(
|
||||
onClick = {
|
||||
log("=== Disconnecting ===")
|
||||
readerJob?.cancel()
|
||||
readerJob = null
|
||||
try { socket?.close() } catch (_: Exception) {}
|
||||
socket = null
|
||||
outputStream = null
|
||||
connected = false
|
||||
},
|
||||
enabled = connected
|
||||
) { Text("Disconnect") }
|
||||
}
|
||||
|
||||
// AAP command buttons
|
||||
if (connected) {
|
||||
FlowRow(modifier = Modifier.padding(vertical = 4.dp), horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
fun sendCommand(name: String, cmd: ByteArray) {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
withContext(Dispatchers.Main) { log(">>> $name [${cmd.size}] ${cmd.hex()}") }
|
||||
outputStream?.write(cmd)
|
||||
outputStream?.flush()
|
||||
} catch (e: Exception) {
|
||||
withContext(Dispatchers.Main) { log(">>> Send error: ${e.message}") }
|
||||
}
|
||||
}
|
||||
}
|
||||
Button(onClick = { sendCommand("ANC Off", ANC_OFF) }) { Text("ANC Off") }
|
||||
Button(onClick = { sendCommand("ANC On", ANC_ON) }) { Text("ANC On") }
|
||||
Button(onClick = { sendCommand("Transparency", TRANSPARENCY) }) { Text("Transparency") }
|
||||
Button(onClick = { sendCommand("Handshake", HANDSHAKE) }) { Text("Handshake") }
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalDivider(modifier = Modifier.padding(vertical = 4.dp))
|
||||
|
||||
LazyColumn(state = logListState, modifier = Modifier.weight(1f)) {
|
||||
items(logEntries) { entry ->
|
||||
Text(text = entry, fontFamily = FontFamily.Monospace, fontSize = 11.sp, lineHeight = 14.sp)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,32 +11,28 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import eu.darken.capod.R
|
||||
import eu.darken.capod.common.compose.PreviewWrapper
|
||||
import eu.darken.capod.main.ui.widget.ComposeWidgetPreview
|
||||
import eu.darken.capod.main.ui.widget.WidgetRenderState
|
||||
import eu.darken.capod.reaction.ui.popup.PopUpContent as PopUpCard
|
||||
import eu.darken.capod.main.ui.widget.WidgetConfigurationScreen
|
||||
import eu.darken.capod.main.ui.widget.WidgetConfigurationViewModel
|
||||
import eu.darken.capod.main.ui.widget.WidgetTheme
|
||||
import eu.darken.capod.common.compose.preview.MOCK_NOW
|
||||
import eu.darken.capod.common.compose.preview.MockPodDataProvider
|
||||
import eu.darken.capod.common.theming.CapodTheme
|
||||
import eu.darken.capod.main.ui.overview.OverviewScreen
|
||||
import eu.darken.capod.main.ui.overview.OverviewViewModel
|
||||
import eu.darken.capod.main.ui.settings.SettingsScreen
|
||||
import eu.darken.capod.main.ui.settings.SettingsViewModel
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
import eu.darken.capod.main.ui.widget.ComposeWidgetPreview
|
||||
import eu.darken.capod.main.ui.widget.WidgetConfigurationScreen
|
||||
import eu.darken.capod.main.ui.widget.WidgetConfigurationViewModel
|
||||
import eu.darken.capod.main.ui.widget.WidgetRenderState
|
||||
import eu.darken.capod.main.ui.widget.WidgetTheme
|
||||
import eu.darken.capod.pods.core.apple.PodModel
|
||||
import eu.darken.capod.profiles.ui.DeviceManagerScreen
|
||||
import eu.darken.capod.profiles.ui.DeviceManagerViewModel
|
||||
import eu.darken.capod.profiles.ui.creation.DeviceProfileCreationScreen
|
||||
import eu.darken.capod.profiles.ui.creation.DeviceProfileCreationViewModel
|
||||
import eu.darken.capod.reaction.core.autoconnect.AutoConnectCondition
|
||||
import eu.darken.capod.reaction.ui.ReactionSettingsScreen
|
||||
import eu.darken.capod.reaction.ui.ReactionSettingsViewModel
|
||||
import eu.darken.capod.main.ui.devicesettings.DeviceSettingsScreen
|
||||
import eu.darken.capod.main.ui.devicesettings.DeviceSettingsViewModel
|
||||
import eu.darken.capod.reaction.ui.popup.PopUpContent as PopUpCard
|
||||
|
||||
internal const val DS = "spec:width=1080px,height=2400px,dpi=428"
|
||||
|
||||
@@ -47,20 +43,21 @@ internal fun DashboardContent() = PreviewWrapper {
|
||||
now = MOCK_NOW,
|
||||
permissions = emptySet(),
|
||||
devices = listOf(
|
||||
MockPodDataProvider.airPodsProMixed(),
|
||||
MockPodDataProvider.airPodsMax(),
|
||||
MockPodDataProvider.unknownDevice(),
|
||||
MockPodDataProvider.dualPodMonitoredMixed(),
|
||||
MockPodDataProvider.singlePodMonitored(),
|
||||
MockPodDataProvider.unknownMonitored(),
|
||||
),
|
||||
isDebugMode = false,
|
||||
isBluetoothEnabled = true,
|
||||
profiles = listOf(
|
||||
MockPodDataProvider.profile("My AirPods Pro", PodDevice.Model.AIRPODS_PRO2),
|
||||
MockPodDataProvider.profile("AirPods Max", PodDevice.Model.AIRPODS_MAX),
|
||||
MockPodDataProvider.profile("My AirPods Pro", PodModel.AIRPODS_PRO2),
|
||||
MockPodDataProvider.profile("AirPods Max", PodModel.AIRPODS_MAX),
|
||||
),
|
||||
upgradeInfo = MockPodDataProvider.gplayInfo(isPro = true),
|
||||
showUnmatchedDevices = false,
|
||||
),
|
||||
onRequestPermission = {},
|
||||
onBluetoothSettings = {},
|
||||
onManageDevices = {},
|
||||
onSettings = {},
|
||||
onUpgrade = {},
|
||||
@@ -73,9 +70,9 @@ internal fun DeviceProfilesContent() = PreviewWrapper {
|
||||
DeviceManagerScreen(
|
||||
state = DeviceManagerViewModel.State(
|
||||
profiles = listOf(
|
||||
MockPodDataProvider.profile("Work AirPods", PodDevice.Model.AIRPODS_PRO2),
|
||||
MockPodDataProvider.profile("AirPods Max", PodDevice.Model.AIRPODS_MAX),
|
||||
MockPodDataProvider.profile("Gym Beats", PodDevice.Model.POWERBEATS_PRO),
|
||||
MockPodDataProvider.profile("Work AirPods", PodModel.AIRPODS_PRO2),
|
||||
MockPodDataProvider.profile("AirPods Max", PodModel.AIRPODS_MAX),
|
||||
MockPodDataProvider.profile("Gym Beats", PodModel.POWERBEATS_PRO),
|
||||
),
|
||||
),
|
||||
onBack = {},
|
||||
@@ -92,11 +89,11 @@ internal fun AddProfileContent() = PreviewWrapper {
|
||||
name = "",
|
||||
nameError = null,
|
||||
selectedModel = null,
|
||||
availableModels = PodDevice.Model.entries.filter { it != PodDevice.Model.UNKNOWN },
|
||||
availableModels = PodModel.entries.filter { it != PodModel.UNKNOWN },
|
||||
identityKey = null,
|
||||
encryptionKey = null,
|
||||
selectedDevice = null,
|
||||
bondedDevices = emptyList(),
|
||||
bondedDeviceItems = emptyList(),
|
||||
minimumSignalQuality = 0.15f,
|
||||
canSave = false,
|
||||
),
|
||||
@@ -114,52 +111,24 @@ internal fun AddProfileContent() = PreviewWrapper {
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun SettingsIndexContent() = PreviewWrapper {
|
||||
SettingsScreen(
|
||||
state = SettingsViewModel.State(isPro = true, sponsorUrl = "https://example.com"),
|
||||
onNavigateUp = {},
|
||||
onGeneralSettings = {},
|
||||
onDeviceManager = {},
|
||||
onReactions = {},
|
||||
onSupport = {},
|
||||
onWiki = {},
|
||||
onChangelog = {},
|
||||
onHelpTranslate = {},
|
||||
onAcknowledgements = {},
|
||||
onPrivacyPolicy = {},
|
||||
onSponsor = {},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun ReactionSettingsContent() = PreviewWrapper {
|
||||
ReactionSettingsScreen(
|
||||
state = ReactionSettingsViewModel.State(
|
||||
internal fun DeviceSettingsReactionsContent() = PreviewWrapper {
|
||||
DeviceSettingsScreen(
|
||||
state = DeviceSettingsViewModel.State(
|
||||
device = MockPodDataProvider.dualPodMonitoredWithReactions(),
|
||||
now = MOCK_NOW,
|
||||
isPro = true,
|
||||
onePodMode = false,
|
||||
autoPlay = true,
|
||||
autoPause = true,
|
||||
autoConnect = false,
|
||||
autoConnectCondition = AutoConnectCondition.WHEN_SEEN,
|
||||
showPopUpOnCaseOpen = true,
|
||||
showPopUpOnConnection = false,
|
||||
isNudgeAvailable = true,
|
||||
isClassicallyConnected = true,
|
||||
),
|
||||
onNavigateUp = {},
|
||||
onOnePodModeChanged = {},
|
||||
onAutoPlayChanged = {},
|
||||
onAutoPauseChanged = {},
|
||||
onAutoConnectChanged = {},
|
||||
onAutoConnectConditionSelected = {},
|
||||
onShowPopUpOnCaseOpenChanged = {},
|
||||
onShowPopUpOnConnectionChanged = {},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun WidgetConfigurationContent() = PreviewWrapper {
|
||||
val profiles = listOf(
|
||||
MockPodDataProvider.profile("My AirPods Pro", PodDevice.Model.AIRPODS_PRO2),
|
||||
MockPodDataProvider.profile("AirPods Max", PodDevice.Model.AIRPODS_MAX),
|
||||
MockPodDataProvider.profile("My AirPods Pro", PodModel.AIRPODS_PRO2),
|
||||
MockPodDataProvider.profile("AirPods Max", PodModel.AIRPODS_MAX),
|
||||
)
|
||||
WidgetConfigurationScreen(
|
||||
state = WidgetConfigurationViewModel.State(
|
||||
@@ -201,7 +170,7 @@ internal fun CasePopUpContent() {
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
PopUpCard(
|
||||
device = MockPodDataProvider.airPodsProMixed(),
|
||||
device = MockPodDataProvider.dualPodMonitoredMixed(),
|
||||
onClose = {},
|
||||
modifier = Modifier.padding(horizontal = 32.dp),
|
||||
)
|
||||
@@ -261,14 +230,10 @@ private fun PreviewDeviceProfiles() = DeviceProfilesContent()
|
||||
@Composable
|
||||
private fun PreviewAddProfile() = AddProfileContent()
|
||||
|
||||
@Preview(name = "7 - Settings", locale = "en", device = DS, showSystemUi = true)
|
||||
@Preview(name = "7 - Device Settings Reactions", locale = "en", device = DS, showSystemUi = true)
|
||||
@Composable
|
||||
private fun PreviewSettingsIndex() = SettingsIndexContent()
|
||||
private fun PreviewDeviceSettingsReactions() = DeviceSettingsReactionsContent()
|
||||
|
||||
@Preview(name = "8 - Reaction Settings", locale = "en", device = DS, showSystemUi = true)
|
||||
@Composable
|
||||
private fun PreviewReactionSettings() = ReactionSettingsContent()
|
||||
|
||||
@Preview(name = "9 - Homescreen Widget", device = DS, showSystemUi = true)
|
||||
@Preview(name = "8 - Homescreen Widget", device = DS, showSystemUi = true)
|
||||
@Composable
|
||||
private fun PreviewHomescreenWidget() = HomescreenWidgetContent()
|
||||
|
||||
@@ -5,14 +5,11 @@ import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.systemBars
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
@@ -24,6 +21,8 @@ import androidx.compose.material.icons.twotone.BluetoothConnected
|
||||
import androidx.compose.material.icons.twotone.Favorite
|
||||
import androidx.compose.material.icons.twotone.Palette
|
||||
import androidx.compose.material.icons.twotone.PlayCircle
|
||||
import androidx.compose.material.icons.twotone.Headphones
|
||||
import androidx.compose.material.icons.twotone.Tune
|
||||
import androidx.compose.material.icons.twotone.Widgets
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Card
|
||||
@@ -50,6 +49,7 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
@@ -108,9 +108,11 @@ fun UpgradeScreen(
|
||||
val benefits = listOf(
|
||||
Benefit(Icons.TwoTone.Palette, R.string.upgrade_benefit_themes),
|
||||
Benefit(Icons.TwoTone.PlayCircle, R.string.upgrade_benefit_autoplay),
|
||||
Benefit(Icons.TwoTone.BluetoothConnected, R.string.upgrade_benefit_autoconnect),
|
||||
|
||||
Benefit(Icons.AutoMirrored.TwoTone.Message, R.string.upgrade_benefit_popups),
|
||||
Benefit(Icons.TwoTone.Widgets, R.string.upgrade_benefit_widgets),
|
||||
Benefit(Icons.TwoTone.Tune, R.string.upgrade_benefit_device_settings),
|
||||
Benefit(Icons.TwoTone.Headphones, R.string.upgrade_benefit_device_controls),
|
||||
Benefit(Icons.TwoTone.Favorite, R.string.upgrade_benefit_support),
|
||||
)
|
||||
|
||||
@@ -118,138 +120,144 @@ fun UpgradeScreen(
|
||||
snackbarHost = { SnackbarHost(snackbarHostState) },
|
||||
containerColor = MaterialTheme.colorScheme.surface,
|
||||
) { paddingValues ->
|
||||
Box(modifier = Modifier
|
||||
.windowInsetsPadding(WindowInsets.systemBars)
|
||||
.padding(paddingValues)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(horizontal = 24.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
Box(contentAlignment = Alignment.Center) {
|
||||
Surface(
|
||||
modifier = Modifier.size(120.dp),
|
||||
shape = CircleShape,
|
||||
color = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.5f),
|
||||
) {}
|
||||
Image(
|
||||
painter = painterResource(R.drawable.splash_graphic2),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(80.dp),
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
Text(
|
||||
text = buildAnnotatedString {
|
||||
append("CAPod ")
|
||||
withStyle(SpanStyle(color = colorResource(R.color.brand_secondary), fontWeight = FontWeight.Bold)) {
|
||||
append("FOSS")
|
||||
}
|
||||
},
|
||||
style = MaterialTheme.typography.headlineLarge,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
Card(
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = MaterialTheme.colorScheme.secondaryContainer,
|
||||
),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
Box(modifier = Modifier.padding(paddingValues)) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(horizontal = 24.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Spacer(modifier = Modifier.height(48.dp))
|
||||
|
||||
Box(contentAlignment = Alignment.Center) {
|
||||
Surface(
|
||||
modifier = Modifier.size(120.dp),
|
||||
shape = CircleShape,
|
||||
color = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.5f),
|
||||
) {}
|
||||
Image(
|
||||
painter = painterResource(R.drawable.splash_graphic2),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(80.dp),
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.upgrade_foss_preamble),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
modifier = Modifier.padding(16.dp),
|
||||
text = buildAnnotatedString {
|
||||
append("CAPod ")
|
||||
withStyle(SpanStyle(color = colorResource(R.color.brand_secondary), fontWeight = FontWeight.Bold)) {
|
||||
append("FOSS")
|
||||
}
|
||||
},
|
||||
style = MaterialTheme.typography.headlineLarge,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
Column(modifier = Modifier.padding(vertical = 4.dp)) {
|
||||
benefits.forEach { benefit ->
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 12.dp, vertical = 4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Surface(
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
color = MaterialTheme.colorScheme.secondaryContainer,
|
||||
modifier = Modifier.size(28.dp),
|
||||
Card(
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = MaterialTheme.colorScheme.secondaryContainer,
|
||||
),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.upgrade_foss_preamble),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
modifier = Modifier.padding(16.dp),
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
Column(modifier = Modifier.padding(vertical = 4.dp)) {
|
||||
benefits.forEach { benefit ->
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 12.dp, vertical = 4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Box(contentAlignment = Alignment.Center) {
|
||||
Icon(
|
||||
imageVector = benefit.icon,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.onSecondaryContainer,
|
||||
modifier = Modifier.size(16.dp),
|
||||
)
|
||||
Surface(
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
color = MaterialTheme.colorScheme.secondaryContainer,
|
||||
modifier = Modifier.size(28.dp),
|
||||
) {
|
||||
Box(contentAlignment = Alignment.Center) {
|
||||
Icon(
|
||||
imageVector = benefit.icon,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.onSecondaryContainer,
|
||||
modifier = Modifier.size(16.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.width(12.dp))
|
||||
Text(
|
||||
text = stringResource(benefit.textRes),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.width(12.dp))
|
||||
Text(
|
||||
text = stringResource(benefit.textRes),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.upgrade_benefit_disclaimer),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.padding(top = 8.dp),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
Button(
|
||||
onClick = onSponsor,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(52.dp),
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.TwoTone.Favorite,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(20.dp),
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text(
|
||||
text = stringResource(R.string.upgrade_foss_sponsor_action),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
)
|
||||
}
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.upgrade_foss_sponsor_subtitle),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(top = 8.dp),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
Button(
|
||||
onClick = onSponsor,
|
||||
IconButton(
|
||||
onClick = onNavigateUp,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(52.dp),
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
.align(Alignment.TopStart)
|
||||
.padding(4.dp),
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.TwoTone.Favorite,
|
||||
imageVector = Icons.AutoMirrored.TwoTone.ArrowBack,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(20.dp),
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text(
|
||||
text = stringResource(R.string.upgrade_foss_sponsor_action),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
)
|
||||
}
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.upgrade_foss_sponsor_subtitle),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(top = 8.dp),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
}
|
||||
|
||||
IconButton(
|
||||
onClick = onNavigateUp,
|
||||
modifier = Modifier.padding(4.dp),
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.TwoTone.ArrowBack,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">Borg ontwikkeling</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Geen advertensies. Geen opsporing. Geen Google Play-binding.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">Alreeds terug? U ondersteuning hou CAPod aan die lewe.</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">ልማትን ይደግፉ</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">ምንም ማስታወቂያ የሉም። ምንም ክትትል የሉም። ምንም Google Play ቆለፋ የሉም።</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">ቀድሞ ተመለሱ፡ ድጋፏዎ CAPodን ህያው ያቆይላል።</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">ادعم التطوير</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">لا إعلانات. لا تتبع. لا قيود على Google Play.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">عدت بسرعة؟ دعمك يبقي CAPod حياً.</string>
|
||||
<string name="upgrade_badge_label">البرمجيات الحرة (FOSS)</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">İnkişafı sponsor edin</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Reklam yoxdur. İzləmə yoxdur. Google Play bağlılığı yoxdur.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">Artıq geri qayıtdınız? Dəstəyiniz CAPod-u yaşadır.</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">Падтрымаць распрацоўку</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Ніякай рэкламы. Ніякай адсочкі. Ніякай залежнасці ад Google Play.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">Ужо вяртаецеся? Ваша падтрымка трымае CAPod ў жывых.</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">Спонсорирайте разработката</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Без реклами. Без следене. Без заключване в Google Play.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">Вече ли? Вашата подкрепа поддържа CAPod жив.</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">স্পনসর ডেভলপমেন্ট</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">কোনো বিজ্ঞাপন নেই। কোনো ট্র্যাকিং নেই। Google Play-এর সাথে বাঁধানো নেই।</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">ইতোমধ্যে ফিরে গেলেন? আপনার সমর্থন CAPodকে বাঁচিয়ে রাখে।</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">Patrocina el desenvolupament</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Sense anuncis. Sense seguiment. Sense dependència del Google Play.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">Ja heu tornat? El vostre suport manté viu el CAPod.</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">Sponzorovat vývoj</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Žádné reklamy. Žádné sledování. Žádná závislost na Google Play.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">Už jste zpět? Vaše podpora udržuje CAPod naživu.</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">Sponsorudvikling</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Ingen annoncer. Ingen sporing. Ingen Google Play-lås.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">Allerede tilbage? Din støtte holder CAPod i live.</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">Fördere Entwicklung</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Keine Werbung. Kein Tracking. Keine Google-Play-Bindung.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">Schon zurück? Deine Unterstützung hält CAPod am Leben.</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">Χορηγήστε την ανάπτυξη</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Χωρίς διαφημίσεις. Χωρίς παρακολούθηση. Χωρίς δέσμευση στο Google Play.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">Επιστρέψατε ήδη; Η υποστήριξή σας κρατά το CAPod ζωντανό.</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">Sponsorear el desarrollo</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Sin publicidad. Sin rastreo. Sin dependencia de Google Play.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">¿Ya te vas? Tu apoyo mantiene CAPod vivo.</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">Patrocinar el desarrollo</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Sin anuncios. Sin rastreo. Sin dependencia de Google Play.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">¿Ya de vuelta? Tu apoyo mantiene CAPod con vida.</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">Patrocinadores</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Sin anuncios. Sin rastreo. Sin dependencia de Google Play.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">¿Ya de vuelta? Tu apoyo mantiene CAPod vivo.</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">Rahasta arendamist</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Reklaami pole. Jälgimist pole. Google Play lukustust pole.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">Juba tagasi? Sinu toetus hoiab CAPodi elus.</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">Garapena babestu</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Iragarkirik ez. Jarraipenik ez. Google Play-ren lotura ez.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">Dagoeneko itzuli? Zure laguntzak CAPod bizirik mantentzen du.</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">حامی توسعه دهنده</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">بدون تبلیغ. بدون ردیابی. بدون وابستگی به Google Play.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">زود برگشتیدی؟ حمایت شما CAPod را زنده نگه میدارد.</string>
|
||||
<string name="upgrade_badge_label">منبعآزاد</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">Sponsoroi kehitystä</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Ei mainoksia. Ei seurantaa. Ei Google Play -riippuvuutta.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">Jo takaisin? Tukesi pitää CAPod-sovelluksen hengissä.</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">Mag-sponsor ng development</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Walang ads. Walang tracking. Walang Google Play lock-in.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">Bumalik na? Ang iyong suporta ay nagpapanatiling buhay ng CAPod.</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">Soutenir le développement</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Pas de publicités. Pas de suivi à la trace. Pas de dépendance à Google Play.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">Déjà de retour ? Votre soutien maintient CAPod en vie.</string>
|
||||
<string name="upgrade_badge_label">Logiciel libre</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">Patrocinar desenvolvemento</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Sen anuncios. Sen rastrexo. Sen dependencia de Google Play.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">És rápido/a! O teu apoio mantén CAPod vivo.</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">विकास को प्रायोजक करें</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">कोई विज्ञापन नहीं। कोई ट्रैकिंग नहीं। Google Play पर निर्भरता नहीं।</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">अभी वापस? आपका समर्थन CAPod को जीवित रखता है।</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">Podržite razvoj</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Bez oglasa. Bez praćenja. Bez zaključanosti na Google Play.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">Već se vraćate? Vaša podrška čuva CAPod na životu.</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">Szponzori fejlesztés</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Nincs hirdetés. Nincs nyomkövetés. Nincs Google Play-függőség.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">Már visszamész? A támogatásodnak köszönhetően él tovább a CAPod.</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">Հովանավորել զարգացումը</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Լրատնություն չկա: հետքխկություն չկա: Google Play-ի կապվածություն չկա:</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">Արդևն հետ? ձևր աիկտակցությունը CAPod-ին կենդանի պահում:</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">Pengembangan sponsor</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Tanpa iklan. Tanpa pelacakan. Tanpa ketergantungan Google Play.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">Sudah kembali? Dukungan Anda menjaga CAPod tetap hidup.</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">Styrkja þróun</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Engar auglysíngar. Engin rakning. Ekkert Google Play-lás.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">Aftur þegar? Styðjað þít heldur CAPod lifandi.</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">Finanzia lo sviluppo</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Nessuna pubblicità. Nessun tracciamento. Nessun vincolo con Google Play.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">Già via? Il tuo supporto mantiene CAPod in vita.</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">פיתוח חסות</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">אין פרסומות. אין מעקב. אין תלות ב-Google Play.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">כבר חוזר? התמיכה שלך שומרת את CAPod בחיים.</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">開発支援者</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">広告なし。追跡なし。Google Play依存なし。</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">もう戻りますか?あなたのサポートがCAPodを支えています。</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">სპონსორის განვითარება</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">რეკლამა არა. თვალთვალება არა. Google Play-ზე დამოკიდება არა.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">უკვე დაბრუნდით? თქვენი მხარდაჯერა CAPod-ს გადარჩენებას ეხმარება.</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">ឧបត្ថម្ភការអភិវឌ្ឍន៍</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">គ្មានការផ្សាយពាណិជ្ជកម្ម។ គ្មានការតាមដាន។ គ្មានការចាក់សោ Google Play។</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">ត្រលប់មកវិញហើយ។? ការគាំទ្ររបស់អ្នកចិញ្ចឹម CAPod ឥ្សរិយនៅរស់។</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">Pêşdebiriyê teref bike</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Reklam tune. Śopandin tune. Girêdana Google Play tune.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">Zirû vegeriya? Piştgiriya we CAPod sax dihêle.</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">ಅಭಿವೃದ್ಧಿಗೆ ಪ್ರಾಯೋಜಕತ್ವ ನೀಡಿ</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">ಜಾಹೀರಾತುಗಳಿಲ್ಲ. ಭಕ್ಷಣೆ ಇಲ್ಲ. Google Play ಲಾಕ್-ಇನ್ ಇಲ್ಲ.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">ಇಷ್ಟೇ ಹಿಂದಿರುಗಿದಿರಿ? ನಿಮ್ಮ ಬೆಂಬಲ CAPod ಅನ್ನು ಜೀವಂತವಾಗಿಡುತ್ತದೆ.</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">개발 후원하기</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">광고 없음. 추적 없음. Google Play 의존 없음.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">벌써 돌아가세요? 여러분의 지원이 CAPod를 유지합니다.</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">Иштелеп чыгууну колдоо</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Жарнама. Кадалоолоо жок. Google Playга байланма.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">Артка кайттыңызбы? Сиздин колдооңуз CAPodты тирүү сактайт.</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">ສະໜັບສະໜູນການພັດທະນາ</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">ບໍ່ມີໂຄສະນາ. ບໍ່ມີການຕິດຕາມ. ບໍ່ຕິດ Google Play.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">ກັບມາແລ້ວ? ການສະໜັບສະໜູນຂອງທ່ານຮັກສາ CAPod ໃຫ້ດຳເນີນຕໍ່ໄປ.</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">Remti plėtrą</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Jokių reklamų. Jokio sekimo. Jokio „Google Play“ priklausomybės.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">Jau grįžte? Jūsų parama palaiko CAPod gyvybę.</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">Sponsorēt attīstību</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Bez reklamām. Bez seklōšanas. Bez Google Play saistībām.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">Jau atpakaļ? Jūsu atbalsts uztur CAPod pie dzīvības.</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">Спонзорирај развој</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Без реклами. Без пратење. Без зависност од Google Play.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">Веќе се вративте? Вашата поддршка го одржува CAPod жив.</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">വികസനം സ്പോൺസർ ചെയ്യുക</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">പരസ്യങ്ങൾ ഇല്ല. ട്രാക്കിംഗ് ഇല്ല. Google Play ലോക്ക്-ഇൻ ഇല്ല.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">ഇത്ര പെട്ടെന്ന് തിരിച്ചു പോകുന്നോ? നിങ്ങളുടെ പിന്തുണ CAPod നിലനിർത്തുന്നു.</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">Хөгжлийг дэмжих</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Реклам байхгүй. Хяналт байхгүй. Google Play-д хамааралгүй.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">Аль хэдэйн буцаж ирлээ? Таны дэмжлэг CAPod-ийг амьд байлгадаг.</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">विकासास प्रायोजित करा</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">कोणत्याही जाहिराती नाहीत. कोणतेही ट्रॅकिंग नाही. Google Play चे बंधन नाही.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">आधीच परत? तुमचा पाठिंबा CAPod जिवंत ठेवतो.</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">Pembangunan penaja</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Tiada iklan. Tiada penjejakan. Tiada ikatan Google Play.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">Sudah kembali? Sokongan anda mengekalkan CAPod.</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">ဖွံ့ဖြိုးတိုးတက်မှုကို ကမကထပံ့ပိုးရန်</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">ကြောင်ညာမရှိ။ ခြေရာမခံ။ Google Play ချုပ်ချယ်မြုမရှိ။</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">ပြန်သွားပြီးလါ? သင်၏ ပံ့ပိုးမှု CAPod ကို ရှင်သန်ဆက်လက်ကေစေသည်။</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">Støtt utviklingen</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Ingen reklame. Ingen sporing. Ingen Google Play-avhengighet.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">Allerede tilbake? Din støtte holder CAPod i live.</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">विकासलाई प्रायोजन गर्नुहोस्</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">कुनै विज्ञापन छैन। कुनै ट्रैकिङ छैन। Google Playमा बाँधिएको छैन।</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">पहिले नै फिर्नुभयो? तपाईंको सहयोगले CAPod जीवित राख्छ।</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">Sponsor ontwikkeling</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Geen advertenties. Geen tracking. Geen Google Play-vergrendeling.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">Alweer terug? Jouw steun houdt CAPod in leven.</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">Wesprzyj rozwój</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Bez reklam. Bez śledzenia. Bez uzależnienia od Google Play.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">Już wróciłeś? Twoje wsparcie utrzymuje CAPod przy życiu.</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">Patrocinador do desenvolvimento</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Sem anúncios. Sem rastreamento. Sem dependência do Google Play.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">Já voltou? Seu apoio mantém o CAPod vivo.</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">Patrocine o desenvolvimento</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Sem anúncios. Sem rastreamento. Sem dependência do Google Play.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">Já está a sair? O seu apoio mantém o CAPod ativo.</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">Sponsurisar il sviluppament</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Nagins annunzis. Nagin tracking. Nagin blocadi da Google Play.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">Gia enavos? Tes sustegn mantegn CAPod viv.</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">Sponsorizează dezvoltarea</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Fara reclame. Fara urmarire. Fara dependenta de Google Play.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">Ati revenit deja? Sprijinul dvs. mentine CAPod in viata.</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
<string name="foss_upgrade_donate_label">Пожертвовать</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Я уже пожертвовал</string>
|
||||
<string name="foss_upgrade_no_money_label">Я потратил все свои деньги на AirPods</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS бесплатный и с открытым исходным кодом. Если вам полезно приложение, рассмотрите возможность спонсировать разработку, чтобы помочь проекту продолжать развиваться.</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS бесплатный и с открытым исходным кодом. Если Вам полезно приложение, рассмотрите возможность спонсировать разработку, чтобы помочь проекту продолжать развиваться.</string>
|
||||
<string name="upgrade_foss_sponsor_action">Поддержать разработку</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Без рекламы. Без слежки. Без привязки к Google Play.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">Вернулись уже? Ваша поддержка помогает CAPod жить.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">Вернулись? Ваша поддержка помогает CAPod жить.</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">Sponsoriza s\'isvilupu</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Peroe publicidade. Peroe trachiamentu. Peroe dipendentzia de Google Play.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">Zas torradas giai? Su sostegnu tuyu mantenet CAPod in vida.</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">සංවර්ධනයට අනුග්රහය දෙන්න</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">අළු නැත. ට්රැකිං නැත. Google Play සලකත්වය නැත.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">එක්බේන්මට පළ්ට? ඔබ෪් සහය් CAPod ක්රියාත්මකව තබා ගෙනයි.</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">Sponzorovaný vývoj</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Žiadne reklamy. Žiadne sledovanie. Žiadna závislosť od Google Play.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">Už späť? Vaša podpora udržiava CAPod pri živote.</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">Podpri razvoj</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Brez oglasov. Brez sledenja. Brez vezave na Google Play.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">Že nazaj? Vaša podpora ohranja CAPod pri življenju.</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">Zhvillimi i sponsorëve</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Pa reklama. Pa gjurmim. Pa varësi nga Google Play.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">Ktheheni tashmjë? Mbështjetja juaj e mban CAPod gjallë.</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">Спонзориши развој</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Без реклама. Без праћења. Без зависности од Google Play.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">Већ одлазите? Ваша подршка чува CAPod живим.</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">Sponsra utvecklingen</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Inga annonser. Ingen spårning. Ingen bindning till Google Play.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">Redan tillbaka? Ditt stöd håller CAPod vid liv.</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">Faidhi maendeleo</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Hakuna matangazo. Hakuna ufuatiliaji. Hakuna kufungwa kwa Google Play.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">Unarudi tayari? Msaada wako unaweka CAPod iwe hai.</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">மேம்பாட்டை ஸ்பான்சர் செய்யுங்கள்</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">ஐவிளம்பரம் இல்லை. கண்காணிப்பு இல்லை. Google Play கட்டுப்பாட்டு இல்லை.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">இத்தனை இருக்கிறீரகளா? உங்கள் ஆதரவு CAPodஐ உயிருடன் வைத்திருக்கிறது.</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">అభివృద్ధిని స్పాన్సర్ చేయండి</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">జాహీరాతులు లేవు. ట్రాకింగ్ లేదు. Google Play ఒక్కటికి పరిమితం కాదు.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">ఇర్వై వెనక్కి వెళ్ళారా? మీ మద్దతు CAPodను సజీవంగా ఉంచుతుంది.</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">สปอนเซอร์การพัฒนา</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">ไม่มีโฆษณา ไม่ติดตาม ไม่ผูกขาด Google Play</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">กลับมาแล้ว? การสนับสนุนของคุณช่วยให้ CAPod ดำเนินต่อไป</string>
|
||||
<string name="upgrade_badge_label">บังคับ</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">Geliştirmeyi destekleyin</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Reklam yok. Takip yok. Google Play sınırlaması yok.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">Şimdiden döndün mü? Desteğin CAPod\'u ayakta tutuyor.</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">Розвиток спонсорів</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Без реклами. Без стеження. Без прив\'язки до Google Play.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">Вже повертаєтеся? Ваша підтримка зберігає CAPod.</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">ترقی کو اسپانسر کریں</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">کوئی اشتہار نہیں۔ کوئی ٹریکنگ نہیں۔ کوئی Google Play کی قید نہیں۔</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">پہلے ہی واپس؟ آپ کی مدد CAPod کو زندہ رکھتی ہے۔</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">Rivojlanishni sponsorlik qilish</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Reklamasiz. Kuzatishsiz. Google Play cheklovlarisiz.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">Allaqachon qaytdingizmi? Sizning yordamingiz CAPod\'ni tirik saqlaydi.</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">Tài trợ nhà phát triển</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Không quảng cáo. Không theo dõi. Không phụ thuộc Google Play.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">Quay lại rồi sao? Sự ủng hộ của bạn giúp CAPod tiếp tục tồn tại.</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">赞助开发</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">无广告。无追踪。无 Google Play 绑定。</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">已经回去了?您的支持是 CAPod 生存的动力。</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">贊助開發</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">沒有廣告。沒有追蹤。沒有 Google Play 鎖定。</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">這麼快就要離開?您的支持讓 CAPod 繼續存活。</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">贊助開發</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">無廣告。無追蹤。無 Google Play 鎖定。</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">已經回容了?您的支持讓 CAPod 繼續運行。</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">Xhasa ukuthuthukiswa</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Azikho izibhangeli. Akunakulandelelwa. Ayikho ukuvinjwa kwe-Google Play.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">Ubuyile? Ukwesekwa kwakho kugcina i-CAPod iphila.</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrade_foss_sponsor_action">Sponsor development</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">No ads. No tracking. No Google Play lock-in.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">Back already? Your support keeps CAPod alive.</string>
|
||||
<string name="upgrade_badge_label">FOSS</string>
|
||||
</resources>
|
||||
@@ -7,14 +7,11 @@ import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.systemBars
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
@@ -27,6 +24,8 @@ import androidx.compose.material.icons.twotone.Favorite
|
||||
import androidx.compose.material.icons.twotone.Palette
|
||||
import androidx.compose.material.icons.twotone.PlayCircle
|
||||
import androidx.compose.material.icons.twotone.Stars
|
||||
import androidx.compose.material.icons.twotone.Headphones
|
||||
import androidx.compose.material.icons.twotone.Tune
|
||||
import androidx.compose.material.icons.twotone.Widgets
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Card
|
||||
@@ -37,6 +36,7 @@ import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -122,125 +122,140 @@ fun UpgradeScreen(
|
||||
val benefits = listOf(
|
||||
Benefit(Icons.TwoTone.Palette, R.string.upgrade_benefit_themes),
|
||||
Benefit(Icons.TwoTone.PlayCircle, R.string.upgrade_benefit_autoplay),
|
||||
Benefit(Icons.TwoTone.BluetoothConnected, R.string.upgrade_benefit_autoconnect),
|
||||
|
||||
Benefit(Icons.AutoMirrored.TwoTone.Message, R.string.upgrade_benefit_popups),
|
||||
Benefit(Icons.TwoTone.Widgets, R.string.upgrade_benefit_widgets),
|
||||
Benefit(Icons.TwoTone.Tune, R.string.upgrade_benefit_device_settings),
|
||||
Benefit(Icons.TwoTone.Headphones, R.string.upgrade_benefit_device_controls),
|
||||
Benefit(Icons.TwoTone.Favorite, R.string.upgrade_benefit_support),
|
||||
)
|
||||
|
||||
Box(modifier = Modifier.windowInsetsPadding(WindowInsets.systemBars)) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(horizontal = 24.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
Box(contentAlignment = Alignment.Center) {
|
||||
Surface(
|
||||
modifier = Modifier.size(120.dp),
|
||||
shape = CircleShape,
|
||||
color = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.5f),
|
||||
) {}
|
||||
Image(
|
||||
painter = painterResource(R.drawable.splash_graphic2),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(80.dp),
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
Text(
|
||||
text = buildAnnotatedString {
|
||||
append("CAPod ")
|
||||
withStyle(SpanStyle(color = colorResource(R.color.brand_tertiary), fontWeight = FontWeight.Bold)) {
|
||||
append("Pro")
|
||||
}
|
||||
},
|
||||
style = MaterialTheme.typography.headlineLarge,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
Card(
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = MaterialTheme.colorScheme.secondaryContainer,
|
||||
),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
Scaffold(
|
||||
containerColor = MaterialTheme.colorScheme.surface,
|
||||
) { paddingValues ->
|
||||
Box(modifier = Modifier.padding(paddingValues)) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(horizontal = 24.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Spacer(modifier = Modifier.height(48.dp))
|
||||
|
||||
Box(contentAlignment = Alignment.Center) {
|
||||
Surface(
|
||||
modifier = Modifier.size(120.dp),
|
||||
shape = CircleShape,
|
||||
color = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.5f),
|
||||
) {}
|
||||
Image(
|
||||
painter = painterResource(R.drawable.splash_graphic2),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(80.dp),
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.upgrade_preamble),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
modifier = Modifier.padding(16.dp),
|
||||
text = buildAnnotatedString {
|
||||
append("CAPod ")
|
||||
withStyle(SpanStyle(color = colorResource(R.color.brand_tertiary), fontWeight = FontWeight.Bold)) {
|
||||
append("Pro")
|
||||
}
|
||||
},
|
||||
style = MaterialTheme.typography.headlineLarge,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
Column(modifier = Modifier.padding(vertical = 4.dp)) {
|
||||
benefits.forEach { benefit ->
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 12.dp, vertical = 4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Surface(
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
color = MaterialTheme.colorScheme.primaryContainer,
|
||||
modifier = Modifier.size(28.dp),
|
||||
Card(
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = MaterialTheme.colorScheme.secondaryContainer,
|
||||
),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.upgrade_preamble),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
modifier = Modifier.padding(16.dp),
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
Column(modifier = Modifier.padding(vertical = 4.dp)) {
|
||||
benefits.forEach { benefit ->
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 12.dp, vertical = 4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Box(contentAlignment = Alignment.Center) {
|
||||
Icon(
|
||||
imageVector = benefit.icon,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.onPrimaryContainer,
|
||||
modifier = Modifier.size(16.dp),
|
||||
)
|
||||
Surface(
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
color = MaterialTheme.colorScheme.primaryContainer,
|
||||
modifier = Modifier.size(28.dp),
|
||||
) {
|
||||
Box(contentAlignment = Alignment.Center) {
|
||||
Icon(
|
||||
imageVector = benefit.icon,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.onPrimaryContainer,
|
||||
modifier = Modifier.size(16.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.width(12.dp))
|
||||
Text(
|
||||
text = stringResource(benefit.textRes),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.width(12.dp))
|
||||
Text(
|
||||
text = stringResource(benefit.textRes),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.upgrade_benefit_disclaimer),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.padding(top = 8.dp),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
if (state == null) {
|
||||
CircularProgressIndicator(modifier = Modifier.padding(16.dp))
|
||||
} else {
|
||||
PricingContent(
|
||||
state = state,
|
||||
onSubscription = onSubscription,
|
||||
onSubscriptionTrial = onSubscriptionTrial,
|
||||
onIap = onIap,
|
||||
onRestore = onRestore,
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
if (state == null) {
|
||||
CircularProgressIndicator(modifier = Modifier.padding(16.dp))
|
||||
} else {
|
||||
PricingContent(
|
||||
state = state,
|
||||
onSubscription = onSubscription,
|
||||
onSubscriptionTrial = onSubscriptionTrial,
|
||||
onIap = onIap,
|
||||
onRestore = onRestore,
|
||||
IconButton(
|
||||
onClick = onNavigateUp,
|
||||
modifier = Modifier
|
||||
.align(Alignment.TopStart)
|
||||
.padding(4.dp),
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.TwoTone.ArrowBack,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
}
|
||||
|
||||
IconButton(
|
||||
onClick = onNavigateUp,
|
||||
modifier = Modifier.padding(4.dp),
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.TwoTone.ArrowBack,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package eu.darken.capod.upgrade.ui
|
||||
import android.app.Activity
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import eu.darken.capod.common.coroutine.DispatcherProvider
|
||||
import eu.darken.capod.common.debug.logging.Logging.Priority.INFO
|
||||
import eu.darken.capod.common.debug.logging.Logging.Priority.WARN
|
||||
import eu.darken.capod.common.debug.logging.asLog
|
||||
import eu.darken.capod.common.debug.logging.log
|
||||
@@ -116,18 +117,22 @@ class UpgradeViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
fun onGoIap() {
|
||||
log(TAG, INFO) { "onGoIap()" }
|
||||
billingEvents.tryEmit(BillingEvent.LaunchIap)
|
||||
}
|
||||
|
||||
fun onGoSubscription() {
|
||||
log(TAG, INFO) { "onGoSubscription()" }
|
||||
billingEvents.tryEmit(BillingEvent.LaunchSubscription)
|
||||
}
|
||||
|
||||
fun onGoSubscriptionTrial() {
|
||||
log(TAG, INFO) { "onGoSubscriptionTrial()" }
|
||||
billingEvents.tryEmit(BillingEvent.LaunchSubscriptionTrial)
|
||||
}
|
||||
|
||||
fun launchBillingIap(activity: Activity) = launch {
|
||||
log(TAG, INFO) { "launchBillingIap()" }
|
||||
try {
|
||||
withContext(dispatcherProvider.Main) {
|
||||
upgradeRepo.launchBillingFlow(activity, CapodSku.Iap.PRO_UPGRADE)
|
||||
@@ -139,6 +144,7 @@ class UpgradeViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
fun launchBillingSubscription(activity: Activity) = launch {
|
||||
log(TAG, INFO) { "launchBillingSubscription()" }
|
||||
try {
|
||||
withContext(dispatcherProvider.Main) {
|
||||
upgradeRepo.launchBillingFlow(
|
||||
@@ -154,6 +160,7 @@ class UpgradeViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
fun launchBillingSubscriptionTrial(activity: Activity) = launch {
|
||||
log(TAG, INFO) { "launchBillingSubscriptionTrial()" }
|
||||
try {
|
||||
withContext(dispatcherProvider.Main) {
|
||||
upgradeRepo.launchBillingFlow(
|
||||
@@ -169,9 +176,10 @@ class UpgradeViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
fun restorePurchase() = launch {
|
||||
log(TAG, INFO) { "restorePurchase()" }
|
||||
try {
|
||||
val data = upgradeRepo.refresh()
|
||||
log(TAG) { "Restore check: $data" }
|
||||
log(TAG, INFO) { "Restore check: $data" }
|
||||
val info = upgradeRepo.upgradeInfo.first()
|
||||
if (info.isPro) {
|
||||
log(TAG) { "Pro purchase found" }
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrades_gplay_billing_result_error_label">Google Play-faktureringsfout</string>
|
||||
<string name="upgrades_gplay_billing_result_error_description">Daar was \'n fout toe Google Play om jou aankoopbesonderhede gevra is. Maak die Google Play-kas skoon en herbegin jou foon.\n\nFout %s</string>
|
||||
<string name="upgrade_restore_action">Herstel aankoop</string>
|
||||
<string name="upgrade_badge_label">Pro</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrades_gplay_billing_result_error_label">የGoogle Play ክፍያ ስህተት</string>
|
||||
<string name="upgrades_gplay_billing_result_error_description">Google Play የግዢ ዝርዝሮችዎን ሲጠይቅ ስህተት ነበር። የGoogle Play መሸጎጫውን ያጽዱ እና ስልክዎን እንደገና ያስጀምሩ።\n\nስህተት %s</string>
|
||||
<string name="upgrade_restore_action">ግዢን ወደነበረበት መልስ</string>
|
||||
<string name="upgrade_badge_label">Pro</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrades_gplay_billing_result_error_label">خطأ في فوترة Google Play</string>
|
||||
<string name="upgrades_gplay_billing_result_error_description">حدث خطأ عند طلب تفاصيل الشراء من Google Play. امسح ذاكرة التخزين المؤقت لـ Google Play وأعد تشغيل هاتفك.\n\nخطأ %s</string>
|
||||
<string name="upgrade_restore_action">استعادة المُشتَرَى</string>
|
||||
<string name="upgrade_badge_label">احترافي</string>
|
||||
</resources>
|
||||
|
||||
@@ -7,4 +7,5 @@
|
||||
<string name="upgrades_gplay_billing_result_error_label">Google Play faktura xətası</string>
|
||||
<string name="upgrades_gplay_billing_result_error_description">Google Play-dən satınalma təfərrüatlarınızı soruşarkən xəta baş verdi. Google Play keşini təmizləyin və telefonunuzu yenidən başladın.\n\nXəta %s</string>
|
||||
<string name="upgrade_restore_action">Alışı bərpa et</string>
|
||||
<string name="upgrade_badge_label">Pro</string>
|
||||
</resources>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user