mirror of
https://github.com/d4rken-org/capod.git
synced 2026-09-14 18:26:11 -04:00
Compare commits
168
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
83b24cced2 | ||
|
|
d913e72c5c | ||
|
|
5c16c08dee | ||
|
|
20e22c0910 | ||
|
|
fa3e56b0f3 | ||
|
|
f25718ab2c | ||
|
|
6e9b275936 | ||
|
|
c6c7cd7858 | ||
|
|
4d7027a9b8 | ||
|
|
992cb5e695 | ||
|
|
039d20d7aa | ||
|
|
52433460be | ||
|
|
95bb225e64 | ||
|
|
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 | ||
|
|
5c2b66fe27 | ||
|
|
c9fe9a43e2 | ||
|
|
395dbbb624 | ||
|
|
fdc6486102 | ||
|
|
6c379451c0 | ||
|
|
221f3ccbed | ||
|
|
12f0662146 | ||
|
|
7cc81bd554 | ||
|
|
618fddb698 | ||
|
|
0f74cb9b39 |
@@ -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": {
|
"permissions": {
|
||||||
"allow": [
|
"allow": [
|
||||||
"mcp__ide__getDiagnostics",
|
"mcp__ide__getDiagnostics",
|
||||||
"Bash(./gradlew tasks:*)",
|
"WebSearch"
|
||||||
"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)"
|
|
||||||
],
|
],
|
||||||
"deny": []
|
"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())
|
|
||||||
@@ -4,7 +4,7 @@ runs:
|
|||||||
using: "composite"
|
using: "composite"
|
||||||
steps:
|
steps:
|
||||||
- name: Set up JDK 17
|
- name: Set up JDK 17
|
||||||
uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 #v4.7.0
|
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 #v5.2.0
|
||||||
with:
|
with:
|
||||||
java-version: '17'
|
java-version: '17'
|
||||||
distribution: 'temurin'
|
distribution: 'temurin'
|
||||||
@@ -14,7 +14,7 @@ runs:
|
|||||||
run: chmod +x gradlew
|
run: chmod +x gradlew
|
||||||
|
|
||||||
- name: Cache Gradle Wrapper
|
- name: Cache Gradle Wrapper
|
||||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 #v4.2.3
|
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 #v5.0.3
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.gradle/wrapper
|
~/.gradle/wrapper
|
||||||
@@ -24,7 +24,7 @@ runs:
|
|||||||
${{ runner.os }}-gradle-wrapper-
|
${{ runner.os }}-gradle-wrapper-
|
||||||
|
|
||||||
- name: Cache Gradle Dependencies
|
- name: Cache Gradle Dependencies
|
||||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 #v4.2.3
|
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 #v5.0.3
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.gradle/caches
|
~/.gradle/caches
|
||||||
@@ -33,7 +33,7 @@ runs:
|
|||||||
${{ runner.os }}-gradle-caches-
|
${{ runner.os }}-gradle-caches-
|
||||||
|
|
||||||
- name: Cache Android Global Build-Cache
|
- name: Cache Android Global Build-Cache
|
||||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 #v4.2.3
|
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 #v5.0.3
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.android/build-cache
|
~/.android/build-cache
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ jobs:
|
|||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout source code
|
- name: Checkout source code
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
|
||||||
- name: Setup project and build environment
|
- name: Setup project and build environment
|
||||||
uses: ./.github/actions/common-setup
|
uses: ./.github/actions/common-setup
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ jobs:
|
|||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout source code
|
- name: Checkout source code
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
|
||||||
- name: Setup project and build environment
|
- name: Setup project and build environment
|
||||||
uses: ./.github/actions/common-setup
|
uses: ./.github/actions/common-setup
|
||||||
|
|
||||||
@@ -48,14 +48,23 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
variant: [ Debug,Beta,Release ]
|
variant: [ Debug ]
|
||||||
flavor: [ testFoss,testGplay ]
|
flavor: [ testFoss,testGplay ]
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout source code
|
- name: Checkout source code
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
|
||||||
- name: Setup project and build environment
|
- name: Setup project and build environment
|
||||||
uses: ./.github/actions/common-setup
|
uses: ./.github/actions/common-setup
|
||||||
|
|
||||||
- name: Test modules
|
- name: Test modules
|
||||||
run: ./gradlew ${{ matrix.flavor }}${{ matrix.variant }}UnitTest
|
run: ./gradlew ${{ matrix.flavor }}${{ matrix.variant }}UnitTest
|
||||||
|
|
||||||
|
check-fastlane-metadata:
|
||||||
|
name: Fastlane metadata
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
steps:
|
||||||
|
- name: Checkout source code
|
||||||
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
|
||||||
|
- name: Validate metadata lengths
|
||||||
|
run: bash fastlane/check_metadata_length.sh
|
||||||
@@ -13,5 +13,5 @@ jobs:
|
|||||||
name: "Validation"
|
name: "Validation"
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
|
||||||
- uses: gradle/actions/wrapper-validation@06832c7b30a0129d7fb559bcc6e43d26f6374244 #v4.3.1
|
- uses: gradle/actions/wrapper-validation@0723195856401067f7a2779048b490ace7a47d7c #v5.0.2
|
||||||
|
|||||||
@@ -4,6 +4,12 @@ on:
|
|||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- 'v*'
|
- 'v*'
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
dry_run:
|
||||||
|
description: 'Build only, skip release/upload'
|
||||||
|
type: boolean
|
||||||
|
default: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release-github:
|
release-github:
|
||||||
@@ -24,23 +30,15 @@ jobs:
|
|||||||
echo "STORE_PATH=$(echo $TMP_KEYSTORE_FILE_PATH)" >> $GITHUB_ENV
|
echo "STORE_PATH=$(echo $TMP_KEYSTORE_FILE_PATH)" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Checkout source code
|
- name: Checkout source code
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Setup project and build environment
|
- name: Setup project and build environment
|
||||||
uses: ./.github/actions/common-setup
|
uses: ./.github/actions/common-setup
|
||||||
|
|
||||||
- name: Get the version
|
|
||||||
id: tagger
|
|
||||||
uses: jimschubert/query-tag-action@0b288a5fff630fea2e96d61b99047ed823ca19dc #v2.2
|
|
||||||
with:
|
|
||||||
skip-unshallow: 'true'
|
|
||||||
abbrev: false
|
|
||||||
commit-ish: HEAD
|
|
||||||
|
|
||||||
- name: Assemble beta APK
|
- name: Assemble beta APK
|
||||||
if: contains(steps.tagger.outputs.tag, '-beta')
|
if: contains(github.ref_name, '-beta')
|
||||||
run: ./gradlew assembleFossBeta
|
run: ./gradlew assembleFossBeta
|
||||||
env:
|
env:
|
||||||
VERSION: ${{ github.ref }}
|
VERSION: ${{ github.ref }}
|
||||||
@@ -49,7 +47,7 @@ jobs:
|
|||||||
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
|
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
|
||||||
|
|
||||||
- name: Assemble production APK
|
- name: Assemble production APK
|
||||||
if: "!contains(steps.tagger.outputs.tag, '-beta')"
|
if: "!contains(github.ref_name, '-beta')"
|
||||||
run: ./gradlew assembleFossRelease
|
run: ./gradlew assembleFossRelease
|
||||||
env:
|
env:
|
||||||
VERSION: ${{ github.ref }}
|
VERSION: ${{ github.ref }}
|
||||||
@@ -58,27 +56,26 @@ jobs:
|
|||||||
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
|
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
|
||||||
|
|
||||||
- name: Create pre-release
|
- name: Create pre-release
|
||||||
if: contains(steps.tagger.outputs.tag, '-beta')
|
if: contains(github.ref_name, '-beta') && !(github.event_name == 'workflow_dispatch' && inputs.dry_run)
|
||||||
uses: softprops/action-gh-release@c95fe1489396fe8a9eb87c0abf8aa5b2ef267fda #v2.2.1
|
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b #v2.5.0
|
||||||
with:
|
with:
|
||||||
prerelease: true
|
prerelease: true
|
||||||
tag_name: ${{ steps.tagger.outputs.tag }}
|
tag_name: ${{ github.ref_name }}
|
||||||
name: ${{ steps.tagger.outputs.tag }}
|
name: ${{ github.ref_name }}
|
||||||
generate_release_notes: true
|
generate_release_notes: true
|
||||||
files: |
|
files: app/build/outputs/apk/foss/beta/eu.darken.capod-*.apk
|
||||||
app/build/outputs/apk/foss/beta/*.apk
|
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Create release
|
- name: Create release
|
||||||
if: "!contains(steps.tagger.outputs.tag, '-beta')"
|
if: "!contains(github.ref_name, '-beta') && !(github.event_name == 'workflow_dispatch' && inputs.dry_run)"
|
||||||
uses: softprops/action-gh-release@c95fe1489396fe8a9eb87c0abf8aa5b2ef267fda #v2.2.1
|
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b #v2.5.0
|
||||||
with:
|
with:
|
||||||
prerelease: false
|
prerelease: false
|
||||||
tag_name: ${{ steps.tagger.outputs.tag }}
|
tag_name: ${{ github.ref_name }}
|
||||||
name: ${{ steps.tagger.outputs.tag }}
|
name: ${{ github.ref_name }}
|
||||||
generate_release_notes: true
|
generate_release_notes: true
|
||||||
files: app/build/outputs/apk/foss/release/*.apk
|
files: app/build/outputs/apk/foss/release/eu.darken.capod-*.apk
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
@@ -108,29 +105,21 @@ jobs:
|
|||||||
echo "SUPPLY_JSON_KEY=$(echo $TMP_SERVICEKEY_FILE_PATH)" >> $GITHUB_ENV
|
echo "SUPPLY_JSON_KEY=$(echo $TMP_SERVICEKEY_FILE_PATH)" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Checkout source code
|
- name: Checkout source code
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Setup project and build environment
|
- name: Setup project and build environment
|
||||||
uses: ./.github/actions/common-setup
|
uses: ./.github/actions/common-setup
|
||||||
|
|
||||||
- name: Get the version
|
|
||||||
id: tagger
|
|
||||||
uses: jimschubert/query-tag-action@0b288a5fff630fea2e96d61b99047ed823ca19dc #v2.2
|
|
||||||
with:
|
|
||||||
skip-unshallow: 'true'
|
|
||||||
abbrev: false
|
|
||||||
commit-ish: HEAD
|
|
||||||
|
|
||||||
- name: Set up ruby env
|
- name: Set up ruby env
|
||||||
uses: ruby/setup-ruby@354a1ad156761f5ee2b7b13fa8e09943a5e8d252 #v1.229.0
|
uses: ruby/setup-ruby@4eb9f110bac952a8b68ecf92e3b5c7a987594ba6 #v1.292.0
|
||||||
with:
|
with:
|
||||||
ruby-version: 3.3.6
|
ruby-version: 3.3.6
|
||||||
bundler-cache: true
|
bundler-cache: true
|
||||||
|
|
||||||
- name: Assemble beta and upload to Google Play
|
- name: Assemble beta and upload to Google Play
|
||||||
if: contains(steps.tagger.outputs.tag, '-beta')
|
if: contains(github.ref_name, '-beta') && !(github.event_name == 'workflow_dispatch' && inputs.dry_run)
|
||||||
run: bundle exec fastlane beta
|
run: bundle exec fastlane beta
|
||||||
env:
|
env:
|
||||||
STORE_PASSWORD: ${{ secrets.STORE_PASSWORD }}
|
STORE_PASSWORD: ${{ secrets.STORE_PASSWORD }}
|
||||||
@@ -138,7 +127,7 @@ jobs:
|
|||||||
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
|
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
|
||||||
|
|
||||||
- name: Assemble production and upload to Google Play
|
- name: Assemble production and upload to Google Play
|
||||||
if: "!contains(steps.tagger.outputs.tag, '-beta')"
|
if: "!contains(github.ref_name, '-beta') && !(github.event_name == 'workflow_dispatch' && inputs.dry_run)"
|
||||||
run: bundle exec fastlane production
|
run: bundle exec fastlane production
|
||||||
env:
|
env:
|
||||||
STORE_PASSWORD: ${{ secrets.STORE_PASSWORD }}
|
STORE_PASSWORD: ${{ secrets.STORE_PASSWORD }}
|
||||||
|
|||||||
+3
-1
@@ -14,4 +14,6 @@
|
|||||||
/fastlane/report.xml
|
/fastlane/report.xml
|
||||||
/fastlane/Appfile
|
/fastlane/Appfile
|
||||||
/fastlane/README.md
|
/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
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
CFPropertyList (3.0.7)
|
CFPropertyList (3.0.8)
|
||||||
base64
|
abbrev (0.1.2)
|
||||||
nkf
|
addressable (2.8.8)
|
||||||
rexml
|
public_suffix (>= 2.0.2, < 8.0)
|
||||||
addressable (2.8.7)
|
|
||||||
public_suffix (>= 2.0.2, < 7.0)
|
|
||||||
artifactory (3.0.17)
|
artifactory (3.0.17)
|
||||||
atomos (0.1.3)
|
atomos (0.1.3)
|
||||||
aws-eventstream (1.3.2)
|
aws-eventstream (1.4.0)
|
||||||
aws-partitions (1.1109.0)
|
aws-partitions (1.1216.0)
|
||||||
aws-sdk-core (3.224.1)
|
aws-sdk-core (3.242.0)
|
||||||
aws-eventstream (~> 1, >= 1.3.0)
|
aws-eventstream (~> 1, >= 1.3.0)
|
||||||
aws-partitions (~> 1, >= 1.992.0)
|
aws-partitions (~> 1, >= 1.992.0)
|
||||||
aws-sigv4 (~> 1.9)
|
aws-sigv4 (~> 1.9)
|
||||||
base64
|
base64
|
||||||
|
bigdecimal
|
||||||
jmespath (~> 1, >= 1.6.1)
|
jmespath (~> 1, >= 1.6.1)
|
||||||
logger
|
logger
|
||||||
aws-sdk-kms (1.101.0)
|
aws-sdk-kms (1.122.0)
|
||||||
aws-sdk-core (~> 3, >= 3.216.0)
|
aws-sdk-core (~> 3, >= 3.241.4)
|
||||||
aws-sigv4 (~> 1.5)
|
aws-sigv4 (~> 1.5)
|
||||||
aws-sdk-s3 (1.188.0)
|
aws-sdk-s3 (1.213.0)
|
||||||
aws-sdk-core (~> 3, >= 3.224.1)
|
aws-sdk-core (~> 3, >= 3.241.4)
|
||||||
aws-sdk-kms (~> 1)
|
aws-sdk-kms (~> 1)
|
||||||
aws-sigv4 (~> 1.5)
|
aws-sigv4 (~> 1.5)
|
||||||
aws-sigv4 (1.11.0)
|
aws-sigv4 (1.12.1)
|
||||||
aws-eventstream (~> 1, >= 1.0.2)
|
aws-eventstream (~> 1, >= 1.0.2)
|
||||||
babosa (1.0.4)
|
babosa (1.0.4)
|
||||||
base64 (0.3.0)
|
base64 (0.2.0)
|
||||||
|
benchmark (0.5.0)
|
||||||
|
bigdecimal (4.0.1)
|
||||||
claide (1.1.0)
|
claide (1.1.0)
|
||||||
colored (1.2)
|
colored (1.2)
|
||||||
colored2 (3.1.2)
|
colored2 (3.1.2)
|
||||||
commander (4.6.0)
|
commander (4.6.0)
|
||||||
highline (~> 2.0.0)
|
highline (~> 2.0.0)
|
||||||
|
csv (3.3.5)
|
||||||
declarative (0.0.20)
|
declarative (0.0.20)
|
||||||
digest-crc (0.7.0)
|
digest-crc (0.7.0)
|
||||||
rake (>= 12.0.0, < 14.0.0)
|
rake (>= 12.0.0, < 14.0.0)
|
||||||
domain_name (0.5.20190701)
|
domain_name (0.6.20240107)
|
||||||
unf (>= 0.0.5, < 1.0.0)
|
|
||||||
dotenv (2.8.1)
|
dotenv (2.8.1)
|
||||||
emoji_regex (3.2.3)
|
emoji_regex (3.2.3)
|
||||||
excon (0.109.0)
|
excon (0.112.0)
|
||||||
faraday (1.10.4)
|
faraday (1.10.5)
|
||||||
faraday-em_http (~> 1.0)
|
faraday-em_http (~> 1.0)
|
||||||
faraday-em_synchrony (~> 1.0)
|
faraday-em_synchrony (~> 1.0)
|
||||||
faraday-excon (~> 1.1)
|
faraday-excon (~> 1.1)
|
||||||
@@ -54,14 +55,14 @@ GEM
|
|||||||
faraday-rack (~> 1.0)
|
faraday-rack (~> 1.0)
|
||||||
faraday-retry (~> 1.0)
|
faraday-retry (~> 1.0)
|
||||||
ruby2_keywords (>= 0.0.4)
|
ruby2_keywords (>= 0.0.4)
|
||||||
faraday-cookie_jar (0.0.7)
|
faraday-cookie_jar (0.0.8)
|
||||||
faraday (>= 0.8.0)
|
faraday (>= 0.8.0)
|
||||||
http-cookie (~> 1.0.0)
|
http-cookie (>= 1.0.0)
|
||||||
faraday-em_http (1.0.0)
|
faraday-em_http (1.0.0)
|
||||||
faraday-em_synchrony (1.0.1)
|
faraday-em_synchrony (1.0.1)
|
||||||
faraday-excon (1.1.0)
|
faraday-excon (1.1.0)
|
||||||
faraday-httpclient (1.0.1)
|
faraday-httpclient (1.0.1)
|
||||||
faraday-multipart (1.1.1)
|
faraday-multipart (1.2.0)
|
||||||
multipart-post (~> 2.0)
|
multipart-post (~> 2.0)
|
||||||
faraday-net_http (1.0.2)
|
faraday-net_http (1.0.2)
|
||||||
faraday-net_http_persistent (1.2.0)
|
faraday-net_http_persistent (1.2.0)
|
||||||
@@ -71,15 +72,19 @@ GEM
|
|||||||
faraday_middleware (1.2.1)
|
faraday_middleware (1.2.1)
|
||||||
faraday (~> 1.0)
|
faraday (~> 1.0)
|
||||||
fastimage (2.4.0)
|
fastimage (2.4.0)
|
||||||
fastlane (2.228.0)
|
fastlane (2.232.1)
|
||||||
CFPropertyList (>= 2.3, < 4.0.0)
|
CFPropertyList (>= 2.3, < 4.0.0)
|
||||||
|
abbrev (~> 0.1.2)
|
||||||
addressable (>= 2.8, < 3.0.0)
|
addressable (>= 2.8, < 3.0.0)
|
||||||
artifactory (~> 3.0)
|
artifactory (~> 3.0)
|
||||||
aws-sdk-s3 (~> 1.0)
|
aws-sdk-s3 (~> 1.197)
|
||||||
babosa (>= 1.0.3, < 2.0.0)
|
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)
|
colored (~> 1.2)
|
||||||
commander (~> 4.6)
|
commander (~> 4.6)
|
||||||
|
csv (~> 3.3)
|
||||||
dotenv (>= 2.1.1, < 3.0.0)
|
dotenv (>= 2.1.1, < 3.0.0)
|
||||||
emoji_regex (>= 0.1, < 4.0)
|
emoji_regex (>= 0.1, < 4.0)
|
||||||
excon (>= 0.71.0, < 1.0.0)
|
excon (>= 0.71.0, < 1.0.0)
|
||||||
@@ -91,16 +96,20 @@ GEM
|
|||||||
gh_inspector (>= 1.1.2, < 2.0.0)
|
gh_inspector (>= 1.1.2, < 2.0.0)
|
||||||
google-apis-androidpublisher_v3 (~> 0.3)
|
google-apis-androidpublisher_v3 (~> 0.3)
|
||||||
google-apis-playcustomapp_v1 (~> 0.1)
|
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)
|
google-cloud-storage (~> 1.31)
|
||||||
highline (~> 2.0)
|
highline (~> 2.0)
|
||||||
http-cookie (~> 1.0.5)
|
http-cookie (~> 1.0.5)
|
||||||
json (< 3.0.0)
|
json (< 3.0.0)
|
||||||
jwt (>= 2.1.0, < 3)
|
jwt (>= 2.1.0, < 3)
|
||||||
|
logger (>= 1.6, < 2.0)
|
||||||
mini_magick (>= 4.9.4, < 5.0.0)
|
mini_magick (>= 4.9.4, < 5.0.0)
|
||||||
multipart-post (>= 2.0.0, < 3.0.0)
|
multipart-post (>= 2.0.0, < 3.0.0)
|
||||||
|
mutex_m (~> 0.3.0)
|
||||||
naturally (~> 2.2)
|
naturally (~> 2.2)
|
||||||
|
nkf (~> 0.2.0)
|
||||||
optparse (>= 0.1.1, < 1.0.0)
|
optparse (>= 0.1.1, < 1.0.0)
|
||||||
|
ostruct (>= 0.1.0)
|
||||||
plist (>= 3.1.0, < 4.0.0)
|
plist (>= 3.1.0, < 4.0.0)
|
||||||
rubyzip (>= 2.0.0, < 3.0.0)
|
rubyzip (>= 2.0.0, < 3.0.0)
|
||||||
security (= 0.1.5)
|
security (= 0.1.5)
|
||||||
@@ -116,38 +125,40 @@ GEM
|
|||||||
fastlane-sirp (1.0.0)
|
fastlane-sirp (1.0.0)
|
||||||
sysrandom (~> 1.0)
|
sysrandom (~> 1.0)
|
||||||
gh_inspector (1.1.3)
|
gh_inspector (1.1.3)
|
||||||
google-apis-androidpublisher_v3 (0.54.0)
|
google-apis-androidpublisher_v3 (0.96.0)
|
||||||
google-apis-core (>= 0.11.0, < 2.a)
|
google-apis-core (>= 0.15.0, < 2.a)
|
||||||
google-apis-core (0.11.3)
|
google-apis-core (0.18.0)
|
||||||
addressable (~> 2.5, >= 2.5.1)
|
addressable (~> 2.5, >= 2.5.1)
|
||||||
googleauth (>= 0.16.2, < 2.a)
|
googleauth (~> 1.9)
|
||||||
httpclient (>= 2.8.1, < 3.a)
|
httpclient (>= 2.8.3, < 3.a)
|
||||||
mini_mime (~> 1.0)
|
mini_mime (~> 1.0)
|
||||||
|
mutex_m
|
||||||
representable (~> 3.0)
|
representable (~> 3.0)
|
||||||
retriable (>= 2.0, < 4.a)
|
retriable (>= 2.0, < 4.a)
|
||||||
rexml
|
google-apis-iamcredentials_v1 (0.26.0)
|
||||||
google-apis-iamcredentials_v1 (0.17.0)
|
google-apis-core (>= 0.15.0, < 2.a)
|
||||||
google-apis-core (>= 0.11.0, < 2.a)
|
google-apis-playcustomapp_v1 (0.17.0)
|
||||||
google-apis-playcustomapp_v1 (0.13.0)
|
google-apis-core (>= 0.15.0, < 2.a)
|
||||||
google-apis-core (>= 0.11.0, < 2.a)
|
google-apis-storage_v1 (0.60.0)
|
||||||
google-apis-storage_v1 (0.29.0)
|
google-apis-core (>= 0.15.0, < 2.a)
|
||||||
google-apis-core (>= 0.11.0, < 2.a)
|
google-cloud-core (1.8.0)
|
||||||
google-cloud-core (1.6.1)
|
|
||||||
google-cloud-env (>= 1.0, < 3.a)
|
google-cloud-env (>= 1.0, < 3.a)
|
||||||
google-cloud-errors (~> 1.0)
|
google-cloud-errors (~> 1.0)
|
||||||
google-cloud-env (1.6.0)
|
google-cloud-env (2.1.1)
|
||||||
faraday (>= 0.17.3, < 3.0)
|
faraday (>= 1.0, < 3.a)
|
||||||
google-cloud-errors (1.3.1)
|
google-cloud-errors (1.5.0)
|
||||||
google-cloud-storage (1.45.0)
|
google-cloud-storage (1.58.0)
|
||||||
addressable (~> 2.8)
|
addressable (~> 2.8)
|
||||||
digest-crc (~> 0.4)
|
digest-crc (~> 0.4)
|
||||||
google-apis-iamcredentials_v1 (~> 0.1)
|
google-apis-core (>= 0.18, < 2)
|
||||||
google-apis-storage_v1 (~> 0.29.0)
|
google-apis-iamcredentials_v1 (~> 0.18)
|
||||||
|
google-apis-storage_v1 (>= 0.42)
|
||||||
google-cloud-core (~> 1.6)
|
google-cloud-core (~> 1.6)
|
||||||
googleauth (>= 0.16.2, < 2.a)
|
googleauth (~> 1.9)
|
||||||
mini_mime (~> 1.0)
|
mini_mime (~> 1.0)
|
||||||
googleauth (1.8.1)
|
googleauth (1.11.2)
|
||||||
faraday (>= 0.17.3, < 3.a)
|
faraday (>= 1.0, < 3.a)
|
||||||
|
google-cloud-env (~> 2.1)
|
||||||
jwt (>= 1.4, < 3.0)
|
jwt (>= 1.4, < 3.0)
|
||||||
multi_json (~> 1.11)
|
multi_json (~> 1.11)
|
||||||
os (>= 0.9, < 2.0)
|
os (>= 0.9, < 2.0)
|
||||||
@@ -158,37 +169,38 @@ GEM
|
|||||||
httpclient (2.9.0)
|
httpclient (2.9.0)
|
||||||
mutex_m
|
mutex_m
|
||||||
jmespath (1.6.2)
|
jmespath (1.6.2)
|
||||||
json (2.7.6)
|
json (2.18.1)
|
||||||
jwt (2.10.1)
|
jwt (2.10.2)
|
||||||
base64
|
base64
|
||||||
logger (1.7.0)
|
logger (1.7.0)
|
||||||
mini_magick (4.13.2)
|
mini_magick (4.13.2)
|
||||||
mini_mime (1.1.5)
|
mini_mime (1.1.5)
|
||||||
multi_json (1.15.0)
|
multi_json (1.19.1)
|
||||||
multipart-post (2.4.1)
|
multipart-post (2.4.1)
|
||||||
mutex_m (0.3.0)
|
mutex_m (0.3.0)
|
||||||
nanaimo (0.4.0)
|
nanaimo (0.4.0)
|
||||||
naturally (2.3.0)
|
naturally (2.3.0)
|
||||||
nkf (0.2.0)
|
nkf (0.2.0)
|
||||||
optparse (0.6.0)
|
optparse (0.8.1)
|
||||||
os (1.1.4)
|
os (1.1.4)
|
||||||
|
ostruct (0.6.3)
|
||||||
plist (3.7.2)
|
plist (3.7.2)
|
||||||
public_suffix (5.1.1)
|
public_suffix (7.0.2)
|
||||||
rake (13.3.0)
|
rake (13.3.1)
|
||||||
representable (3.2.0)
|
representable (3.2.0)
|
||||||
declarative (< 0.1.0)
|
declarative (< 0.1.0)
|
||||||
trailblazer-option (>= 0.1.1, < 0.2.0)
|
trailblazer-option (>= 0.1.1, < 0.2.0)
|
||||||
uber (< 0.2.0)
|
uber (< 0.2.0)
|
||||||
retriable (3.1.2)
|
retriable (3.2.0)
|
||||||
rexml (3.4.1)
|
rexml (3.4.4)
|
||||||
rouge (3.28.0)
|
rouge (3.28.0)
|
||||||
ruby2_keywords (0.0.5)
|
ruby2_keywords (0.0.5)
|
||||||
rubyzip (2.4.1)
|
rubyzip (2.4.1)
|
||||||
security (0.1.5)
|
security (0.1.5)
|
||||||
signet (0.18.0)
|
signet (0.21.0)
|
||||||
addressable (~> 2.8)
|
addressable (~> 2.8)
|
||||||
faraday (>= 0.17.5, < 3.a)
|
faraday (>= 0.17.5, < 3.a)
|
||||||
jwt (>= 1.5, < 3.0)
|
jwt (>= 1.5, < 4.0)
|
||||||
multi_json (~> 1.10)
|
multi_json (~> 1.10)
|
||||||
simctl (1.6.10)
|
simctl (1.6.10)
|
||||||
CFPropertyList
|
CFPropertyList
|
||||||
@@ -203,7 +215,6 @@ GEM
|
|||||||
tty-spinner (0.9.3)
|
tty-spinner (0.9.3)
|
||||||
tty-cursor (~> 0.7)
|
tty-cursor (~> 0.7)
|
||||||
uber (0.1.0)
|
uber (0.1.0)
|
||||||
unf (0.2.0)
|
|
||||||
unicode-display_width (2.6.0)
|
unicode-display_width (2.6.0)
|
||||||
word_wrap (1.0.0)
|
word_wrap (1.0.0)
|
||||||
xcodeproj (1.27.0)
|
xcodeproj (1.27.0)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
# Companion App for AirPods (CAPod)
|
# Companion App for AirPods (CAPod)
|
||||||
|
|
||||||
[](https://github.com/d4rken-org/capod/releases/latest)
|
[](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://github.com/d4rken/capod/actions/workflows/code-checks.yml)
|
||||||
[](https://crowdin.com/project/capod)
|
[](https://crowdin.com/project/capod)
|
||||||
[](https://github.com/d4rken-org/capod/edit/main/README.md#download)
|
[](https://github.com/d4rken-org/capod/edit/main/README.md#download)
|
||||||
@@ -51,11 +52,12 @@ Currently supported models:
|
|||||||
|
|
||||||
| Source | Status |
|
| 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](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) | |
|
| [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) |
|
| [Github (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/) |
|
| [Github (Pre-Release)](https://github.com/d4rken-org/capod/releases) | )  |
|
||||||
| [F-Droid (IzzyOnDroid)](https://apt.izzysoft.de/packages/eu.darken.capod/) | [](https://apt.izzysoft.de/packages/eu.darken.capod/) |
|
| [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
|
## Support the project
|
||||||
|
|
||||||
|
|||||||
@@ -26,4 +26,3 @@ exclude:
|
|||||||
- app
|
- app
|
||||||
- app-common
|
- app-common
|
||||||
- CONTRIBUTING.md
|
- CONTRIBUTING.md
|
||||||
- CLAUDE.md
|
|
||||||
|
|||||||
+66
-25
@@ -1,36 +1,42 @@
|
|||||||
plugins {
|
plugins {
|
||||||
|
id("projectConfig")
|
||||||
id("com.android.application")
|
id("com.android.application")
|
||||||
id("kotlin-android")
|
id("kotlin-android")
|
||||||
id("com.google.devtools.ksp")
|
id("com.google.devtools.ksp")
|
||||||
id("kotlin-kapt")
|
|
||||||
id("kotlin-parcelize")
|
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 = "dagger.hilt.android.plugin")
|
||||||
apply(plugin = "androidx.navigation.safeargs.kotlin")
|
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdk = ProjectConfig.compileSdk
|
compileSdk = projectConfig.compileSdk
|
||||||
namespace = ProjectConfig.packageName
|
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = ProjectConfig.packageName
|
namespace = projectConfig.packageName
|
||||||
|
|
||||||
minSdk = ProjectConfig.minSdk
|
minSdk = projectConfig.minSdk
|
||||||
targetSdk = ProjectConfig.targetSdk
|
targetSdk = projectConfig.targetSdk
|
||||||
|
|
||||||
versionCode = ProjectConfig.Version.code + 0 // Base app
|
versionCode = projectConfig.version.code.toInt()
|
||||||
versionName = ProjectConfig.Version.name
|
versionName = projectConfig.version.name
|
||||||
|
|
||||||
testInstrumentationRunner = "eu.darken.capod.HiltTestRunner"
|
testInstrumentationRunner = "eu.darken.capod.HiltTestRunner"
|
||||||
|
|
||||||
|
buildConfigField("String", "PACKAGENAME", "\"${projectConfig.packageName}\"")
|
||||||
|
buildConfigField("String", "VERSION_CODE", "\"${projectConfig.version.code}\"")
|
||||||
|
buildConfigField("String", "VERSION_NAME", "\"${projectConfig.version.name}\"")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enable automatic per-app language preferences generation
|
// Enable automatic per-app language preferences generation
|
||||||
androidResources {
|
androidResources {
|
||||||
|
@Suppress("UnstableApiUsage")
|
||||||
generateLocaleConfig = true
|
generateLocaleConfig = true
|
||||||
}
|
}
|
||||||
|
|
||||||
signingConfigs {
|
signingConfigs {
|
||||||
val basePath = File(System.getProperty("user.home"), ".appconfig/${ProjectConfig.packageName}")
|
val basePath = File(System.getProperty("user.home"), ".config/projects/${projectConfig.packageName}")
|
||||||
create("releaseFoss") {
|
create("releaseFoss") {
|
||||||
setupCredentials(File(basePath, "signing-foss.properties"))
|
setupCredentials(File(basePath, "signing-foss.properties"))
|
||||||
}
|
}
|
||||||
@@ -89,21 +95,12 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buildOutputs.all {
|
experimentalProperties["android.experimental.enableScreenshotTest"] = true
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
viewBinding = true
|
viewBinding = true
|
||||||
|
buildConfig = true
|
||||||
|
compose = true
|
||||||
}
|
}
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
@@ -121,6 +118,7 @@ android {
|
|||||||
"-opt-in=kotlin.time.ExperimentalTime",
|
"-opt-in=kotlin.time.ExperimentalTime",
|
||||||
"-opt-in=kotlin.RequiresOptIn",
|
"-opt-in=kotlin.RequiresOptIn",
|
||||||
"-opt-in=kotlin.ExperimentalUnsignedTypes",
|
"-opt-in=kotlin.ExperimentalUnsignedTypes",
|
||||||
|
"-opt-in=androidx.compose.material3.ExperimentalMaterial3Api",
|
||||||
"-Xannotation-default-target=param-property"
|
"-Xannotation-default-target=param-property"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -136,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 {
|
dependencies {
|
||||||
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.5")
|
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.5")
|
||||||
|
|
||||||
@@ -143,8 +177,6 @@ dependencies {
|
|||||||
|
|
||||||
addDagger()
|
addDagger()
|
||||||
|
|
||||||
addMoshi()
|
|
||||||
|
|
||||||
addOkio()
|
addOkio()
|
||||||
|
|
||||||
addBaseAndroid()
|
addBaseAndroid()
|
||||||
@@ -153,10 +185,19 @@ dependencies {
|
|||||||
implementation("androidx.core:core-splashscreen:1.0.0-alpha02")
|
implementation("androidx.core:core-splashscreen:1.0.0-alpha02")
|
||||||
|
|
||||||
addNavigation()
|
addNavigation()
|
||||||
addBaseWorkManager()
|
|
||||||
|
addCompose()
|
||||||
|
addGlance()
|
||||||
|
addDataStore()
|
||||||
|
addNavigation3()
|
||||||
|
addSerialization()
|
||||||
|
|
||||||
addTesting()
|
addTesting()
|
||||||
|
|
||||||
"gplayImplementation"("com.android.billingclient:billing:8.0.0")
|
"gplayImplementation"("com.android.billingclient:billing:8.0.0")
|
||||||
"gplayImplementation"("com.android.billingclient:billing-ktx: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.
|
-keep class eu.darken.capod.BuildConfig { *; }
|
||||||
# You can control the set of applied configuration files using the
|
-dontobfuscate
|
||||||
# proguardFiles setting in build.gradle.
|
|
||||||
#
|
|
||||||
# For more details, see
|
|
||||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
|
||||||
|
|
||||||
# If your project uses WebView with JS, uncomment the following
|
# work-runtime 2.7.1 (pulled by Glance) uses Class.newInstance() reflection throughout.
|
||||||
# and specify the fully qualified class name to the JavaScript interface
|
# R8 full mode strips no-arg constructors not reachable by static analysis.
|
||||||
# class:
|
# Keep all constructors in the work package. Remove when Glance upgrades to work-runtime 2.10+.
|
||||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
# See: https://issuetracker.google.com/issues/243257364
|
||||||
# public *;
|
-keep class androidx.work.** { <init>(...); }
|
||||||
#}
|
|
||||||
|
|
||||||
# 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
|
|
||||||
@@ -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
|
package eu.darken.capod.common.upgrade.core
|
||||||
|
|
||||||
import android.content.Context
|
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 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.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
class FossCache @Inject constructor(
|
class FossCache @Inject constructor(
|
||||||
@ApplicationContext context: Context,
|
@ApplicationContext context: Context,
|
||||||
moshi: Moshi
|
@SerializationCapod json: Json,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val preferences = context.getSharedPreferences("settings_foss", Context.MODE_PRIVATE)
|
private val Context.dataStore by preferencesDataStore(
|
||||||
|
name = "settings_foss",
|
||||||
val upgrade = preferences.createFlowPreference<FossUpgrade?>(
|
produceMigrations = { ctx -> listOf(SharedPreferencesMigration(ctx, "settings_foss")) }
|
||||||
key = "foss.upgrade",
|
|
||||||
moshi = moshi,
|
|
||||||
defaultValue = null,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
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
|
package eu.darken.capod.common.upgrade.core
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import eu.darken.capod.common.serialization.InstantEpochMillisSerializer
|
||||||
import com.squareup.moshi.JsonClass
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@Serializable
|
||||||
data class FossUpgrade(
|
data class FossUpgrade(
|
||||||
val upgradedAt: Instant,
|
@SerialName("upgradedAt") @Serializable(with = InstantEpochMillisSerializer::class) val upgradedAt: Instant,
|
||||||
val reason: Reason
|
@SerialName("reason") val reason: Reason
|
||||||
) {
|
) {
|
||||||
@JsonClass(generateAdapter = false)
|
@Serializable
|
||||||
enum class Reason {
|
enum class Reason {
|
||||||
@Json(name = "foss.upgrade.reason.donated") DONATED,
|
@SerialName("foss.upgrade.reason.donated") DONATED,
|
||||||
@Json(name = "foss.upgrade.reason.alreadydonated") ALREADY_DONATED,
|
@SerialName("foss.upgrade.reason.alreadydonated") ALREADY_DONATED,
|
||||||
@Json(name = "foss.upgrade.reason.nomoney") NO_MONEY;
|
@SerialName("foss.upgrade.reason.nomoney") NO_MONEY;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,21 +1,16 @@
|
|||||||
package eu.darken.capod.common.upgrade.core
|
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 eu.darken.capod.common.upgrade.UpgradeRepo
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
import eu.darken.capod.common.datastore.valueBlocking
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
class UpgradeControlFoss @Inject constructor(
|
class UpgradeControlFoss @Inject constructor(
|
||||||
private val fossCache: FossCache,
|
private val fossCache: FossCache,
|
||||||
private val webpageTool: WebpageTool,
|
|
||||||
) : UpgradeRepo {
|
) : UpgradeRepo {
|
||||||
|
|
||||||
override val upgradeInfo: Flow<UpgradeRepo.Info> = fossCache.upgrade.flow.map { data ->
|
override val upgradeInfo: Flow<UpgradeRepo.Info> = fossCache.upgrade.flow.map { data ->
|
||||||
@@ -30,34 +25,11 @@ class UpgradeControlFoss @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun launchBillingFlow(activity: Activity) {
|
fun upgrade(reason: FossUpgrade.Reason) {
|
||||||
MaterialAlertDialogBuilder(activity).apply {
|
fossCache.upgrade.valueBlocking = FossUpgrade(
|
||||||
setIcon(R.drawable.ic_heart)
|
upgradedAt = Instant.now(),
|
||||||
setTitle(R.string.upgrade_capod_label)
|
reason = reason
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data class Info(
|
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_donate_label">Skenk</string>
|
||||||
<string name="foss_upgrade_alreadydonated_label">Ek het reeds geskenk</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="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>
|
</resources>
|
||||||
|
|||||||
@@ -3,4 +3,8 @@
|
|||||||
<string name="foss_upgrade_donate_label">ልገሳ</string>
|
<string name="foss_upgrade_donate_label">ልገሳ</string>
|
||||||
<string name="foss_upgrade_alreadydonated_label">አስቀድሜ ልገሳ ፈጽሜአለሁ</string>
|
<string name="foss_upgrade_alreadydonated_label">አስቀድሜ ልገሳ ፈጽሜአለሁ</string>
|
||||||
<string name="foss_upgrade_no_money_label">ገንዘቤን በሙሉ በAirPods ላይ አውጥቻለሁ</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>
|
</resources>
|
||||||
|
|||||||
@@ -3,4 +3,8 @@
|
|||||||
<string name="foss_upgrade_donate_label">تبرُّع</string>
|
<string name="foss_upgrade_donate_label">تبرُّع</string>
|
||||||
<string name="foss_upgrade_alreadydonated_label">أنا متبرع بالفعل</string>
|
<string name="foss_upgrade_alreadydonated_label">أنا متبرع بالفعل</string>
|
||||||
<string name="foss_upgrade_no_money_label">أُنفق كل أموالي على AirPods</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>
|
</resources>
|
||||||
|
|||||||
@@ -3,4 +3,8 @@
|
|||||||
<string name="foss_upgrade_donate_label">İanə ver</string>
|
<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_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="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>
|
</resources>
|
||||||
|
|||||||
@@ -3,4 +3,8 @@
|
|||||||
<string name="foss_upgrade_donate_label">Ахвяраваць</string>
|
<string name="foss_upgrade_donate_label">Ахвяраваць</string>
|
||||||
<string name="foss_upgrade_alreadydonated_label">Я ўжо ахвяраваў</string>
|
<string name="foss_upgrade_alreadydonated_label">Я ўжо ахвяраваў</string>
|
||||||
<string name="foss_upgrade_no_money_label">Я патраціў усе грошы на AirPods</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>
|
</resources>
|
||||||
|
|||||||
@@ -3,4 +3,8 @@
|
|||||||
<string name="foss_upgrade_donate_label">Дарение</string>
|
<string name="foss_upgrade_donate_label">Дарение</string>
|
||||||
<string name="foss_upgrade_alreadydonated_label">Вече съм дарил</string>
|
<string name="foss_upgrade_alreadydonated_label">Вече съм дарил</string>
|
||||||
<string name="foss_upgrade_no_money_label">Харча всичките си пари за AirPods</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>
|
</resources>
|
||||||
|
|||||||
@@ -3,4 +3,8 @@
|
|||||||
<string name="foss_upgrade_donate_label">দান করুন</string>
|
<string name="foss_upgrade_donate_label">দান করুন</string>
|
||||||
<string name="foss_upgrade_alreadydonated_label">আমি ইতিমধ্যে দান করেছি</string>
|
<string name="foss_upgrade_alreadydonated_label">আমি ইতিমধ্যে দান করেছি</string>
|
||||||
<string name="foss_upgrade_no_money_label">আমি আমার সব টাকা AirPods এর জন্য খরচ করি</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>
|
</resources>
|
||||||
|
|||||||
@@ -3,4 +3,8 @@
|
|||||||
<string name="foss_upgrade_donate_label">Donacions</string>
|
<string name="foss_upgrade_donate_label">Donacions</string>
|
||||||
<string name="foss_upgrade_alreadydonated_label">Ja he donat</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="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>
|
</resources>
|
||||||
|
|||||||
@@ -3,4 +3,8 @@
|
|||||||
<string name="foss_upgrade_donate_label">Přispět</string>
|
<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_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="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>
|
</resources>
|
||||||
|
|||||||
@@ -3,4 +3,8 @@
|
|||||||
<string name="foss_upgrade_donate_label">Doner</string>
|
<string name="foss_upgrade_donate_label">Doner</string>
|
||||||
<string name="foss_upgrade_alreadydonated_label">Jeg har allerede doneret</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="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>
|
</resources>
|
||||||
|
|||||||
@@ -3,4 +3,8 @@
|
|||||||
<string name="foss_upgrade_donate_label">Spenden</string>
|
<string name="foss_upgrade_donate_label">Spenden</string>
|
||||||
<string name="foss_upgrade_alreadydonated_label">Ich habe schon gespendet</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="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>
|
</resources>
|
||||||
|
|||||||
@@ -3,4 +3,8 @@
|
|||||||
<string name="foss_upgrade_donate_label">Δωρεά</string>
|
<string name="foss_upgrade_donate_label">Δωρεά</string>
|
||||||
<string name="foss_upgrade_alreadydonated_label">Ήδη δώρισα</string>
|
<string name="foss_upgrade_alreadydonated_label">Ήδη δώρισα</string>
|
||||||
<string name="foss_upgrade_no_money_label">Ξόδεψα όλα τα χρήματά μου στα AirPods</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>
|
</resources>
|
||||||
|
|||||||
@@ -3,4 +3,8 @@
|
|||||||
<string name="foss_upgrade_donate_label">Donar</string>
|
<string name="foss_upgrade_donate_label">Donar</string>
|
||||||
<string name="foss_upgrade_alreadydonated_label">Ya doné</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="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>
|
</resources>
|
||||||
|
|||||||
@@ -3,4 +3,8 @@
|
|||||||
<string name="foss_upgrade_donate_label">Donar</string>
|
<string name="foss_upgrade_donate_label">Donar</string>
|
||||||
<string name="foss_upgrade_alreadydonated_label">Ya doné</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="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>
|
</resources>
|
||||||
|
|||||||
@@ -3,4 +3,8 @@
|
|||||||
<string name="foss_upgrade_donate_label">Donar</string>
|
<string name="foss_upgrade_donate_label">Donar</string>
|
||||||
<string name="foss_upgrade_alreadydonated_label">Ya he donado</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="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>
|
</resources>
|
||||||
|
|||||||
@@ -3,4 +3,8 @@
|
|||||||
<string name="foss_upgrade_donate_label">Anneta</string>
|
<string name="foss_upgrade_donate_label">Anneta</string>
|
||||||
<string name="foss_upgrade_alreadydonated_label">Juba annetasin</string>
|
<string name="foss_upgrade_alreadydonated_label">Juba annetasin</string>
|
||||||
<string name="foss_upgrade_no_money_label">Kulutasin kogu raha AirPodsidele</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>
|
</resources>
|
||||||
|
|||||||
@@ -3,4 +3,8 @@
|
|||||||
<string name="foss_upgrade_donate_label">Eman dohaintza</string>
|
<string name="foss_upgrade_donate_label">Eman dohaintza</string>
|
||||||
<string name="foss_upgrade_alreadydonated_label">Dagoeneko eman dut 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="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>
|
</resources>
|
||||||
|
|||||||
@@ -3,4 +3,8 @@
|
|||||||
<string name="foss_upgrade_donate_label">کمک مالی</string>
|
<string name="foss_upgrade_donate_label">کمک مالی</string>
|
||||||
<string name="foss_upgrade_alreadydonated_label">کمک مالی کردهام</string>
|
<string name="foss_upgrade_alreadydonated_label">کمک مالی کردهام</string>
|
||||||
<string name="foss_upgrade_no_money_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>
|
</resources>
|
||||||
|
|||||||
@@ -3,4 +3,8 @@
|
|||||||
<string name="foss_upgrade_donate_label">Lahjoita</string>
|
<string name="foss_upgrade_donate_label">Lahjoita</string>
|
||||||
<string name="foss_upgrade_alreadydonated_label">Olen jo lahjoittanut</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="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>
|
</resources>
|
||||||
|
|||||||
@@ -3,4 +3,8 @@
|
|||||||
<string name="foss_upgrade_donate_label">Mag-donate</string>
|
<string name="foss_upgrade_donate_label">Mag-donate</string>
|
||||||
<string name="foss_upgrade_alreadydonated_label">Nakapag-donate na ako</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="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>
|
</resources>
|
||||||
|
|||||||
@@ -2,5 +2,9 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<string name="foss_upgrade_donate_label">Faire un don</string>
|
<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_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>
|
</resources>
|
||||||
|
|||||||
@@ -3,4 +3,8 @@
|
|||||||
<string name="foss_upgrade_donate_label">Doar</string>
|
<string name="foss_upgrade_donate_label">Doar</string>
|
||||||
<string name="foss_upgrade_alreadydonated_label">Xa doei</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="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>
|
</resources>
|
||||||
|
|||||||
@@ -3,4 +3,8 @@
|
|||||||
<string name="foss_upgrade_donate_label">दान करें</string>
|
<string name="foss_upgrade_donate_label">दान करें</string>
|
||||||
<string name="foss_upgrade_alreadydonated_label">मैंने पहले ही दान कर दिया है</string>
|
<string name="foss_upgrade_alreadydonated_label">मैंने पहले ही दान कर दिया है</string>
|
||||||
<string name="foss_upgrade_no_money_label">मैं अपना सारा पैसा AirPods पर खर्च करता हूं</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>
|
</resources>
|
||||||
|
|||||||
@@ -3,4 +3,8 @@
|
|||||||
<string name="foss_upgrade_donate_label">Donirajte</string>
|
<string name="foss_upgrade_donate_label">Donirajte</string>
|
||||||
<string name="foss_upgrade_alreadydonated_label">Već sam donirao</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="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>
|
</resources>
|
||||||
|
|||||||
@@ -3,4 +3,8 @@
|
|||||||
<string name="foss_upgrade_donate_label">Adományoz</string>
|
<string name="foss_upgrade_donate_label">Adományoz</string>
|
||||||
<string name="foss_upgrade_alreadydonated_label">Már adományoztam</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="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>
|
</resources>
|
||||||
|
|||||||
@@ -3,4 +3,8 @@
|
|||||||
<string name="foss_upgrade_donate_label">Նվիրաբերել</string>
|
<string name="foss_upgrade_donate_label">Նվիրաբերել</string>
|
||||||
<string name="foss_upgrade_alreadydonated_label">Ես արդեն նվիրաբերել եմ</string>
|
<string name="foss_upgrade_alreadydonated_label">Ես արդեն նվիրաբերել եմ</string>
|
||||||
<string name="foss_upgrade_no_money_label">Ես իմ ամբողջ գումարը ծախսում եմ AirPods-ի վրա</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>
|
</resources>
|
||||||
|
|||||||
@@ -3,4 +3,8 @@
|
|||||||
<string name="foss_upgrade_donate_label">Menyumbang</string>
|
<string name="foss_upgrade_donate_label">Menyumbang</string>
|
||||||
<string name="foss_upgrade_alreadydonated_label">Saya telah berdonasi</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="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>
|
</resources>
|
||||||
|
|||||||
@@ -3,4 +3,8 @@
|
|||||||
<string name="foss_upgrade_donate_label">Gefa</string>
|
<string name="foss_upgrade_donate_label">Gefa</string>
|
||||||
<string name="foss_upgrade_alreadydonated_label">Ég hef þegar gefið</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="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>
|
</resources>
|
||||||
|
|||||||
@@ -3,4 +3,8 @@
|
|||||||
<string name="foss_upgrade_donate_label">Dona</string>
|
<string name="foss_upgrade_donate_label">Dona</string>
|
||||||
<string name="foss_upgrade_alreadydonated_label">Ho già donato</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="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>
|
</resources>
|
||||||
|
|||||||
@@ -3,4 +3,8 @@
|
|||||||
<string name="foss_upgrade_donate_label">תרומה</string>
|
<string name="foss_upgrade_donate_label">תרומה</string>
|
||||||
<string name="foss_upgrade_alreadydonated_label">כבר תרמתי</string>
|
<string name="foss_upgrade_alreadydonated_label">כבר תרמתי</string>
|
||||||
<string name="foss_upgrade_no_money_label">מיטב כספי מושקע ב-AirPods</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>
|
</resources>
|
||||||
|
|||||||
@@ -3,4 +3,8 @@
|
|||||||
<string name="foss_upgrade_donate_label">寄付</string>
|
<string name="foss_upgrade_donate_label">寄付</string>
|
||||||
<string name="foss_upgrade_alreadydonated_label">すでに寄付しました</string>
|
<string name="foss_upgrade_alreadydonated_label">すでに寄付しました</string>
|
||||||
<string name="foss_upgrade_no_money_label">AirPodsに全財産をつぎ込んでいます</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>
|
</resources>
|
||||||
|
|||||||
@@ -3,4 +3,8 @@
|
|||||||
<string name="foss_upgrade_donate_label">შემოწირულობა</string>
|
<string name="foss_upgrade_donate_label">შემოწირულობა</string>
|
||||||
<string name="foss_upgrade_alreadydonated_label">მე უკვე შემოწირული მაქვს</string>
|
<string name="foss_upgrade_alreadydonated_label">მე უკვე შემოწირული მაქვს</string>
|
||||||
<string name="foss_upgrade_no_money_label">მთელ ჩემს ფულს AirPods-ებზე ვხარჯავ</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>
|
</resources>
|
||||||
|
|||||||
@@ -3,4 +3,8 @@
|
|||||||
<string name="foss_upgrade_donate_label">បរិច្ចាគ</string>
|
<string name="foss_upgrade_donate_label">បរិច្ចាគ</string>
|
||||||
<string name="foss_upgrade_alreadydonated_label">ខ្ញុំបានបរិច្ចាគរួចហើយ</string>
|
<string name="foss_upgrade_alreadydonated_label">ខ្ញុំបានបរិច្ចាគរួចហើយ</string>
|
||||||
<string name="foss_upgrade_no_money_label">ខ្ញុំចំណាយលុយទាំងអស់របស់ខ្ញុំលើ AirPods</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>
|
</resources>
|
||||||
|
|||||||
@@ -3,4 +3,8 @@
|
|||||||
<string name="foss_upgrade_donate_label">Beşdar bike</string>
|
<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_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="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>
|
</resources>
|
||||||
|
|||||||
@@ -3,4 +3,8 @@
|
|||||||
<string name="foss_upgrade_donate_label">ದಾನ ಮಾಡಿ</string>
|
<string name="foss_upgrade_donate_label">ದಾನ ಮಾಡಿ</string>
|
||||||
<string name="foss_upgrade_alreadydonated_label">ನಾನು ಈಗಾಗಲೇ ದಾನ ಮಾಡಿದ್ದೇನೆ</string>
|
<string name="foss_upgrade_alreadydonated_label">ನಾನು ಈಗಾಗಲೇ ದಾನ ಮಾಡಿದ್ದೇನೆ</string>
|
||||||
<string name="foss_upgrade_no_money_label">ನಾನು ನನ್ನ ಎಲ್ಲಾ ಹಣವನ್ನು AirPods ಗಾಗಿ ಖರ್ಚು ಮಾಡುತ್ತೇನೆ</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>
|
</resources>
|
||||||
|
|||||||
@@ -3,4 +3,8 @@
|
|||||||
<string name="foss_upgrade_donate_label">후원하기</string>
|
<string name="foss_upgrade_donate_label">후원하기</string>
|
||||||
<string name="foss_upgrade_alreadydonated_label">이미 후원했어요</string>
|
<string name="foss_upgrade_alreadydonated_label">이미 후원했어요</string>
|
||||||
<string name="foss_upgrade_no_money_label">AirPods에 돈을 다 썼어요</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>
|
</resources>
|
||||||
|
|||||||
@@ -3,4 +3,8 @@
|
|||||||
<string name="foss_upgrade_donate_label">Демөөрчүлүк кылуу</string>
|
<string name="foss_upgrade_donate_label">Демөөрчүлүк кылуу</string>
|
||||||
<string name="foss_upgrade_alreadydonated_label">Мен буга чейин демөөрчүлүк кылгам</string>
|
<string name="foss_upgrade_alreadydonated_label">Мен буга чейин демөөрчүлүк кылгам</string>
|
||||||
<string name="foss_upgrade_no_money_label">Мен бүт акчамды AirPods үчүн короттум</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>
|
</resources>
|
||||||
|
|||||||
@@ -3,4 +3,8 @@
|
|||||||
<string name="foss_upgrade_donate_label">ບໍລິຈາກ</string>
|
<string name="foss_upgrade_donate_label">ບໍລິຈາກ</string>
|
||||||
<string name="foss_upgrade_alreadydonated_label">ຂ້ອຍໄດ້ບໍລິຈາກແລ້ວ</string>
|
<string name="foss_upgrade_alreadydonated_label">ຂ້ອຍໄດ້ບໍລິຈາກແລ້ວ</string>
|
||||||
<string name="foss_upgrade_no_money_label">ຂ້ອຍໃຊ້ເງິນທັງໝົດຂອງຂ້ອຍໃສ່ AirPods</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>
|
</resources>
|
||||||
|
|||||||
@@ -3,4 +3,8 @@
|
|||||||
<string name="foss_upgrade_donate_label">Aukoti</string>
|
<string name="foss_upgrade_donate_label">Aukoti</string>
|
||||||
<string name="foss_upgrade_alreadydonated_label">Aš jau paaukojau</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="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>
|
</resources>
|
||||||
|
|||||||
@@ -3,4 +3,8 @@
|
|||||||
<string name="foss_upgrade_donate_label">Ziedot</string>
|
<string name="foss_upgrade_donate_label">Ziedot</string>
|
||||||
<string name="foss_upgrade_alreadydonated_label">Es jau ziedoju</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="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>
|
</resources>
|
||||||
|
|||||||
@@ -3,4 +3,8 @@
|
|||||||
<string name="foss_upgrade_donate_label">Донирај</string>
|
<string name="foss_upgrade_donate_label">Донирај</string>
|
||||||
<string name="foss_upgrade_alreadydonated_label">Веќе донирав</string>
|
<string name="foss_upgrade_alreadydonated_label">Веќе донирав</string>
|
||||||
<string name="foss_upgrade_no_money_label">Ги трошам сите пари на AirPods</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>
|
</resources>
|
||||||
|
|||||||
@@ -3,4 +3,8 @@
|
|||||||
<string name="foss_upgrade_donate_label">സംഭാവന ചെയ്യുക</string>
|
<string name="foss_upgrade_donate_label">സംഭാവന ചെയ്യുക</string>
|
||||||
<string name="foss_upgrade_alreadydonated_label">ഞാൻ ഇതിനകം സംഭാവന ചെയ്തു</string>
|
<string name="foss_upgrade_alreadydonated_label">ഞാൻ ഇതിനകം സംഭാവന ചെയ്തു</string>
|
||||||
<string name="foss_upgrade_no_money_label">ഞാൻ എൻ്റെ എല്ലാ പണവും AirPods-നായി ചെലവഴിക്കുന്നു</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>
|
</resources>
|
||||||
|
|||||||
@@ -3,4 +3,8 @@
|
|||||||
<string name="foss_upgrade_donate_label">Хандивлах</string>
|
<string name="foss_upgrade_donate_label">Хандивлах</string>
|
||||||
<string name="foss_upgrade_alreadydonated_label">Би аль хэдийн хандивласан</string>
|
<string name="foss_upgrade_alreadydonated_label">Би аль хэдийн хандивласан</string>
|
||||||
<string name="foss_upgrade_no_money_label">Би бүх мөнгөө AirPods-д зарцуулдаг</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>
|
</resources>
|
||||||
|
|||||||
@@ -3,4 +3,8 @@
|
|||||||
<string name="foss_upgrade_donate_label">दान करा</string>
|
<string name="foss_upgrade_donate_label">दान करा</string>
|
||||||
<string name="foss_upgrade_alreadydonated_label">मी आधीच दान केले आहे</string>
|
<string name="foss_upgrade_alreadydonated_label">मी आधीच दान केले आहे</string>
|
||||||
<string name="foss_upgrade_no_money_label">मी माझे सर्व पैसे AirPods वर खर्च करतो</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>
|
</resources>
|
||||||
|
|||||||
@@ -3,4 +3,8 @@
|
|||||||
<string name="foss_upgrade_donate_label">Sumbang</string>
|
<string name="foss_upgrade_donate_label">Sumbang</string>
|
||||||
<string name="foss_upgrade_alreadydonated_label">Saya telah menyumbang</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="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>
|
</resources>
|
||||||
|
|||||||
@@ -3,4 +3,8 @@
|
|||||||
<string name="foss_upgrade_donate_label">လှူဒါန်းပါ</string>
|
<string name="foss_upgrade_donate_label">လှူဒါန်းပါ</string>
|
||||||
<string name="foss_upgrade_alreadydonated_label">ကျွန်တော် လှူပြီးပါပြီ</string>
|
<string name="foss_upgrade_alreadydonated_label">ကျွန်တော် လှူပြီးပါပြီ</string>
|
||||||
<string name="foss_upgrade_no_money_label">ကျွန်တော် ပိုက်ဆံအားလုံးကို AirPods မှာ သုံးတယ်</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>
|
</resources>
|
||||||
|
|||||||
@@ -3,4 +3,8 @@
|
|||||||
<string name="foss_upgrade_donate_label">Doner</string>
|
<string name="foss_upgrade_donate_label">Doner</string>
|
||||||
<string name="foss_upgrade_alreadydonated_label">Jeg har allerede donert</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="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>
|
</resources>
|
||||||
|
|||||||
@@ -3,4 +3,8 @@
|
|||||||
<string name="foss_upgrade_donate_label">दान गर्नुहोस्</string>
|
<string name="foss_upgrade_donate_label">दान गर्नुहोस्</string>
|
||||||
<string name="foss_upgrade_alreadydonated_label">मैले पहिले नै दान गरिसकेको छु</string>
|
<string name="foss_upgrade_alreadydonated_label">मैले पहिले नै दान गरिसकेको छु</string>
|
||||||
<string name="foss_upgrade_no_money_label">म आफ्नो सबै पैसा AirPods मा खर्च गर्छु</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>
|
</resources>
|
||||||
|
|||||||
@@ -3,4 +3,8 @@
|
|||||||
<string name="foss_upgrade_donate_label">Doneren</string>
|
<string name="foss_upgrade_donate_label">Doneren</string>
|
||||||
<string name="foss_upgrade_alreadydonated_label">Ik heb al gedoneerd</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="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>
|
</resources>
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="foss_upgrade_donate_label">Wsparcie</string>
|
<string name="foss_upgrade_donate_label">Wesprzyj</string>
|
||||||
<string name="foss_upgrade_alreadydonated_label">Już się zrzuciłem</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="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>
|
</resources>
|
||||||
|
|||||||
@@ -3,4 +3,8 @@
|
|||||||
<string name="foss_upgrade_donate_label">Doar</string>
|
<string name="foss_upgrade_donate_label">Doar</string>
|
||||||
<string name="foss_upgrade_alreadydonated_label">Eu já doei</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="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>
|
</resources>
|
||||||
|
|||||||
@@ -3,4 +3,8 @@
|
|||||||
<string name="foss_upgrade_donate_label">Doar</string>
|
<string name="foss_upgrade_donate_label">Doar</string>
|
||||||
<string name="foss_upgrade_alreadydonated_label">Já doei</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="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>
|
</resources>
|
||||||
|
|||||||
@@ -1,2 +1,10 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?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_donate_label">Donează</string>
|
||||||
<string name="foss_upgrade_alreadydonated_label">Am donat deja</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="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>
|
</resources>
|
||||||
|
|||||||
@@ -3,4 +3,8 @@
|
|||||||
<string name="foss_upgrade_donate_label">Пожертвовать</string>
|
<string name="foss_upgrade_donate_label">Пожертвовать</string>
|
||||||
<string name="foss_upgrade_alreadydonated_label">Я уже пожертвовал</string>
|
<string name="foss_upgrade_alreadydonated_label">Я уже пожертвовал</string>
|
||||||
<string name="foss_upgrade_no_money_label">Я потратил все свои деньги на AirPods</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>
|
</resources>
|
||||||
|
|||||||
@@ -3,4 +3,8 @@
|
|||||||
<string name="foss_upgrade_donate_label">Dona</string>
|
<string name="foss_upgrade_donate_label">Dona</string>
|
||||||
<string name="foss_upgrade_alreadydonated_label">Apo giai donadu</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="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>
|
</resources>
|
||||||
|
|||||||
@@ -3,4 +3,8 @@
|
|||||||
<string name="foss_upgrade_donate_label">පරිත්යාග</string>
|
<string name="foss_upgrade_donate_label">පරිත්යාග</string>
|
||||||
<string name="foss_upgrade_alreadydonated_label">මම දැනටමත් පරිත්යාග කර ඇත</string>
|
<string name="foss_upgrade_alreadydonated_label">මම දැනටමත් පරිත්යාග කර ඇත</string>
|
||||||
<string name="foss_upgrade_no_money_label">මම මගේ මුදල් සියල්ල AirPods සඳහා වියදම් කරමි</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>
|
</resources>
|
||||||
|
|||||||
@@ -3,4 +3,8 @@
|
|||||||
<string name="foss_upgrade_donate_label">Prispieť</string>
|
<string name="foss_upgrade_donate_label">Prispieť</string>
|
||||||
<string name="foss_upgrade_alreadydonated_label">Už som prispel/a</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="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>
|
</resources>
|
||||||
|
|||||||
@@ -3,4 +3,8 @@
|
|||||||
<string name="foss_upgrade_donate_label">Doniraj</string>
|
<string name="foss_upgrade_donate_label">Doniraj</string>
|
||||||
<string name="foss_upgrade_alreadydonated_label">Sem že doniral</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="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>
|
</resources>
|
||||||
|
|||||||
@@ -3,4 +3,8 @@
|
|||||||
<string name="foss_upgrade_donate_label">Dhuro</string>
|
<string name="foss_upgrade_donate_label">Dhuro</string>
|
||||||
<string name="foss_upgrade_alreadydonated_label">Unë tashmë kam dhuruar</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="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>
|
</resources>
|
||||||
|
|||||||
@@ -3,4 +3,8 @@
|
|||||||
<string name="foss_upgrade_donate_label">Донације</string>
|
<string name="foss_upgrade_donate_label">Донације</string>
|
||||||
<string name="foss_upgrade_alreadydonated_label">Већ сам донирао/ла</string>
|
<string name="foss_upgrade_alreadydonated_label">Већ сам донирао/ла</string>
|
||||||
<string name="foss_upgrade_no_money_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>
|
</resources>
|
||||||
|
|||||||
@@ -3,4 +3,8 @@
|
|||||||
<string name="foss_upgrade_donate_label">Donera</string>
|
<string name="foss_upgrade_donate_label">Donera</string>
|
||||||
<string name="foss_upgrade_alreadydonated_label">Jag har redan donerat</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="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>
|
</resources>
|
||||||
|
|||||||
@@ -3,4 +3,8 @@
|
|||||||
<string name="foss_upgrade_donate_label">Changia</string>
|
<string name="foss_upgrade_donate_label">Changia</string>
|
||||||
<string name="foss_upgrade_alreadydonated_label">Nimeshachangia tayari</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="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>
|
</resources>
|
||||||
|
|||||||
@@ -3,4 +3,8 @@
|
|||||||
<string name="foss_upgrade_donate_label">நன்கொடை</string>
|
<string name="foss_upgrade_donate_label">நன்கொடை</string>
|
||||||
<string name="foss_upgrade_alreadydonated_label">நான் ஏற்கனவே நன்கொடை அளித்துவிட்டேன்</string>
|
<string name="foss_upgrade_alreadydonated_label">நான் ஏற்கனவே நன்கொடை அளித்துவிட்டேன்</string>
|
||||||
<string name="foss_upgrade_no_money_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>
|
</resources>
|
||||||
|
|||||||
@@ -3,4 +3,8 @@
|
|||||||
<string name="foss_upgrade_donate_label">విరాళం ఇవ్వండి</string>
|
<string name="foss_upgrade_donate_label">విరాళం ఇవ్వండి</string>
|
||||||
<string name="foss_upgrade_alreadydonated_label">నేను ఇప్పటికే విరాళం ఇచ్చాను</string>
|
<string name="foss_upgrade_alreadydonated_label">నేను ఇప్పటికే విరాళం ఇచ్చాను</string>
|
||||||
<string name="foss_upgrade_no_money_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>
|
</resources>
|
||||||
|
|||||||
@@ -3,4 +3,8 @@
|
|||||||
<string name="foss_upgrade_donate_label">บริจาค</string>
|
<string name="foss_upgrade_donate_label">บริจาค</string>
|
||||||
<string name="foss_upgrade_alreadydonated_label">ฉันบริจาคแล้ว</string>
|
<string name="foss_upgrade_alreadydonated_label">ฉันบริจาคแล้ว</string>
|
||||||
<string name="foss_upgrade_no_money_label">ฉันใช้เงินทั้งหมดไปกับ AirPods</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>
|
</resources>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user