mirror of
https://github.com/d4rken-org/capod.git
synced 2026-09-14 18:26:11 -04:00
Compare commits
145
Commits
v3.0.1-rc0
...
v4.0.1-rc0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ac477113cf | ||
|
|
5e21941765 | ||
|
|
e5032ee35a | ||
|
|
1ceac349ab | ||
|
|
2db01b410b | ||
|
|
193a1030fb | ||
|
|
12e35631d6 | ||
|
|
67474a30da | ||
|
|
44cd0eec2a | ||
|
|
a79f6a0535 | ||
|
|
8675d73a98 | ||
|
|
3423786f06 | ||
|
|
00083707c6 | ||
|
|
0cdd2a4f7a | ||
|
|
8e3b235c81 | ||
|
|
879ec1b69c | ||
|
|
c833fd2c92 | ||
|
|
d7d9d53ff3 | ||
|
|
cba239f411 | ||
|
|
853e877a59 | ||
|
|
de89c51457 | ||
|
|
7f8daddfe0 | ||
|
|
a76e43ac04 | ||
|
|
fd0dc49396 | ||
|
|
a5f5feacae | ||
|
|
70a3114876 | ||
|
|
557ee9b3ed | ||
|
|
58bd0f91f5 | ||
|
|
b2f8586dc9 | ||
|
|
62797e8ea2 | ||
|
|
c614e7bd4f | ||
|
|
62e39ce3d2 | ||
|
|
7df74e3b44 | ||
|
|
06e5954ef5 | ||
|
|
450a187b65 | ||
|
|
da6a6f6ff2 | ||
|
|
d37c8aa3a5 | ||
|
|
5cd97805b4 | ||
|
|
e35bce4d19 | ||
|
|
1a196689db | ||
|
|
8ada7b071d | ||
|
|
a92e364ca6 | ||
|
|
6326f4a5b9 | ||
|
|
abb189f43c | ||
|
|
e80bf1db6c | ||
|
|
cd6488a713 | ||
|
|
e13331dfcf | ||
|
|
dcf2ade9ec | ||
|
|
40f0aba462 | ||
|
|
c760d5758d | ||
|
|
b609c1aced | ||
|
|
b3b55f1ca3 | ||
|
|
073de3cf35 | ||
|
|
8c278f2a3c | ||
|
|
c103c6adc9 | ||
|
|
4db2edd29a | ||
|
|
eefe740b6f | ||
|
|
a47addc8bc | ||
|
|
9cc5a82adf | ||
|
|
bec24bfd6f | ||
|
|
e6eb0d818f | ||
|
|
25e3e7d792 | ||
|
|
ebcb997bdd | ||
|
|
76c55bf3be | ||
|
|
fc9bcafae9 | ||
|
|
c4e58917da | ||
|
|
3730a78e7a | ||
|
|
222a6c43c9 | ||
|
|
b1fe8490d3 | ||
|
|
a0fe20e296 | ||
|
|
e2d127dae9 | ||
|
|
b5c48be08e | ||
|
|
90f0c89b79 | ||
|
|
8dd165e1c8 | ||
|
|
119139cbc5 | ||
|
|
5159e4c21b | ||
|
|
1c94354831 | ||
|
|
75d2bf0169 | ||
|
|
7e73777469 | ||
|
|
3d07c284b9 | ||
|
|
f6b1c69edc | ||
|
|
f421deca49 | ||
|
|
e19608f567 | ||
|
|
06f4115808 | ||
|
|
2ca8f686c8 | ||
|
|
679250f7da | ||
|
|
8df2d78070 | ||
|
|
63692595a8 | ||
|
|
576c6db8d0 | ||
|
|
524cac8d30 | ||
|
|
8939f788ad | ||
|
|
9ad3f3c180 | ||
|
|
5b59ddab0f | ||
|
|
ab24c48024 | ||
|
|
a6482c7d76 | ||
|
|
99aa44b448 | ||
|
|
a801f2d701 | ||
|
|
8405406d22 | ||
|
|
9785b5ea7f | ||
|
|
fd7f9f8ab3 | ||
|
|
46bb8c98de | ||
|
|
b3ead17b12 | ||
|
|
a794d59276 | ||
|
|
584c5c70d0 | ||
|
|
5a55796518 | ||
|
|
bdfdd93735 | ||
|
|
e9a937e4f6 | ||
|
|
0a8bae46a9 | ||
|
|
7efcafbc71 | ||
|
|
6b3f431e9e | ||
|
|
6014004c0f | ||
|
|
ebbc5394a7 | ||
|
|
e36868a2fc | ||
|
|
1c6fb8ed31 | ||
|
|
e780fd17d2 | ||
|
|
86d3f3bc61 | ||
|
|
ba554c8787 | ||
|
|
968829072a | ||
|
|
0d1f331551 | ||
|
|
a8bb42d40b | ||
|
|
9a3fb11ef7 | ||
|
|
12d2c4dd06 | ||
|
|
cfed733fa7 | ||
|
|
1af8743533 | ||
|
|
2f3f1f3f70 | ||
|
|
6223524a1e | ||
|
|
ebfffb1536 | ||
|
|
5aa36b148b | ||
|
|
29391feaf8 | ||
|
|
4e279ca44d | ||
|
|
9ff8d9e04e | ||
|
|
10d5a823fc | ||
|
|
54afadb136 | ||
|
|
e4db9387ca | ||
|
|
4834eb276a | ||
|
|
5860bbffb6 | ||
|
|
da53d68190 | ||
|
|
b397bf190c | ||
|
|
15d664c361 | ||
|
|
d2f24d8b95 | ||
|
|
b5436659b6 | ||
|
|
1cfc4611cb | ||
|
|
bda0a20743 | ||
|
|
09bad90c0c | ||
|
|
34856607f8 |
@@ -0,0 +1 @@
|
||||
worktrees/
|
||||
@@ -0,0 +1,49 @@
|
||||
# CAPod - Companion for AirPods
|
||||
|
||||
Android app that detects and monitors AirPods via Bluetooth LE. Displays battery levels, triggers popup notifications on case open, and provides home screen widgets.
|
||||
|
||||
## Project Structure
|
||||
|
||||
| Module | Description |
|
||||
|--------|-------------|
|
||||
| `app/` | Main Android app (FOSS and Google Play flavors) |
|
||||
| `app-common/` | Shared code between phone and Wear OS apps |
|
||||
|
||||
## Build Flavors
|
||||
|
||||
- **FOSS** (`foss`): Open-source, no Google Play dependencies
|
||||
- **Google Play** (`gplay`): Includes billing client for IAP
|
||||
|
||||
Quick build check: `./gradlew assembleFossDebug`
|
||||
|
||||
## Key Locations
|
||||
|
||||
| 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/res/` | Layouts, drawables, strings |
|
||||
| `app-common/src/test/` | Unit tests |
|
||||
| `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 |
|
||||
|
||||
## 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
|
||||
|
||||
## Rules Reference
|
||||
|
||||
Detailed guidelines are in `.claude/rules/`:
|
||||
|
||||
- `architecture.md` — Module structure, key components, data flow, dependencies
|
||||
- `build-commands.md` — Build, test, lint, and release commands
|
||||
- `localization.md` — String resource naming conventions
|
||||
- `commit-guidelines.md` — Commit message format and prefixes
|
||||
- `pull-requests.md` — PR title and description conventions
|
||||
- `agent-instructions.md` — Sub-agent delegation and critical thinking
|
||||
- `screenshots.md` — Play Store screenshot pipeline, commands, adding new screens
|
||||
@@ -0,0 +1,39 @@
|
||||
---
|
||||
description: Instructions for Claude Code sub-agents and task delegation
|
||||
globs:
|
||||
- "**"
|
||||
---
|
||||
|
||||
# Agent Instructions
|
||||
|
||||
## Critical Thinking
|
||||
|
||||
- Do not blindly accept information at face value
|
||||
- Verify assumptions against actual code before proceeding
|
||||
- When encountering unexpected behavior, investigate root causes rather than applying workarounds
|
||||
- If something seems wrong, it probably is — dig deeper
|
||||
|
||||
## Explore vs. Implement
|
||||
|
||||
- **Explore first**: Before making changes, understand the existing code structure and patterns
|
||||
- **Read before writing**: Always read relevant files before modifying them
|
||||
- **Follow existing patterns**: Match the code style and architecture already in use
|
||||
- **Minimal changes**: Only change what's necessary to accomplish the task
|
||||
|
||||
## Sub-Agent Delegation
|
||||
|
||||
When using Task tool to spawn sub-agents:
|
||||
|
||||
- Provide complete context — sub-agents don't share your conversation history unless noted
|
||||
- Be specific about what you need: research only, or research + implementation
|
||||
- Use `Explore` agent type for codebase investigation
|
||||
- Use `Bash` agent type for running builds and tests
|
||||
- Parallelize independent sub-agent tasks for efficiency
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
- Don't create new files when editing existing ones would suffice
|
||||
- Don't add features beyond what was requested
|
||||
- Don't refactor surrounding code when fixing a bug
|
||||
- Don't add comments or documentation to code you didn't change
|
||||
- Don't guess at file paths — use Glob/Grep to find them
|
||||
@@ -0,0 +1,84 @@
|
||||
---
|
||||
description: Architecture overview, module structure, key components, data flow, and dependencies
|
||||
globs:
|
||||
- "app/**/*.kt"
|
||||
- "app-common/**/*.kt"
|
||||
- "**/*.gradle.kts"
|
||||
---
|
||||
|
||||
# Architecture
|
||||
|
||||
## Multi-Module Structure
|
||||
|
||||
- **app/**: Main Android application with FOSS and Google Play flavors
|
||||
- **app-common/**: Shared code between main app and Wear OS app
|
||||
|
||||
## Core Patterns
|
||||
|
||||
- **MVVM**: ViewModels with LiveData/StateFlow for UI state management
|
||||
- **Dependency Injection**: Hilt/Dagger for dependency management
|
||||
- **Coroutines**: Kotlin coroutines for async operations
|
||||
- **Repository Pattern**: Data layer abstraction for monitoring and settings
|
||||
|
||||
## Key Components
|
||||
|
||||
### PodMonitor System
|
||||
|
||||
- `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
|
||||
|
||||
### Reaction System
|
||||
|
||||
- `ReactionSettingsFragment`: Configuration for popup notifications
|
||||
- `PopUpWindow`: Displays AirPods status when case is opened
|
||||
- `PopUpPodViewFactory`: Creates UI components for different pod models
|
||||
|
||||
### Common Utilities
|
||||
|
||||
- `EdgeToEdgeHelper`: Handles Android edge-to-edge display insets
|
||||
|
||||
## Build Configuration
|
||||
|
||||
### Flavors
|
||||
|
||||
- **FOSS**: Open-source version without Google Play dependencies
|
||||
- **Google Play (gplay)**: Version with billing client for in-app purchases
|
||||
|
||||
### Build Types
|
||||
|
||||
- **debug**: Unobfuscated, full logging, no minification
|
||||
- **beta**: Obfuscated, production-ready with strict lint checks
|
||||
- **release**: Fully optimized for production distribution
|
||||
|
||||
## 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.
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
- **Unit Tests**: Located in `app-common/src/test/` for shared logic
|
||||
- **Test Flavors**: Separate test configurations for FOSS and Google Play variants
|
||||
|
||||
## Key Dependencies
|
||||
|
||||
- **Hilt**: Dependency injection framework
|
||||
- **AndroidX Navigation**: Fragment navigation with SafeArgs
|
||||
- **kotlinx.serialization**: JSON serialization for configuration and caching
|
||||
- **Material Design**: UI components following Material Design guidelines
|
||||
@@ -0,0 +1,71 @@
|
||||
---
|
||||
description: Build, test, lint, and release commands for Gradle
|
||||
globs:
|
||||
- "**/*.gradle.kts"
|
||||
- "**/*.gradle"
|
||||
- "gradle/**"
|
||||
---
|
||||
|
||||
# Build Commands
|
||||
|
||||
## Build
|
||||
|
||||
```bash
|
||||
# Build debug version
|
||||
./gradlew assembleDebug
|
||||
|
||||
# Build all variants (FOSS and Google Play flavors)
|
||||
./gradlew assemble
|
||||
|
||||
# Build specific flavor and type
|
||||
./gradlew assembleFossDebug
|
||||
./gradlew assembleGplayRelease
|
||||
|
||||
# Build app bundles for Play Store
|
||||
./gradlew bundleGplayRelease
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
# Run all unit tests
|
||||
./gradlew test
|
||||
|
||||
# Run unit tests for specific variant
|
||||
./gradlew testFossDebugUnitTest
|
||||
|
||||
# Run instrumentation tests (requires connected device/emulator)
|
||||
./gradlew connectedAndroidTest
|
||||
./gradlew connectedFossDebugAndroidTest
|
||||
|
||||
# Run all checks (lint + tests)
|
||||
./gradlew check
|
||||
```
|
||||
|
||||
## Code Quality
|
||||
|
||||
```bash
|
||||
# Run lint for all variants
|
||||
./gradlew lint
|
||||
|
||||
# Run lint for specific variant
|
||||
./gradlew lintFossDebug
|
||||
|
||||
# Auto-fix lint issues where possible
|
||||
./gradlew lintFix
|
||||
|
||||
# Update lint baseline
|
||||
./gradlew updateLintBaseline
|
||||
```
|
||||
|
||||
## Release
|
||||
|
||||
```bash
|
||||
./gradlew assembleFossRelease assembleGplayRelease
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- Use `assembleFossDebug` as the default quick-check build (fastest variant)
|
||||
- Run `./gradlew check` before submitting changes to catch lint and test issues
|
||||
- Instrumentation tests require a connected device or running emulator
|
||||
@@ -0,0 +1,41 @@
|
||||
---
|
||||
description: Git commit message format and conventions
|
||||
globs:
|
||||
- "**"
|
||||
---
|
||||
|
||||
# Commit Guidelines
|
||||
|
||||
## Format
|
||||
|
||||
```
|
||||
<prefix>: <Short summary>
|
||||
```
|
||||
|
||||
Summary line should be concise and describe the change. No period at the end.
|
||||
|
||||
## Prefixes
|
||||
|
||||
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
|
||||
- **chore**: Maintenance, dependency updates, build config
|
||||
- **docs**: Documentation changes
|
||||
|
||||
## Component Scope (optional)
|
||||
|
||||
When a change is scoped to a specific area, include it after the prefix:
|
||||
|
||||
- `fix(widget): Fix layout for devices with single charge detection`
|
||||
- `feat(monitor): Add battery level caching`
|
||||
- `refactor(popup): Extract pod view factory`
|
||||
|
||||
## Rules
|
||||
|
||||
- Keep the summary line under 72 characters
|
||||
- Use imperative mood ("Add feature" not "Added feature")
|
||||
- Reference issue numbers when applicable
|
||||
- Do not include `Co-authored-by` trailers
|
||||
- Look at recent `git log` output to match the project's existing style
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
description: Guidelines for adding and naming Android string resources
|
||||
globs:
|
||||
- "**/res/values*/strings.xml"
|
||||
---
|
||||
|
||||
# Localization Guidelines
|
||||
|
||||
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`)
|
||||
- **Provide context**: String names should indicate usage and location
|
||||
- **Consider pluralization**: Use Android plural resources (`<plurals>`) when quantities vary
|
||||
|
||||
## Naming Examples
|
||||
|
||||
- `profiles_create_title` (screen title)
|
||||
- `profiles_name_label` (form field label)
|
||||
- `profiles_delete_confirmation` (dialog message)
|
||||
- `error_network_unavailable` (error message)
|
||||
@@ -0,0 +1,78 @@
|
||||
---
|
||||
description: Pull request naming and description conventions
|
||||
globs:
|
||||
- "**"
|
||||
---
|
||||
|
||||
# Pull Request Guidelines
|
||||
|
||||
## PR Title Format
|
||||
|
||||
```
|
||||
<Category>: <Short user-facing summary>
|
||||
```
|
||||
|
||||
PR titles appear in auto-generated changelogs and are read by users. Use **ELI5, user-facing language** — no internal class names, library names, or implementation details.
|
||||
|
||||
## Category Prefixes
|
||||
|
||||
| Prefix | Covers |
|
||||
|--------|--------|
|
||||
| **Widget** | Home screen widget |
|
||||
| **Reaction** | Case-open popup, auto-play/pause, notification triggers |
|
||||
| **Device** | AirPods detection, compatibility, Bluetooth scanning, battery reading, device profiles |
|
||||
| **General** | Dashboard, settings, notifications, themes, onboarding, support, app-wide UI |
|
||||
| **Fix** | Bug fixes spanning multiple areas |
|
||||
|
||||
### Title Examples
|
||||
|
||||
- `Widget: Add color themes and transparency slider`
|
||||
- `Reaction: Fix popup appearing twice when opening AirPods case`
|
||||
- `Device: Add support for AirPods 4 with ANC`
|
||||
- `General: Add dark mode and color theme settings`
|
||||
- `Fix: Fix battery display stuck at 0% after reconnecting`
|
||||
|
||||
### Bad Titles (too technical)
|
||||
|
||||
- `refactor(settings): Migrate preferences to AndroidX DataStore`
|
||||
- `feat(widget): Migrate to Jetpack Glance`
|
||||
- `refactor(ui): Migrate from Fragments to Jetpack Compose`
|
||||
|
||||
## PR Description Format
|
||||
|
||||
### 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.
|
||||
|
||||
For non-user-facing PRs (refactors, tests, CI, dependency bumps): write "No user-facing behavior change" followed by a brief internal description.
|
||||
|
||||
### Technical Context
|
||||
|
||||
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).
|
||||
|
||||
### Example
|
||||
|
||||
```markdown
|
||||
## What changed
|
||||
|
||||
Fixed a crash that could happen when the AirPods case is opened while Bluetooth is turning off.
|
||||
|
||||
## Technical Context
|
||||
|
||||
- Root cause: `MonitorService` continued processing scan results during Bluetooth adapter state change, hitting a null adapter reference
|
||||
- Chose to gate on adapter state in the scan callback rather than adding a separate BroadcastReceiver, since the service already observes adapter state for restart logic
|
||||
- The timing window is ~200ms between ACTION_STATE_CHANGING and ACTION_STATE_OFF — only reproducible on Pixel devices with aggressive Bluetooth power management
|
||||
```
|
||||
|
||||
## Conventions
|
||||
|
||||
- **Issue references**: Use "Closes #123", "Fixes #123", or "Resolves #123"
|
||||
- **Breaking changes**: Mark with "BREAKING:" prefix if applicable
|
||||
- **No `Co-authored-by` trailers** (per project convention)
|
||||
@@ -0,0 +1,68 @@
|
||||
# Play Store Screenshot Pipeline
|
||||
|
||||
## Overview
|
||||
|
||||
Localized screenshots are generated using Compose Preview Screenshot Testing (alpha), rendered offline (no device needed), and sorted into fastlane metadata directories for Play Store upload.
|
||||
|
||||
## Pipeline
|
||||
|
||||
```
|
||||
ScreenshotContent.kt (mock data + composables)
|
||||
→ PlayStoreScreenshots.kt (@PreviewTest entry points)
|
||||
→ PlayStoreLocales.kt (multi-preview locale annotations, auto-generated per batch)
|
||||
→ generate_screenshots.sh (batched Gradle runs to avoid OOM)
|
||||
→ copy_screenshots.sh (sort PNGs into fastlane locale dirs)
|
||||
→ fastlane/metadata/android/{locale}/images/phoneScreenshots/
|
||||
```
|
||||
|
||||
## Key Files
|
||||
|
||||
| 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/screenshotTest/kotlin/.../screenshots/PlayStoreLocales.kt` | Multi-preview annotations (auto-generated by batch script) |
|
||||
| `fastlane/generate_screenshots.sh` | Batched generation across 68 locales |
|
||||
| `fastlane/copy_screenshots.sh` | Copies rendered PNGs into fastlane structure |
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
# Full run — all 68 locales, ~12 batches, ~7 minutes
|
||||
./fastlane/generate_screenshots.sh
|
||||
|
||||
# Smoke test — 6 locales (en, de, ja, ar, zh-CN, pt-BR), single batch
|
||||
./fastlane/generate_screenshots.sh --smoke
|
||||
|
||||
# Copy into fastlane directories (run after generate)
|
||||
./fastlane/copy_screenshots.sh
|
||||
|
||||
# Clean copy (removes old screenshots first)
|
||||
./fastlane/copy_screenshots.sh --clean
|
||||
```
|
||||
|
||||
## Adding a New Screenshot
|
||||
|
||||
1. Add a composable content function in `ScreenshotContent.kt` (e.g. `NewScreenContent()`)
|
||||
2. Add a `@PreviewTest` function in `PlayStoreScreenshots.kt` that calls it
|
||||
3. Add the function name → filename mapping in `copy_screenshots.sh` `SCREEN_MAP`
|
||||
4. Update the expected count in `generate_screenshots.sh` (composables per locale)
|
||||
5. Run the full pipeline: `generate_screenshots.sh` then `copy_screenshots.sh`
|
||||
|
||||
## After UI Changes
|
||||
|
||||
When modifying a screen that appears in screenshots (check `ScreenshotContent.kt`), regenerate:
|
||||
|
||||
```bash
|
||||
./fastlane/generate_screenshots.sh
|
||||
./fastlane/copy_screenshots.sh --clean
|
||||
```
|
||||
|
||||
## Technical Notes
|
||||
|
||||
- Batch size defaults to 2 locales (16 renders) to avoid layoutlib memory leak (~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)
|
||||
- Uses `com.android.compose.screenshot` plugin v0.0.1-alpha13
|
||||
- Output: `app/src/screenshotTestGplayDebug/reference/`
|
||||
+8
-13
@@ -2,20 +2,15 @@
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"mcp__ide__getDiagnostics",
|
||||
"Bash(./gradlew tasks:*)",
|
||||
"Bash(./gradlew:*)",
|
||||
"Bash(find:*)",
|
||||
"Bash(ls:*)",
|
||||
"Bash(grep:*)",
|
||||
"Bash(rg:*)",
|
||||
"WebSearch",
|
||||
"WebFetch(domain:support.google.com)",
|
||||
"WebFetch(domain:github.com)",
|
||||
"WebFetch(domain:mvnrepository.com)",
|
||||
"WebFetch(domain:kotlinlang.org)",
|
||||
"WebFetch(domain:developer.android.com)",
|
||||
"WebFetch(domain:issuetracker.google.com)"
|
||||
"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
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Script to fix import statements after removing app-common module.
|
||||
This script fixes R class imports from app-common to app module.
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
def fix_r_import(file_path):
|
||||
"""Fix R import in a single file."""
|
||||
print(f"Fixing R import in: {file_path}")
|
||||
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# Replace the import statement
|
||||
updated_content = re.sub(
|
||||
r'import eu\.darken\.capod\.common\.R$',
|
||||
'import eu.darken.capod.R',
|
||||
content,
|
||||
flags=re.MULTILINE
|
||||
)
|
||||
|
||||
if updated_content != content:
|
||||
with open(file_path, 'w', encoding='utf-8') as f:
|
||||
f.write(updated_content)
|
||||
print(f" ✓ Updated import in {file_path}")
|
||||
return True
|
||||
else:
|
||||
print(f" - No changes needed in {file_path}")
|
||||
return False
|
||||
|
||||
def main():
|
||||
"""Main function to fix all import statements."""
|
||||
project_root = Path.cwd()
|
||||
app_src = project_root / "app" / "src"
|
||||
|
||||
# Find all Kotlin files that import eu.darken.capod.common.R
|
||||
files_to_fix = []
|
||||
for kt_file in app_src.rglob("*.kt"):
|
||||
with open(kt_file, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
if re.search(r'import eu\.darken\.capod\.common\.R$', content, re.MULTILINE):
|
||||
files_to_fix.append(kt_file)
|
||||
|
||||
print(f"Found {len(files_to_fix)} files with incorrect R imports")
|
||||
|
||||
success_count = 0
|
||||
for file_path in files_to_fix:
|
||||
if fix_r_import(file_path):
|
||||
success_count += 1
|
||||
|
||||
print(f"\nFixed imports in {success_count} files")
|
||||
return 0
|
||||
|
||||
if __name__ == "__main__":
|
||||
exit(main())
|
||||
@@ -1,58 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Script to fix fully qualified R references in code.
|
||||
This script finds and replaces eu.darken.capod.common.R with R.
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
def fix_qualified_r_references(file_path):
|
||||
"""Fix fully qualified R references in a single file."""
|
||||
print(f"Fixing qualified R references in: {file_path}")
|
||||
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# Replace fully qualified references
|
||||
updated_content = re.sub(
|
||||
r'\beu\.darken\.capod\.common\.R\.',
|
||||
'R.',
|
||||
content
|
||||
)
|
||||
|
||||
if updated_content != content:
|
||||
with open(file_path, 'w', encoding='utf-8') as f:
|
||||
f.write(updated_content)
|
||||
print(f" ✓ Fixed qualified R references in {file_path}")
|
||||
return True
|
||||
else:
|
||||
print(f" - No changes needed in {file_path}")
|
||||
return False
|
||||
|
||||
def main():
|
||||
"""Main function to fix all qualified R references."""
|
||||
project_root = Path.cwd()
|
||||
app_src = project_root / "app" / "src"
|
||||
|
||||
# Find all Kotlin files that have qualified R references
|
||||
files_to_fix = []
|
||||
for kt_file in app_src.rglob("*.kt"):
|
||||
with open(kt_file, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
if re.search(r'\beu\.darken\.capod\.common\.R\.', content):
|
||||
files_to_fix.append(kt_file)
|
||||
|
||||
print(f"Found {len(files_to_fix)} files with qualified R references")
|
||||
|
||||
success_count = 0
|
||||
for file_path in files_to_fix:
|
||||
if fix_qualified_r_references(file_path):
|
||||
success_count += 1
|
||||
|
||||
print(f"\nFixed qualified R references in {success_count} files")
|
||||
return 0
|
||||
|
||||
if __name__ == "__main__":
|
||||
exit(main())
|
||||
@@ -1,87 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Script to fix remaining R import issues.
|
||||
This script finds files that use R.* but don't have R imports and adds them.
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
def needs_r_import(file_path):
|
||||
"""Check if file uses R.* but doesn't import R."""
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# Check if file uses R.something
|
||||
uses_r = re.search(r'\bR\.[a-zA-Z_]', content)
|
||||
if not uses_r:
|
||||
return False
|
||||
|
||||
# Check if file already imports R
|
||||
has_import = re.search(r'import.*\.R$', content, re.MULTILINE)
|
||||
if has_import:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def add_r_import(file_path):
|
||||
"""Add R import to a file."""
|
||||
print(f"Adding R import to: {file_path}")
|
||||
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# Find the package line
|
||||
package_match = re.search(r'^package\s+[^\n]+$', content, re.MULTILINE)
|
||||
if not package_match:
|
||||
print(f" Error: No package declaration found in {file_path}")
|
||||
return False
|
||||
|
||||
# Find existing imports
|
||||
import_pattern = r'^import\s+[^\n]+$'
|
||||
existing_imports = list(re.finditer(import_pattern, content, re.MULTILINE))
|
||||
|
||||
if existing_imports:
|
||||
# Insert after the last import
|
||||
last_import = existing_imports[-1]
|
||||
insert_pos = last_import.end()
|
||||
updated_content = (content[:insert_pos] +
|
||||
"\nimport eu.darken.capod.R" +
|
||||
content[insert_pos:])
|
||||
else:
|
||||
# Insert after package line
|
||||
insert_pos = package_match.end()
|
||||
updated_content = (content[:insert_pos] +
|
||||
"\n\nimport eu.darken.capod.R" +
|
||||
content[insert_pos:])
|
||||
|
||||
with open(file_path, 'w', encoding='utf-8') as f:
|
||||
f.write(updated_content)
|
||||
|
||||
print(f" ✓ Added R import to {file_path}")
|
||||
return True
|
||||
|
||||
def main():
|
||||
"""Main function to fix all R import issues."""
|
||||
project_root = Path.cwd()
|
||||
app_src = project_root / "app" / "src"
|
||||
|
||||
# Find all Kotlin files that need R import
|
||||
files_to_fix = []
|
||||
for kt_file in app_src.rglob("*.kt"):
|
||||
if needs_r_import(kt_file):
|
||||
files_to_fix.append(kt_file)
|
||||
|
||||
print(f"Found {len(files_to_fix)} files that need R imports")
|
||||
|
||||
success_count = 0
|
||||
for file_path in files_to_fix:
|
||||
if add_r_import(file_path):
|
||||
success_count += 1
|
||||
|
||||
print(f"\nAdded R imports to {success_count} files")
|
||||
return 0
|
||||
|
||||
if __name__ == "__main__":
|
||||
exit(main())
|
||||
@@ -1,105 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Script to merge strings.xml files from app-common module to app module.
|
||||
This script merges all string entries from app-common into the corresponding app module files.
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import xml.etree.ElementTree as ET
|
||||
from pathlib import Path
|
||||
|
||||
def merge_strings_xml(app_common_file, app_file):
|
||||
"""Merge strings from app-common file into app file."""
|
||||
print(f"Processing: {app_common_file.name} -> {app_file.name}")
|
||||
|
||||
# Read the app-common strings.xml content
|
||||
with open(app_common_file, 'r', encoding='utf-8') as f:
|
||||
common_content = f.read()
|
||||
|
||||
# Read the app strings.xml content
|
||||
with open(app_file, 'r', encoding='utf-8') as f:
|
||||
app_content = f.read()
|
||||
|
||||
# Extract string entries from app-common (including comments)
|
||||
# Find everything between <resources> and </resources> tags, excluding the tags themselves
|
||||
common_match = re.search(r'<resources[^>]*>(.*?)</resources>', common_content, re.DOTALL)
|
||||
if not common_match:
|
||||
print(f" Warning: No resources found in {app_common_file}")
|
||||
return False
|
||||
|
||||
common_strings = common_match.group(1).strip()
|
||||
|
||||
if not common_strings:
|
||||
print(f" Warning: No string content found in {app_common_file}")
|
||||
return False
|
||||
|
||||
# Find the insertion point in the app file (before </resources>)
|
||||
app_match = re.search(r'(.*?)(\s*</resources>)', app_content, re.DOTALL)
|
||||
if not app_match:
|
||||
print(f" Error: Invalid XML structure in {app_file}")
|
||||
return False
|
||||
|
||||
# Merge the content
|
||||
before_closing = app_match.group(1)
|
||||
closing_tag = app_match.group(2)
|
||||
|
||||
# Add the common strings with proper spacing
|
||||
merged_content = f"{before_closing}\n\n <!-- Strings from app-common -->\n{common_strings}\n{closing_tag}"
|
||||
|
||||
# Write the merged content back to the app file
|
||||
with open(app_file, 'w', encoding='utf-8') as f:
|
||||
f.write(merged_content)
|
||||
|
||||
print(f" ✓ Merged successfully")
|
||||
return True
|
||||
|
||||
def main():
|
||||
"""Main function to merge all strings.xml files."""
|
||||
project_root = Path.cwd()
|
||||
app_common_res = project_root / "app-common" / "src" / "main" / "res"
|
||||
app_res = project_root / "app" / "src" / "main" / "res"
|
||||
|
||||
if not app_common_res.exists():
|
||||
print(f"Error: app-common resources directory not found: {app_common_res}")
|
||||
return 1
|
||||
|
||||
if not app_res.exists():
|
||||
print(f"Error: app resources directory not found: {app_res}")
|
||||
return 1
|
||||
|
||||
# Find all strings.xml files in app-common
|
||||
common_strings_files = list(app_common_res.glob("*/strings.xml"))
|
||||
|
||||
if not common_strings_files:
|
||||
print("Error: No strings.xml files found in app-common")
|
||||
return 1
|
||||
|
||||
print(f"Found {len(common_strings_files)} strings.xml files to merge")
|
||||
|
||||
success_count = 0
|
||||
error_count = 0
|
||||
|
||||
for common_file in sorted(common_strings_files):
|
||||
# Determine the corresponding app file
|
||||
locale_dir = common_file.parent.name
|
||||
app_file = app_res / locale_dir / "strings.xml"
|
||||
|
||||
if not app_file.exists():
|
||||
print(f" Error: Corresponding app file does not exist: {app_file}")
|
||||
error_count += 1
|
||||
continue
|
||||
|
||||
if merge_strings_xml(common_file, app_file):
|
||||
success_count += 1
|
||||
else:
|
||||
error_count += 1
|
||||
|
||||
print(f"\nMerge complete:")
|
||||
print(f" ✓ Successfully merged: {success_count}")
|
||||
print(f" ✗ Errors: {error_count}")
|
||||
|
||||
return 0 if error_count == 0 else 1
|
||||
|
||||
if __name__ == "__main__":
|
||||
exit(main())
|
||||
@@ -48,7 +48,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
variant: [ Debug,Beta,Release ]
|
||||
variant: [ Debug ]
|
||||
flavor: [ testFoss,testGplay ]
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
|
||||
@@ -65,7 +65,7 @@ jobs:
|
||||
tag_name: ${{ steps.tagger.outputs.tag }}
|
||||
name: ${{ steps.tagger.outputs.tag }}
|
||||
generate_release_notes: true
|
||||
files: app/build/outputs/apk/foss/beta/*.apk
|
||||
files: app/build/outputs/apk/foss/beta/eu.darken.capod-*.apk
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -77,7 +77,7 @@ jobs:
|
||||
tag_name: ${{ steps.tagger.outputs.tag }}
|
||||
name: ${{ steps.tagger.outputs.tag }}
|
||||
generate_release_notes: true
|
||||
files: app/build/outputs/apk/foss/release/*.apk
|
||||
files: app/build/outputs/apk/foss/release/eu.darken.capod-*.apk
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
|
||||
+3
-1
@@ -14,4 +14,6 @@
|
||||
/fastlane/report.xml
|
||||
/fastlane/Appfile
|
||||
/fastlane/README.md
|
||||
.kotlin
|
||||
.kotlin
|
||||
# Screenshot test reference images (ephemeral, regenerated on demand)
|
||||
app/src/screenshotTest*/reference/
|
||||
@@ -1,158 +0,0 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Commands
|
||||
|
||||
### Build Commands
|
||||
|
||||
```bash
|
||||
# Build debug version
|
||||
./gradlew assembleDebug
|
||||
|
||||
# Build all variants (FOSS and Google Play flavors)
|
||||
./gradlew assemble
|
||||
|
||||
# Build specific flavor and type
|
||||
./gradlew assembleFossDebug
|
||||
./gradlew assembleGplayRelease
|
||||
|
||||
# Build app bundles for Play Store
|
||||
./gradlew bundleGplayRelease
|
||||
```
|
||||
|
||||
### Testing Commands
|
||||
|
||||
```bash
|
||||
# Run all unit tests
|
||||
./gradlew test
|
||||
|
||||
# Run unit tests for specific variant
|
||||
./gradlew testFossDebugUnitTest
|
||||
|
||||
# Run instrumentation tests (requires connected device/emulator)
|
||||
./gradlew connectedAndroidTest
|
||||
./gradlew connectedFossDebugAndroidTest
|
||||
|
||||
# Run all checks (lint + tests)
|
||||
./gradlew check
|
||||
```
|
||||
|
||||
### Code Quality Commands
|
||||
|
||||
```bash
|
||||
# Run lint for all variants
|
||||
./gradlew lint
|
||||
|
||||
# Run lint for specific variant
|
||||
./gradlew lintFossDebug
|
||||
|
||||
# Auto-fix lint issues where possible
|
||||
./gradlew lintFix
|
||||
|
||||
# Update lint baseline
|
||||
./gradlew updateLintBaseline
|
||||
```
|
||||
|
||||
### Release Commands
|
||||
|
||||
```bash
|
||||
./gradlew assembleFossRelease assembleGplayRelease
|
||||
```
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
### Multi-Module Structure
|
||||
|
||||
- **app/**: Main Android application with FOSS and Google Play flavors
|
||||
- **app-common/**: Shared code between main app
|
||||
|
||||
### Core Architecture Patterns
|
||||
|
||||
- **MVVM**: ViewModels with LiveData/StateFlow for UI state management
|
||||
- **Dependency Injection**: Hilt/Dagger for dependency management
|
||||
- **Coroutines**: Extensive use of Kotlin coroutines for async operations
|
||||
- **Repository Pattern**: Data layer abstraction for monitoring and settings
|
||||
|
||||
### Key Components
|
||||
|
||||
#### PodMonitor System
|
||||
|
||||
- `PodMonitor`: Core service that detects and tracks AirPods via Bluetooth LE
|
||||
- `MonitorControl`: Manages background monitoring worker lifecycle
|
||||
- `MonitorWorker`: Background worker that continuously scans for AirPods
|
||||
- `BluetoothEventReceiver`: Handles system Bluetooth events
|
||||
|
||||
#### Reaction System
|
||||
|
||||
- `ReactionSettingsFragment`: Configuration for popup notifications
|
||||
- `PopUpWindow`: Displays AirPods status when case is opened
|
||||
- `PopUpPodViewFactory`: Creates UI components for different pod models
|
||||
|
||||
#### Common Utilities
|
||||
|
||||
- `EdgeToEdgeHelper`: Handles Android edge-to-edge display insets
|
||||
|
||||
### Build Configuration
|
||||
|
||||
#### Flavors
|
||||
|
||||
- **FOSS**: Open-source version without Google Play dependencies
|
||||
- **Google Play**: Version with billing client for in-app purchases
|
||||
|
||||
#### Build Types
|
||||
|
||||
- **debug**: Unobfuscated, full logging, no minification
|
||||
- **beta**: Obfuscated, production-ready with strict lint checks
|
||||
- **release**: Fully optimized for production distribution
|
||||
|
||||
### Data Flow Architecture
|
||||
|
||||
The app follows a unidirectional data flow:
|
||||
|
||||
1. `BluetoothEventReceiver` detects Bluetooth events
|
||||
2. `MonitorWorker` 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
|
||||
|
||||
### Testing Strategy
|
||||
|
||||
- **Unit Tests**: Located in `app-common/src/test/` for shared logic
|
||||
- **Test Flavors**: Separate test configurations for FOSS and Google Play variants
|
||||
|
||||
### Key Dependencies
|
||||
|
||||
- **Hilt**: Dependency injection framework
|
||||
- **AndroidX Navigation**: Fragment navigation with SafeArgs
|
||||
- **WorkManager**: Background task scheduling for monitoring
|
||||
- **Moshi**: JSON serialization for configuration and debugging
|
||||
- **Material Design**: UI components following Material Design guidelines
|
||||
|
||||
## Development Notes
|
||||
|
||||
### Bluetooth LE Implementation
|
||||
|
||||
The app uses Android's Bluetooth LE APIs to scan for Apple device advertisements. The core scanning logic is in
|
||||
`MonitorWorker` which runs as a long-lived background task.
|
||||
|
||||
### 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.
|
||||
|
||||
### Localization Guidelines
|
||||
|
||||
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`)
|
||||
- **Provide context**: String names should indicate usage and location
|
||||
- **Consider pluralization**: Use Android plural resources (`<plurals>`) when quantities vary
|
||||
|
||||
Examples of correct string naming:
|
||||
- `profiles_create_title` (screen title)
|
||||
- `profiles_name_label` (form field label)
|
||||
- `profiles_delete_confirmation` (dialog message)
|
||||
- `error_network_unavailable` (error message)
|
||||
+70
-59
@@ -1,48 +1,49 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
CFPropertyList (3.0.7)
|
||||
base64
|
||||
nkf
|
||||
rexml
|
||||
addressable (2.8.7)
|
||||
public_suffix (>= 2.0.2, < 7.0)
|
||||
CFPropertyList (3.0.8)
|
||||
abbrev (0.1.2)
|
||||
addressable (2.8.8)
|
||||
public_suffix (>= 2.0.2, < 8.0)
|
||||
artifactory (3.0.17)
|
||||
atomos (0.1.3)
|
||||
aws-eventstream (1.3.2)
|
||||
aws-partitions (1.1109.0)
|
||||
aws-sdk-core (3.224.1)
|
||||
aws-eventstream (1.4.0)
|
||||
aws-partitions (1.1216.0)
|
||||
aws-sdk-core (3.242.0)
|
||||
aws-eventstream (~> 1, >= 1.3.0)
|
||||
aws-partitions (~> 1, >= 1.992.0)
|
||||
aws-sigv4 (~> 1.9)
|
||||
base64
|
||||
bigdecimal
|
||||
jmespath (~> 1, >= 1.6.1)
|
||||
logger
|
||||
aws-sdk-kms (1.101.0)
|
||||
aws-sdk-core (~> 3, >= 3.216.0)
|
||||
aws-sdk-kms (1.122.0)
|
||||
aws-sdk-core (~> 3, >= 3.241.4)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sdk-s3 (1.188.0)
|
||||
aws-sdk-core (~> 3, >= 3.224.1)
|
||||
aws-sdk-s3 (1.213.0)
|
||||
aws-sdk-core (~> 3, >= 3.241.4)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sigv4 (1.11.0)
|
||||
aws-sigv4 (1.12.1)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
babosa (1.0.4)
|
||||
base64 (0.3.0)
|
||||
base64 (0.2.0)
|
||||
benchmark (0.5.0)
|
||||
bigdecimal (4.0.1)
|
||||
claide (1.1.0)
|
||||
colored (1.2)
|
||||
colored2 (3.1.2)
|
||||
commander (4.6.0)
|
||||
highline (~> 2.0.0)
|
||||
csv (3.3.5)
|
||||
declarative (0.0.20)
|
||||
digest-crc (0.7.0)
|
||||
rake (>= 12.0.0, < 14.0.0)
|
||||
domain_name (0.5.20190701)
|
||||
unf (>= 0.0.5, < 1.0.0)
|
||||
domain_name (0.6.20240107)
|
||||
dotenv (2.8.1)
|
||||
emoji_regex (3.2.3)
|
||||
excon (0.109.0)
|
||||
faraday (1.10.4)
|
||||
excon (0.112.0)
|
||||
faraday (1.10.5)
|
||||
faraday-em_http (~> 1.0)
|
||||
faraday-em_synchrony (~> 1.0)
|
||||
faraday-excon (~> 1.1)
|
||||
@@ -54,14 +55,14 @@ GEM
|
||||
faraday-rack (~> 1.0)
|
||||
faraday-retry (~> 1.0)
|
||||
ruby2_keywords (>= 0.0.4)
|
||||
faraday-cookie_jar (0.0.7)
|
||||
faraday-cookie_jar (0.0.8)
|
||||
faraday (>= 0.8.0)
|
||||
http-cookie (~> 1.0.0)
|
||||
http-cookie (>= 1.0.0)
|
||||
faraday-em_http (1.0.0)
|
||||
faraday-em_synchrony (1.0.1)
|
||||
faraday-excon (1.1.0)
|
||||
faraday-httpclient (1.0.1)
|
||||
faraday-multipart (1.1.1)
|
||||
faraday-multipart (1.2.0)
|
||||
multipart-post (~> 2.0)
|
||||
faraday-net_http (1.0.2)
|
||||
faraday-net_http_persistent (1.2.0)
|
||||
@@ -71,15 +72,19 @@ GEM
|
||||
faraday_middleware (1.2.1)
|
||||
faraday (~> 1.0)
|
||||
fastimage (2.4.0)
|
||||
fastlane (2.228.0)
|
||||
fastlane (2.232.1)
|
||||
CFPropertyList (>= 2.3, < 4.0.0)
|
||||
abbrev (~> 0.1.2)
|
||||
addressable (>= 2.8, < 3.0.0)
|
||||
artifactory (~> 3.0)
|
||||
aws-sdk-s3 (~> 1.0)
|
||||
aws-sdk-s3 (~> 1.197)
|
||||
babosa (>= 1.0.3, < 2.0.0)
|
||||
bundler (>= 1.12.0, < 3.0.0)
|
||||
base64 (~> 0.2.0)
|
||||
benchmark (>= 0.1.0)
|
||||
bundler (>= 1.17.3, < 5.0.0)
|
||||
colored (~> 1.2)
|
||||
commander (~> 4.6)
|
||||
csv (~> 3.3)
|
||||
dotenv (>= 2.1.1, < 3.0.0)
|
||||
emoji_regex (>= 0.1, < 4.0)
|
||||
excon (>= 0.71.0, < 1.0.0)
|
||||
@@ -91,16 +96,20 @@ GEM
|
||||
gh_inspector (>= 1.1.2, < 2.0.0)
|
||||
google-apis-androidpublisher_v3 (~> 0.3)
|
||||
google-apis-playcustomapp_v1 (~> 0.1)
|
||||
google-cloud-env (>= 1.6.0, < 2.0.0)
|
||||
google-cloud-env (>= 1.6.0, <= 2.1.1)
|
||||
google-cloud-storage (~> 1.31)
|
||||
highline (~> 2.0)
|
||||
http-cookie (~> 1.0.5)
|
||||
json (< 3.0.0)
|
||||
jwt (>= 2.1.0, < 3)
|
||||
logger (>= 1.6, < 2.0)
|
||||
mini_magick (>= 4.9.4, < 5.0.0)
|
||||
multipart-post (>= 2.0.0, < 3.0.0)
|
||||
mutex_m (~> 0.3.0)
|
||||
naturally (~> 2.2)
|
||||
nkf (~> 0.2.0)
|
||||
optparse (>= 0.1.1, < 1.0.0)
|
||||
ostruct (>= 0.1.0)
|
||||
plist (>= 3.1.0, < 4.0.0)
|
||||
rubyzip (>= 2.0.0, < 3.0.0)
|
||||
security (= 0.1.5)
|
||||
@@ -116,38 +125,40 @@ GEM
|
||||
fastlane-sirp (1.0.0)
|
||||
sysrandom (~> 1.0)
|
||||
gh_inspector (1.1.3)
|
||||
google-apis-androidpublisher_v3 (0.54.0)
|
||||
google-apis-core (>= 0.11.0, < 2.a)
|
||||
google-apis-core (0.11.3)
|
||||
google-apis-androidpublisher_v3 (0.96.0)
|
||||
google-apis-core (>= 0.15.0, < 2.a)
|
||||
google-apis-core (0.18.0)
|
||||
addressable (~> 2.5, >= 2.5.1)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
httpclient (>= 2.8.1, < 3.a)
|
||||
googleauth (~> 1.9)
|
||||
httpclient (>= 2.8.3, < 3.a)
|
||||
mini_mime (~> 1.0)
|
||||
mutex_m
|
||||
representable (~> 3.0)
|
||||
retriable (>= 2.0, < 4.a)
|
||||
rexml
|
||||
google-apis-iamcredentials_v1 (0.17.0)
|
||||
google-apis-core (>= 0.11.0, < 2.a)
|
||||
google-apis-playcustomapp_v1 (0.13.0)
|
||||
google-apis-core (>= 0.11.0, < 2.a)
|
||||
google-apis-storage_v1 (0.29.0)
|
||||
google-apis-core (>= 0.11.0, < 2.a)
|
||||
google-cloud-core (1.6.1)
|
||||
google-apis-iamcredentials_v1 (0.26.0)
|
||||
google-apis-core (>= 0.15.0, < 2.a)
|
||||
google-apis-playcustomapp_v1 (0.17.0)
|
||||
google-apis-core (>= 0.15.0, < 2.a)
|
||||
google-apis-storage_v1 (0.60.0)
|
||||
google-apis-core (>= 0.15.0, < 2.a)
|
||||
google-cloud-core (1.8.0)
|
||||
google-cloud-env (>= 1.0, < 3.a)
|
||||
google-cloud-errors (~> 1.0)
|
||||
google-cloud-env (1.6.0)
|
||||
faraday (>= 0.17.3, < 3.0)
|
||||
google-cloud-errors (1.3.1)
|
||||
google-cloud-storage (1.45.0)
|
||||
google-cloud-env (2.1.1)
|
||||
faraday (>= 1.0, < 3.a)
|
||||
google-cloud-errors (1.5.0)
|
||||
google-cloud-storage (1.58.0)
|
||||
addressable (~> 2.8)
|
||||
digest-crc (~> 0.4)
|
||||
google-apis-iamcredentials_v1 (~> 0.1)
|
||||
google-apis-storage_v1 (~> 0.29.0)
|
||||
google-apis-core (>= 0.18, < 2)
|
||||
google-apis-iamcredentials_v1 (~> 0.18)
|
||||
google-apis-storage_v1 (>= 0.42)
|
||||
google-cloud-core (~> 1.6)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
googleauth (~> 1.9)
|
||||
mini_mime (~> 1.0)
|
||||
googleauth (1.8.1)
|
||||
faraday (>= 0.17.3, < 3.a)
|
||||
googleauth (1.11.2)
|
||||
faraday (>= 1.0, < 3.a)
|
||||
google-cloud-env (~> 2.1)
|
||||
jwt (>= 1.4, < 3.0)
|
||||
multi_json (~> 1.11)
|
||||
os (>= 0.9, < 2.0)
|
||||
@@ -158,37 +169,38 @@ GEM
|
||||
httpclient (2.9.0)
|
||||
mutex_m
|
||||
jmespath (1.6.2)
|
||||
json (2.7.6)
|
||||
jwt (2.10.1)
|
||||
json (2.18.1)
|
||||
jwt (2.10.2)
|
||||
base64
|
||||
logger (1.7.0)
|
||||
mini_magick (4.13.2)
|
||||
mini_mime (1.1.5)
|
||||
multi_json (1.15.0)
|
||||
multi_json (1.19.1)
|
||||
multipart-post (2.4.1)
|
||||
mutex_m (0.3.0)
|
||||
nanaimo (0.4.0)
|
||||
naturally (2.3.0)
|
||||
nkf (0.2.0)
|
||||
optparse (0.6.0)
|
||||
optparse (0.8.1)
|
||||
os (1.1.4)
|
||||
ostruct (0.6.3)
|
||||
plist (3.7.2)
|
||||
public_suffix (5.1.1)
|
||||
rake (13.3.0)
|
||||
public_suffix (7.0.2)
|
||||
rake (13.3.1)
|
||||
representable (3.2.0)
|
||||
declarative (< 0.1.0)
|
||||
trailblazer-option (>= 0.1.1, < 0.2.0)
|
||||
uber (< 0.2.0)
|
||||
retriable (3.1.2)
|
||||
rexml (3.4.1)
|
||||
retriable (3.2.0)
|
||||
rexml (3.4.4)
|
||||
rouge (3.28.0)
|
||||
ruby2_keywords (0.0.5)
|
||||
rubyzip (2.4.1)
|
||||
security (0.1.5)
|
||||
signet (0.18.0)
|
||||
signet (0.21.0)
|
||||
addressable (~> 2.8)
|
||||
faraday (>= 0.17.5, < 3.a)
|
||||
jwt (>= 1.5, < 3.0)
|
||||
jwt (>= 1.5, < 4.0)
|
||||
multi_json (~> 1.10)
|
||||
simctl (1.6.10)
|
||||
CFPropertyList
|
||||
@@ -203,7 +215,6 @@ GEM
|
||||
tty-spinner (0.9.3)
|
||||
tty-cursor (~> 0.7)
|
||||
uber (0.1.0)
|
||||
unf (0.2.0)
|
||||
unicode-display_width (2.6.0)
|
||||
word_wrap (1.0.0)
|
||||
xcodeproj (1.27.0)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
# Companion App for AirPods (CAPod)
|
||||
|
||||
[](https://github.com/d4rken-org/capod/releases/latest)
|
||||
[](https://shields.rbtlog.dev/eu.darken.capod)
|
||||
[](https://github.com/d4rken/capod/actions/workflows/code-checks.yml)
|
||||
[](https://crowdin.com/project/capod)
|
||||
[](https://github.com/d4rken-org/capod/edit/main/README.md#download)
|
||||
@@ -51,11 +52,12 @@ Currently supported models:
|
||||
|
||||
| Source | Status |
|
||||
|------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| [Google Play](https://play.google.com/store/apps/details?id=eu.darken.capod) | [](https://play.google.com/store/apps/details?id=eu.darken.capod) |
|
||||
| [Google Play Beta](https://play.google.com/apps/testing/eu.darken.capod) | [](https://play.google.com/apps/testing/eu.darken.capod) | |
|
||||
| [Github Releases](https://github.com/d4rken-org/capod/releases) | [](https://github.com/d4rken-org/capod/releases/latest) |
|
||||
| [F-Droid](https://f-droid.org/en/packages/eu.darken.capod/) | [](https://f-droid.org/en/packages/eu.darken.capod/) |
|
||||
| [F-Droid (IzzyOnDroid)](https://apt.izzysoft.de/packages/eu.darken.capod/) | [](https://apt.izzysoft.de/packages/eu.darken.capod/) |
|
||||
| [Google Play](https://play.google.com/store/apps/details?id=eu.darken.capod) | [](https://play.google.com/store/apps/details?id=eu.darken.capod) |
|
||||
| [Google Play Beta](https://play.google.com/apps/testing/eu.darken.capod) | [](https://play.google.com/apps/testing/eu.darken.capod) | |
|
||||
| [Github (Release)](https://github.com/d4rken-org/capod/releases) | )  |
|
||||
| [Github (Pre-Release)](https://github.com/d4rken-org/capod/releases) | )  |
|
||||
| [F-Droid](https://f-droid.org/en/packages/eu.darken.capod/) | [](https://f-droid.org/en/packages/eu.darken.capod/) |
|
||||
| [F-Droid (IzzyOnDroid)](https://apt.izzysoft.de/packages/eu.darken.capod/) | [](https://apt.izzysoft.de/packages/eu.darken.capod/) |
|
||||
|
||||
## Support the project
|
||||
|
||||
|
||||
@@ -26,4 +26,3 @@ exclude:
|
||||
- app
|
||||
- app-common
|
||||
- CONTRIBUTING.md
|
||||
- CLAUDE.md
|
||||
|
||||
+54
-18
@@ -3,11 +3,12 @@ plugins {
|
||||
id("com.android.application")
|
||||
id("kotlin-android")
|
||||
id("com.google.devtools.ksp")
|
||||
id("kotlin-kapt")
|
||||
id("kotlin-parcelize")
|
||||
id("org.jetbrains.kotlin.plugin.compose")
|
||||
id("org.jetbrains.kotlin.plugin.serialization")
|
||||
id("com.android.compose.screenshot") version "0.0.1-alpha13"
|
||||
}
|
||||
apply(plugin = "dagger.hilt.android.plugin")
|
||||
apply(plugin = "androidx.navigation.safeargs.kotlin")
|
||||
|
||||
android {
|
||||
compileSdk = projectConfig.compileSdk
|
||||
@@ -35,7 +36,7 @@ android {
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
val basePath = File(System.getProperty("user.home"), ".appconfig/${projectConfig.packageName}")
|
||||
val basePath = File(System.getProperty("user.home"), ".config/projects/${projectConfig.packageName}")
|
||||
create("releaseFoss") {
|
||||
setupCredentials(File(basePath, "signing-foss.properties"))
|
||||
}
|
||||
@@ -94,21 +95,12 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
buildOutputs.all {
|
||||
val variantOutputImpl = this as com.android.build.gradle.internal.api.BaseVariantOutputImpl
|
||||
val variantName: String = variantOutputImpl.name
|
||||
|
||||
if (listOf("release", "beta").any { variantName.lowercase().contains(it) }) {
|
||||
val outputFileName = projectConfig.packageName +
|
||||
"-v${defaultConfig.versionName}-${defaultConfig.versionCode}" +
|
||||
"-${variantName.uppercase()}.apk"
|
||||
|
||||
variantOutputImpl.outputFileName = outputFileName
|
||||
}
|
||||
}
|
||||
experimentalProperties["android.experimental.enableScreenshotTest"] = true
|
||||
|
||||
buildFeatures {
|
||||
viewBinding = true
|
||||
buildConfig = true
|
||||
compose = true
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
@@ -126,6 +118,7 @@ android {
|
||||
"-opt-in=kotlin.time.ExperimentalTime",
|
||||
"-opt-in=kotlin.RequiresOptIn",
|
||||
"-opt-in=kotlin.ExperimentalUnsignedTypes",
|
||||
"-opt-in=androidx.compose.material3.ExperimentalMaterial3Api",
|
||||
"-Xannotation-default-target=param-property"
|
||||
)
|
||||
}
|
||||
@@ -141,6 +134,42 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
androidComponents {
|
||||
onVariants { variant ->
|
||||
val buildType = variant.buildType ?: return@onVariants
|
||||
if (buildType != "release" && buildType != "beta") return@onVariants
|
||||
|
||||
val formattedVariantName = variant.name
|
||||
.replace(Regex("([a-z])([A-Z])"), "$1-$2")
|
||||
.uppercase()
|
||||
|
||||
val apkFolder = variant.artifacts.get(com.android.build.api.artifact.SingleArtifact.APK)
|
||||
val loader = variant.artifacts.getBuiltArtifactsLoader()
|
||||
val packageName = projectConfig.packageName
|
||||
|
||||
val renameTask = tasks.register("rename${variant.name.replaceFirstChar { it.uppercase() }}Apk") {
|
||||
inputs.files(apkFolder)
|
||||
outputs.upToDateWhen { false }
|
||||
|
||||
doLast {
|
||||
val builtArtifacts = loader.load(apkFolder.get()) ?: return@doLast
|
||||
|
||||
builtArtifacts.elements.forEach { element ->
|
||||
val apkFile = File(element.outputFile)
|
||||
val outputFileName = "$packageName-v${element.versionName}-${element.versionCode}-$formattedVariantName.apk"
|
||||
if (apkFile.exists() && apkFile.name != outputFileName) {
|
||||
apkFile.copyTo(File(apkFile.parentFile, outputFileName), overwrite = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.matching { it.name == "assemble${variant.name.replaceFirstChar { it.uppercase() }}" }.configureEach {
|
||||
finalizedBy(renameTask)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.5")
|
||||
|
||||
@@ -148,8 +177,6 @@ dependencies {
|
||||
|
||||
addDagger()
|
||||
|
||||
addMoshi()
|
||||
|
||||
addOkio()
|
||||
|
||||
addBaseAndroid()
|
||||
@@ -158,10 +185,19 @@ dependencies {
|
||||
implementation("androidx.core:core-splashscreen:1.0.0-alpha02")
|
||||
|
||||
addNavigation()
|
||||
addBaseWorkManager()
|
||||
|
||||
addCompose()
|
||||
addGlance()
|
||||
addDataStore()
|
||||
addNavigation3()
|
||||
addSerialization()
|
||||
|
||||
addTesting()
|
||||
|
||||
"gplayImplementation"("com.android.billingclient:billing:8.0.0")
|
||||
"gplayImplementation"("com.android.billingclient:billing-ktx:8.0.0")
|
||||
|
||||
"screenshotTestImplementation"(platform("androidx.compose:compose-bom:${Versions.Compose.bom}"))
|
||||
"screenshotTestImplementation"("com.android.tools.screenshot:screenshot-validation-api:0.0.1-alpha13")
|
||||
"screenshotTestImplementation"("androidx.compose.ui:ui-tooling")
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
-keepclassmembernames @com.squareup.moshi.JsonClass class * extends java.lang.Enum {
|
||||
<fields>;
|
||||
}
|
||||
Vendored
+7
-22
@@ -1,23 +1,8 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
-keep class eu.darken.capod.BuildConfig { *; }
|
||||
-dontobfuscate
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
|
||||
-dontobfuscate
|
||||
# work-runtime 2.7.1 (pulled by Glance) uses Class.newInstance() reflection throughout.
|
||||
# R8 full mode strips no-arg constructors not reachable by static analysis.
|
||||
# Keep all constructors in the work package. Remove when Glance upgrades to work-runtime 2.10+.
|
||||
# See: https://issuetracker.google.com/issues/243257364
|
||||
-keep class androidx.work.** { <init>(...); }
|
||||
@@ -0,0 +1,274 @@
|
||||
package eu.darken.capod.screenshots
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
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.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.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
|
||||
|
||||
internal const val DS = "spec:width=1080px,height=2400px,dpi=428"
|
||||
|
||||
@Composable
|
||||
internal fun DashboardContent() = PreviewWrapper {
|
||||
OverviewScreen(
|
||||
state = OverviewViewModel.State(
|
||||
now = MOCK_NOW,
|
||||
permissions = emptySet(),
|
||||
devices = listOf(
|
||||
MockPodDataProvider.airPodsProMixed(),
|
||||
MockPodDataProvider.airPodsMax(),
|
||||
MockPodDataProvider.unknownDevice(),
|
||||
),
|
||||
isDebugMode = false,
|
||||
isBluetoothEnabled = true,
|
||||
profiles = listOf(
|
||||
MockPodDataProvider.profile("My AirPods Pro", PodDevice.Model.AIRPODS_PRO2),
|
||||
MockPodDataProvider.profile("AirPods Max", PodDevice.Model.AIRPODS_MAX),
|
||||
),
|
||||
upgradeInfo = MockPodDataProvider.gplayInfo(isPro = true),
|
||||
showUnmatchedDevices = false,
|
||||
),
|
||||
onRequestPermission = {},
|
||||
onManageDevices = {},
|
||||
onSettings = {},
|
||||
onUpgrade = {},
|
||||
onToggleUnmatched = {},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
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),
|
||||
),
|
||||
),
|
||||
onBack = {},
|
||||
onAddDevice = {},
|
||||
onEditProfile = {},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun AddProfileContent() = PreviewWrapper {
|
||||
DeviceProfileCreationScreen(
|
||||
state = DeviceProfileCreationViewModel.State(
|
||||
isEditMode = false,
|
||||
name = "",
|
||||
nameError = null,
|
||||
selectedModel = null,
|
||||
availableModels = PodDevice.Model.entries.filter { it != PodDevice.Model.UNKNOWN },
|
||||
identityKey = null,
|
||||
encryptionKey = null,
|
||||
selectedDevice = null,
|
||||
bondedDevices = emptyList(),
|
||||
minimumSignalQuality = 0.15f,
|
||||
canSave = false,
|
||||
),
|
||||
onBack = {},
|
||||
onSave = {},
|
||||
onDelete = {},
|
||||
onNameChange = {},
|
||||
onModelChange = {},
|
||||
onDeviceChange = {},
|
||||
onIdentityKeyChange = {},
|
||||
onEncryptionKeyChange = {},
|
||||
onSignalQualityChange = {},
|
||||
onKeyGuide = {},
|
||||
)
|
||||
}
|
||||
|
||||
@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(
|
||||
isPro = true,
|
||||
onePodMode = false,
|
||||
autoPlay = true,
|
||||
autoPause = true,
|
||||
autoConnect = false,
|
||||
autoConnectCondition = AutoConnectCondition.WHEN_SEEN,
|
||||
showPopUpOnCaseOpen = true,
|
||||
showPopUpOnConnection = false,
|
||||
),
|
||||
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),
|
||||
)
|
||||
WidgetConfigurationScreen(
|
||||
state = WidgetConfigurationViewModel.State(
|
||||
profiles = profiles,
|
||||
selectedProfile = profiles.first().id,
|
||||
isPro = true,
|
||||
theme = WidgetTheme.DEFAULT,
|
||||
activePreset = WidgetTheme.Preset.MATERIAL_YOU,
|
||||
isCustomMode = false,
|
||||
),
|
||||
onSelectProfile = {},
|
||||
onSelectPreset = {},
|
||||
onEnterCustomMode = { _, _ -> },
|
||||
onSetBackgroundColor = {},
|
||||
onSetForegroundColor = {},
|
||||
onSetBackgroundAlpha = {},
|
||||
onSetShowDeviceLabel = {},
|
||||
onReset = {},
|
||||
onConfirm = {},
|
||||
onCancel = {},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun CasePopUpContent() {
|
||||
CapodTheme {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(
|
||||
Brush.verticalGradient(
|
||||
colors = listOf(
|
||||
Color(0xFF1A237E),
|
||||
Color(0xFF0D47A1),
|
||||
Color(0xFF006064),
|
||||
)
|
||||
)
|
||||
),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
PopUpCard(
|
||||
device = MockPodDataProvider.airPodsProMixed(),
|
||||
onClose = {},
|
||||
modifier = Modifier.padding(horizontal = 32.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun HomescreenWidgetContent() {
|
||||
CapodTheme {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(
|
||||
Brush.verticalGradient(
|
||||
colors = listOf(
|
||||
Color(0xFF1A237E),
|
||||
Color(0xFF0D47A1),
|
||||
Color(0xFF006064),
|
||||
)
|
||||
)
|
||||
),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
ComposeWidgetPreview(
|
||||
state = WidgetRenderState.previewDualPod(
|
||||
bgColor = MaterialTheme.colorScheme.surface.toArgb(),
|
||||
textColor = MaterialTheme.colorScheme.onSurface.toArgb(),
|
||||
iconColor = MaterialTheme.colorScheme.onSurface.toArgb(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(name = "1 - Dashboard Light", locale = "en", device = DS, showSystemUi = true)
|
||||
@Composable
|
||||
private fun PreviewDashboardLight() = DashboardContent()
|
||||
|
||||
@Preview(name = "2 - Dashboard Dark", locale = "en", device = DS, uiMode = Configuration.UI_MODE_NIGHT_YES, showSystemUi = true)
|
||||
@Composable
|
||||
private fun PreviewDashboardDark() = DashboardContent()
|
||||
|
||||
@Preview(name = "3 - Case Pop-up", device = DS, showSystemUi = true)
|
||||
@Composable
|
||||
private fun PreviewCasePopUp() = CasePopUpContent()
|
||||
|
||||
@Preview(name = "4 - Widget Configuration", locale = "en", device = DS, showSystemUi = true)
|
||||
@Composable
|
||||
private fun PreviewWidgetConfiguration() = WidgetConfigurationContent()
|
||||
|
||||
@Preview(name = "5 - Device Profiles", locale = "en", device = DS, showSystemUi = true)
|
||||
@Composable
|
||||
private fun PreviewDeviceProfiles() = DeviceProfilesContent()
|
||||
|
||||
@Preview(name = "6 - Add Profile", locale = "en", device = DS, showSystemUi = true)
|
||||
@Composable
|
||||
private fun PreviewAddProfile() = AddProfileContent()
|
||||
|
||||
@Preview(name = "7 - Settings", locale = "en", device = DS, showSystemUi = true)
|
||||
@Composable
|
||||
private fun PreviewSettingsIndex() = SettingsIndexContent()
|
||||
|
||||
@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)
|
||||
@Composable
|
||||
private fun PreviewHomescreenWidget() = HomescreenWidgetContent()
|
||||
@@ -1,24 +1,35 @@
|
||||
package eu.darken.capod.common.upgrade.core
|
||||
|
||||
import android.content.Context
|
||||
import com.squareup.moshi.Moshi
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.SharedPreferencesMigration
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.preferencesDataStore
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import eu.darken.capod.common.preferences.createFlowPreference
|
||||
import eu.darken.capod.common.datastore.createValue
|
||||
import eu.darken.capod.common.serialization.SerializationCapod
|
||||
import kotlinx.serialization.json.Json
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class FossCache @Inject constructor(
|
||||
@ApplicationContext context: Context,
|
||||
moshi: Moshi
|
||||
@SerializationCapod json: Json,
|
||||
) {
|
||||
|
||||
private val preferences = context.getSharedPreferences("settings_foss", Context.MODE_PRIVATE)
|
||||
|
||||
val upgrade = preferences.createFlowPreference<FossUpgrade?>(
|
||||
key = "foss.upgrade",
|
||||
moshi = moshi,
|
||||
defaultValue = null,
|
||||
private val Context.dataStore by preferencesDataStore(
|
||||
name = "settings_foss",
|
||||
produceMigrations = { ctx -> listOf(SharedPreferencesMigration(ctx, "settings_foss")) }
|
||||
)
|
||||
|
||||
}
|
||||
private val dataStore: DataStore<Preferences> = context.dataStore
|
||||
|
||||
val upgrade = dataStore.createValue<FossUpgrade?>(
|
||||
key = "foss.upgrade",
|
||||
defaultValue = null,
|
||||
json = json,
|
||||
onErrorFallbackToDefault = true,
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
package eu.darken.capod.common.upgrade.core
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import eu.darken.capod.common.serialization.InstantEpochMillisSerializer
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.time.Instant
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
@Serializable
|
||||
data class FossUpgrade(
|
||||
val upgradedAt: Instant,
|
||||
val reason: Reason
|
||||
@SerialName("upgradedAt") @Serializable(with = InstantEpochMillisSerializer::class) val upgradedAt: Instant,
|
||||
@SerialName("reason") val reason: Reason
|
||||
) {
|
||||
@JsonClass(generateAdapter = false)
|
||||
@Serializable
|
||||
enum class Reason {
|
||||
@Json(name = "foss.upgrade.reason.donated") DONATED,
|
||||
@Json(name = "foss.upgrade.reason.alreadydonated") ALREADY_DONATED,
|
||||
@Json(name = "foss.upgrade.reason.nomoney") NO_MONEY;
|
||||
@SerialName("foss.upgrade.reason.donated") DONATED,
|
||||
@SerialName("foss.upgrade.reason.alreadydonated") ALREADY_DONATED,
|
||||
@SerialName("foss.upgrade.reason.nomoney") NO_MONEY;
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,16 @@
|
||||
package eu.darken.capod.common.upgrade.core
|
||||
|
||||
import android.app.Activity
|
||||
import android.widget.Toast
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import eu.darken.capod.R
|
||||
import eu.darken.capod.common.WebpageTool
|
||||
import eu.darken.capod.common.upgrade.UpgradeRepo
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import java.time.Instant
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import eu.darken.capod.common.datastore.valueBlocking
|
||||
|
||||
@Singleton
|
||||
class UpgradeControlFoss @Inject constructor(
|
||||
private val fossCache: FossCache,
|
||||
private val webpageTool: WebpageTool,
|
||||
) : UpgradeRepo {
|
||||
|
||||
override val upgradeInfo: Flow<UpgradeRepo.Info> = fossCache.upgrade.flow.map { data ->
|
||||
@@ -30,34 +25,11 @@ class UpgradeControlFoss @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
override fun launchBillingFlow(activity: Activity) {
|
||||
MaterialAlertDialogBuilder(activity).apply {
|
||||
setIcon(R.drawable.ic_heart)
|
||||
setTitle(R.string.upgrade_capod_label)
|
||||
setMessage(R.string.upgrade_capod_description)
|
||||
setPositiveButton(R.string.foss_upgrade_donate_label) { _, _ ->
|
||||
fossCache.upgrade.value = FossUpgrade(
|
||||
upgradedAt = Instant.now(),
|
||||
reason = FossUpgrade.Reason.DONATED
|
||||
)
|
||||
webpageTool.open("https://github.com/sponsors/d4rken")
|
||||
Toast.makeText(activity, R.string.general_thank_you_label, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
setNegativeButton(R.string.foss_upgrade_alreadydonated_label) { _, _ ->
|
||||
fossCache.upgrade.value = FossUpgrade(
|
||||
upgradedAt = Instant.now(),
|
||||
reason = FossUpgrade.Reason.ALREADY_DONATED
|
||||
)
|
||||
Toast.makeText(activity, R.string.general_thank_you_label, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
setNeutralButton(R.string.foss_upgrade_no_money_label) { _, _ ->
|
||||
fossCache.upgrade.value = FossUpgrade(
|
||||
upgradedAt = Instant.now(),
|
||||
reason = FossUpgrade.Reason.NO_MONEY
|
||||
)
|
||||
Toast.makeText(activity, "¯\\_(ツ)_/¯", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}.show()
|
||||
fun upgrade(reason: FossUpgrade.Reason) {
|
||||
fossCache.upgrade.valueBlocking = FossUpgrade(
|
||||
upgradedAt = Instant.now(),
|
||||
reason = reason
|
||||
)
|
||||
}
|
||||
|
||||
data class Info(
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package eu.darken.capod.upgrade.ui
|
||||
|
||||
import androidx.navigation3.runtime.EntryProviderScope
|
||||
import androidx.navigation3.runtime.NavKey
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import dagger.multibindings.IntoSet
|
||||
import eu.darken.capod.common.navigation.Nav
|
||||
import eu.darken.capod.common.navigation.NavigationEntry
|
||||
import javax.inject.Inject
|
||||
|
||||
class UpgradeNavigation @Inject constructor() : NavigationEntry {
|
||||
override fun EntryProviderScope<NavKey>.setup() {
|
||||
entry<Nav.Main.Upgrade> { UpgradeScreenHost() }
|
||||
}
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
abstract class Mod {
|
||||
@Binds
|
||||
@IntoSet
|
||||
abstract fun bind(entry: UpgradeNavigation): NavigationEntry
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,263 @@
|
||||
package eu.darken.capod.upgrade.ui
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
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
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.twotone.ArrowBack
|
||||
import androidx.compose.material.icons.automirrored.twotone.Message
|
||||
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.Widgets
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.res.painterResource
|
||||
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.withStyle
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleEventObserver
|
||||
import eu.darken.capod.R
|
||||
import eu.darken.capod.common.compose.Preview2
|
||||
import eu.darken.capod.common.compose.PreviewWrapper
|
||||
import eu.darken.capod.common.error.ErrorEventHandler
|
||||
import eu.darken.capod.common.navigation.NavigationEventHandler
|
||||
|
||||
@Composable
|
||||
fun UpgradeScreenHost(vm: UpgradeViewModel = hiltViewModel()) {
|
||||
ErrorEventHandler(vm)
|
||||
NavigationEventHandler(vm)
|
||||
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
val returnedEarlyMessage = stringResource(R.string.upgrade_foss_sponsor_returned_early)
|
||||
|
||||
val lifecycleOwner = LocalLifecycleOwner.current
|
||||
DisposableEffect(lifecycleOwner) {
|
||||
val observer = LifecycleEventObserver { _, event ->
|
||||
if (event == Lifecycle.Event.ON_RESUME) {
|
||||
vm.onResume()
|
||||
}
|
||||
}
|
||||
lifecycleOwner.lifecycle.addObserver(observer)
|
||||
onDispose { lifecycleOwner.lifecycle.removeObserver(observer) }
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
vm.sponsorEvents.collect { event ->
|
||||
when (event) {
|
||||
UpgradeViewModel.SponsorEvent.ReturnedTooEarly -> {
|
||||
snackbarHostState.showSnackbar(returnedEarlyMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UpgradeScreen(
|
||||
snackbarHostState = snackbarHostState,
|
||||
onNavigateUp = { vm.navUp() },
|
||||
onSponsor = { vm.sponsor() },
|
||||
)
|
||||
}
|
||||
|
||||
private data class Benefit(val icon: ImageVector, val textRes: Int)
|
||||
|
||||
@Composable
|
||||
fun UpgradeScreen(
|
||||
snackbarHostState: SnackbarHostState = remember { SnackbarHostState() },
|
||||
onNavigateUp: () -> Unit,
|
||||
onSponsor: () -> Unit,
|
||||
) {
|
||||
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.Favorite, R.string.upgrade_benefit_support),
|
||||
)
|
||||
|
||||
Scaffold(
|
||||
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(),
|
||||
) {
|
||||
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,
|
||||
) {
|
||||
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.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))
|
||||
}
|
||||
|
||||
IconButton(
|
||||
onClick = onNavigateUp,
|
||||
modifier = Modifier.padding(4.dp),
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.TwoTone.ArrowBack,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview2
|
||||
@Composable
|
||||
private fun UpgradeScreenPreview() = PreviewWrapper {
|
||||
UpgradeScreen(
|
||||
onNavigateUp = {},
|
||||
onSponsor = {},
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package eu.darken.capod.upgrade.ui
|
||||
|
||||
import android.os.SystemClock
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import eu.darken.capod.common.WebpageTool
|
||||
import eu.darken.capod.common.coroutine.DispatcherProvider
|
||||
import eu.darken.capod.common.flow.SingleEventFlow
|
||||
import eu.darken.capod.common.uix.ViewModel4
|
||||
import eu.darken.capod.common.upgrade.core.FossUpgrade
|
||||
import eu.darken.capod.common.upgrade.core.UpgradeControlFoss
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class UpgradeViewModel @Inject constructor(
|
||||
private val savedStateHandle: SavedStateHandle,
|
||||
dispatcherProvider: DispatcherProvider,
|
||||
private val upgradeControlFoss: UpgradeControlFoss,
|
||||
private val webpageTool: WebpageTool,
|
||||
) : ViewModel4(dispatcherProvider) {
|
||||
|
||||
sealed interface SponsorEvent {
|
||||
data object ReturnedTooEarly : SponsorEvent
|
||||
}
|
||||
|
||||
val sponsorEvents = SingleEventFlow<SponsorEvent>()
|
||||
|
||||
fun sponsor() {
|
||||
savedStateHandle[KEY_SPONSOR_OPENED_AT] = SystemClock.elapsedRealtime()
|
||||
webpageTool.open("https://github.com/sponsors/d4rken")
|
||||
}
|
||||
|
||||
fun onResume() {
|
||||
val openedAt = savedStateHandle.get<Long>(KEY_SPONSOR_OPENED_AT) ?: return
|
||||
savedStateHandle.remove<Long>(KEY_SPONSOR_OPENED_AT)
|
||||
|
||||
if (SystemClock.elapsedRealtime() - openedAt >= 5_000L) {
|
||||
upgradeControlFoss.upgrade(FossUpgrade.Reason.DONATED)
|
||||
navUp()
|
||||
} else {
|
||||
sponsorEvents.tryEmit(SponsorEvent.ReturnedTooEarly)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val KEY_SPONSOR_OPENED_AT = "sponsor_opened_at"
|
||||
}
|
||||
}
|
||||
@@ -3,4 +3,8 @@
|
||||
<string name="foss_upgrade_donate_label">Skenk</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Ek het reeds geskenk</string>
|
||||
<string name="foss_upgrade_no_money_label">Ek spandeer al my geld op AirPods</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS is gratis en oopbron. As u dit nuttig vind, oorweeg dit om ontwikkeling te befonds om die projek aan die gang te hou.</string>
|
||||
<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>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
<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_sponsor_action">ልማትን ይደግፉ</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">ምንም ማስታወቂያ የሉም። ምንም ክትትል የሉም። ምንም Google Play ቆለፋ የሉም።</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">ቀድሞ ተመለሱ፡ ድጋፏዎ CAPodን ህያው ያቆይላል።</string>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
<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_sponsor_action">ادعم التطوير</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">لا إعلانات. لا تتبع. لا قيود على Google Play.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">عدت بسرعة؟ دعمك يبقي CAPod حياً.</string>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
<string name="foss_upgrade_donate_label">İanə ver</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Artıq ianə vermişəm</string>
|
||||
<string name="foss_upgrade_no_money_label">Bütün pulumu AirPods-lara xərcləyirəm</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS pulsuzdur və açıq mənbəlidir. Faydalı tapırsınızsa, layihənin davamına kömək etmək üçün inkişafı maliyyələşdirməyi düşünün.</string>
|
||||
<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>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
<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_sponsor_action">Падтрымаць распрацоўку</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Ніякай рэкламы. Ніякай адсочкі. Ніякай залежнасці ад Google Play.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">Ужо вяртаецеся? Ваша падтрымка трымае CAPod ў жывых.</string>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
<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_sponsor_action">Спонсорирайте разработката</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Без реклами. Без следене. Без заключване в Google Play.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">Вече ли? Вашата подкрепа поддържа CAPod жив.</string>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
<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_sponsor_action">স্পনসর ডেভলপমেন্ট</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">কোনো বিজ্ঞাপন নেই। কোনো ট্র্যাকিং নেই। Google Play-এর সাথে বাঁধানো নেই।</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">ইতোমধ্যে ফিরে গেলেন? আপনার সমর্থন CAPodকে বাঁচিয়ে রাখে।</string>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
<string name="foss_upgrade_donate_label">Donacions</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Ja he donat</string>
|
||||
<string name="foss_upgrade_no_money_label">Gasto tots els meus diners en AirPods</string>
|
||||
<string name="upgrade_foss_preamble">El CAPod FOSS és gratuït i de codi obert. Si el trobeu útil, considereu patrocinar el desenvolupament per ajudar a mantenir el projecte en marxa.</string>
|
||||
<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>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
<string name="foss_upgrade_donate_label">Přispět</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Již jsem přispěl/a</string>
|
||||
<string name="foss_upgrade_no_money_label">Všechny peníze jsem utratil/a za AirPods</string>
|
||||
<string name="upgrade_foss_preamble">Aplikace CAPod FOSS je zdarma a open source. Pokud vám přijde užitečná, zvažte sponzorování vývoje, abyste pomohli udržet projekt v chodu.</string>
|
||||
<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>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
<string name="foss_upgrade_donate_label">Doner</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Jeg har allerede doneret</string>
|
||||
<string name="foss_upgrade_no_money_label">Jeg bruger alle mine penge på AirPods</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS er gratis og open source. Hvis du finder det nüttigt, kan du overveje at sponsorere udviklingen for at hjælpe projektet med at fortsætte.</string>
|
||||
<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>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
<string name="foss_upgrade_donate_label">Spenden</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Ich habe schon gespendet</string>
|
||||
<string name="foss_upgrade_no_money_label">Hab alles für AirPods ausgegeben</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS ist kostenlos und Open Source. Wenn du es nützlich findest, erwäge, die Entwicklung zu sponsern, um das Projekt am Laufen zu halten.</string>
|
||||
<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>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
<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_sponsor_action">Χορηγήστε την ανάπτυξη</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Χωρίς διαφημίσεις. Χωρίς παρακολούθηση. Χωρίς δέσμευση στο Google Play.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">Επιστρέψατε ήδη; Η υποστήριξή σας κρατά το CAPod ζωντανό.</string>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
<string name="foss_upgrade_donate_label">Donar</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Ya doné</string>
|
||||
<string name="foss_upgrade_no_money_label">Gasto todo mi dinero en AirPods</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS es gratuito y de código abierto. Si lo encontrás útil, considerá patrocinar el desarrollo para ayudar a mantener el proyecto en marcha.</string>
|
||||
<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>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
<string name="foss_upgrade_donate_label">Donar</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Ya doné</string>
|
||||
<string name="foss_upgrade_no_money_label">Gasto todo mi dinero en AirPods</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS es gratuito y de código abierto. Si lo encuentras útil, considera patrocinar el desarrollo para ayudar a mantener el proyecto.</string>
|
||||
<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>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
<string name="foss_upgrade_donate_label">Donar</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Ya he donado</string>
|
||||
<string name="foss_upgrade_no_money_label">Gasto todo mi dinero en AirPods</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS es gratuito y de código abierto. Si lo encuentras útil, considera patrocinar el desarrollo para ayudar a mantener el proyecto en marcha.</string>
|
||||
<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>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
<string name="foss_upgrade_donate_label">Anneta</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Juba annetasin</string>
|
||||
<string name="foss_upgrade_no_money_label">Kulutasin kogu raha AirPodsidele</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS on tasuta ja avatud lähtekoodiga. Kui sellest on sulle kasu, kaalu arenduse rahastamist, et aidata projekti arendada.</string>
|
||||
<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>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
<string name="foss_upgrade_donate_label">Eman dohaintza</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Dagoeneko eman dut dohaintza</string>
|
||||
<string name="foss_upgrade_no_money_label">Nire diru guztia AirPods-etan gastatzen dut</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS doakoa eta kode irekikoa da. Erabilgarria iruditzen bazaizu, kontuan hartu garapena babestu proiektua aurrera jarraitzeko.</string>
|
||||
<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>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
<string name="foss_upgrade_donate_label">کمک مالی</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">کمک مالی کردهام</string>
|
||||
<string name="foss_upgrade_no_money_label">من تمام پولم را خرج ایرپاد میکنم</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>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
<string name="foss_upgrade_donate_label">Lahjoita</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Olen jo lahjoittanut</string>
|
||||
<string name="foss_upgrade_no_money_label">Käytän kaikki rahani AirPodeihin</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS on ilmainen ja avoimen lähdekoodin ohjelma. Jos löydät sen hyödylliseksi, harkitse kehityksen tukemista projektin elässä pitämiseksi.</string>
|
||||
<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>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
<string name="foss_upgrade_donate_label">Mag-donate</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Nakapag-donate na ako</string>
|
||||
<string name="foss_upgrade_no_money_label">Ginagastos ko lahat ng pera ko sa AirPods</string>
|
||||
<string name="upgrade_foss_preamble">Ang CAPod FOSS ay libre at open source. Kung nakita mong kapaki-pakinabang ito, isaalang-alang ang pag-sponsor ng development para mapanatiling aktibo ang proyekto.</string>
|
||||
<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>
|
||||
</resources>
|
||||
|
||||
@@ -2,5 +2,9 @@
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Faire un don</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">J’ai déjà fait un don</string>
|
||||
<string name="foss_upgrade_no_money_label">J’ai dépensé tout mon argent sur les AirPods</string>
|
||||
<string name="foss_upgrade_no_money_label">J’ai dépensé tout mon argent sur des AirPods</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS est gratuite et à code source ouvert. Si vous la trouvez utile, pensez à soutenir le développement pour contribuer à la pérennité du projet.</string>
|
||||
<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>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
<string name="foss_upgrade_donate_label">Doar</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Xa doei</string>
|
||||
<string name="foss_upgrade_no_money_label">Gasto todos os meus cartos en AirPods</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS é gratuito e de código aberto. Se o atopas útil, considera patrocinar o desenvolvemento para axudar a manter o proxecto en marcha.</string>
|
||||
<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>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
<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_sponsor_action">विकास को प्रायोजक करें</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">कोई विज्ञापन नहीं। कोई ट्रैकिंग नहीं। Google Play पर निर्भरता नहीं।</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">अभी वापस? आपका समर्थन CAPod को जीवित रखता है।</string>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
<string name="foss_upgrade_donate_label">Donirajte</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Već sam donirao</string>
|
||||
<string name="foss_upgrade_no_money_label">Sav svoj novac trošim na AirPods</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS je besplatan i otvorenog koda. Ako vam je koristan, razmislite o sponzoriranju razvoja kako bi projekt ostao živ.</string>
|
||||
<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>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
<string name="foss_upgrade_donate_label">Adományoz</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Már adományoztam</string>
|
||||
<string name="foss_upgrade_no_money_label">Minden pénzemet az AirPods-ra költöttem</string>
|
||||
<string name="upgrade_foss_preamble">A CAPod FOSS ingyenes és nyílt forráskódú. Ha hasznosnak találod, fontold meg a fejlesztés támogatását, hogy a projekt tovább élhessen.</string>
|
||||
<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>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
<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_sponsor_action">Հովանավորել զարգացումը</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Լրատնություն չկա: հետքխկություն չկա: Google Play-ի կապվածություն չկա:</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">Արդևն հետ? ձևր աիկտակցությունը CAPod-ին կենդանի պահում:</string>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
<string name="foss_upgrade_donate_label">Menyumbang</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Saya telah berdonasi</string>
|
||||
<string name="foss_upgrade_no_money_label">Saya menghabiskan semua uang saya untuk AirPods</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS gratis dan sumber terbuka. Jika Anda merasa bermanfaat, pertimbangkan untuk mensponsori pengembangan guna menjaga proyek ini berjalan.</string>
|
||||
<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>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
<string name="foss_upgrade_donate_label">Gefa</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Ég hef þegar gefið</string>
|
||||
<string name="foss_upgrade_no_money_label">Ég eyði öllum peningunum mínum í AirPods</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS er ókeypis og opinn kóði. Ef þú finnur það gagnlegt, þakkaðu þér fyrir að stuðla að þróun til að hjálpa til við að halda verkefninu gangandi.</string>
|
||||
<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>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
<string name="foss_upgrade_donate_label">Dona</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Ho già donato</string>
|
||||
<string name="foss_upgrade_no_money_label">Spendo tutti i miei soldi per gli AirPods</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS è gratuito e open source. Se lo trovi utile, considera di sponsorizzare lo sviluppo per contribuire a mantenere il progetto in vita.</string>
|
||||
<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>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
<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_sponsor_action">פיתוח חסות</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">אין פרסומות. אין מעקב. אין תלות ב-Google Play.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">כבר חוזר? התמיכה שלך שומרת את CAPod בחיים.</string>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
<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_sponsor_action">開発支援者</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">広告なし。追跡なし。Google Play依存なし。</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">もう戻りますか?あなたのサポートがCAPodを支えています。</string>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
<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_sponsor_action">სპონსორის განვითარება</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">რეკლამა არა. თვალთვალება არა. Google Play-ზე დამოკიდება არა.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">უკვე დაბრუნდით? თქვენი მხარდაჯერა CAPod-ს გადარჩენებას ეხმარება.</string>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
<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_sponsor_action">ឧបត្ថម្ភការអភិវឌ្ឍន៍</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">គ្មានការផ្សាយពាណិជ្ជកម្ម។ គ្មានការតាមដាន។ គ្មានការចាក់សោ Google Play។</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">ត្រលប់មកវិញហើយ។? ការគាំទ្ររបស់អ្នកចិញ្ចឹម CAPod ឥ្សរិយនៅរស់។</string>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
<string name="foss_upgrade_donate_label">Beşdar bike</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Min berê bexş kir</string>
|
||||
<string name="foss_upgrade_no_money_label">Ez hemû pereyê xwe li AirPods xerc dikim</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS belaş û çavkaniya vekirî ye. Ger hûn wê kêrhastî dibînin, fikir bikin ku perkirezan bike da ku alîkariya berdewamî projeya bide.</string>
|
||||
<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>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
<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_sponsor_action">ಅಭಿವೃದ್ಧಿಗೆ ಪ್ರಾಯೋಜಕತ್ವ ನೀಡಿ</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">ಜಾಹೀರಾತುಗಳಿಲ್ಲ. ಭಕ್ಷಣೆ ಇಲ್ಲ. Google Play ಲಾಕ್-ಇನ್ ಇಲ್ಲ.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">ಇಷ್ಟೇ ಹಿಂದಿರುಗಿದಿರಿ? ನಿಮ್ಮ ಬೆಂಬಲ CAPod ಅನ್ನು ಜೀವಂತವಾಗಿಡುತ್ತದೆ.</string>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
<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_sponsor_action">개발 후원하기</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">광고 없음. 추적 없음. Google Play 의존 없음.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">벌써 돌아가세요? 여러분의 지원이 CAPod를 유지합니다.</string>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
<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_sponsor_action">Иштелеп чыгууну колдоо</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Жарнама. Кадалоолоо жок. Google Playга байланма.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">Артка кайттыңызбы? Сиздин колдооңуз CAPodты тирүү сактайт.</string>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
<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_sponsor_action">ສະໜັບສະໜູນການພັດທະນາ</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">ບໍ່ມີໂຄສະນາ. ບໍ່ມີການຕິດຕາມ. ບໍ່ຕິດ Google Play.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">ກັບມາແລ້ວ? ການສະໜັບສະໜູນຂອງທ່ານຮັກສາ CAPod ໃຫ້ດຳເນີນຕໍ່ໄປ.</string>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
<string name="foss_upgrade_donate_label">Aukoti</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Aš jau paaukojau</string>
|
||||
<string name="foss_upgrade_no_money_label">Visus savo pinigus išleidžiu „AirPods“ ausinėms</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS yra nemokama ir atviro kodo programa. Jei ji jums naudinga, pagalvokite apie rėmimą, kad projektą būtų galima tęsti.</string>
|
||||
<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>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
<string name="foss_upgrade_donate_label">Ziedot</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Es jau ziedoju</string>
|
||||
<string name="foss_upgrade_no_money_label">Es iztērēju visu savu naudu AirPods austiņām</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS ir bezmaksas un atkļējkoda. Ja tā ir noderba, apsveriet attīstības sponsēšanu, lai palīdzētu turpināt projektu.</string>
|
||||
<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>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
<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_sponsor_action">Спонзорирај развој</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Без реклами. Без пратење. Без зависност од Google Play.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">Веќе се вративте? Вашата поддршка го одржува CAPod жив.</string>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
<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_sponsor_action">വികസനം സ്പോൺസർ ചെയ്യുക</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">പരസ്യങ്ങൾ ഇല്ല. ട്രാക്കിംഗ് ഇല്ല. Google Play ലോക്ക്-ഇൻ ഇല്ല.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">ഇത്ര പെട്ടെന്ന് തിരിച്ചു പോകുന്നോ? നിങ്ങളുടെ പിന്തുണ CAPod നിലനിർത്തുന്നു.</string>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
<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_sponsor_action">Хөгжлийг дэмжих</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Реклам байхгүй. Хяналт байхгүй. Google Play-д хамааралгүй.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">Аль хэдэйн буцаж ирлээ? Таны дэмжлэг CAPod-ийг амьд байлгадаг.</string>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
<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_sponsor_action">विकासास प्रायोजित करा</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">कोणत्याही जाहिराती नाहीत. कोणतेही ट्रॅकिंग नाही. Google Play चे बंधन नाही.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">आधीच परत? तुमचा पाठिंबा CAPod जिवंत ठेवतो.</string>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
<string name="foss_upgrade_donate_label">Sumbang</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Saya telah menyumbang</string>
|
||||
<string name="foss_upgrade_no_money_label">Saya belanjakan semua wang saya untuk AirPods</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS adalah percuma dan sumber terbuka. Jika anda mendapatinya berguna, pertimbangkan untuk menaja pembangunannya bagi membantu projek ini terus berjalan.</string>
|
||||
<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>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
<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 သည် အခမဲ့နှင့် open source ဖြစ်သည်။ အသုံးပြုခုပ်ဆည်းနိုင် အသားဖြစ်ပါက ပရာဗက်ဆက်လက်ရန် ဖွဲ့ဖြီးရေးကို ပံ့ပိုးပေးသည်။</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>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
<string name="foss_upgrade_donate_label">Doner</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Jeg har allerede donert</string>
|
||||
<string name="foss_upgrade_no_money_label">Jeg bruker alle pengene mine på AirPods</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS er gratis og åpen kildekode. Hvis du synes den er nyttig, kan du vurdere å sponse utviklingen for å bidra til at prosjektet går videre.</string>
|
||||
<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>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
<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_sponsor_action">विकासलाई प्रायोजन गर्नुहोस्</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">कुनै विज्ञापन छैन। कुनै ट्रैकिङ छैन। Google Playमा बाँधिएको छैन।</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">पहिले नै फिर्नुभयो? तपाईंको सहयोगले CAPod जीवित राख्छ।</string>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
<string name="foss_upgrade_donate_label">Doneren</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Ik heb al gedoneerd</string>
|
||||
<string name="foss_upgrade_no_money_label">Ik besteed al mijn geld aan AirPods</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS is gratis en open source. Als je het nuttig vindt, overweeg dan om de ontwikkeling te sponsoren om het project gaande te houden.</string>
|
||||
<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>
|
||||
</resources>
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Wsparcie</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Już się zrzuciłem</string>
|
||||
<string name="foss_upgrade_donate_label">Wesprzyj</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Już wsparłem</string>
|
||||
<string name="foss_upgrade_no_money_label">Wydałem wszystkie pieniądze na AirPods</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS jest bezpłatny i otwartoźródłowy. Jeśli uważasz go za użyteczny, rozważ sponsorowanie rozwoju, aby pomóc kontynuować projekt.</string>
|
||||
<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>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
<string name="foss_upgrade_donate_label">Doar</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Eu já doei</string>
|
||||
<string name="foss_upgrade_no_money_label">Eu gasto todo o meu dinheiro em AirPods</string>
|
||||
<string name="upgrade_foss_preamble">O CAPod FOSS é gratuito e de código aberto. Se você o achar útil, considere patrocinar o desenvolvimento para ajudar a manter o projeto ativo.</string>
|
||||
<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>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
<string name="foss_upgrade_donate_label">Doar</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Já doei</string>
|
||||
<string name="foss_upgrade_no_money_label">Gastei todo o meu dinheiro em AirPods</string>
|
||||
<string name="upgrade_foss_preamble">O CAPod FOSS é gratuito e de código aberto. Se o achar útil, considere patrocinar o desenvolvimento para ajudar a manter o projeto ativo.</string>
|
||||
<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>
|
||||
</resources>
|
||||
|
||||
@@ -1,2 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Donar</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Hai gia donat</string>
|
||||
<string name="foss_upgrade_no_money_label">Mett tut mes daners en AirPods</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS è gratuit ed open source. Sche ti chattast ch\'el è util, considerescha da sponsurisar il sviluppament per agidà a mantegnair il project.</string>
|
||||
<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>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
<string name="foss_upgrade_donate_label">Donează</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Am donat deja</string>
|
||||
<string name="foss_upgrade_no_money_label">Mi-am cheltuit toți banii pe AirPods</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS este gratuit si open source. Daca il gasiti util, luati in considerare sponsorizarea dezvoltarii pentru a ajuta la continuarea proiectului.</string>
|
||||
<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>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
<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_sponsor_action">Поддержать разработку</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Без рекламы. Без слежки. Без привязки к Google Play.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">Вернулись уже? Ваша поддержка помогает CAPod жить.</string>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
<string name="foss_upgrade_donate_label">Dona</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Apo giai donadu</string>
|
||||
<string name="foss_upgrade_no_money_label">Apo spèndidu totu is soddos meos pro is AirPods</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS est gratis e fonte aberta. Si t’est de agiudu, considera de patrotzinare su isviluppu pro agiudare a mantennere su progetu.</string>
|
||||
<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>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
<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_sponsor_action">සංවර්ධනයට අනුග්රහය දෙන්න</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">අළු නැත. ට්රැකිං නැත. Google Play සලකත්වය නැත.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">එක්බේන්මට පළ්ට? ඔබ෪් සහය් CAPod ක්රියාත්මකව තබා ගෙනයි.</string>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
<string name="foss_upgrade_donate_label">Prispieť</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Už som prispel/a</string>
|
||||
<string name="foss_upgrade_no_money_label">Všetky peniaze som minul na AirPods</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS je zadarmo a open source. Ak ho nájdete užitočným, zvažte sponzorovanie vývoja, aby ste pomohli udržiavať projekt v chode.</string>
|
||||
<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>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
<string name="foss_upgrade_donate_label">Doniraj</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Sem že doniral</string>
|
||||
<string name="foss_upgrade_no_money_label">Ves svoj denar porabim za AirPods</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS je brezplačen in odprtokoden. Če se vam zdi koristen, razmislite o sponzoriranju razvoja, da pomagate ohranjati projekt.</string>
|
||||
<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>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
<string name="foss_upgrade_donate_label">Dhuro</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Unë tashmë kam dhuruar</string>
|
||||
<string name="foss_upgrade_no_money_label">Unë i shpenzoj të gjitha paratë e mia për AirPods</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS është falas dhe me burim të hapur. Nëse e gjeni të dobishmëm, konsideroni sponsorizimin e zhvillimit për ta mbajtur projektin gjallë.</string>
|
||||
<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>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
<string name="foss_upgrade_donate_label">Донације</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Већ сам донирао/ла</string>
|
||||
<string name="foss_upgrade_no_money_label">Сав новац трошим на АирПодс</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>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
<string name="foss_upgrade_donate_label">Donera</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Jag har redan donerat</string>
|
||||
<string name="foss_upgrade_no_money_label">Jag spenderar alla mina pengar på AirPods</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS är gratis och öppen källkod. Om du tycker att det är användbart kan du överväga att sponsra utvecklingen för att hjälpa till att hålla projektet vid liv.</string>
|
||||
<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>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
<string name="foss_upgrade_donate_label">Changia</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Nimeshachangia tayari</string>
|
||||
<string name="foss_upgrade_no_money_label">Natumia pesa zangu zote kwenye AirPods</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS ni bure na chanzo huria. Ikiwa unaikuta ni muhimu, fikiria kuchangia maendeleo ili kusaidia mradi kuendelea.</string>
|
||||
<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>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
<string name="foss_upgrade_donate_label">நன்கொடை</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">நான் ஏற்கனவே நன்கொடை அளித்துவிட்டேன்</string>
|
||||
<string name="foss_upgrade_no_money_label">எனது பணத்தை எல்லாம் ஏர்பாட்களில் செலவிடுகிறேன்</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>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
<string name="foss_upgrade_donate_label">విరాళం ఇవ్వండి</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">నేను ఇప్పటికే విరాళం ఇచ్చాను</string>
|
||||
<string name="foss_upgrade_no_money_label">నా డబ్బు అంతా ఎయిర్పాడ్స్కే ఖర్చు చేస్తాను</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>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
<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_sponsor_action">สปอนเซอร์การพัฒนา</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">ไม่มีโฆษณา ไม่ติดตาม ไม่ผูกขาด Google Play</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">กลับมาแล้ว? การสนับสนุนของคุณช่วยให้ CAPod ดำเนินต่อไป</string>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
<string name="foss_upgrade_donate_label">Bağış yap</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Zaten bağış yaptım</string>
|
||||
<string name="foss_upgrade_no_money_label">Tüm paramı AirPods\'a harcıyorum</string>
|
||||
<string name="upgrade_foss_preamble">CAPod FOSS ücretsiz ve açık kaynaklıdır. Eğer faydalı bulduysan, projenin devam edebilmesi için lütfen geliştirmeye destek olmayı düşün.</string>
|
||||
<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>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
<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_sponsor_action">Розвиток спонсорів</string>
|
||||
<string name="upgrade_foss_sponsor_subtitle">Без реклами. Без стеження. Без прив\'язки до Google Play.</string>
|
||||
<string name="upgrade_foss_sponsor_returned_early">Вже повертаєтеся? Ваша підтримка зберігає CAPod.</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