mirror of
https://github.com/d4rken-org/capod.git
synced 2026-09-14 18:26:11 -04:00
Compare commits
28
Commits
v3.0.4-rc1
...
v3.1.0-rc0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
+5
-16
@@ -2,22 +2,6 @@
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"mcp__ide__getDiagnostics",
|
||||
"Bash(./gradlew tasks:*)",
|
||||
"Bash(./gradlew:*)",
|
||||
"Bash(find:*)",
|
||||
"Bash(ls:*)",
|
||||
"Bash(grep:*)",
|
||||
"Bash(rg:*)",
|
||||
"Bash(git add:*)",
|
||||
"Bash(git log:*)",
|
||||
"Bash(git commit:*)",
|
||||
"Bash(git show:*)",
|
||||
"Bash(git checkout:*)",
|
||||
"Bash(git branch:*)",
|
||||
"Bash(git stash:*)",
|
||||
"Bash(gh issue list:*)",
|
||||
"Bash(gh pr list:*)",
|
||||
"Bash(gh label list:*)",
|
||||
"WebSearch",
|
||||
"WebFetch(domain:support.google.com)",
|
||||
"WebFetch(domain:github.com)",
|
||||
@@ -27,5 +11,10 @@
|
||||
"WebFetch(domain:issuetracker.google.com)"
|
||||
],
|
||||
"deny": []
|
||||
},
|
||||
"enabledPlugins": {
|
||||
"android-translation@claude-code-cafe": true,
|
||||
"debugbadger@claude-code-cafe": true,
|
||||
"jvm-tools@claude-code-cafe": true
|
||||
}
|
||||
}
|
||||
+70
-59
@@ -1,48 +1,49 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
CFPropertyList (3.0.7)
|
||||
base64
|
||||
nkf
|
||||
rexml
|
||||
addressable (2.8.7)
|
||||
public_suffix (>= 2.0.2, < 7.0)
|
||||
CFPropertyList (3.0.8)
|
||||
abbrev (0.1.2)
|
||||
addressable (2.8.8)
|
||||
public_suffix (>= 2.0.2, < 8.0)
|
||||
artifactory (3.0.17)
|
||||
atomos (0.1.3)
|
||||
aws-eventstream (1.3.2)
|
||||
aws-partitions (1.1109.0)
|
||||
aws-sdk-core (3.224.1)
|
||||
aws-eventstream (1.4.0)
|
||||
aws-partitions (1.1216.0)
|
||||
aws-sdk-core (3.242.0)
|
||||
aws-eventstream (~> 1, >= 1.3.0)
|
||||
aws-partitions (~> 1, >= 1.992.0)
|
||||
aws-sigv4 (~> 1.9)
|
||||
base64
|
||||
bigdecimal
|
||||
jmespath (~> 1, >= 1.6.1)
|
||||
logger
|
||||
aws-sdk-kms (1.101.0)
|
||||
aws-sdk-core (~> 3, >= 3.216.0)
|
||||
aws-sdk-kms (1.122.0)
|
||||
aws-sdk-core (~> 3, >= 3.241.4)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sdk-s3 (1.188.0)
|
||||
aws-sdk-core (~> 3, >= 3.224.1)
|
||||
aws-sdk-s3 (1.213.0)
|
||||
aws-sdk-core (~> 3, >= 3.241.4)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sigv4 (1.11.0)
|
||||
aws-sigv4 (1.12.1)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
babosa (1.0.4)
|
||||
base64 (0.3.0)
|
||||
base64 (0.2.0)
|
||||
benchmark (0.5.0)
|
||||
bigdecimal (4.0.1)
|
||||
claide (1.1.0)
|
||||
colored (1.2)
|
||||
colored2 (3.1.2)
|
||||
commander (4.6.0)
|
||||
highline (~> 2.0.0)
|
||||
csv (3.3.5)
|
||||
declarative (0.0.20)
|
||||
digest-crc (0.7.0)
|
||||
rake (>= 12.0.0, < 14.0.0)
|
||||
domain_name (0.5.20190701)
|
||||
unf (>= 0.0.5, < 1.0.0)
|
||||
domain_name (0.6.20240107)
|
||||
dotenv (2.8.1)
|
||||
emoji_regex (3.2.3)
|
||||
excon (0.109.0)
|
||||
faraday (1.10.4)
|
||||
excon (0.112.0)
|
||||
faraday (1.10.5)
|
||||
faraday-em_http (~> 1.0)
|
||||
faraday-em_synchrony (~> 1.0)
|
||||
faraday-excon (~> 1.1)
|
||||
@@ -54,14 +55,14 @@ GEM
|
||||
faraday-rack (~> 1.0)
|
||||
faraday-retry (~> 1.0)
|
||||
ruby2_keywords (>= 0.0.4)
|
||||
faraday-cookie_jar (0.0.7)
|
||||
faraday-cookie_jar (0.0.8)
|
||||
faraday (>= 0.8.0)
|
||||
http-cookie (~> 1.0.0)
|
||||
http-cookie (>= 1.0.0)
|
||||
faraday-em_http (1.0.0)
|
||||
faraday-em_synchrony (1.0.1)
|
||||
faraday-excon (1.1.0)
|
||||
faraday-httpclient (1.0.1)
|
||||
faraday-multipart (1.1.1)
|
||||
faraday-multipart (1.2.0)
|
||||
multipart-post (~> 2.0)
|
||||
faraday-net_http (1.0.2)
|
||||
faraday-net_http_persistent (1.2.0)
|
||||
@@ -71,15 +72,19 @@ GEM
|
||||
faraday_middleware (1.2.1)
|
||||
faraday (~> 1.0)
|
||||
fastimage (2.4.0)
|
||||
fastlane (2.228.0)
|
||||
fastlane (2.232.1)
|
||||
CFPropertyList (>= 2.3, < 4.0.0)
|
||||
abbrev (~> 0.1.2)
|
||||
addressable (>= 2.8, < 3.0.0)
|
||||
artifactory (~> 3.0)
|
||||
aws-sdk-s3 (~> 1.0)
|
||||
aws-sdk-s3 (~> 1.197)
|
||||
babosa (>= 1.0.3, < 2.0.0)
|
||||
bundler (>= 1.12.0, < 3.0.0)
|
||||
base64 (~> 0.2.0)
|
||||
benchmark (>= 0.1.0)
|
||||
bundler (>= 1.17.3, < 5.0.0)
|
||||
colored (~> 1.2)
|
||||
commander (~> 4.6)
|
||||
csv (~> 3.3)
|
||||
dotenv (>= 2.1.1, < 3.0.0)
|
||||
emoji_regex (>= 0.1, < 4.0)
|
||||
excon (>= 0.71.0, < 1.0.0)
|
||||
@@ -91,16 +96,20 @@ GEM
|
||||
gh_inspector (>= 1.1.2, < 2.0.0)
|
||||
google-apis-androidpublisher_v3 (~> 0.3)
|
||||
google-apis-playcustomapp_v1 (~> 0.1)
|
||||
google-cloud-env (>= 1.6.0, < 2.0.0)
|
||||
google-cloud-env (>= 1.6.0, <= 2.1.1)
|
||||
google-cloud-storage (~> 1.31)
|
||||
highline (~> 2.0)
|
||||
http-cookie (~> 1.0.5)
|
||||
json (< 3.0.0)
|
||||
jwt (>= 2.1.0, < 3)
|
||||
logger (>= 1.6, < 2.0)
|
||||
mini_magick (>= 4.9.4, < 5.0.0)
|
||||
multipart-post (>= 2.0.0, < 3.0.0)
|
||||
mutex_m (~> 0.3.0)
|
||||
naturally (~> 2.2)
|
||||
nkf (~> 0.2.0)
|
||||
optparse (>= 0.1.1, < 1.0.0)
|
||||
ostruct (>= 0.1.0)
|
||||
plist (>= 3.1.0, < 4.0.0)
|
||||
rubyzip (>= 2.0.0, < 3.0.0)
|
||||
security (= 0.1.5)
|
||||
@@ -116,38 +125,40 @@ GEM
|
||||
fastlane-sirp (1.0.0)
|
||||
sysrandom (~> 1.0)
|
||||
gh_inspector (1.1.3)
|
||||
google-apis-androidpublisher_v3 (0.54.0)
|
||||
google-apis-core (>= 0.11.0, < 2.a)
|
||||
google-apis-core (0.11.3)
|
||||
google-apis-androidpublisher_v3 (0.96.0)
|
||||
google-apis-core (>= 0.15.0, < 2.a)
|
||||
google-apis-core (0.18.0)
|
||||
addressable (~> 2.5, >= 2.5.1)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
httpclient (>= 2.8.1, < 3.a)
|
||||
googleauth (~> 1.9)
|
||||
httpclient (>= 2.8.3, < 3.a)
|
||||
mini_mime (~> 1.0)
|
||||
mutex_m
|
||||
representable (~> 3.0)
|
||||
retriable (>= 2.0, < 4.a)
|
||||
rexml
|
||||
google-apis-iamcredentials_v1 (0.17.0)
|
||||
google-apis-core (>= 0.11.0, < 2.a)
|
||||
google-apis-playcustomapp_v1 (0.13.0)
|
||||
google-apis-core (>= 0.11.0, < 2.a)
|
||||
google-apis-storage_v1 (0.29.0)
|
||||
google-apis-core (>= 0.11.0, < 2.a)
|
||||
google-cloud-core (1.6.1)
|
||||
google-apis-iamcredentials_v1 (0.26.0)
|
||||
google-apis-core (>= 0.15.0, < 2.a)
|
||||
google-apis-playcustomapp_v1 (0.17.0)
|
||||
google-apis-core (>= 0.15.0, < 2.a)
|
||||
google-apis-storage_v1 (0.60.0)
|
||||
google-apis-core (>= 0.15.0, < 2.a)
|
||||
google-cloud-core (1.8.0)
|
||||
google-cloud-env (>= 1.0, < 3.a)
|
||||
google-cloud-errors (~> 1.0)
|
||||
google-cloud-env (1.6.0)
|
||||
faraday (>= 0.17.3, < 3.0)
|
||||
google-cloud-errors (1.3.1)
|
||||
google-cloud-storage (1.45.0)
|
||||
google-cloud-env (2.1.1)
|
||||
faraday (>= 1.0, < 3.a)
|
||||
google-cloud-errors (1.5.0)
|
||||
google-cloud-storage (1.58.0)
|
||||
addressable (~> 2.8)
|
||||
digest-crc (~> 0.4)
|
||||
google-apis-iamcredentials_v1 (~> 0.1)
|
||||
google-apis-storage_v1 (~> 0.29.0)
|
||||
google-apis-core (>= 0.18, < 2)
|
||||
google-apis-iamcredentials_v1 (~> 0.18)
|
||||
google-apis-storage_v1 (>= 0.42)
|
||||
google-cloud-core (~> 1.6)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
googleauth (~> 1.9)
|
||||
mini_mime (~> 1.0)
|
||||
googleauth (1.8.1)
|
||||
faraday (>= 0.17.3, < 3.a)
|
||||
googleauth (1.11.2)
|
||||
faraday (>= 1.0, < 3.a)
|
||||
google-cloud-env (~> 2.1)
|
||||
jwt (>= 1.4, < 3.0)
|
||||
multi_json (~> 1.11)
|
||||
os (>= 0.9, < 2.0)
|
||||
@@ -158,37 +169,38 @@ GEM
|
||||
httpclient (2.9.0)
|
||||
mutex_m
|
||||
jmespath (1.6.2)
|
||||
json (2.7.6)
|
||||
jwt (2.10.1)
|
||||
json (2.18.1)
|
||||
jwt (2.10.2)
|
||||
base64
|
||||
logger (1.7.0)
|
||||
mini_magick (4.13.2)
|
||||
mini_mime (1.1.5)
|
||||
multi_json (1.15.0)
|
||||
multi_json (1.19.1)
|
||||
multipart-post (2.4.1)
|
||||
mutex_m (0.3.0)
|
||||
nanaimo (0.4.0)
|
||||
naturally (2.3.0)
|
||||
nkf (0.2.0)
|
||||
optparse (0.6.0)
|
||||
optparse (0.8.1)
|
||||
os (1.1.4)
|
||||
ostruct (0.6.3)
|
||||
plist (3.7.2)
|
||||
public_suffix (5.1.1)
|
||||
rake (13.3.0)
|
||||
public_suffix (7.0.2)
|
||||
rake (13.3.1)
|
||||
representable (3.2.0)
|
||||
declarative (< 0.1.0)
|
||||
trailblazer-option (>= 0.1.1, < 0.2.0)
|
||||
uber (< 0.2.0)
|
||||
retriable (3.1.2)
|
||||
rexml (3.4.1)
|
||||
retriable (3.2.0)
|
||||
rexml (3.4.4)
|
||||
rouge (3.28.0)
|
||||
ruby2_keywords (0.0.5)
|
||||
rubyzip (2.4.1)
|
||||
security (0.1.5)
|
||||
signet (0.18.0)
|
||||
signet (0.21.0)
|
||||
addressable (~> 2.8)
|
||||
faraday (>= 0.17.5, < 3.a)
|
||||
jwt (>= 1.5, < 3.0)
|
||||
jwt (>= 1.5, < 4.0)
|
||||
multi_json (~> 1.10)
|
||||
simctl (1.6.10)
|
||||
CFPropertyList
|
||||
@@ -203,7 +215,6 @@ GEM
|
||||
tty-spinner (0.9.3)
|
||||
tty-cursor (~> 0.7)
|
||||
uber (0.1.0)
|
||||
unf (0.2.0)
|
||||
unicode-display_width (2.6.0)
|
||||
word_wrap (1.0.0)
|
||||
xcodeproj (1.27.0)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
# Companion App for AirPods (CAPod)
|
||||
|
||||
[](https://github.com/d4rken-org/capod/releases/latest)
|
||||
[](https://shields.rbtlog.dev/eu.darken.capod)
|
||||
[](https://github.com/d4rken/capod/actions/workflows/code-checks.yml)
|
||||
[](https://crowdin.com/project/capod)
|
||||
[](https://github.com/d4rken-org/capod/edit/main/README.md#download)
|
||||
|
||||
@@ -35,7 +35,7 @@ android {
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
val basePath = File(System.getProperty("user.home"), ".appconfig/${projectConfig.packageName}")
|
||||
val basePath = File(System.getProperty("user.home"), ".config/projects/${projectConfig.packageName}")
|
||||
create("releaseFoss") {
|
||||
setupCredentials(File(basePath, "signing-foss.properties"))
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="foss_upgrade_donate_label">Wsparcie</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Już się zrzuciłem</string>
|
||||
<string name="foss_upgrade_donate_label">Wesprzyj</string>
|
||||
<string name="foss_upgrade_alreadydonated_label">Już wsparłem</string>
|
||||
<string name="foss_upgrade_no_money_label">Wydałem wszystkie pieniądze na AirPods</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
<?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>
|
||||
</resources>
|
||||
|
||||
@@ -2,4 +2,8 @@
|
||||
<resources>
|
||||
<string name="upgrades_gplay_unavailable_error">Google Play-dienste is nie beskikbaar nie.</string>
|
||||
<string name="upgrades_no_purchases_found_check_account">Geen aankope gevind nie. Gebruik jy die regte rekening?</string>
|
||||
<string name="upgrades_gplay_billing_error_label">Google Play-fout</string>
|
||||
<string name="upgrades_gplay_billing_error_description">Daar was \'n fout in Google Play. Probeer asseblief later weer of herbegin jou foon.\n\nFout: %s</string>
|
||||
<string name="upgrades_gplay_billing_result_error_label">Google Play-faktureringsfout</string>
|
||||
<string name="upgrades_gplay_billing_result_error_description">Daar was \'n fout toe Google Play om jou aankoopbesonderhede gevra is. Maak die Google Play-kas skoon en herbegin jou foon.\n\nFout %s</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,4 +2,8 @@
|
||||
<resources>
|
||||
<string name="upgrades_gplay_unavailable_error">የGoogle Play አገልግሎቶች አይገኙም።</string>
|
||||
<string name="upgrades_no_purchases_found_check_account">ምንም ግዢዎች አልተገኙም። ትክክለኛውን መለያ እየተጠቀሙ ነው?</string>
|
||||
<string name="upgrades_gplay_billing_error_label">የGoogle Play ስህተት</string>
|
||||
<string name="upgrades_gplay_billing_error_description">በGoogle Play ላይ ስህተት ነበር። እባክዎ ቆይተው ይሞክሩ ወይም ስልክዎን እንደገና ያስጀምሩ።\n\nስህተት: %s</string>
|
||||
<string name="upgrades_gplay_billing_result_error_label">የGoogle Play ክፍያ ስህተት</string>
|
||||
<string name="upgrades_gplay_billing_result_error_description">Google Play የግዢ ዝርዝሮችዎን ሲጠይቅ ስህተት ነበር። የGoogle Play መሸጎጫውን ያጽዱ እና ስልክዎን እንደገና ያስጀምሩ።\n\nስህተት %s</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,4 +2,8 @@
|
||||
<resources>
|
||||
<string name="upgrades_gplay_unavailable_error">خدمات Google Play غير متاحة.</string>
|
||||
<string name="upgrades_no_purchases_found_check_account">لم يتم العثور على أي مشتريات. هل تستخدم الحساب الصحيح؟</string>
|
||||
<string name="upgrades_gplay_billing_error_label">خطأ في Google Play</string>
|
||||
<string name="upgrades_gplay_billing_error_description">حدث خطأ في Google Play. يرجى المحاولة مرة أخرى لاحقًا أو إعادة تشغيل هاتفك.\n\nخطأ: %s</string>
|
||||
<string name="upgrades_gplay_billing_result_error_label">خطأ في فوترة Google Play</string>
|
||||
<string name="upgrades_gplay_billing_result_error_description">حدث خطأ عند طلب تفاصيل الشراء من Google Play. امسح ذاكرة التخزين المؤقت لـ Google Play وأعد تشغيل هاتفك.\n\nخطأ %s</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,4 +2,8 @@
|
||||
<resources>
|
||||
<string name="upgrades_gplay_unavailable_error">Google Play xidmətləri əlçatmazdır.</string>
|
||||
<string name="upgrades_no_purchases_found_check_account">Heç bir satın alma tapılmadı. Doğru hesabı istifadə edirsiniz?</string>
|
||||
<string name="upgrades_gplay_billing_error_label">Google Play xətası</string>
|
||||
<string name="upgrades_gplay_billing_error_description">Google Play-də xəta baş verdi. Zəhmət olmasa daha sonra yenidən cəhd edin və ya telefonunuzu yenidən başladın.\n\nXəta: %s</string>
|
||||
<string name="upgrades_gplay_billing_result_error_label">Google Play faktura xətası</string>
|
||||
<string name="upgrades_gplay_billing_result_error_description">Google Play-dən satınalma təfərrüatlarınızı soruşarkən xəta baş verdi. Google Play keşini təmizləyin və telefonunuzu yenidən başladın.\n\nXəta %s</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,4 +2,8 @@
|
||||
<resources>
|
||||
<string name="upgrades_gplay_unavailable_error">Службы Google Play недаступныя.</string>
|
||||
<string name="upgrades_no_purchases_found_check_account">Пакупак не знойдзена. Вы выкарыстоўваеце правільны ўліковы запіс?</string>
|
||||
<string name="upgrades_gplay_billing_error_label">Памылка Google Play</string>
|
||||
<string name="upgrades_gplay_billing_error_description">Адбылася памылка ў Google Play. Калі ласка, паспрабуйце пазней або перазагрузіце тэлефон.\n\nПамылка: %s</string>
|
||||
<string name="upgrades_gplay_billing_result_error_label">Памылка выстаўлення рахункаў Google Play</string>
|
||||
<string name="upgrades_gplay_billing_result_error_description">Адбылася памылка пры запыце дэталяў пакупкі ў Google Play. Ачысціце кэш Google Play і перазагрузіце тэлефон.\n\nПамылка %s</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,4 +2,8 @@
|
||||
<resources>
|
||||
<string name="upgrades_gplay_unavailable_error">Услугите на Google Play не са налични.</string>
|
||||
<string name="upgrades_no_purchases_found_check_account">Няма намерени покупки. Правилния акаунт ли използвате?</string>
|
||||
<string name="upgrades_gplay_billing_error_label">Грешка в Google Play</string>
|
||||
<string name="upgrades_gplay_billing_error_description">Възникна грешка в Google Play. Моля, опитайте отново по-късно или рестартирайте телефона си.\n\nГрешка: %s</string>
|
||||
<string name="upgrades_gplay_billing_result_error_label">Грешка при фактуриране в Google Play</string>
|
||||
<string name="upgrades_gplay_billing_result_error_description">Възникна грешка при заявката за данни за покупката ви от Google Play. Изчистете кеша на Google Play и рестартирайте телефона си.\n\nГрешка %s</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="upgrades_gplay_unavailable_error">Google Play পরিষেবাগুলি উপলব্ধ নেই৷</string>
|
||||
<string name="upgrades_no_purchases_found_check_account">কোনও কেনাকাটা খুঁজে পাওয়া যায়নি। আপনি কি সঠিক অ্যাকাউন্ট ব্যবহার করছেন?</string>
|
||||
<string name="upgrades_no_purchases_found_check_account">কোনও কেনাকাটা খুঁজে পাওয়া যায়নি। আপনি কি সঠিক অ্যাকাউন্ট ব্যবহার করছেন?</string>
|
||||
<string name="upgrades_gplay_billing_error_label">Google Play ত্রুটি</string>
|
||||
<string name="upgrades_gplay_billing_error_description">Google Play-তে একটি ত্রুটি হয়েছে। অনুগ্রহ করে পরে আবার চেষ্টা করুন অথবা আপনার ফোন রিবুট করুন।\n\nত্রুটি: %s</string>
|
||||
<string name="upgrades_gplay_billing_result_error_label">Google Play বিলিং ত্রুটি</string>
|
||||
<string name="upgrades_gplay_billing_result_error_description">Google Play-তে আপনার কেনাকাটার বিবরণ জানতে চাইলে একটি ত্রুটি হয়েছে। Google Play ক্যাশে সাফ করুন এবং আপনার ফোন রিবুট করুন।\n\nত্রুটি %s</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,4 +2,8 @@
|
||||
<resources>
|
||||
<string name="upgrades_gplay_unavailable_error">Google Play-tjenester er ikke tilgængelige.</string>
|
||||
<string name="upgrades_no_purchases_found_check_account">Ingen køb fundet. Bruger du den rigtige konto?</string>
|
||||
<string name="upgrades_gplay_billing_error_label">Google Play-fejl</string>
|
||||
<string name="upgrades_gplay_billing_error_description">Der opstod en fejl i Google Play. Prøv venligst igen senere, eller genstart din telefon.\n\nFejl: %s</string>
|
||||
<string name="upgrades_gplay_billing_result_error_label">Google Play-faktureringsfejl</string>
|
||||
<string name="upgrades_gplay_billing_result_error_description">Der opstod en fejl under forespørgsel af dine købsoplysninger fra Google Play. Ryd Google Play-cachen, og genstart din telefon.\n\nFejl %s</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,4 +2,8 @@
|
||||
<resources>
|
||||
<string name="upgrades_gplay_unavailable_error">Οι υπηρεσίες Google Play δεν είναι διαθέσιμες.</string>
|
||||
<string name="upgrades_no_purchases_found_check_account">Δε βρέθηκαν αγορές. Χρησιμοποιείτε τον σωστό λογαριασμό;</string>
|
||||
<string name="upgrades_gplay_billing_error_label">Σφάλμα Google Play</string>
|
||||
<string name="upgrades_gplay_billing_error_description">Παρουσιάστηκε σφάλμα στο Google Play. Παρακαλώ προσπαθήστε ξανά αργότερα ή επανεκκινήστε το τηλέφωνό σας.\n\nΣφάλμα: %s</string>
|
||||
<string name="upgrades_gplay_billing_result_error_label">Σφάλμα χρέωσης Google Play</string>
|
||||
<string name="upgrades_gplay_billing_result_error_description">Παρουσιάστηκε σφάλμα κατά την αίτηση των στοιχείων αγοράς σας από το Google Play. Εκκαθαρίστε την προσωρινή μνήμη του Google Play και επανεκκινήστε το τηλέφωνό σας.\n\nΣφάλμα %s</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,4 +2,8 @@
|
||||
<resources>
|
||||
<string name="upgrades_gplay_unavailable_error">Google Play zerbitzuak ez daude erabilgarri.</string>
|
||||
<string name="upgrades_no_purchases_found_check_account">Ez da erosketarik aurkitu. Kontu egokia erabiltzen ari zara?</string>
|
||||
<string name="upgrades_gplay_billing_error_label">Google Play errorea</string>
|
||||
<string name="upgrades_gplay_billing_error_description">Errore bat gertatu da Google Play-n. Mesedez, saiatu berriro geroago edo berrabiarazi telefonoa.\n\nErrorea: %s</string>
|
||||
<string name="upgrades_gplay_billing_result_error_label">Google Play fakturazio errorea</string>
|
||||
<string name="upgrades_gplay_billing_result_error_description">Errore bat gertatu da Google Play-ri zure erosketa xehetasunak eskatzean. Garbitu Google Play-ren cachea eta berrabiarazi telefonoa.\n\nErrorea %s</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,4 +2,8 @@
|
||||
<resources>
|
||||
<string name="upgrades_gplay_unavailable_error">سرویسهای Google Play در دسترس نیستند.</string>
|
||||
<string name="upgrades_no_purchases_found_check_account">خریدی یافت نشد. آیا از حساب درستی استفاده میکنید؟</string>
|
||||
<string name="upgrades_gplay_billing_error_label">خطای Google Play</string>
|
||||
<string name="upgrades_gplay_billing_error_description">خطایی در Google Play رخ داد. لطفاً بعداً دوباره امتحان کنید یا گوشی خود را مجدداً راهاندازی کنید.\n\nخطا: %s</string>
|
||||
<string name="upgrades_gplay_billing_result_error_label">خطای صورتحساب Google Play</string>
|
||||
<string name="upgrades_gplay_billing_result_error_description">هنگام درخواست جزئیات خرید از Google Play خطایی رخ داد. حافظه پنهان Google Play را پاک کنید و گوشی خود را مجدداً راهاندازی کنید.\n\nخطا %s</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,4 +2,8 @@
|
||||
<resources>
|
||||
<string name="upgrades_gplay_unavailable_error">Google Play -palvelut eivät ole käytettävissä.</string>
|
||||
<string name="upgrades_no_purchases_found_check_account">Ostoksia ei löytynyt. Käytätkö oikeaa tiliä?</string>
|
||||
<string name="upgrades_gplay_billing_error_label">Google Play -virhe</string>
|
||||
<string name="upgrades_gplay_billing_error_description">Google Playssa tapahtui virhe. Yritä myöhemmin uudelleen tai käynnistä puhelimesi uudelleen.\n\nVirhe: %s</string>
|
||||
<string name="upgrades_gplay_billing_result_error_label">Google Play -laskutusvirhe</string>
|
||||
<string name="upgrades_gplay_billing_result_error_description">Ostotietojen hakemisessa Google Playsta tapahtui virhe. Tyhjennä Google Playn välimuisti ja käynnistä puhelimesi uudelleen.\n\nVirhe %s</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,4 +2,8 @@
|
||||
<resources>
|
||||
<string name="upgrades_gplay_unavailable_error">Hindi available ang mga serbisyo ng Google Play.</string>
|
||||
<string name="upgrades_no_purchases_found_check_account">Walang nakitang binili. Tamang account ba ang gamit mo?</string>
|
||||
<string name="upgrades_gplay_billing_error_label">Error sa Google Play</string>
|
||||
<string name="upgrades_gplay_billing_error_description">Nagkaroon ng error sa Google Play. Pakisubukan ulit mamaya o i-reboot ang iyong telepono.\n\nError: %s</string>
|
||||
<string name="upgrades_gplay_billing_result_error_label">Error sa Google Play Billing</string>
|
||||
<string name="upgrades_gplay_billing_result_error_description">Nagkaroon ng error sa paghingi ng detalye ng iyong pagbili sa Google Play. I-clear ang Google Play cache at i-reboot ang iyong telepono.\n\nError %s</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,4 +2,8 @@
|
||||
<resources>
|
||||
<string name="upgrades_gplay_unavailable_error">Os servizos de Google Play non están dispoñibles.</string>
|
||||
<string name="upgrades_no_purchases_found_check_account">Non se atoparon compras. Estás a usar a conta correcta?</string>
|
||||
<string name="upgrades_gplay_billing_error_label">Erro de Google Play</string>
|
||||
<string name="upgrades_gplay_billing_error_description">Houbo un erro en Google Play. Téntao de novo máis tarde ou reinicia o teu teléfono.\n\nErro: %s</string>
|
||||
<string name="upgrades_gplay_billing_result_error_label">Erro de facturación de Google Play</string>
|
||||
<string name="upgrades_gplay_billing_result_error_description">Houbo un erro ao solicitar os detalles da túa compra a Google Play. Limpa a caché de Google Play e reinicia o teu teléfono.\n\nErro %s</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,4 +2,8 @@
|
||||
<resources>
|
||||
<string name="upgrades_gplay_unavailable_error">Google Play सेवाएं अनुपलब्ध हैं।</string>
|
||||
<string name="upgrades_no_purchases_found_check_account">कोई खरीदारी नहीं मिली, क्या आप सही खाते का उपयोग कर रहे हैं?</string>
|
||||
<string name="upgrades_gplay_billing_error_label">Google Play त्रुटि</string>
|
||||
<string name="upgrades_gplay_billing_error_description">Google Play में एक त्रुटि हुई। कृपया बाद में पुनः प्रयास करें या अपना फ़ोन रीबूट करें।\n\nत्रुटि: %s</string>
|
||||
<string name="upgrades_gplay_billing_result_error_label">Google Play बिलिंग त्रुटि</string>
|
||||
<string name="upgrades_gplay_billing_result_error_description">Google Play से आपकी खरीदारी का विवरण प्राप्त करते समय एक त्रुटि हुई। Google Play कैश साफ़ करें और अपना फ़ोन रीबूट करें।\n\nत्रुटि %s</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,4 +2,8 @@
|
||||
<resources>
|
||||
<string name="upgrades_gplay_unavailable_error">Usluge Google Playa nisu dostupne.</string>
|
||||
<string name="upgrades_no_purchases_found_check_account">Nisu pronađene kupnje. Koristite li pravi račun?</string>
|
||||
<string name="upgrades_gplay_billing_error_label">Greška Google Playa</string>
|
||||
<string name="upgrades_gplay_billing_error_description">Došlo je do greške u Google Playu. Pokušajte ponovo kasnije ili ponovo pokrenite telefon.\n\nGreška: %s</string>
|
||||
<string name="upgrades_gplay_billing_result_error_label">Greška naplate Google Playa</string>
|
||||
<string name="upgrades_gplay_billing_result_error_description">Došlo je do greške prilikom dohvaćanja podataka o kupnji iz Google Playa. Očistite predmemoriju Google Playa i ponovo pokrenite telefon.\n\nGreška %s</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,4 +2,8 @@
|
||||
<resources>
|
||||
<string name="upgrades_gplay_unavailable_error">A Google Play szolgáltatások nem érhetők el.</string>
|
||||
<string name="upgrades_no_purchases_found_check_account">Nem található vásárlás. A megfelelő fiókot használja?</string>
|
||||
<string name="upgrades_gplay_billing_error_label">Google Play hiba</string>
|
||||
<string name="upgrades_gplay_billing_error_description">Hiba történt a Google Playben. Kérjük, próbálja újra később, vagy indítsa újra a telefonját.\n\nHiba: %s</string>
|
||||
<string name="upgrades_gplay_billing_result_error_label">Google Play számlázási hiba</string>
|
||||
<string name="upgrades_gplay_billing_result_error_description">Hiba történt a vásárlási adatok lekérésekor a Google Playből. Törölje a Google Play gyorsítótárát, és indítsa újra a telefonját.\n\nHiba %s</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,4 +2,8 @@
|
||||
<resources>
|
||||
<string name="upgrades_gplay_unavailable_error">Google Play ծառայությունները հասանելի չեն:</string>
|
||||
<string name="upgrades_no_purchases_found_check_account">Գնումներ չեն գտնվել: Արդյո՞ք ճիշտ հաշիվն եք օգտագործում:</string>
|
||||
<string name="upgrades_gplay_billing_error_label">Google Play-ի սխալ</string>
|
||||
<string name="upgrades_gplay_billing_error_description">Google Play-ում սխալ է տեղի ունեցել: Խնդրում ենք փորձել ավելի ուշ կամ վերագործարկել ձեր հեռախոսը.\n\nՍխալ: %s</string>
|
||||
<string name="upgrades_gplay_billing_result_error_label">Google Play Billing-ի սխալ</string>
|
||||
<string name="upgrades_gplay_billing_result_error_description">Google Play-ից ձեր գնումների տվյալները հարցնելիս սխալ է տեղի ունեցել: Մաքրեք Google Play-ի քեշը և վերագործարկեք ձեր հեռախոսը.\n\nՍխալ %s</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,4 +2,8 @@
|
||||
<resources>
|
||||
<string name="upgrades_gplay_unavailable_error">Layanan Google Play tidak tersedia.</string>
|
||||
<string name="upgrades_no_purchases_found_check_account">Tidak ada pembelian yang ditemukan. Apakah Anda menggunakan akun yang benar?</string>
|
||||
<string name="upgrades_gplay_billing_error_label">Kesalahan Google Play</string>
|
||||
<string name="upgrades_gplay_billing_error_description">Terjadi kesalahan di Google Play. Silakan coba lagi nanti atau mulai ulang ponsel Anda.\n\nKesalahan: %s</string>
|
||||
<string name="upgrades_gplay_billing_result_error_label">Kesalahan Penagihan Google Play</string>
|
||||
<string name="upgrades_gplay_billing_result_error_description">Terjadi kesalahan saat meminta detail pembelian Anda dari Google Play. Hapus cache Google Play dan mulai ulang ponsel Anda.\n\nKesalahan %s</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,4 +2,8 @@
|
||||
<resources>
|
||||
<string name="upgrades_gplay_unavailable_error">Google Play þjónustur eru ekki tiltækar.</string>
|
||||
<string name="upgrades_no_purchases_found_check_account">Engin kaup fundust. Ertu að nota réttan aðgang?</string>
|
||||
<string name="upgrades_gplay_billing_error_label">Google Play villa</string>
|
||||
<string name="upgrades_gplay_billing_error_description">Villa kom upp í Google Play. Vinsamlegast reyndu aftur síðar eða endurræstu símann þinn.\n\nVilla: %s</string>
|
||||
<string name="upgrades_gplay_billing_result_error_label">Google Play innheimtuvilla</string>
|
||||
<string name="upgrades_gplay_billing_result_error_description">Villa kom upp við að sækja upplýsingar um kaupin þín frá Google Play. Hreinsaðu skyndiminni Google Play og endurræstu símann þinn.\n\nVilla %s</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,4 +2,8 @@
|
||||
<resources>
|
||||
<string name="upgrades_gplay_unavailable_error">I servizi di Google Play non sono disponibili.</string>
|
||||
<string name="upgrades_no_purchases_found_check_account">Nessun acquisto trovato. Stai usando l\'account giusto?</string>
|
||||
<string name="upgrades_gplay_billing_error_label">Errore di Google Play</string>
|
||||
<string name="upgrades_gplay_billing_error_description">Si è verificato un errore in Google Play. Riprova più tardi o riavvia il telefono.\n\nErrore: %s</string>
|
||||
<string name="upgrades_gplay_billing_result_error_label">Errore di fatturazione Google Play</string>
|
||||
<string name="upgrades_gplay_billing_result_error_description">Si è verificato un errore durante la richiesta dei dettagli di acquisto a Google Play. Svuota la cache di Google Play e riavvia il telefono.\n\nErrore %s</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,4 +2,8 @@
|
||||
<resources>
|
||||
<string name="upgrades_gplay_unavailable_error">שירותי Google Play אינם זמינים.</string>
|
||||
<string name="upgrades_no_purchases_found_check_account">לא נמצאו רכישות. האם אתה משתמש בחשבון הנכון?</string>
|
||||
<string name="upgrades_gplay_billing_error_label">שגיאת Google Play</string>
|
||||
<string name="upgrades_gplay_billing_error_description">אירעה שגיאה ב-Google Play. נסה שוב מאוחר יותר או הפעל מחדש את הטלפון.\n\nשגיאה: %s</string>
|
||||
<string name="upgrades_gplay_billing_result_error_label">שגיאת חיוב Google Play</string>
|
||||
<string name="upgrades_gplay_billing_result_error_description">אירעה שגיאה בעת בקשת פרטי הרכישה מ-Google Play. נקה את המטמון של Google Play והפעל מחדש את הטלפון.\n\nשגיאה %s</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,4 +2,8 @@
|
||||
<resources>
|
||||
<string name="upgrades_gplay_unavailable_error">Google Playサービスが利用できません。</string>
|
||||
<string name="upgrades_no_purchases_found_check_account">購入履歴がありません。正しいアカウントを使用していますか?</string>
|
||||
<string name="upgrades_gplay_billing_error_label">Google Playエラー</string>
|
||||
<string name="upgrades_gplay_billing_error_description">Google Playでエラーが発生しました。後でもう一度お試しいただくか、スマートフォンを再起動してください。\n\nエラー: %s</string>
|
||||
<string name="upgrades_gplay_billing_result_error_label">Google Play請求エラー</string>
|
||||
<string name="upgrades_gplay_billing_result_error_description">Google Playに購入情報を問い合わせる際にエラーが発生しました。Google Playのキャッシュをクリアし、スマートフォンを再起動してください。\n\nエラー %s</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,4 +2,8 @@
|
||||
<resources>
|
||||
<string name="upgrades_gplay_unavailable_error">Google Play სერვისები მიუწვდომელია.</string>
|
||||
<string name="upgrades_no_purchases_found_check_account">შენაძენები ვერ მოიძებნა. იყენებთ სწორ ანგარიშს?</string>
|
||||
<string name="upgrades_gplay_billing_error_label">Google Play-ის შეცდომა</string>
|
||||
<string name="upgrades_gplay_billing_error_description">Google Play-ში მოხდა შეცდომა. სცადეთ მოგვიანებით ან გადატვირთეთ ტელეფონი.\n\nშეცდომა: %s</string>
|
||||
<string name="upgrades_gplay_billing_result_error_label">Google Play ბილინგის შეცდომა</string>
|
||||
<string name="upgrades_gplay_billing_result_error_description">შეცდომა მოხდა Google Play-დან შენაძენის დეტალების მოთხოვნისას. გაასუფთავეთ Google Play-ის ქეში და გადატვირთეთ ტელეფონი.\n\nშეცდომა %s</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,4 +2,8 @@
|
||||
<resources>
|
||||
<string name="upgrades_gplay_unavailable_error">សេវាកម្ម Google Play មិនអាចប្រើបានទេ</string>
|
||||
<string name="upgrades_no_purchases_found_check_account">រកមិនឃើញការទិញទេ។ តើអ្នកកំពុងប្រើគណនីត្រឹមត្រូវទេ?</string>
|
||||
<string name="upgrades_gplay_billing_error_label">កំហុស Google Play</string>
|
||||
<string name="upgrades_gplay_billing_error_description">មានកំហុសនៅក្នុង Google Play។ សូមព្យាយាមម្តងទៀតនៅពេលក្រោយ ឬចាប់ផ្តើមទូរសព្ទឡើងវិញ។\n\nកំហុស: %s</string>
|
||||
<string name="upgrades_gplay_billing_result_error_label">កំហុសការចេញវិក្កយបត្រ Google Play</string>
|
||||
<string name="upgrades_gplay_billing_result_error_description">មានកំហុសពេលស្នើសុំព័ត៌មានការទិញពី Google Play។ សូមសម្អាតឃ្លាំងសម្ងាត់ Google Play និងចាប់ផ្តើមទូរសព្ទឡើងវិញ។\n\nកំហុស %s</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,4 +2,8 @@
|
||||
<resources>
|
||||
<string name="upgrades_gplay_unavailable_error">Xizmetên Google Play ne berdest in.</string>
|
||||
<string name="upgrades_no_purchases_found_check_account">Kirîn nehatin dîtin. Hûn hesabê rast bikar tînin?</string>
|
||||
<string name="upgrades_gplay_billing_error_label">Çewtiya Google Play</string>
|
||||
<string name="upgrades_gplay_billing_error_description">Di Google Play de çewtiyek çêbû. Ji kerema xwe paşê dîsa biceribîne an jî têlefona xwe ji nû ve bide destpêkirin.\n\nÇewtî: %s</string>
|
||||
<string name="upgrades_gplay_billing_result_error_label">Çewtiya Fatûrekirina Google Play</string>
|
||||
<string name="upgrades_gplay_billing_result_error_description">Dema ku ji Google Play agahdariyên kirîna we hat xwestin çewtiyek çêbû. Pêşbîra Google Play paqij bikin û têlefona xwe ji nû ve bide destpêkirin.\n\nÇewtî %s</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,4 +2,8 @@
|
||||
<resources>
|
||||
<string name="upgrades_gplay_unavailable_error">Google Play ಸೇವೆಗಳು ಲಭ್ಯವಿಲ್ಲ.</string>
|
||||
<string name="upgrades_no_purchases_found_check_account">ಯಾವುದೇ ಖರೀದಿಗಳು ಕಂಡುಬಂದಿಲ್ಲ. ನೀವು ಸರಿಯಾದ ಖಾತೆಯನ್ನು ಬಳಸುತ್ತಿದ್ದೀರಾ?</string>
|
||||
<string name="upgrades_gplay_billing_error_label">Google Play ದೋಷ</string>
|
||||
<string name="upgrades_gplay_billing_error_description">Google Play ನಲ್ಲಿ ದೋಷ ಸಂಭವಿಸಿದೆ. ದಯವಿಟ್ಟು ನಂತರ ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ ಅಥವಾ ನಿಮ್ಮ ಫೋನ್ ಅನ್ನು ಮರುಪ್ರಾರಂಭಿಸಿ.\n\nದೋಷ: %s</string>
|
||||
<string name="upgrades_gplay_billing_result_error_label">Google Play ಬಿಲ್ಲಿಂಗ್ ದೋಷ</string>
|
||||
<string name="upgrades_gplay_billing_result_error_description">Google Play ನಿಂದ ನಿಮ್ಮ ಖರೀದಿ ವಿವರಗಳನ್ನು ಕೇಳುವಾಗ ದೋಷ ಸಂಭವಿಸಿದೆ. Google Play ಕ್ಯಾಶ್ ಅನ್ನು ತೆರವುಗೊಳಿಸಿ ಮತ್ತು ನಿಮ್ಮ ಫೋನ್ ಅನ್ನು ಮರುಪ್ರಾರಂಭಿಸಿ.\n\nದೋಷ %s</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,4 +2,8 @@
|
||||
<resources>
|
||||
<string name="upgrades_gplay_unavailable_error">Google Play кызматтары жеткиликсиз.</string>
|
||||
<string name="upgrades_no_purchases_found_check_account">Эч кандай сатып алуулар табылган жок. Туура каттоо эсебин колдонуп жатасызбы?</string>
|
||||
<string name="upgrades_gplay_billing_error_label">Google Play катасы</string>
|
||||
<string name="upgrades_gplay_billing_error_description">Google Play\'де ката кетти. Кийинчерээк кайра аракет кылыңыз же телефонуңузду өчүрүп күйгүзүңүз.\n\nКата: %s</string>
|
||||
<string name="upgrades_gplay_billing_result_error_label">Google Play төлөм катасы</string>
|
||||
<string name="upgrades_gplay_billing_result_error_description">Google Play\'ден сатып алуу маалыматын сурап жатканда ката кетти. Google Play кэшин тазалаңыз жана телефонуңузду өчүрүп күйгүзүңүз.\n\nКата %s</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,4 +2,8 @@
|
||||
<resources>
|
||||
<string name="upgrades_gplay_unavailable_error">ບໍລິການ Google Play ບໍ່ສາມາດໃຊ້ໄດ້.</string>
|
||||
<string name="upgrades_no_purchases_found_check_account">ບໍ່ພົບການຊື້. ທ່ານກຳລັງໃຊ້ບັນຊີທີ່ຖືກຕ້ອງບໍ?</string>
|
||||
<string name="upgrades_gplay_billing_error_label">ຂໍ້ຜິດພາດ Google Play</string>
|
||||
<string name="upgrades_gplay_billing_error_description">ເກີດຂໍ້ຜິດພາດໃນ Google Play. ກະລຸນາລອງໃໝ່ພາຍຫຼັງ ຫຼືປິດເປີດໂທລະສັບຂອງທ່ານໃໝ່.\n\nຂໍ້ຜິດພາດ: %s</string>
|
||||
<string name="upgrades_gplay_billing_result_error_label">ຂໍ້ຜິດພາດການຮຽກເກັບເງິນ Google Play</string>
|
||||
<string name="upgrades_gplay_billing_result_error_description">ເກີດຂໍ້ຜິດພາດໃນການຂໍລາຍລະອຽດການຊື້ຈາກ Google Play. ລຶບແຄສ Google Play ແລະປິດເປີດໂທລະສັບຂອງທ່ານໃໝ່.\n\nຂໍ້ຜິດພາດ %s</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,4 +2,8 @@
|
||||
<resources>
|
||||
<string name="upgrades_gplay_unavailable_error">„Google Play“ paslaugos nepasiekiamos.</string>
|
||||
<string name="upgrades_no_purchases_found_check_account">Nerasta jokių pirkinių. Ar naudojate tinkamą paskyrą?</string>
|
||||
<string name="upgrades_gplay_billing_error_label">„Google Play\" klaida</string>
|
||||
<string name="upgrades_gplay_billing_error_description">„Google Play\" įvyko klaida. Bandykite dar kartą vėliau arba paleiskite telefoną iš naujo.\n\nKlaida: %s</string>
|
||||
<string name="upgrades_gplay_billing_result_error_label">„Google Play\" atsiskaitymo klaida</string>
|
||||
<string name="upgrades_gplay_billing_result_error_description">Įvyko klaida gaunant pirkinių informaciją iš „Google Play\". Išvalykite „Google Play\" talpyklą ir paleiskite telefoną iš naujo.\n\nKlaida %s</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,4 +2,8 @@
|
||||
<resources>
|
||||
<string name="upgrades_gplay_unavailable_error">Google Play pakalpojumi nav pieejami.</string>
|
||||
<string name="upgrades_no_purchases_found_check_account">Nav atrasts neviens pirkums. Vai izmantojat pareizo kontu?</string>
|
||||
<string name="upgrades_gplay_billing_error_label">Google Play kļūda</string>
|
||||
<string name="upgrades_gplay_billing_error_description">Google Play radās kļūda. Lūdzu, mēģiniet vēlreiz vēlāk vai pārstartējiet tālruni.\n\nKļūda: %s</string>
|
||||
<string name="upgrades_gplay_billing_result_error_label">Google Play norēķinu kļūda</string>
|
||||
<string name="upgrades_gplay_billing_result_error_description">Radās kļūda, pieprasot no Google Play jūsu pirkuma informāciju. Notīriet Google Play kešatmiņu un pārstartējiet tālruni.\n\nKļūda %s</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,4 +2,8 @@
|
||||
<resources>
|
||||
<string name="upgrades_gplay_unavailable_error">Услугите на Google Play не се достапни.</string>
|
||||
<string name="upgrades_no_purchases_found_check_account">Не се пронајдени купувања. Дали ја користите вистинската сметка?</string>
|
||||
<string name="upgrades_gplay_billing_error_label">Грешка во Google Play</string>
|
||||
<string name="upgrades_gplay_billing_error_description">Настана грешка во Google Play. Обидете се повторно подоцна или рестартирајте го телефонот.\n\nГрешка: %s</string>
|
||||
<string name="upgrades_gplay_billing_result_error_label">Грешка при наплата на Google Play</string>
|
||||
<string name="upgrades_gplay_billing_result_error_description">Настана грешка при барање на деталите за вашата купувања од Google Play. Исчистете го кешот на Google Play и рестартирајте го телефонот.\n\nГрешка %s</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,4 +2,8 @@
|
||||
<resources>
|
||||
<string name="upgrades_gplay_unavailable_error">Google Play സേവനങ്ങൾ ലഭ്യമല്ല.</string>
|
||||
<string name="upgrades_no_purchases_found_check_account">വാങ്ങലുകളൊന്നും കണ്ടെത്തിയില്ല. നിങ്ങൾ ശരിയായ അക്കൗണ്ട് ആണോ ഉപയോഗിക്കുന്നത്?</string>
|
||||
<string name="upgrades_gplay_billing_error_label">Google Play പിശക്</string>
|
||||
<string name="upgrades_gplay_billing_error_description">Google Play-ൽ ഒരു പിശക് സംഭവിച്ചു. ദയവായി പിന്നീട് വീണ്ടും ശ്രമിക്കുക അല്ലെങ്കിൽ നിങ്ങളുടെ ഫോൺ റീബൂട്ട് ചെയ്യുക.\n\nപിശക്: %s</string>
|
||||
<string name="upgrades_gplay_billing_result_error_label">Google Play ബില്ലിംഗ് പിശക്</string>
|
||||
<string name="upgrades_gplay_billing_result_error_description">നിങ്ങളുടെ വാങ്ങൽ വിശദാംശങ്ങൾക്കായി Google Play-യോട് അഭ്യർത്ഥിക്കുമ്പോൾ ഒരു പിശക് സംഭവിച്ചു. Google Play കാഷെ മായ്ച്ച് നിങ്ങളുടെ ഫോൺ റീബൂട്ട് ചെയ്യുക.\n\nപിശക് %s</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,4 +2,8 @@
|
||||
<resources>
|
||||
<string name="upgrades_gplay_unavailable_error">Google Play үйлчилгээ боломжгүй байна.</string>
|
||||
<string name="upgrades_no_purchases_found_check_account">Худалдан авалт олдсонгүй. Та зөв бүртгэл ашиглаж байна уу?</string>
|
||||
<string name="upgrades_gplay_billing_error_label">Google Play алдаа</string>
|
||||
<string name="upgrades_gplay_billing_error_description">Google Play-д алдаа гарлаа. Дараа дахин оролдоно уу эсвэл утсаа дахин асаана уу.\n\nАлдаа: %s</string>
|
||||
<string name="upgrades_gplay_billing_result_error_label">Google Play төлбөрийн алдаа</string>
|
||||
<string name="upgrades_gplay_billing_result_error_description">Google Play-с таны худалдан авалтын мэдээллийг авахад алдаа гарлаа. Google Play кэшийг цэвэрлээд утсаа дахин асаана уу.\n\nАлдаа %s</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,4 +2,8 @@
|
||||
<resources>
|
||||
<string name="upgrades_gplay_unavailable_error">Google Play सेवा अनुपलब्ध आहेत.</string>
|
||||
<string name="upgrades_no_purchases_found_check_account">कोणतीही खरेदी आढळली नाही. तुम्ही योग्य खाते वापरत आहात का?</string>
|
||||
<string name="upgrades_gplay_billing_error_label">Google Play त्रुटी</string>
|
||||
<string name="upgrades_gplay_billing_error_description">Google Play मध्ये त्रुटी आली. कृपया नंतर पुन्हा प्रयत्न करा किंवा तुमचा फोन रीबूट करा.\n\nत्रुटी: %s</string>
|
||||
<string name="upgrades_gplay_billing_result_error_label">Google Play बिलिंग त्रुटी</string>
|
||||
<string name="upgrades_gplay_billing_result_error_description">तुमच्या खरेदीचे तपशील Google Play कडून मिळवताना त्रुटी आली. Google Play कॅशे साफ करा आणि तुमचा फोन रीबूट करा.\n\nत्रुटी %s</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,4 +2,8 @@
|
||||
<resources>
|
||||
<string name="upgrades_gplay_unavailable_error">Perkhidmatan Google Play tidak tersedia.</string>
|
||||
<string name="upgrades_no_purchases_found_check_account">Tiada pembelian ditemui. Adakah anda menggunakan akaun yang betul?</string>
|
||||
<string name="upgrades_gplay_billing_error_label">Ralat Google Play</string>
|
||||
<string name="upgrades_gplay_billing_error_description">Terdapat ralat dalam Google Play. Sila cuba lagi kemudian atau mulakan semula telefon anda.\n\nRalat: %s</string>
|
||||
<string name="upgrades_gplay_billing_result_error_label">Ralat Pengebilan Google Play</string>
|
||||
<string name="upgrades_gplay_billing_result_error_description">Terdapat ralat semasa meminta butiran pembelian anda daripada Google Play. Kosongkan cache Google Play dan mulakan semula telefon anda.\n\nRalat %s</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,4 +2,8 @@
|
||||
<resources>
|
||||
<string name="upgrades_gplay_unavailable_error">Google Play ဝန်ဆောင်မှုများ မရရှိနိုင်ပါ။</string>
|
||||
<string name="upgrades_no_purchases_found_check_account">ဝယ်ယူမှုများ မတွေ့ပါ။ သင်အသုံးပြုနေသော အကောင့်မှန်ကန်ပါသလား။</string>
|
||||
<string name="upgrades_gplay_billing_error_label">Google Play အမှား</string>
|
||||
<string name="upgrades_gplay_billing_error_description">Google Play တွင် အမှားတစ်ခု ဖြစ်ပေါ်ခဲ့သည်။ ကျေးဇူးပြု၍ နောက်မှ ထပ်ကြိုးစားပါ သို့မဟုတ် သင့်ဖုန်းကို ပြန်လည်စတင်ပါ။\n\nအမှား: %s</string>
|
||||
<string name="upgrades_gplay_billing_result_error_label">Google Play ငွေတောင်းခံမှု အမှား</string>
|
||||
<string name="upgrades_gplay_billing_result_error_description">Google Play မှ သင့်ဝယ်ယူမှု အသေးစိတ်အချက်အလက်များကို တောင်းဆိုရာတွင် အမှားတစ်ခု ဖြစ်ပေါ်ခဲ့သည်။ Google Play ကက်ရှ်ကို ရှင်းလင်းပြီး သင့်ဖုန်းကို ပြန်လည်စတင်ပါ။\n\nအမှား %s</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,4 +2,8 @@
|
||||
<resources>
|
||||
<string name="upgrades_gplay_unavailable_error">Google Play-tjenester er utilgjengelige.</string>
|
||||
<string name="upgrades_no_purchases_found_check_account">Ingen kjøp funnet. Bruker du riktig konto?</string>
|
||||
<string name="upgrades_gplay_billing_error_label">Google Play-feil</string>
|
||||
<string name="upgrades_gplay_billing_error_description">Det oppstod en feil i Google Play. Prøv igjen senere eller start telefonen på nytt.\n\nFeil: %s</string>
|
||||
<string name="upgrades_gplay_billing_result_error_label">Google Play-faktureringsfeil</string>
|
||||
<string name="upgrades_gplay_billing_result_error_description">Det oppstod en feil da Google Play ble spurt om kjøpsdetaljene dine. Tøm Google Play-hurtigbufferen og start telefonen på nytt.\n\nFeil %s</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,4 +2,8 @@
|
||||
<resources>
|
||||
<string name="upgrades_gplay_unavailable_error">Google Play सेवाहरू अनुपलब्ध छन्।</string>
|
||||
<string name="upgrades_no_purchases_found_check_account">कुनै खरिदहरू फेला परेनन्। के तपाईंले सही खाता प्रयोग गरिरहनुभएको छ?</string>
|
||||
<string name="upgrades_gplay_billing_error_label">Google Play त्रुटि</string>
|
||||
<string name="upgrades_gplay_billing_error_description">Google Play मा त्रुटि भयो। कृपया पछि पुन: प्रयास गर्नुहोस् वा आफ्नो फोन रिबुट गर्नुहोस्।\n\nत्रुटि: %s</string>
|
||||
<string name="upgrades_gplay_billing_result_error_label">Google Play बिलिङ त्रुटि</string>
|
||||
<string name="upgrades_gplay_billing_result_error_description">Google Play बाट तपाईंको खरिद विवरणहरू माग्दा त्रुटि भयो। Google Play क्यास खाली गर्नुहोस् र आफ्नो फोन रिबुट गर्नुहोस्।\n\nत्रुटि %s</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="upgrades_gplay_unavailable_error">Usługi Google Play nie są dostępne.</string>
|
||||
<string name="upgrades_no_purchases_found_check_account">Nie znaleziono zakupu. Czy na pewno korzystasz z poprawnego konta?</string>
|
||||
<string name="upgrades_gplay_unavailable_error">Usługi Google Play są niedostępne.</string>
|
||||
<string name="upgrades_no_purchases_found_check_account">Nie znaleziono zakupów. Czy używasz właściwego konta?</string>
|
||||
<string name="upgrades_gplay_billing_error_label">Błąd Google Play</string>
|
||||
<string name="upgrades_gplay_billing_error_description">Wystąpił błąd w Google Play. Spróbuj ponownie później lub uruchom ponownie telefon.\n\nBłąd: %s</string>
|
||||
<string name="upgrades_gplay_billing_result_error_label">Błąd płatności Google Play</string>
|
||||
|
||||
@@ -2,4 +2,8 @@
|
||||
<resources>
|
||||
<string name="upgrades_gplay_unavailable_error">Os serviços do Google Play estão indisponíveis.</string>
|
||||
<string name="upgrades_no_purchases_found_check_account">Nenhuma compra encontrada. Está a usar a conta correta?</string>
|
||||
<string name="upgrades_gplay_billing_error_label">Erro do Google Play</string>
|
||||
<string name="upgrades_gplay_billing_error_description">Ocorreu um erro no Google Play. Tente novamente mais tarde ou reinicie o telemóvel.\n\nErro: %s</string>
|
||||
<string name="upgrades_gplay_billing_result_error_label">Erro de faturação do Google Play</string>
|
||||
<string name="upgrades_gplay_billing_result_error_description">Ocorreu um erro ao solicitar os detalhes da sua compra ao Google Play. Limpe a cache do Google Play e reinicie o telemóvel.\n\nErro %s</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,4 +2,8 @@
|
||||
<resources>
|
||||
<string name="upgrades_gplay_unavailable_error">Ils servezzans da Google Play n\'èn betg disponibels.</string>
|
||||
<string name="upgrades_no_purchases_found_check_account">Nagins acquists chattads. Utiliseschas ti il conto gist?</string>
|
||||
<string name="upgrades_gplay_billing_error_label">Errur da Google Play</string>
|
||||
<string name="upgrades_gplay_billing_error_description">Igl ha dà ina errur en Google Play. Emprova per plaschair pli tard u reavvia tes telefon.\n\nErrur: %s</string>
|
||||
<string name="upgrades_gplay_billing_result_error_label">Errur da facturaziun da Google Play</string>
|
||||
<string name="upgrades_gplay_billing_result_error_description">Igl ha dà ina errur cun la dumonda dals detagls da tes acquist tar Google Play. Stizza il cache da Google Play e reavvia tes telefon.\n\nErrur %s</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,4 +2,8 @@
|
||||
<resources>
|
||||
<string name="upgrades_gplay_unavailable_error">Serviciile Google Play nu sunt disponibile.</string>
|
||||
<string name="upgrades_no_purchases_found_check_account">Nu s-au găsit achiziții. Folosești contul corect?</string>
|
||||
<string name="upgrades_gplay_billing_error_label">Eroare Google Play</string>
|
||||
<string name="upgrades_gplay_billing_error_description">A apărut o eroare în Google Play. Vă rugăm să încercați din nou mai târziu sau reporniți telefonul.\n\nEroare: %s</string>
|
||||
<string name="upgrades_gplay_billing_result_error_label">Eroare de facturare Google Play</string>
|
||||
<string name="upgrades_gplay_billing_result_error_description">A apărut o eroare la solicitarea detaliilor achiziției de la Google Play. Ștergeți memoria cache Google Play și reporniți telefonul.\n\nEroare %s</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,4 +2,8 @@
|
||||
<resources>
|
||||
<string name="upgrades_gplay_unavailable_error">Servìtzios Google Play non sunt disponìbiles.</string>
|
||||
<string name="upgrades_no_purchases_found_check_account">Perunu achistu agatadu. Ses usende su contu giustu?</string>
|
||||
<string name="upgrades_gplay_billing_error_label">Errore de Google Play</string>
|
||||
<string name="upgrades_gplay_billing_error_description">B\'at àpidu un errore in Google Play. Proa torra prus a tardu o torra a allùere su telèfonu.\n\nErrore: %s</string>
|
||||
<string name="upgrades_gplay_billing_result_error_label">Errore de fatturatzione de Google Play</string>
|
||||
<string name="upgrades_gplay_billing_result_error_description">B\'at àpidu un errore cando si cherent is detàllios de s\'achistu a Google Play. Isbòida sa cache de Google Play e torra a allùere su telèfonu.\n\nErrore %s</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,4 +2,8 @@
|
||||
<resources>
|
||||
<string name="upgrades_gplay_unavailable_error">ගූගල් ප්ලේ සේවා නොතිබේ.</string>
|
||||
<string name="upgrades_no_purchases_found_check_account">කිසිදු මිලදී ගැනීමක් හමු නොවීය. ඔබ ትክክለኛ ගිණුම භාවිතා කරනවාද?</string>
|
||||
<string name="upgrades_gplay_billing_error_label">Google Play දෝෂය</string>
|
||||
<string name="upgrades_gplay_billing_error_description">Google Play හි දෝෂයක් ඇති විය. කරුණාකර පසුව නැවත උත්සාහ කරන්න හෝ ඔබේ දුරකථනය නැවත ආරම්භ කරන්න.\n\nදෝෂය: %s</string>
|
||||
<string name="upgrades_gplay_billing_result_error_label">Google Play බිල්පත් දෝෂය</string>
|
||||
<string name="upgrades_gplay_billing_result_error_description">ඔබේ මිලදී ගැනීමේ විස්තර සඳහා Google Play වෙත ඉල්ලීමේදී දෝෂයක් ඇති විය. Google Play හැඹිලිය හිස් කර ඔබේ දුරකථනය නැවත ආරම්භ කරන්න.\n\nදෝෂය %s</string>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,7 @@
|
||||
<string name="upgrades_gplay_unavailable_error">Služby Google Play nie sú k dispozícii.</string>
|
||||
<string name="upgrades_no_purchases_found_check_account">Nenašli sa žiadne nákupy. Používate správny účet?</string>
|
||||
<string name="upgrades_gplay_billing_error_label">Chyba Google Play</string>
|
||||
<string name="upgrades_gplay_billing_error_description">V službe Google Play sa vyskytla chyba. Skúste to prosím neskôr alebo reštartujte telefón.\n\nChyba: %s</string>
|
||||
<string name="upgrades_gplay_billing_result_error_label">Chyba fakturácie Google Play</string>
|
||||
<string name="upgrades_gplay_billing_result_error_description">Pri získavaní podrobností o vašom nákupe z Google Play sa vyskytla chyba. Vymažte vyrovnávaciu pamäť Google Play a reštartujte telefón.\n\nChyba %s</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,4 +2,8 @@
|
||||
<resources>
|
||||
<string name="upgrades_gplay_unavailable_error">Storitve Google Play niso na voljo.</string>
|
||||
<string name="upgrades_no_purchases_found_check_account">Ni najdenih nakupov. Ali uporabljate pravi račun?</string>
|
||||
<string name="upgrades_gplay_billing_error_label">Napaka Google Play</string>
|
||||
<string name="upgrades_gplay_billing_error_description">Prišlo je do napake v storitvi Google Play. Poskusite znova pozneje ali znova zaženite telefon.\n\nNapaka: %s</string>
|
||||
<string name="upgrades_gplay_billing_result_error_label">Napaka pri obračunavanju Google Play</string>
|
||||
<string name="upgrades_gplay_billing_result_error_description">Prišlo je do napake pri pridobivanju podrobnosti o vašem nakupu iz Google Play. Počistite predpomnilnik Google Play in znova zaženite telefon.\n\nNapaka %s</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,4 +2,8 @@
|
||||
<resources>
|
||||
<string name="upgrades_gplay_unavailable_error">Shërbimet e Google Play nuk janë të disponueshme.</string>
|
||||
<string name="upgrades_no_purchases_found_check_account">Nuk u gjetën blerje. Po përdorni llogarinë e duhur?</string>
|
||||
<string name="upgrades_gplay_billing_error_label">Gabim i Google Play</string>
|
||||
<string name="upgrades_gplay_billing_error_description">Kishte një gabim në Google Play. Ju lutemi provoni përsëri më vonë ose rinisni telefonin tuaj.\n\nGabim: %s</string>
|
||||
<string name="upgrades_gplay_billing_result_error_label">Gabim i faturimit të Google Play</string>
|
||||
<string name="upgrades_gplay_billing_result_error_description">Kishte një gabim kur u kërkuan detajet e blerjes suaj nga Google Play. Pastroni memorien e përkohshme të Google Play dhe rinisni telefonin tuaj.\n\nGabim %s</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,4 +2,8 @@
|
||||
<resources>
|
||||
<string name="upgrades_gplay_unavailable_error">Google Play услуге нису доступне.</string>
|
||||
<string name="upgrades_no_purchases_found_check_account">Није пронађена ниједна куповина. Да ли користите прави налог?</string>
|
||||
<string name="upgrades_gplay_billing_error_label">Грешка Google Play-а</string>
|
||||
<string name="upgrades_gplay_billing_error_description">Дошло је до грешке у Google Play-у. Покушајте поново касније или рестартујте телефон.\n\nГрешка: %s</string>
|
||||
<string name="upgrades_gplay_billing_result_error_label">Грешка наплате Google Play-а</string>
|
||||
<string name="upgrades_gplay_billing_result_error_description">Дошло је до грешке при захтевању детаља о вашој куповини од Google Play-а. Обришите кеш Google Play-а и рестартујте телефон.\n\nГрешка %s</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,4 +2,8 @@
|
||||
<resources>
|
||||
<string name="upgrades_gplay_unavailable_error">Google Play-tjänster är inte tillgängliga.</string>
|
||||
<string name="upgrades_no_purchases_found_check_account">Inga köp hittades. Använder du rätt konto?</string>
|
||||
<string name="upgrades_gplay_billing_error_label">Google Play-fel</string>
|
||||
<string name="upgrades_gplay_billing_error_description">Det uppstod ett fel i Google Play. Försök igen senare eller starta om din telefon.\n\nFel: %s</string>
|
||||
<string name="upgrades_gplay_billing_result_error_label">Google Play faktureringsfel</string>
|
||||
<string name="upgrades_gplay_billing_result_error_description">Det uppstod ett fel vid hämtning av dina köpuppgifter från Google Play. Rensa Google Play-cachen och starta om din telefon.\n\nFel %s</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,4 +2,8 @@
|
||||
<resources>
|
||||
<string name="upgrades_gplay_unavailable_error">Huduma za Google Play hazipatikani.</string>
|
||||
<string name="upgrades_no_purchases_found_check_account">Hakuna manunuzi yaliyopatikana. Je, unatumia akaunti sahihi?</string>
|
||||
<string name="upgrades_gplay_billing_error_label">Hitilafu ya Google Play</string>
|
||||
<string name="upgrades_gplay_billing_error_description">Kulikuwa na hitilafu katika Google Play. Tafadhali jaribu tena baadaye au uzime na uwashe simu yako.\n\nHitilafu: %s</string>
|
||||
<string name="upgrades_gplay_billing_result_error_label">Hitilafu ya malipo ya Google Play</string>
|
||||
<string name="upgrades_gplay_billing_result_error_description">Kulikuwa na hitilafu wakati wa kuomba maelezo ya ununuzi wako kutoka Google Play. Futa kashe ya Google Play na uzime na uwashe simu yako.\n\nHitilafu %s</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,4 +2,8 @@
|
||||
<resources>
|
||||
<string name="upgrades_gplay_unavailable_error">Google Play சேவைகள் கிடைக்கவில்லை.</string>
|
||||
<string name="upgrades_no_purchases_found_check_account">வாங்குதல்கள் எதுவும் இல்லை. நீங்கள் சரியான கணக்கைப் பயன்படுத்துகிறீர்களா?</string>
|
||||
<string name="upgrades_gplay_billing_error_label">Google Play பிழை</string>
|
||||
<string name="upgrades_gplay_billing_error_description">Google Play இல் ஒரு பிழை ஏற்பட்டது. தயவுசெய்து பின்னர் மீண்டும் முயற்சிக்கவும் அல்லது உங்கள் தொலைபேசியை மறுதொடக்கம் செய்யவும்.\n\nபிழை: %s</string>
|
||||
<string name="upgrades_gplay_billing_result_error_label">Google Play பில்லிங் பிழை</string>
|
||||
<string name="upgrades_gplay_billing_result_error_description">உங்கள் வாங்குதல் விவரங்களை Google Play-யிடம் கேட்கும்போது ஒரு பிழை ஏற்பட்டது. Google Play கேச்-ஐ அழித்து உங்கள் தொலைபேசியை மறுதொடக்கம் செய்யவும்.\n\nபிழை %s</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,4 +2,8 @@
|
||||
<resources>
|
||||
<string name="upgrades_gplay_unavailable_error">Google Play సేవలు అందుబాటులో లేవు.</string>
|
||||
<string name="upgrades_no_purchases_found_check_account">కొనుగోళ్లు ఏవీ కనుగొనబడలేదు. మీరు సరైన ఖాతాను ఉపయోగిస్తున్నారా?</string>
|
||||
<string name="upgrades_gplay_billing_error_label">Google Play లోపం</string>
|
||||
<string name="upgrades_gplay_billing_error_description">Google Play లో ఒక లోపం జరిగింది. దయచేసి తర్వాత మళ్లీ ప్రయత్నించండి లేదా మీ ఫోన్ను రీబూట్ చేయండి.\n\nలోపం: %s</string>
|
||||
<string name="upgrades_gplay_billing_result_error_label">Google Play Billing లోపం</string>
|
||||
<string name="upgrades_gplay_billing_result_error_description">మీ కొనుగోలు వివరాల కోసం Google Play ను అడిగేటప్పుడు ఒక లోపం జరిగింది. Google Play క్యాష్ను క్లియర్ చేసి మీ ఫోన్ను రీబూట్ చేయండి.\n\nలోపం %s</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,4 +2,8 @@
|
||||
<resources>
|
||||
<string name="upgrades_gplay_unavailable_error">บริการ Google Play ไม่พร้อมใช้งาน</string>
|
||||
<string name="upgrades_no_purchases_found_check_account">ไม่พบการสั่งซื้อ คุณใช้บัญชีที่ถูกต้องหรือไม่</string>
|
||||
<string name="upgrades_gplay_billing_error_label">ข้อผิดพลาด Google Play</string>
|
||||
<string name="upgrades_gplay_billing_error_description">เกิดข้อผิดพลาดใน Google Play โปรดลองอีกครั้งในภายหลังหรือรีบูตโทรศัพท์ของคุณ\n\nข้อผิดพลาด: %s</string>
|
||||
<string name="upgrades_gplay_billing_result_error_label">ข้อผิดพลาดการเรียกเก็บเงินของ Google Play</string>
|
||||
<string name="upgrades_gplay_billing_result_error_description">เกิดข้อผิดพลาดขณะขอรายละเอียดการสั่งซื้อของคุณจาก Google Play ล้างแคช Google Play แล้วรีบูตโทรศัพท์ของคุณ\n\nข้อผิดพลาด %s</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,4 +2,8 @@
|
||||
<resources>
|
||||
<string name="upgrades_gplay_unavailable_error">Google Play hizmetleri kullanılamıyor.</string>
|
||||
<string name="upgrades_no_purchases_found_check_account">Satın alma bulunamadı. Doğru hesabı mı kullanıyorsunuz?</string>
|
||||
<string name="upgrades_gplay_billing_error_label">Google Play Hatası</string>
|
||||
<string name="upgrades_gplay_billing_error_description">Google Play üzerinde bir hata oluştu. Lütfen daha sonra tekrar deneyin veya telefonunuzu yeniden başlatın.\n\nHata: %s</string>
|
||||
<string name="upgrades_gplay_billing_result_error_label">Google Play Faturalandırma Hatası</string>
|
||||
<string name="upgrades_gplay_billing_result_error_description">Google Play üzerinden satın alma ayrıntılarınız istenirken bir hata oluştu. Google Play önbelleğini temizleyin ve telefonunuzu yeniden başlatın.\n\nHata %s</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,4 +2,8 @@
|
||||
<resources>
|
||||
<string name="upgrades_gplay_unavailable_error">Google Play کی سروسز دستیاب نہیں ہیں۔</string>
|
||||
<string name="upgrades_no_purchases_found_check_account">کوئی خریداری نہیں ملی۔ کیا آپ درست اکاؤنٹ استعمال کر رہے ہیں؟</string>
|
||||
<string name="upgrades_gplay_billing_error_label">Google Play خرابی</string>
|
||||
<string name="upgrades_gplay_billing_error_description">Google Play میں ایک خرابی پیش آئی۔ براہ کرم بعد میں دوبارہ کوشش کریں یا اپنا فون ری بوٹ کریں۔\n\nخرابی: %s</string>
|
||||
<string name="upgrades_gplay_billing_result_error_label">Google Play بلنگ خرابی</string>
|
||||
<string name="upgrades_gplay_billing_result_error_description">آپ کی خریداری کی تفصیلات کے لیے Google Play سے پوچھتے وقت ایک خرابی پیش آئی۔ Google Play کیش صاف کریں اور اپنا فون ری بوٹ کریں۔\n\nخرابی %s</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,4 +2,8 @@
|
||||
<resources>
|
||||
<string name="upgrades_gplay_unavailable_error">Google Play xizmatlari mavjud emas.</string>
|
||||
<string name="upgrades_no_purchases_found_check_account">Xaridlar topilmadi. To‘g‘ri hisobdan foydalanayapsizmi?</string>
|
||||
<string name="upgrades_gplay_billing_error_label">Google Play xatosi</string>
|
||||
<string name="upgrades_gplay_billing_error_description">Google Play’da xatolik yuz berdi. Iltimos, keyinroq qayta urinib ko‘ring yoki telefoningizni qayta ishga tushiring.\n\nXato: %s</string>
|
||||
<string name="upgrades_gplay_billing_result_error_label">Google Play Billing xatosi</string>
|
||||
<string name="upgrades_gplay_billing_result_error_description">Google Play’dan xaridingiz tafsilotlarini so‘rashda xatolik yuz berdi. Google Play keshini tozalang va telefoningizni qayta ishga tushiring.\n\nXato %s</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,4 +2,8 @@
|
||||
<resources>
|
||||
<string name="upgrades_gplay_unavailable_error">Các dịch vụ của Google Play không khả dụng.</string>
|
||||
<string name="upgrades_no_purchases_found_check_account">Không tìm thấy giao dịch mua nào. Bạn có đang sử dụng đúng tài khoản không?</string>
|
||||
<string name="upgrades_gplay_billing_error_label">Lỗi Google Play</string>
|
||||
<string name="upgrades_gplay_billing_error_description">Đã xảy ra lỗi trong Google Play. Vui lòng thử lại sau hoặc khởi động lại điện thoại của bạn.\n\nLỗi: %s</string>
|
||||
<string name="upgrades_gplay_billing_result_error_label">Lỗi thanh toán Google Play</string>
|
||||
<string name="upgrades_gplay_billing_result_error_description">Đã xảy ra lỗi khi yêu cầu Google Play cung cấp chi tiết giao dịch mua của bạn. Hãy xóa bộ nhớ đệm của Google Play và khởi động lại điện thoại của bạn.\n\nLỗi %s</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,4 +2,8 @@
|
||||
<resources>
|
||||
<string name="upgrades_gplay_unavailable_error">Google Play 服务不可用</string>
|
||||
<string name="upgrades_no_purchases_found_check_account">未找到购买记录,您使用的帐户正确吗?</string>
|
||||
<string name="upgrades_gplay_billing_error_label">Google Play 错误</string>
|
||||
<string name="upgrades_gplay_billing_error_description">Google Play 出现错误。请稍后重试或重启手机。\n\n错误:%s</string>
|
||||
<string name="upgrades_gplay_billing_result_error_label">Google Play 结算错误</string>
|
||||
<string name="upgrades_gplay_billing_result_error_description">向 Google Play 请求您的购买详情时出错。请清除 Google Play 缓存并重启手机。\n\n错误 %s</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,4 +2,8 @@
|
||||
<resources>
|
||||
<string name="upgrades_gplay_unavailable_error">Google Play 服務無法使用。</string>
|
||||
<string name="upgrades_no_purchases_found_check_account">未找到訂單,確定帳戶無誤?</string>
|
||||
<string name="upgrades_gplay_billing_error_label">Google Play 錯誤</string>
|
||||
<string name="upgrades_gplay_billing_error_description">Google Play 發生錯誤。請稍後再試或重新啟動手機。\n\n錯誤:%s</string>
|
||||
<string name="upgrades_gplay_billing_result_error_label">Google Play 結帳錯誤</string>
|
||||
<string name="upgrades_gplay_billing_result_error_description">向 Google Play 請求購買明細時發生錯誤。請清除 Google Play 快取並重新啟動手機。\n\n錯誤:%s</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,4 +2,8 @@
|
||||
<resources>
|
||||
<string name="upgrades_gplay_unavailable_error">Amasevisi e-Google Play awakutholakali.</string>
|
||||
<string name="upgrades_no_purchases_found_check_account">Akukho ukuthenga okutholakele. Ingabe usebenzisa i-akhawunti efanele?</string>
|
||||
<string name="upgrades_gplay_billing_error_label">Iphutha le-Google Play</string>
|
||||
<string name="upgrades_gplay_billing_error_description">Kube nephutha ku-Google Play. Sicela uzame futhi ngemuva kwesikhathi noma uqalise kabusha ifoni yakho.\n\nIphutha: %s</string>
|
||||
<string name="upgrades_gplay_billing_result_error_label">Iphutha Lokukhokha le-Google Play</string>
|
||||
<string name="upgrades_gplay_billing_result_error_description">Kube nephutha lapho kucelwa ku-Google Play imininingwane yokuthenga kwakho. Sula inqolobane ye-Google Play bese uqalisa kabusha ifoni yakho.\n\nIphutha %s</string>
|
||||
</resources>
|
||||
|
||||
@@ -36,6 +36,27 @@ inline fun <T1, T2, T3, R> combine(
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST", "LongParameterList")
|
||||
inline fun <T1, T2, T3, T4, R> combine(
|
||||
flow: Flow<T1>,
|
||||
flow2: Flow<T2>,
|
||||
flow3: Flow<T3>,
|
||||
flow4: Flow<T4>,
|
||||
crossinline transform: suspend (T1, T2, T3, T4) -> R
|
||||
): Flow<R> = kotlinx.coroutines.flow.combine(
|
||||
flow,
|
||||
flow2,
|
||||
flow3,
|
||||
flow4,
|
||||
) { args: Array<*> ->
|
||||
transform(
|
||||
args[0] as T1,
|
||||
args[1] as T2,
|
||||
args[2] as T3,
|
||||
args[3] as T4,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST", "LongParameterList")
|
||||
inline fun <T1, T2, T3, T4, T5, R> combine(
|
||||
flow: Flow<T1>,
|
||||
|
||||
@@ -3,10 +3,32 @@ package eu.darken.capod.main.ui.widget
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.graphics.Paint
|
||||
import android.graphics.PorterDuff
|
||||
import android.graphics.Shader
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.graphics.drawable.GradientDrawable
|
||||
import android.os.Bundle
|
||||
import android.text.Editable
|
||||
import android.text.InputFilter
|
||||
import android.text.TextWatcher
|
||||
import android.view.Gravity
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.GridLayout
|
||||
import android.widget.ImageView
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.appcompat.view.ContextThemeWrapper
|
||||
import androidx.core.graphics.createBitmap
|
||||
import androidx.core.graphics.drawable.toDrawable
|
||||
import androidx.core.graphics.toColorInt
|
||||
import androidx.core.view.isVisible
|
||||
import com.google.android.material.chip.Chip
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import eu.darken.capod.R
|
||||
@@ -30,14 +52,15 @@ class WidgetConfigurationActivity : Activity2() {
|
||||
|
||||
private var widgetId: Int = AppWidgetManager.INVALID_APPWIDGET_ID
|
||||
|
||||
private var isUpdatingHexFromCode = false
|
||||
private val checkerboardDrawable: BitmapDrawable by lazy { createCheckerboardDrawable() }
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
enableEdgeToEdge()
|
||||
|
||||
// Set result to CANCELED in case user backs out
|
||||
setResult(RESULT_CANCELED)
|
||||
|
||||
// Get widget ID from intent
|
||||
widgetId = intent.getIntExtra(
|
||||
AppWidgetManager.EXTRA_APPWIDGET_ID,
|
||||
AppWidgetManager.INVALID_APPWIDGET_ID
|
||||
@@ -45,7 +68,6 @@ class WidgetConfigurationActivity : Activity2() {
|
||||
|
||||
log(TAG) { "onCreate(widgetId=$widgetId)" }
|
||||
|
||||
// If widget ID is invalid, finish
|
||||
if (widgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {
|
||||
log(TAG) { "Invalid widget ID, finishing" }
|
||||
finish()
|
||||
@@ -61,6 +83,15 @@ class WidgetConfigurationActivity : Activity2() {
|
||||
|
||||
ui.profilesRecycler.adapter = profileAdapter
|
||||
|
||||
setupPreviewContainer()
|
||||
setupPresetChips()
|
||||
setupColorSwatches(ui.bgColorGrid, isBg = true)
|
||||
setupColorSwatches(ui.fgColorGrid, isBg = false)
|
||||
setupHexInput()
|
||||
setupTransparencySlider()
|
||||
setupShowDeviceLabelSwitch()
|
||||
setupResetButton()
|
||||
|
||||
ui.cancelButton.setOnClickListener {
|
||||
log(TAG) { "Cancel clicked" }
|
||||
finish()
|
||||
@@ -95,6 +126,310 @@ class WidgetConfigurationActivity : Activity2() {
|
||||
ui.confirmButton.text = getString(R.string.general_upgrade_action)
|
||||
ui.confirmButton.isEnabled = true
|
||||
}
|
||||
|
||||
updatePresetChipSelection(state.activePreset)
|
||||
updateCustomSectionsVisibility(state.isCustomMode)
|
||||
updateSwatchSelection(ui.bgColorGrid, state.theme.backgroundColor)
|
||||
updateSwatchSelection(ui.fgColorGrid, state.theme.foregroundColor)
|
||||
updateHexInputs(state.theme)
|
||||
|
||||
// Disable transparency slider when using Material You (no custom bg to apply alpha to)
|
||||
val hasCustomBg = state.theme.backgroundColor != null
|
||||
ui.transparencySlider.isEnabled = hasCustomBg
|
||||
ui.transparencyLabel.alpha = if (hasCustomBg) 1.0f else 0.5f
|
||||
|
||||
val transparencyPercent = ((255 - state.theme.backgroundAlpha) / 255f * 100f)
|
||||
val displayPercent = (transparencyPercent / 5f).toInt() * 5
|
||||
if (!ui.transparencySlider.isPressed) {
|
||||
ui.transparencySlider.value = displayPercent.toFloat()
|
||||
}
|
||||
ui.transparencyLabel.text = getString(
|
||||
R.string.widget_config_transparency_label
|
||||
) + if (hasCustomBg && displayPercent > 0) " ($displayPercent%)" else ""
|
||||
|
||||
ui.showDeviceLabelSwitch.isChecked = state.theme.showDeviceLabel
|
||||
|
||||
val selectedProfileLabel = state.profiles.firstOrNull { it.id == state.selectedProfile }?.label
|
||||
val deviceLabel = ui.previewCard.findViewById<android.widget.TextView>(R.id.preview_device_label)
|
||||
deviceLabel?.text = selectedProfileLabel ?: ""
|
||||
|
||||
updatePreview(state.theme)
|
||||
}
|
||||
}
|
||||
|
||||
private val widgetThemeContext: Context by lazy {
|
||||
ContextThemeWrapper(this, com.google.android.material.R.style.Theme_Material3_DynamicColors_DayNight)
|
||||
}
|
||||
|
||||
private fun resolveWidgetThemeColor(attr: Int): Int {
|
||||
val typedArray = widgetThemeContext.theme.obtainStyledAttributes(intArrayOf(attr))
|
||||
val color = typedArray.getColor(0, Color.BLACK)
|
||||
typedArray.recycle()
|
||||
return color
|
||||
}
|
||||
|
||||
private fun setupPreviewContainer() {
|
||||
// Initial background set from XML, updated dynamically in updatePreview
|
||||
}
|
||||
|
||||
private fun updatePreview(theme: WidgetTheme) {
|
||||
val previewRoot = ui.previewCard.findViewById<View>(R.id.preview_widget_root)
|
||||
|
||||
// Background color applied to the inner view, matching the real widget
|
||||
val bgColor = theme.backgroundColor
|
||||
if (bgColor != null) {
|
||||
previewRoot.setBackgroundColor(WidgetTheme.applyAlpha(bgColor, theme.backgroundAlpha))
|
||||
} else {
|
||||
previewRoot.setBackgroundColor(resolveWidgetThemeColor(android.R.attr.colorBackground))
|
||||
}
|
||||
|
||||
// Show checkerboard behind preview only when there's actual transparency to visualize
|
||||
val hasTransparency = bgColor != null && theme.backgroundAlpha < 255
|
||||
ui.previewContainer.background = if (hasTransparency) {
|
||||
checkerboardDrawable
|
||||
} else {
|
||||
AppCompatResources.getDrawable(this, R.drawable.widget_preview_checkerboard)
|
||||
}
|
||||
|
||||
// Foreground (text + icon colors)
|
||||
val fgColor = theme.foregroundColor
|
||||
val defaultTextColor = resolveWidgetThemeColor(android.R.attr.textColorPrimary)
|
||||
val defaultIconColor = resolveWidgetThemeColor(android.R.attr.colorAccent)
|
||||
|
||||
val textViews = listOf(
|
||||
R.id.preview_left_label,
|
||||
R.id.preview_right_label,
|
||||
R.id.preview_case_label,
|
||||
R.id.preview_device_label,
|
||||
)
|
||||
val iconViews = listOf(
|
||||
R.id.preview_left_icon,
|
||||
R.id.preview_right_icon,
|
||||
R.id.preview_case_icon,
|
||||
)
|
||||
|
||||
for (id in textViews) {
|
||||
val tv = ui.previewCard.findViewById<android.widget.TextView>(id) ?: continue
|
||||
tv.setTextColor(fgColor ?: defaultTextColor)
|
||||
}
|
||||
|
||||
for (id in iconViews) {
|
||||
val iv = ui.previewCard.findViewById<ImageView>(id) ?: continue
|
||||
if (fgColor != null) {
|
||||
iv.setColorFilter(fgColor, PorterDuff.Mode.SRC_IN)
|
||||
} else {
|
||||
iv.setColorFilter(defaultIconColor, PorterDuff.Mode.SRC_IN)
|
||||
}
|
||||
}
|
||||
|
||||
// Device label visibility
|
||||
val deviceLabel = ui.previewCard.findViewById<View>(R.id.preview_device_label)
|
||||
deviceLabel?.isVisible = theme.showDeviceLabel
|
||||
}
|
||||
|
||||
private fun createCheckerboardDrawable(): BitmapDrawable {
|
||||
val cellSize = (8 * resources.displayMetrics.density).toInt()
|
||||
val bitmap = createBitmap(cellSize * 2, cellSize * 2)
|
||||
val canvas = Canvas(bitmap)
|
||||
val paint = Paint()
|
||||
// Light squares
|
||||
paint.color = 0xFFE8E8E8.toInt()
|
||||
canvas.drawRect(0f, 0f, (cellSize * 2).toFloat(), (cellSize * 2).toFloat(), paint)
|
||||
// Dark squares
|
||||
paint.color = 0xFFD0D0D0.toInt()
|
||||
canvas.drawRect(0f, 0f, cellSize.toFloat(), cellSize.toFloat(), paint)
|
||||
canvas.drawRect(
|
||||
cellSize.toFloat(),
|
||||
cellSize.toFloat(),
|
||||
(cellSize * 2).toFloat(),
|
||||
(cellSize * 2).toFloat(),
|
||||
paint
|
||||
)
|
||||
|
||||
return bitmap.toDrawable(resources).apply {
|
||||
tileModeX = Shader.TileMode.REPEAT
|
||||
tileModeY = Shader.TileMode.REPEAT
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupPresetChips() {
|
||||
val presetNames = mapOf(
|
||||
WidgetTheme.Preset.MATERIAL_YOU to getString(R.string.widget_config_preset_material_you),
|
||||
WidgetTheme.Preset.CLASSIC_DARK to getString(R.string.widget_config_preset_dark),
|
||||
WidgetTheme.Preset.CLASSIC_LIGHT to getString(R.string.widget_config_preset_light),
|
||||
WidgetTheme.Preset.BLUE to getString(R.string.widget_config_preset_blue),
|
||||
WidgetTheme.Preset.GREEN to getString(R.string.widget_config_preset_green),
|
||||
WidgetTheme.Preset.RED to getString(R.string.widget_config_preset_red),
|
||||
)
|
||||
|
||||
for (preset in WidgetTheme.Preset.entries) {
|
||||
val chip = Chip(this).apply {
|
||||
text = presetNames[preset] ?: preset.name
|
||||
isCheckable = true
|
||||
tag = preset
|
||||
setOnClickListener { vm.selectPreset(preset) }
|
||||
}
|
||||
ui.presetChipGroup.addView(chip)
|
||||
}
|
||||
|
||||
// Custom chip
|
||||
val customChip = Chip(this).apply {
|
||||
text = getString(R.string.widget_config_custom_label)
|
||||
isCheckable = true
|
||||
tag = CUSTOM_CHIP_TAG
|
||||
setOnClickListener {
|
||||
val defaultBg = resolveWidgetThemeColor(android.R.attr.colorBackground)
|
||||
val defaultFg = resolveWidgetThemeColor(android.R.attr.textColorPrimary)
|
||||
vm.enterCustomMode(defaultBg, defaultFg)
|
||||
}
|
||||
}
|
||||
ui.presetChipGroup.addView(customChip)
|
||||
}
|
||||
|
||||
private fun updatePresetChipSelection(activePreset: WidgetTheme.Preset?) {
|
||||
for (i in 0 until ui.presetChipGroup.childCount) {
|
||||
val chip = ui.presetChipGroup.getChildAt(i) as? Chip ?: continue
|
||||
chip.isChecked = if (activePreset != null) {
|
||||
chip.tag == activePreset
|
||||
} else {
|
||||
chip.tag == CUSTOM_CHIP_TAG
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateCustomSectionsVisibility(isCustomMode: Boolean) {
|
||||
val visibility = if (isCustomMode) View.VISIBLE else View.GONE
|
||||
ui.bgColorLabel.visibility = visibility
|
||||
ui.bgColorGrid.visibility = visibility
|
||||
ui.bgHexInputLayout.visibility = visibility
|
||||
ui.fgColorLabel.visibility = visibility
|
||||
ui.fgColorGrid.visibility = visibility
|
||||
ui.fgHexInputLayout.visibility = visibility
|
||||
}
|
||||
|
||||
private fun setupColorSwatches(grid: GridLayout, isBg: Boolean) {
|
||||
for (color in SWATCH_COLORS) {
|
||||
val itemView = LayoutInflater.from(this)
|
||||
.inflate(R.layout.widget_color_swatch_item, grid, false)
|
||||
|
||||
val swatchColor = itemView.findViewById<View>(R.id.swatch_color)
|
||||
val bgDrawable = swatchColor.background?.mutate() as? GradientDrawable ?: continue
|
||||
bgDrawable.setColor(color)
|
||||
swatchColor.background = bgDrawable
|
||||
|
||||
itemView.setOnClickListener {
|
||||
ui.bgHexInput.clearFocus()
|
||||
ui.fgHexInput.clearFocus()
|
||||
if (isBg) vm.setBackgroundColor(color) else vm.setForegroundColor(color)
|
||||
}
|
||||
|
||||
val params = GridLayout.LayoutParams(
|
||||
GridLayout.spec(GridLayout.UNDEFINED),
|
||||
GridLayout.spec(GridLayout.UNDEFINED, 1f),
|
||||
).apply {
|
||||
width = GridLayout.LayoutParams.WRAP_CONTENT
|
||||
height = GridLayout.LayoutParams.WRAP_CONTENT
|
||||
setGravity(Gravity.CENTER)
|
||||
}
|
||||
grid.addView(itemView, params)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateSwatchSelection(grid: GridLayout, selectedColor: Int?) {
|
||||
for (i in 0 until grid.childCount) {
|
||||
val itemView = grid.getChildAt(i) as? FrameLayout ?: continue
|
||||
val color = SWATCH_COLORS.getOrNull(i) ?: continue
|
||||
val isSelected =
|
||||
selectedColor != null && (selectedColor or 0xFF000000.toInt()) == (color or 0xFF000000.toInt())
|
||||
|
||||
itemView.findViewById<View>(R.id.swatch_selected_ring)?.isVisible = isSelected
|
||||
itemView.findViewById<ImageView>(R.id.swatch_check)?.apply {
|
||||
isVisible = isSelected
|
||||
if (isSelected) {
|
||||
val checkColor = WidgetTheme.bestContrastForeground(color)
|
||||
setColorFilter(checkColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupHexInput() {
|
||||
val hexFilter = InputFilter { source, _, _, _, _, _ ->
|
||||
val filtered = source.toString().uppercase().filter { it in "0123456789ABCDEF" }
|
||||
if (filtered == source.toString()) null else filtered
|
||||
}
|
||||
|
||||
ui.bgHexInput.filters = arrayOf(hexFilter, InputFilter.LengthFilter(6))
|
||||
ui.fgHexInput.filters = arrayOf(hexFilter, InputFilter.LengthFilter(6))
|
||||
|
||||
ui.bgHexInput.addTextChangedListener(object : TextWatcher {
|
||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
|
||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
|
||||
override fun afterTextChanged(s: Editable?) {
|
||||
if (isUpdatingHexFromCode) return
|
||||
val hex = s?.toString() ?: return
|
||||
if (hex.length == 6) {
|
||||
try {
|
||||
val color = "#$hex".toColorInt()
|
||||
vm.setBackgroundColor(color)
|
||||
} catch (_: IllegalArgumentException) {
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
ui.fgHexInput.addTextChangedListener(object : TextWatcher {
|
||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
|
||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
|
||||
override fun afterTextChanged(s: Editable?) {
|
||||
if (isUpdatingHexFromCode) return
|
||||
val hex = s?.toString() ?: return
|
||||
if (hex.length == 6) {
|
||||
try {
|
||||
val color = "#$hex".toColorInt()
|
||||
vm.setForegroundColor(color)
|
||||
} catch (_: IllegalArgumentException) {
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun updateHexInputs(theme: WidgetTheme) {
|
||||
isUpdatingHexFromCode = true
|
||||
try {
|
||||
val bgHex = theme.backgroundColor?.let { String.format("%06X", 0xFFFFFF and it) } ?: ""
|
||||
if (ui.bgHexInput.text.toString() != bgHex && !ui.bgHexInput.hasFocus()) {
|
||||
ui.bgHexInput.setText(bgHex)
|
||||
}
|
||||
|
||||
val fgHex = theme.foregroundColor?.let { String.format("%06X", 0xFFFFFF and it) } ?: ""
|
||||
if (ui.fgHexInput.text.toString() != fgHex && !ui.fgHexInput.hasFocus()) {
|
||||
ui.fgHexInput.setText(fgHex)
|
||||
}
|
||||
} finally {
|
||||
isUpdatingHexFromCode = false
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupTransparencySlider() {
|
||||
ui.transparencySlider.addOnChangeListener { _, value, fromUser ->
|
||||
if (fromUser) {
|
||||
val alpha = 255 - (value / 100f * 255f).toInt()
|
||||
vm.setBackgroundAlpha(alpha)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupShowDeviceLabelSwitch() {
|
||||
ui.showDeviceLabelSwitch.setOnCheckedChangeListener { _, isChecked ->
|
||||
vm.setShowDeviceLabel(isChecked)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupResetButton() {
|
||||
ui.resetButton.setOnClickListener {
|
||||
vm.resetToDefaults()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,10 +448,37 @@ class WidgetConfigurationActivity : Activity2() {
|
||||
)
|
||||
|
||||
finish()
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = logTag("Widget", "ConfigurationActivity")
|
||||
private const val CUSTOM_CHIP_TAG = "custom"
|
||||
|
||||
private val SWATCH_COLORS = intArrayOf(
|
||||
0xFFF44336.toInt(), // Red
|
||||
0xFFE91E63.toInt(), // Pink
|
||||
0xFF9C27B0.toInt(), // Purple
|
||||
0xFF673AB7.toInt(), // Deep Purple
|
||||
0xFF3F51B5.toInt(), // Indigo
|
||||
0xFF2196F3.toInt(), // Blue
|
||||
0xFF03A9F4.toInt(), // Light Blue
|
||||
0xFF00BCD4.toInt(), // Cyan
|
||||
0xFF009688.toInt(), // Teal
|
||||
0xFF4CAF50.toInt(), // Green
|
||||
0xFF8BC34A.toInt(), // Light Green
|
||||
0xFFCDDC39.toInt(), // Lime
|
||||
0xFFFFEB3B.toInt(), // Yellow
|
||||
0xFFFFC107.toInt(), // Amber
|
||||
0xFFFF9800.toInt(), // Orange
|
||||
0xFFFF5722.toInt(), // Deep Orange
|
||||
0xFF795548.toInt(), // Brown
|
||||
0xFF9E9E9E.toInt(), // Grey
|
||||
0xFF607D8B.toInt(), // Blue Grey
|
||||
0xFFFFFFFF.toInt(), // White
|
||||
0xFF1E1E1E.toInt(), // Near Black
|
||||
0xFF37474F.toInt(), // Dark Blue Grey
|
||||
0xFF1B5E20.toInt(), // Dark Green
|
||||
0xFF0D47A1.toInt(), // Dark Blue
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package eu.darken.capod.main.ui.widget
|
||||
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import eu.darken.capod.common.coroutine.DispatcherProvider
|
||||
import eu.darken.capod.common.debug.logging.log
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
@@ -22,8 +24,11 @@ class WidgetConfigurationViewModel @Inject constructor(
|
||||
private val deviceProfilesRepo: DeviceProfilesRepo,
|
||||
private val widgetSettings: WidgetSettings,
|
||||
private val upgradeRepo: UpgradeRepo,
|
||||
@ApplicationContext private val context: Context,
|
||||
) : ViewModel3(dispatcherProvider) {
|
||||
|
||||
private val appWidgetManager by lazy { AppWidgetManager.getInstance(context) }
|
||||
|
||||
val widgetId: Int
|
||||
get() = savedStateHandle.get<Int>(AppWidgetManager.EXTRA_APPWIDGET_ID) ?: AppWidgetManager.INVALID_APPWIDGET_ID
|
||||
|
||||
@@ -37,18 +42,32 @@ class WidgetConfigurationViewModel @Inject constructor(
|
||||
|
||||
private val selectedProfile = MutableStateFlow(widgetSettings.getWidgetProfile(widgetId))
|
||||
|
||||
val state = combine(
|
||||
private val initialTheme: WidgetTheme = run {
|
||||
if (widgetId == AppWidgetManager.INVALID_APPWIDGET_ID) return@run WidgetTheme.DEFAULT
|
||||
WidgetTheme.fromBundle(appWidgetManager.getAppWidgetOptions(widgetId))
|
||||
}
|
||||
|
||||
private val forceCustomMode = MutableStateFlow(WidgetTheme.matchPreset(initialTheme) == null)
|
||||
|
||||
private val currentTheme = MutableStateFlow(initialTheme)
|
||||
|
||||
val state = eu.darken.capod.common.flow.combine(
|
||||
selectedProfile,
|
||||
currentTheme,
|
||||
forceCustomMode,
|
||||
deviceProfilesRepo.profiles,
|
||||
upgradeRepo.upgradeInfo,
|
||||
) { selected, profiles, upgradeInfo ->
|
||||
log(TAG) { "loadProfiles()" }
|
||||
|
||||
) { selected, theme, forceCustom, profiles, upgradeInfo ->
|
||||
log(TAG) { "state update: profile=$selected, theme=$theme, forceCustom=$forceCustom" }
|
||||
|
||||
val activePreset = if (forceCustom) null else WidgetTheme.matchPreset(theme)
|
||||
State(
|
||||
profiles = profiles,
|
||||
isPro = upgradeInfo.isPro,
|
||||
selectedProfile = selected,
|
||||
theme = theme,
|
||||
activePreset = activePreset,
|
||||
isCustomMode = activePreset == null,
|
||||
)
|
||||
}.asLiveData2()
|
||||
|
||||
@@ -56,13 +75,70 @@ class WidgetConfigurationViewModel @Inject constructor(
|
||||
val profiles: List<DeviceProfile> = emptyList(),
|
||||
val selectedProfile: ProfileId? = null,
|
||||
val isPro: Boolean = false,
|
||||
val theme: WidgetTheme = WidgetTheme.DEFAULT,
|
||||
val activePreset: WidgetTheme.Preset? = WidgetTheme.Preset.MATERIAL_YOU,
|
||||
val isCustomMode: Boolean = false,
|
||||
) {
|
||||
val canConfirm: Boolean = selectedProfile != null
|
||||
}
|
||||
|
||||
fun selectProfile(profileId: ProfileId) {
|
||||
log(TAG) { "selectProfile(profileId=$profileId)" }
|
||||
selectedProfile.value = profileId
|
||||
selectedProfile.value = profileId
|
||||
}
|
||||
|
||||
fun selectPreset(preset: WidgetTheme.Preset) {
|
||||
log(TAG) { "selectPreset(preset=$preset)" }
|
||||
forceCustomMode.value = false
|
||||
currentTheme.value = WidgetTheme(
|
||||
backgroundColor = preset.presetBg,
|
||||
foregroundColor = preset.presetFg,
|
||||
backgroundAlpha = currentTheme.value.backgroundAlpha,
|
||||
showDeviceLabel = currentTheme.value.showDeviceLabel,
|
||||
)
|
||||
}
|
||||
|
||||
fun enterCustomMode(resolvedBg: Int, resolvedFg: Int) {
|
||||
log(TAG) { "enterCustomMode(resolvedBg=${String.format("#%06X", 0xFFFFFF and resolvedBg)}, resolvedFg=${String.format("#%06X", 0xFFFFFF and resolvedFg)})" }
|
||||
forceCustomMode.value = true
|
||||
// Populate null colors with the currently displayed values so the user has a starting point
|
||||
val theme = currentTheme.value
|
||||
currentTheme.value = theme.copy(
|
||||
backgroundColor = theme.backgroundColor ?: (resolvedBg or 0xFF000000.toInt()),
|
||||
foregroundColor = theme.foregroundColor ?: (resolvedFg or 0xFF000000.toInt()),
|
||||
)
|
||||
}
|
||||
|
||||
fun setBackgroundColor(color: Int) {
|
||||
log(TAG) { "setBackgroundColor(color=${String.format("#%06X", 0xFFFFFF and color)})" }
|
||||
currentTheme.value = currentTheme.value.copy(backgroundColor = color or 0xFF000000.toInt())
|
||||
}
|
||||
|
||||
fun setForegroundColor(color: Int) {
|
||||
log(TAG) { "setForegroundColor(color=${String.format("#%06X", 0xFFFFFF and color)})" }
|
||||
currentTheme.value = currentTheme.value.copy(foregroundColor = color or 0xFF000000.toInt())
|
||||
}
|
||||
|
||||
fun setBackgroundAlpha(alpha: Int) {
|
||||
log(TAG) { "setBackgroundAlpha(alpha=$alpha)" }
|
||||
currentTheme.value = currentTheme.value.copy(backgroundAlpha = alpha.coerceIn(0, 255))
|
||||
}
|
||||
|
||||
fun toggleDeviceLabel() {
|
||||
val newValue = !currentTheme.value.showDeviceLabel
|
||||
log(TAG) { "toggleDeviceLabel(showDeviceLabel=$newValue)" }
|
||||
currentTheme.value = currentTheme.value.copy(showDeviceLabel = newValue)
|
||||
}
|
||||
|
||||
fun setShowDeviceLabel(show: Boolean) {
|
||||
log(TAG) { "setShowDeviceLabel(show=$show)" }
|
||||
currentTheme.value = currentTheme.value.copy(showDeviceLabel = show)
|
||||
}
|
||||
|
||||
fun resetToDefaults() {
|
||||
log(TAG) { "resetToDefaults()" }
|
||||
forceCustomMode.value = false
|
||||
currentTheme.value = WidgetTheme.DEFAULT
|
||||
}
|
||||
|
||||
fun confirmSelection() {
|
||||
@@ -71,9 +147,16 @@ class WidgetConfigurationViewModel @Inject constructor(
|
||||
log(TAG) { "confirmSelection(widgetId=$widgetId, selectedProfile=$selectedProfile)" }
|
||||
widgetSettings.saveWidgetProfile(widgetId, selectedProfile)
|
||||
}
|
||||
|
||||
// Save theme to AppWidgetOptions bundle
|
||||
val theme = currentTheme.value
|
||||
log(TAG) { "confirmSelection: saving theme=$theme" }
|
||||
val options = appWidgetManager.getAppWidgetOptions(widgetId)
|
||||
theme.toBundle(options)
|
||||
appWidgetManager.updateAppWidgetOptions(widgetId, options)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = logTag("Widget", "ConfigurationVM")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,10 +5,13 @@ import android.appwidget.AppWidgetManager
|
||||
import android.appwidget.AppWidgetProvider
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.res.ColorStateList
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.RemoteViews
|
||||
import androidx.annotation.LayoutRes
|
||||
import androidx.appcompat.view.ContextThemeWrapper
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import eu.darken.capod.R
|
||||
import eu.darken.capod.common.coroutine.AppScope
|
||||
@@ -32,8 +35,10 @@ import eu.darken.capod.pods.core.PodFactory
|
||||
import eu.darken.capod.pods.core.SinglePodDevice
|
||||
import eu.darken.capod.pods.core.formatBatteryPercent
|
||||
import eu.darken.capod.pods.core.getBatteryDrawable
|
||||
import eu.darken.capod.profiles.core.DeviceProfilesRepo
|
||||
import eu.darken.capod.profiles.core.ProfileId
|
||||
import finish2
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withTimeout
|
||||
@@ -49,6 +54,7 @@ class WidgetProvider : AppWidgetProvider() {
|
||||
@Inject lateinit var podFactory: PodFactory
|
||||
@Inject lateinit var upgradeRepo: UpgradeRepo
|
||||
@Inject lateinit var widgetSettings: WidgetSettings
|
||||
@Inject lateinit var deviceProfilesRepo: DeviceProfilesRepo
|
||||
@AppScope @Inject lateinit var appScope: CoroutineScope
|
||||
|
||||
private fun executeAsync(
|
||||
@@ -136,9 +142,15 @@ class WidgetProvider : AppWidgetProvider() {
|
||||
log(TAG) { "updateWidget(widgetId=$widgetId, profileId=$profileId options=$options)" }
|
||||
|
||||
val device: PodDevice? = profileId?.let { podMonitor.getDeviceForProfile(it) }
|
||||
val profileLabel: String? = profileId?.let { id ->
|
||||
deviceProfilesRepo.profiles.first().firstOrNull { it.id == id }?.label
|
||||
}
|
||||
|
||||
val theme = WidgetTheme.fromBundle(widgetManager.getAppWidgetOptions(widgetId))
|
||||
log(TAG, VERBOSE) { "updateWidget: theme=$theme" }
|
||||
|
||||
val layout = when {
|
||||
!upgradeRepo.isPro() -> createUpgradeRequiredLayout(context, widgetId)
|
||||
!upgradeRepo.isPro() -> createUpgradeRequiredLayout(context, widgetId, theme)
|
||||
device is DualPodDevice -> {
|
||||
val minWidth = widgetManager.getAppWidgetOptions(widgetId)
|
||||
.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH)
|
||||
@@ -154,19 +166,84 @@ class WidgetProvider : AppWidgetProvider() {
|
||||
else -> R.layout.widget_pod_dual_wide_layout
|
||||
}
|
||||
|
||||
createDualPodLayout(context, device, layout, widgetId)
|
||||
createDualPodLayout(context, device, layout, widgetId, theme, profileLabel)
|
||||
}
|
||||
|
||||
device is SinglePodDevice -> createSinglePodLayout(context, device, widgetId)
|
||||
device is PodDevice -> createUnknownPodLayout(context, device, widgetId)
|
||||
else -> createNoDeviceLayout(context, profileId != null, widgetId)
|
||||
device is SinglePodDevice -> createSinglePodLayout(context, device, widgetId, theme, profileLabel)
|
||||
device is PodDevice -> createUnknownPodLayout(context, device, widgetId, theme)
|
||||
else -> createNoDeviceLayout(context, profileId != null, widgetId, theme)
|
||||
}
|
||||
widgetManager.updateAppWidget(widgetId, layout)
|
||||
}
|
||||
|
||||
private fun applyThemeColors(
|
||||
context: Context,
|
||||
views: RemoteViews,
|
||||
theme: WidgetTheme,
|
||||
textViewIds: List<Int>,
|
||||
iconViewIds: List<Int>,
|
||||
hasDeviceLabel: Boolean,
|
||||
) {
|
||||
// Always explicitly set background color to ensure previous custom colors are overwritten
|
||||
val bgColor = theme.backgroundColor
|
||||
if (bgColor != null) {
|
||||
val colorWithAlpha = WidgetTheme.applyAlpha(bgColor, theme.backgroundAlpha)
|
||||
views.setInt(R.id.widget_root, "setBackgroundColor", colorWithAlpha)
|
||||
} else {
|
||||
// Reset to theme default — resolve ?android:attr/colorBackground
|
||||
val defaultBg = resolveThemeColor(context, android.R.attr.colorBackground)
|
||||
views.setInt(R.id.widget_root, "setBackgroundColor", defaultBg)
|
||||
}
|
||||
|
||||
// Always explicitly set text/icon colors
|
||||
val fgColor = theme.foregroundColor
|
||||
if (fgColor != null) {
|
||||
for (textViewId in textViewIds) {
|
||||
views.setTextColor(textViewId, fgColor)
|
||||
}
|
||||
for (iconViewId in iconViewIds) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
views.setColorStateList(iconViewId, "setImageTintList", ColorStateList.valueOf(fgColor))
|
||||
} else {
|
||||
views.setInt(iconViewId, "setColorFilter", fgColor)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Reset to theme defaults
|
||||
val defaultTextColor = resolveThemeColor(context, android.R.attr.textColorPrimary)
|
||||
val defaultIconColor = resolveThemeColor(context, android.R.attr.colorAccent)
|
||||
for (textViewId in textViewIds) {
|
||||
views.setTextColor(textViewId, defaultTextColor)
|
||||
}
|
||||
for (iconViewId in iconViewIds) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
views.setColorStateList(iconViewId, "setImageTintList", ColorStateList.valueOf(defaultIconColor))
|
||||
} else {
|
||||
views.setInt(iconViewId, "setColorFilter", defaultIconColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasDeviceLabel) {
|
||||
views.setViewVisibility(
|
||||
R.id.headphones_label,
|
||||
if (theme.showDeviceLabel) View.VISIBLE else View.GONE
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun resolveThemeColor(context: Context, attr: Int): Int {
|
||||
val themedContext = ContextThemeWrapper(context, com.google.android.material.R.style.Theme_Material3_DynamicColors_DayNight)
|
||||
val typedArray = themedContext.theme.obtainStyledAttributes(intArrayOf(attr))
|
||||
val color = typedArray.getColor(0, android.graphics.Color.BLACK)
|
||||
typedArray.recycle()
|
||||
return color
|
||||
}
|
||||
|
||||
private suspend fun createUpgradeRequiredLayout(
|
||||
context: Context,
|
||||
widgetId: Int
|
||||
widgetId: Int,
|
||||
theme: WidgetTheme,
|
||||
) = RemoteViews(context.packageName, R.layout.widget_message_layout).apply {
|
||||
log(TAG, VERBOSE) { "createUpgradeRequiredLayout(context=$context)" }
|
||||
val pendingIntent: PendingIntent = PendingIntent.getActivity(
|
||||
@@ -181,12 +258,22 @@ class WidgetProvider : AppWidgetProvider() {
|
||||
setTextViewText(R.id.primary, context.getString(R.string.upgrade_capod_label))
|
||||
setTextViewText(R.id.secondary, context.getString(R.string.upgrade_capod_description))
|
||||
setViewVisibility(R.id.secondary, View.VISIBLE)
|
||||
|
||||
applyThemeColors(
|
||||
context = context,
|
||||
views = this,
|
||||
theme = theme,
|
||||
textViewIds = listOf(R.id.primary, R.id.secondary),
|
||||
iconViewIds = emptyList(),
|
||||
hasDeviceLabel = false,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createUnknownPodLayout(
|
||||
context: Context,
|
||||
podDevice: PodDevice,
|
||||
widgetId: Int
|
||||
widgetId: Int,
|
||||
theme: WidgetTheme,
|
||||
): RemoteViews = RemoteViews(context.packageName, R.layout.widget_message_layout).apply {
|
||||
log(TAG, VERBOSE) { "createUnknownPodLayout(context=$context, podDevice=$podDevice)" }
|
||||
val pendingIntent: PendingIntent = PendingIntent.getActivity(
|
||||
@@ -199,12 +286,22 @@ class WidgetProvider : AppWidgetProvider() {
|
||||
setOnClickPendingIntent(R.id.widget_root, pendingIntent)
|
||||
|
||||
setTextViewText(R.id.primary, context.getString(R.string.pods_unknown_label))
|
||||
|
||||
applyThemeColors(
|
||||
context = context,
|
||||
views = this,
|
||||
theme = theme,
|
||||
textViewIds = listOf(R.id.primary),
|
||||
iconViewIds = emptyList(),
|
||||
hasDeviceLabel = false,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createNoDeviceLayout(
|
||||
context: Context,
|
||||
hasConfiguredProfile: Boolean = false,
|
||||
widgetId: Int
|
||||
widgetId: Int,
|
||||
theme: WidgetTheme,
|
||||
): RemoteViews = RemoteViews(context.packageName, R.layout.widget_message_layout).apply {
|
||||
log(TAG, VERBOSE) { "createNoDeviceLayout(context=$context, hasConfiguredProfile=$hasConfiguredProfile)" }
|
||||
val pendingIntent: PendingIntent = PendingIntent.getActivity(
|
||||
@@ -222,15 +319,26 @@ class WidgetProvider : AppWidgetProvider() {
|
||||
R.string.overview_nomaindevice_label
|
||||
}
|
||||
setTextViewText(R.id.primary, context.getString(messageRes))
|
||||
|
||||
applyThemeColors(
|
||||
context = context,
|
||||
views = this,
|
||||
theme = theme,
|
||||
textViewIds = listOf(R.id.primary),
|
||||
iconViewIds = emptyList(),
|
||||
hasDeviceLabel = false,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createDualPodLayout(
|
||||
context: Context,
|
||||
podDevice: DualPodDevice,
|
||||
@LayoutRes layout: Int,
|
||||
widgetId: Int
|
||||
widgetId: Int,
|
||||
theme: WidgetTheme,
|
||||
profileLabel: String?,
|
||||
): RemoteViews = RemoteViews(context.packageName, layout).apply {
|
||||
log(TAG, VERBOSE) { "createSinglePodLayout(context=$context, podDevice=$podDevice), layout=${layout}" }
|
||||
log(TAG, VERBOSE) { "createDualPodLayout(context=$context, podDevice=$podDevice), layout=${layout}" }
|
||||
val pendingIntent: PendingIntent = PendingIntent.getActivity(
|
||||
context,
|
||||
widgetId,
|
||||
@@ -240,7 +348,7 @@ class WidgetProvider : AppWidgetProvider() {
|
||||
|
||||
setOnClickPendingIntent(R.id.widget_root, pendingIntent)
|
||||
|
||||
setTextViewText(R.id.headphones_label, podDevice.getLabel(context))
|
||||
setTextViewText(R.id.headphones_label, profileLabel ?: podDevice.getLabel(context))
|
||||
|
||||
// Left
|
||||
val leftPercent = podDevice.batteryLeftPodPercent
|
||||
@@ -276,12 +384,37 @@ class WidgetProvider : AppWidgetProvider() {
|
||||
R.id.pod_right_ear,
|
||||
if (podDevice is HasEarDetectionDual && podDevice.isRightPodInEar) View.VISIBLE else View.GONE
|
||||
)
|
||||
|
||||
applyThemeColors(
|
||||
context = context,
|
||||
views = this,
|
||||
theme = theme,
|
||||
textViewIds = listOf(
|
||||
R.id.headphones_label,
|
||||
R.id.pod_left_label,
|
||||
R.id.pod_right_label,
|
||||
R.id.pod_case_label,
|
||||
),
|
||||
iconViewIds = listOf(
|
||||
R.id.pod_left_icon,
|
||||
R.id.pod_left_charging,
|
||||
R.id.pod_left_ear,
|
||||
R.id.pod_case_icon,
|
||||
R.id.pod_case_charging,
|
||||
R.id.pod_right_icon,
|
||||
R.id.pod_right_charging,
|
||||
R.id.pod_right_ear,
|
||||
),
|
||||
hasDeviceLabel = true,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createSinglePodLayout(
|
||||
context: Context,
|
||||
podDevice: SinglePodDevice,
|
||||
widgetId: Int
|
||||
widgetId: Int,
|
||||
theme: WidgetTheme,
|
||||
profileLabel: String?,
|
||||
): RemoteViews = RemoteViews(context.packageName, R.layout.widget_pod_single_layout).apply {
|
||||
log(TAG, VERBOSE) { "createSinglePodLayout(context=$context, podDevice=$podDevice)" }
|
||||
val pendingIntent: PendingIntent = PendingIntent.getActivity(
|
||||
@@ -294,7 +427,7 @@ class WidgetProvider : AppWidgetProvider() {
|
||||
setOnClickPendingIntent(R.id.widget_root, pendingIntent)
|
||||
|
||||
val headsetPercent = podDevice.batteryHeadsetPercent
|
||||
setTextViewText(R.id.headphones_label, podDevice.getLabel(context))
|
||||
setTextViewText(R.id.headphones_label, profileLabel ?: podDevice.getLabel(context))
|
||||
setImageViewResource(R.id.headphones_icon, podDevice.iconRes)
|
||||
setImageViewResource(R.id.headphones_battery_icon, getBatteryDrawable(headsetPercent))
|
||||
setTextViewText(R.id.headphones_battery_label, formatBatteryPercent(context, headsetPercent))
|
||||
@@ -308,6 +441,23 @@ class WidgetProvider : AppWidgetProvider() {
|
||||
R.id.headphones_charging,
|
||||
if (podDevice is HasChargeDetectionDual && podDevice.isHeadsetBeingCharged) View.VISIBLE else View.GONE
|
||||
)
|
||||
|
||||
applyThemeColors(
|
||||
context = context,
|
||||
views = this,
|
||||
theme = theme,
|
||||
textViewIds = listOf(
|
||||
R.id.headphones_label,
|
||||
R.id.headphones_battery_label,
|
||||
),
|
||||
iconViewIds = listOf(
|
||||
R.id.headphones_icon,
|
||||
R.id.headphones_battery_icon,
|
||||
R.id.headphones_charging,
|
||||
R.id.headphones_worn,
|
||||
),
|
||||
hasDeviceLabel = true,
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@@ -321,4 +471,4 @@ class WidgetProvider : AppWidgetProvider() {
|
||||
context.sendBroadcast(intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
package eu.darken.capod.main.ui.widget
|
||||
|
||||
import android.graphics.Color
|
||||
import android.os.Bundle
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.core.graphics.ColorUtils
|
||||
|
||||
data class WidgetTheme(
|
||||
@ColorInt val backgroundColor: Int? = null,
|
||||
@ColorInt val foregroundColor: Int? = null,
|
||||
val backgroundAlpha: Int = 255,
|
||||
val showDeviceLabel: Boolean = true,
|
||||
) {
|
||||
|
||||
enum class Preset(
|
||||
@ColorInt val presetBg: Int?,
|
||||
@ColorInt val presetFg: Int?,
|
||||
) {
|
||||
MATERIAL_YOU(null, null),
|
||||
CLASSIC_DARK(0xFF1E1E1E.toInt(), 0xFFFFFFFF.toInt()),
|
||||
CLASSIC_LIGHT(0xFFFFFFFF.toInt(), 0xFF1E1E1E.toInt()),
|
||||
BLUE(0xFF1565C0.toInt(), 0xFFFFFFFF.toInt()),
|
||||
GREEN(0xFF2E7D32.toInt(), 0xFFFFFFFF.toInt()),
|
||||
RED(0xFFC62828.toInt(), 0xFFFFFFFF.toInt()),
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val KEY_THEME_MODE = "capod_theme_mode"
|
||||
private const val KEY_CUSTOM_BG = "capod_custom_bg"
|
||||
private const val KEY_CUSTOM_FG = "capod_custom_fg"
|
||||
private const val KEY_BG_ALPHA = "capod_bg_alpha"
|
||||
private const val KEY_SHOW_LABEL = "capod_show_label"
|
||||
|
||||
private const val MODE_MATERIAL_YOU = "material_you"
|
||||
private const val MODE_CUSTOM = "custom"
|
||||
|
||||
val DEFAULT = WidgetTheme()
|
||||
|
||||
fun fromBundle(bundle: Bundle): WidgetTheme {
|
||||
val mode = bundle.getString(KEY_THEME_MODE, MODE_MATERIAL_YOU)
|
||||
val showLabel = bundle.getBoolean(KEY_SHOW_LABEL, true)
|
||||
val alpha = bundle.getInt(KEY_BG_ALPHA, 255).coerceIn(0, 255)
|
||||
return if (mode == MODE_CUSTOM) {
|
||||
WidgetTheme(
|
||||
backgroundColor = if (bundle.containsKey(KEY_CUSTOM_BG)) bundle.getInt(KEY_CUSTOM_BG) else null,
|
||||
foregroundColor = if (bundle.containsKey(KEY_CUSTOM_FG)) bundle.getInt(KEY_CUSTOM_FG) else null,
|
||||
backgroundAlpha = alpha,
|
||||
showDeviceLabel = showLabel,
|
||||
)
|
||||
} else {
|
||||
WidgetTheme(
|
||||
backgroundAlpha = alpha,
|
||||
showDeviceLabel = showLabel,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun applyAlpha(@ColorInt color: Int, alpha: Int): Int {
|
||||
val clampedAlpha = alpha.coerceIn(0, 255)
|
||||
return Color.argb(clampedAlpha, Color.red(color), Color.green(color), Color.blue(color))
|
||||
}
|
||||
|
||||
fun bestContrastForeground(@ColorInt bgColor: Int): Int {
|
||||
val whiteContrast = ColorUtils.calculateContrast(Color.WHITE, bgColor)
|
||||
val blackContrast = ColorUtils.calculateContrast(Color.BLACK, bgColor)
|
||||
return if (whiteContrast >= blackContrast) Color.WHITE else Color.BLACK
|
||||
}
|
||||
|
||||
fun matchPreset(theme: WidgetTheme): Preset? = Preset.entries.firstOrNull { preset ->
|
||||
preset.presetBg == theme.backgroundColor && preset.presetFg == theme.foregroundColor && theme.backgroundAlpha == 255
|
||||
}
|
||||
}
|
||||
|
||||
fun toBundle(bundle: Bundle) {
|
||||
if (backgroundColor == null && foregroundColor == null) {
|
||||
bundle.putString(KEY_THEME_MODE, MODE_MATERIAL_YOU)
|
||||
bundle.remove(KEY_CUSTOM_BG)
|
||||
bundle.remove(KEY_CUSTOM_FG)
|
||||
} else {
|
||||
bundle.putString(KEY_THEME_MODE, MODE_CUSTOM)
|
||||
if (backgroundColor != null) {
|
||||
bundle.putInt(KEY_CUSTOM_BG, backgroundColor)
|
||||
} else {
|
||||
bundle.remove(KEY_CUSTOM_BG)
|
||||
}
|
||||
if (foregroundColor != null) {
|
||||
bundle.putInt(KEY_CUSTOM_FG, foregroundColor)
|
||||
} else {
|
||||
bundle.remove(KEY_CUSTOM_FG)
|
||||
}
|
||||
}
|
||||
bundle.putInt(KEY_BG_ALPHA, backgroundAlpha.coerceIn(0, 255))
|
||||
bundle.putBoolean(KEY_SHOW_LABEL, showDeviceLabel)
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,14 @@
|
||||
package eu.darken.capod.monitor.core.worker
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.ForegroundServiceStartNotAllowedException
|
||||
import android.app.Notification
|
||||
import android.app.NotificationManager
|
||||
import android.app.Service
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.ServiceInfo
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import eu.darken.capod.common.bluetooth.BluetoothDevice2
|
||||
@@ -70,27 +73,63 @@ class MonitorService : Service() {
|
||||
private val monitorScope = MonitorCoroutineScope()
|
||||
private var monitoringJob: Job? = null
|
||||
@Volatile private var monitorGeneration = 0
|
||||
private var foregroundStartFailed = false
|
||||
|
||||
@SuppressLint("InlinedApi")
|
||||
private fun promoteToForeground(notification: Notification): Boolean {
|
||||
return try {
|
||||
if (hasApiLevel(29)) {
|
||||
startForeground(
|
||||
MonitorNotifications.NOTIFICATION_ID,
|
||||
notification,
|
||||
ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE,
|
||||
)
|
||||
} else {
|
||||
startForeground(MonitorNotifications.NOTIFICATION_ID, notification)
|
||||
}
|
||||
true
|
||||
} catch (e: IllegalStateException) {
|
||||
@Suppress("NewApi")
|
||||
if (Build.VERSION.SDK_INT >= 31 && e is ForegroundServiceStartNotAllowedException) {
|
||||
log(TAG, WARN) { "Foreground service start denied by OS: ${e.message}" }
|
||||
false
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
} catch (e: SecurityException) {
|
||||
log(TAG, WARN) { "Foreground service start denied (security): ${e.message}" }
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
// Promote to foreground BEFORE Hilt DI (triggered by super.onCreate()) to avoid
|
||||
// ForegroundServiceDidNotStartInTimeException when DI is slow on backgrounded cold starts.
|
||||
MonitorNotifications.ensureChannel(this)
|
||||
if (!promoteToForeground(MonitorNotifications.createEarlyNotification(this))) {
|
||||
foregroundStartFailed = true
|
||||
stopSelf()
|
||||
super.onCreate()
|
||||
return
|
||||
}
|
||||
|
||||
super.onCreate()
|
||||
log(TAG, VERBOSE) { "onCreate()" }
|
||||
|
||||
val notification = notifications.getStartupNotification()
|
||||
if (hasApiLevel(29)) {
|
||||
startForeground(
|
||||
MonitorNotifications.NOTIFICATION_ID,
|
||||
notification,
|
||||
ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE,
|
||||
)
|
||||
} else {
|
||||
startForeground(MonitorNotifications.NOTIFICATION_ID, notification)
|
||||
}
|
||||
// Replace early notification with the full one from injected MonitorNotifications.
|
||||
// Failure here is non-fatal — the service is already foreground from the early call.
|
||||
promoteToForeground(notifications.getStartupNotification())
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
log(TAG, VERBOSE) { "onStartCommand(intent=$intent, flags=$flags, startId=$startId)" }
|
||||
|
||||
if (foregroundStartFailed) {
|
||||
log(TAG, WARN) { "Skipping monitor start, foreground promotion was denied." }
|
||||
stopSelf(startId)
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
|
||||
val forceStart = intent?.getBooleanExtra(EXTRA_FORCE_START, false) ?: false
|
||||
|
||||
if (monitoringJob?.isActive == true && !forceStart) {
|
||||
|
||||
@@ -22,8 +22,6 @@ import eu.darken.capod.pods.core.HasEarDetection
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
import eu.darken.capod.pods.core.SinglePodDevice
|
||||
import eu.darken.capod.pods.core.formatBatteryPercent
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
@@ -33,50 +31,42 @@ class MonitorNotifications @Inject constructor(
|
||||
private val notificationViewFactory: MonitorNotificationViewFactory
|
||||
) {
|
||||
|
||||
private val builderLock = Mutex()
|
||||
private val builder: NotificationCompat.Builder
|
||||
private val openPi: PendingIntent
|
||||
|
||||
init {
|
||||
NotificationChannel(
|
||||
NOTIFICATION_CHANNEL_ID,
|
||||
context.getString(R.string.notification_channel_device_status_label),
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
).run { notificationManager.createNotificationChannel(this) }
|
||||
ensureChannel(context)
|
||||
NotificationChannel(
|
||||
NOTIFICATION_CHANNEL_ID_CONNECTED,
|
||||
context.getString(R.string.notification_channel_device_status_connected_label),
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
).run { notificationManager.createNotificationChannel(this) }
|
||||
|
||||
val openIntent = Intent(context, MainActivity::class.java)
|
||||
val openPi = PendingIntent.getActivity(
|
||||
openPi = PendingIntent.getActivity(
|
||||
context,
|
||||
0,
|
||||
openIntent,
|
||||
PENDING_INTENT_REQUEST_CODE,
|
||||
Intent(context, MainActivity::class.java),
|
||||
PendingIntentCompat.FLAG_IMMUTABLE
|
||||
)
|
||||
|
||||
builder = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID).apply {
|
||||
setContentIntent(openPi)
|
||||
priority = NotificationCompat.PRIORITY_LOW
|
||||
setSmallIcon(R.drawable.devic_earbuds_generic_both)
|
||||
setOngoing(true)
|
||||
setContentTitle(context.getString(R.string.app_name))
|
||||
}
|
||||
}
|
||||
|
||||
private fun getBuilder(device: PodDevice?): NotificationCompat.Builder {
|
||||
private fun baseBuilder(channelId: String): NotificationCompat.Builder =
|
||||
NotificationCompat.Builder(context, channelId).apply {
|
||||
setContentIntent(openPi)
|
||||
priority = NotificationCompat.PRIORITY_LOW
|
||||
setSmallIcon(R.drawable.device_earbuds_generic_both)
|
||||
setOngoing(true)
|
||||
}
|
||||
|
||||
private fun getBuilder(device: PodDevice?, channelId: String): NotificationCompat.Builder {
|
||||
if (device == null) {
|
||||
return builder.apply {
|
||||
setCustomContentView(null)
|
||||
return baseBuilder(channelId).apply {
|
||||
setStyle(NotificationCompat.BigTextStyle())
|
||||
setContentTitle(context.getString(R.string.pods_none_label_short))
|
||||
setSubText(context.getString(R.string.app_name))
|
||||
setSmallIcon(R.drawable.devic_earbuds_generic_both)
|
||||
}
|
||||
}
|
||||
|
||||
return builder.apply {
|
||||
return baseBuilder(channelId).apply {
|
||||
|
||||
// Options here should be mutually exclusive, and are prioritized by their order of importance
|
||||
// Some options are omitted here, as they will conflict with other options
|
||||
@@ -139,35 +129,54 @@ class MonitorNotifications @Inject constructor(
|
||||
|
||||
setStyle(NotificationCompat.DecoratedCustomViewStyle())
|
||||
setCustomBigContentView(notificationViewFactory.createContentView(device))
|
||||
setSmallIcon(device.iconRes)
|
||||
setContentTitle("$batteryText ~ $stateText")
|
||||
setSubText(null)
|
||||
log(TAG, VERBOSE) { "updatingNotification(): $device" }
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getNotification(podDevice: PodDevice?): Notification = builderLock.withLock {
|
||||
getBuilder(podDevice).apply {
|
||||
setChannelId(NOTIFICATION_CHANNEL_ID)
|
||||
}.build()
|
||||
}
|
||||
fun getNotification(podDevice: PodDevice?): Notification =
|
||||
getBuilder(podDevice, NOTIFICATION_CHANNEL_ID).build()
|
||||
|
||||
suspend fun getNotificationConnected(podDevice: PodDevice?): Notification = builderLock.withLock {
|
||||
getBuilder(podDevice).apply {
|
||||
setChannelId(NOTIFICATION_CHANNEL_ID_CONNECTED)
|
||||
}.build()
|
||||
}
|
||||
fun getNotificationConnected(podDevice: PodDevice?): Notification =
|
||||
getBuilder(podDevice, NOTIFICATION_CHANNEL_ID_CONNECTED).build()
|
||||
|
||||
fun getStartupNotification(): Notification = getBuilder(null).apply {
|
||||
setChannelId(NOTIFICATION_CHANNEL_ID)
|
||||
}.build()
|
||||
fun getStartupNotification(): Notification =
|
||||
getBuilder(null, NOTIFICATION_CHANNEL_ID).build()
|
||||
|
||||
companion object {
|
||||
val TAG = logTag("Monitor", "Notifications")
|
||||
private val NOTIFICATION_CHANNEL_ID = "${BuildConfigWrap.APPLICATION_ID}.notification.channel.device.status"
|
||||
internal val NOTIFICATION_CHANNEL_ID = "${BuildConfigWrap.APPLICATION_ID}.notification.channel.device.status"
|
||||
private val NOTIFICATION_CHANNEL_ID_CONNECTED =
|
||||
"${BuildConfigWrap.APPLICATION_ID}.notification.channel.device.status.connected"
|
||||
internal const val NOTIFICATION_ID = 1
|
||||
internal const val NOTIFICATION_ID_CONNECTED = 2
|
||||
private const val PENDING_INTENT_REQUEST_CODE = 0
|
||||
|
||||
fun ensureChannel(context: Context) {
|
||||
val nm = context.getSystemService(NotificationManager::class.java)
|
||||
nm.createNotificationChannel(
|
||||
NotificationChannel(
|
||||
NOTIFICATION_CHANNEL_ID,
|
||||
context.getString(R.string.notification_channel_device_status_label),
|
||||
NotificationManager.IMPORTANCE_LOW,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun createEarlyNotification(context: Context): Notification {
|
||||
val openPi = PendingIntent.getActivity(
|
||||
context, PENDING_INTENT_REQUEST_CODE,
|
||||
Intent(context, MainActivity::class.java),
|
||||
PendingIntentCompat.FLAG_IMMUTABLE,
|
||||
)
|
||||
return NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID).apply {
|
||||
setContentIntent(openPi)
|
||||
setSmallIcon(R.drawable.device_earbuds_generic_both)
|
||||
setContentTitle(context.getString(R.string.app_name))
|
||||
setPriority(NotificationCompat.PRIORITY_LOW)
|
||||
setOngoing(true)
|
||||
}.build()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,9 +16,9 @@ interface DualPodDevice : PodDevice {
|
||||
|
||||
@get:DrawableRes
|
||||
val leftPodIcon: Int
|
||||
get() = R.drawable.devic_airpods_gen1_left
|
||||
get() = R.drawable.device_airpods_gen1_left
|
||||
|
||||
@get:DrawableRes
|
||||
val rightPodIcon: Int
|
||||
get() = R.drawable.devic_airpods_gen1_right
|
||||
get() = R.drawable.device_airpods_gen1_right
|
||||
}
|
||||
@@ -11,5 +11,5 @@ interface HasCase {
|
||||
|
||||
@get:DrawableRes
|
||||
val caseIcon: Int
|
||||
get() = R.drawable.devic_airpods_gen1_case
|
||||
get() = R.drawable.device_airpods_gen1_case
|
||||
}
|
||||
@@ -77,98 +77,107 @@ interface PodDevice {
|
||||
@JsonClass(generateAdapter = false)
|
||||
enum class Model(
|
||||
val label: String,
|
||||
@DrawableRes val iconRes: Int = R.drawable.devic_earbuds_generic_both,
|
||||
@DrawableRes val iconRes: Int = R.drawable.device_earbuds_generic_both,
|
||||
) {
|
||||
@Json(name = "airpods.gen1") AIRPODS_GEN1(
|
||||
label = "AirPods (Gen 1)",
|
||||
iconRes = R.drawable.devic_airpods_gen1_both,
|
||||
iconRes = R.drawable.device_airpods_gen1_both,
|
||||
),
|
||||
@Json(name = "airpods.gen2") AIRPODS_GEN2(
|
||||
"AirPods (Gen 2)",
|
||||
R.drawable.devic_airpods_gen2_both,
|
||||
R.drawable.device_airpods_gen1_both,
|
||||
),
|
||||
@Json(name = "airpods.gen3") AIRPODS_GEN3(
|
||||
"AirPods (Gen 3)",
|
||||
R.drawable.devic_airpods_gen2_both,
|
||||
R.drawable.device_airpods_gen3_both,
|
||||
),
|
||||
@Json(name = "airpods.gen4") AIRPODS_GEN4(
|
||||
"AirPods (Gen 4)",
|
||||
R.drawable.devic_airpods_gen2_both,
|
||||
R.drawable.device_airpods_gen3_both,
|
||||
),
|
||||
@Json(name = "airpods.gen4.anc") AIRPODS_GEN4_ANC(
|
||||
"AirPods (Gen 4 ANC)",
|
||||
R.drawable.devic_airpods_gen2_both,
|
||||
R.drawable.device_airpods_gen4anc_both,
|
||||
),
|
||||
@Json(name = "airpods.pro") AIRPODS_PRO(
|
||||
"AirPods Pro",
|
||||
R.drawable.devic_airpods_pro2_both
|
||||
R.drawable.device_airpods_pro2_both
|
||||
),
|
||||
@Json(name = "airpods.pro2") AIRPODS_PRO2(
|
||||
"AirPods Pro 2",
|
||||
R.drawable.devic_airpods_pro2_both
|
||||
R.drawable.device_airpods_pro2_both
|
||||
),
|
||||
@Json(name = "airpods.pro2.usbc") AIRPODS_PRO2_USBC(
|
||||
"AirPods Pro 2 USB-C",
|
||||
R.drawable.devic_airpods_pro2_both
|
||||
R.drawable.device_airpods_pro2_both
|
||||
),
|
||||
@Json(name = "airpods.pro3") AIRPODS_PRO3(
|
||||
"AirPods Pro 3",
|
||||
R.drawable.devic_airpods_pro2_both
|
||||
R.drawable.device_airpods_pro2_both
|
||||
),
|
||||
@Json(name = "airpods.max") AIRPODS_MAX(
|
||||
"AirPods Max",
|
||||
R.drawable.devic_headphones_generic
|
||||
R.drawable.device_airpods_max
|
||||
),
|
||||
@Json(name = "airpods.max.usbc") AIRPODS_MAX_USBC(
|
||||
"AirPods Max USB-C",
|
||||
R.drawable.devic_headphones_generic
|
||||
R.drawable.device_airpods_max
|
||||
),
|
||||
@Json(name = "beats.flex") BEATS_FLEX(
|
||||
"Beats Flex"
|
||||
"Beats Flex",
|
||||
R.drawable.device_beats_earbuds,
|
||||
),
|
||||
@Json(name = "beats.solo.3") BEATS_SOLO_3(
|
||||
"Beats Solo 3"
|
||||
"Beats Solo 3",
|
||||
R.drawable.device_beats_headphones,
|
||||
),
|
||||
@Json(name = "beats.studio.3") BEATS_STUDIO_3(
|
||||
"Beats Studio 3"
|
||||
"Beats Studio 3",
|
||||
R.drawable.device_beats_studio3,
|
||||
),
|
||||
@Json(name = "beats.x") BEATS_X(
|
||||
"Beats X"
|
||||
"Beats X",
|
||||
R.drawable.device_beats_x,
|
||||
),
|
||||
@Json(name = "beats.powerbeats.3") POWERBEATS_3(
|
||||
"Power Beats 3"
|
||||
"Power Beats 3",
|
||||
R.drawable.device_powerbeats_3,
|
||||
),
|
||||
@Json(name = "beats.powerbeats.4") POWERBEATS_4(
|
||||
"Power Beats 4"
|
||||
"Power Beats 4",
|
||||
R.drawable.device_powerbeats_4,
|
||||
),
|
||||
@Json(name = "beats.powerbeats.pro") POWERBEATS_PRO(
|
||||
"Power Beats Pro"
|
||||
"Power Beats Pro",
|
||||
R.drawable.device_powerbeats_pro_both,
|
||||
),
|
||||
@Json(name = "beats.powerbeats.pro2") POWERBEATS_PRO2(
|
||||
"Power Beats Pro 2"
|
||||
"Power Beats Pro 2",
|
||||
R.drawable.device_powerbeats_pro2_both,
|
||||
),
|
||||
@Json(name = "beats.fit.pro") BEATS_FIT_PRO(
|
||||
"Beats Fit Pro"
|
||||
"Beats Fit Pro",
|
||||
R.drawable.device_beats_fitpro_both,
|
||||
),
|
||||
@Json(name = "fakes.tws.i99999") FAKE_AIRPODS_GEN1(
|
||||
"AirPods (Gen 1)? \uD83C\uDFAD",
|
||||
R.drawable.devic_airpods_gen1_both,
|
||||
R.drawable.device_airpods_gen1_both,
|
||||
),
|
||||
@Json(name = "fakes.generic.airpods.gen2") FAKE_AIRPODS_GEN2(
|
||||
"AirPods (Gen 2)? \uD83C\uDFAD",
|
||||
R.drawable.devic_airpods_gen2_both,
|
||||
R.drawable.device_airpods_gen1_both,
|
||||
),
|
||||
@Json(name = "fakes.generic.airpods.gen3") FAKE_AIRPODS_GEN3(
|
||||
"AirPods (Gen 3)? \uD83C\uDFAD",
|
||||
R.drawable.devic_airpods_gen2_both,
|
||||
R.drawable.device_airpods_gen3_both,
|
||||
),
|
||||
@Json(name = "fakes.varunr.airpodspro") FAKE_AIRPODS_PRO(
|
||||
"AirPods Pro? \uD83C\uDFAD",
|
||||
R.drawable.devic_airpods_pro2_both,
|
||||
R.drawable.device_airpods_pro2_both,
|
||||
),
|
||||
@Json(name = "fakes.generic.airpods.pro2") FAKE_AIRPODS_PRO2(
|
||||
"AirPods Pro2? \uD83C\uDFAD",
|
||||
R.drawable.devic_airpods_pro2_both,
|
||||
R.drawable.device_airpods_pro2_both,
|
||||
),
|
||||
@Json(name = "unknown") UNKNOWN(
|
||||
"Unknown"
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package eu.darken.capod.pods.core.apple.airpods
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import eu.darken.capod.R
|
||||
import eu.darken.capod.common.bluetooth.BleScanResult
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
@@ -29,6 +31,18 @@ data class AirPodsGen3(
|
||||
|
||||
override val model: PodDevice.Model = PodDevice.Model.AIRPODS_GEN3
|
||||
|
||||
@get:DrawableRes
|
||||
override val leftPodIcon: Int
|
||||
get() = R.drawable.device_airpods_gen3_left
|
||||
|
||||
@get:DrawableRes
|
||||
override val rightPodIcon: Int
|
||||
get() = R.drawable.device_airpods_gen3_right
|
||||
|
||||
@get:DrawableRes
|
||||
override val caseIcon: Int
|
||||
get() = R.drawable.device_airpods_gen3_case
|
||||
|
||||
override val batteryCasePercent: Float?
|
||||
get() = super.batteryCasePercent ?: cachedBatteryPercentage
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package eu.darken.capod.pods.core.apple.airpods
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import eu.darken.capod.R
|
||||
import eu.darken.capod.common.bluetooth.BleScanResult
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
@@ -29,6 +31,18 @@ data class AirPodsGen4(
|
||||
|
||||
override val model: PodDevice.Model = PodDevice.Model.AIRPODS_GEN4
|
||||
|
||||
@get:DrawableRes
|
||||
override val leftPodIcon: Int
|
||||
get() = R.drawable.device_airpods_gen3_left
|
||||
|
||||
@get:DrawableRes
|
||||
override val rightPodIcon: Int
|
||||
get() = R.drawable.device_airpods_gen3_right
|
||||
|
||||
@get:DrawableRes
|
||||
override val caseIcon: Int
|
||||
get() = R.drawable.device_airpods_gen3_case
|
||||
|
||||
override val batteryCasePercent: Float?
|
||||
get() = super.batteryCasePercent ?: cachedBatteryPercentage
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package eu.darken.capod.pods.core.apple.airpods
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import eu.darken.capod.R
|
||||
import eu.darken.capod.common.bluetooth.BleScanResult
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
@@ -29,6 +31,18 @@ data class AirPodsGen4Anc(
|
||||
|
||||
override val model: PodDevice.Model = PodDevice.Model.AIRPODS_GEN4_ANC
|
||||
|
||||
@get:DrawableRes
|
||||
override val leftPodIcon: Int
|
||||
get() = R.drawable.device_airpods_gen4anc_left
|
||||
|
||||
@get:DrawableRes
|
||||
override val rightPodIcon: Int
|
||||
get() = R.drawable.device_airpods_gen4anc_right
|
||||
|
||||
@get:DrawableRes
|
||||
override val caseIcon: Int
|
||||
get() = R.drawable.device_airpods_gen4anc_case
|
||||
|
||||
override val batteryCasePercent: Float?
|
||||
get() = super.batteryCasePercent ?: cachedBatteryPercentage
|
||||
|
||||
|
||||
@@ -34,19 +34,19 @@ data class AirPodsPro(
|
||||
|
||||
@get:DrawableRes
|
||||
override val iconRes: Int
|
||||
get() = R.drawable.devic_airpods_pro2_both
|
||||
get() = R.drawable.device_airpods_pro2_both
|
||||
|
||||
@get:DrawableRes
|
||||
override val caseIcon: Int
|
||||
get() = R.drawable.devic_airpods_pro2_case
|
||||
get() = R.drawable.device_airpods_pro2_case
|
||||
|
||||
@get:DrawableRes
|
||||
override val leftPodIcon: Int
|
||||
get() = R.drawable.devic_airpods_pro2_left
|
||||
get() = R.drawable.device_airpods_pro2_left
|
||||
|
||||
@get:DrawableRes
|
||||
override val rightPodIcon: Int
|
||||
get() = R.drawable.devic_airpods_pro2_right
|
||||
get() = R.drawable.device_airpods_pro2_right
|
||||
|
||||
override val batteryCasePercent: Float?
|
||||
get() = super.batteryCasePercent ?: cachedBatteryPercentage
|
||||
|
||||
@@ -34,19 +34,19 @@ data class AirPodsPro2(
|
||||
|
||||
@get:DrawableRes
|
||||
override val iconRes: Int
|
||||
get() = R.drawable.devic_airpods_pro2_both
|
||||
get() = R.drawable.device_airpods_pro2_both
|
||||
|
||||
@get:DrawableRes
|
||||
override val caseIcon: Int
|
||||
get() = R.drawable.devic_airpods_pro2_case
|
||||
get() = R.drawable.device_airpods_pro2_case
|
||||
|
||||
@get:DrawableRes
|
||||
override val leftPodIcon: Int
|
||||
get() = R.drawable.devic_airpods_pro2_left
|
||||
get() = R.drawable.device_airpods_pro2_left
|
||||
|
||||
@get:DrawableRes
|
||||
override val rightPodIcon: Int
|
||||
get() = R.drawable.devic_airpods_pro2_right
|
||||
get() = R.drawable.device_airpods_pro2_right
|
||||
|
||||
override val batteryCasePercent: Float?
|
||||
get() = super.batteryCasePercent ?: cachedBatteryPercentage
|
||||
|
||||
@@ -34,19 +34,19 @@ data class AirPodsPro2Usbc(
|
||||
|
||||
@get:DrawableRes
|
||||
override val iconRes: Int
|
||||
get() = R.drawable.devic_airpods_pro2_both
|
||||
get() = R.drawable.device_airpods_pro2_both
|
||||
|
||||
@get:DrawableRes
|
||||
override val caseIcon: Int
|
||||
get() = R.drawable.devic_airpods_pro2_case
|
||||
get() = R.drawable.device_airpods_pro2_case
|
||||
|
||||
@get:DrawableRes
|
||||
override val leftPodIcon: Int
|
||||
get() = R.drawable.devic_airpods_pro2_left
|
||||
get() = R.drawable.device_airpods_pro2_left
|
||||
|
||||
@get:DrawableRes
|
||||
override val rightPodIcon: Int
|
||||
get() = R.drawable.devic_airpods_pro2_right
|
||||
get() = R.drawable.device_airpods_pro2_right
|
||||
|
||||
override val batteryCasePercent: Float?
|
||||
get() = super.batteryCasePercent ?: cachedBatteryPercentage
|
||||
|
||||
@@ -34,19 +34,19 @@ data class AirPodsPro3(
|
||||
|
||||
@get:DrawableRes
|
||||
override val iconRes: Int
|
||||
get() = R.drawable.devic_airpods_pro2_both
|
||||
get() = R.drawable.device_airpods_pro2_both
|
||||
|
||||
@get:DrawableRes
|
||||
override val caseIcon: Int
|
||||
get() = R.drawable.devic_airpods_pro2_case
|
||||
get() = R.drawable.device_airpods_pro2_case
|
||||
|
||||
@get:DrawableRes
|
||||
override val leftPodIcon: Int
|
||||
get() = R.drawable.devic_airpods_pro2_left
|
||||
get() = R.drawable.device_airpods_pro2_left
|
||||
|
||||
@get:DrawableRes
|
||||
override val rightPodIcon: Int
|
||||
get() = R.drawable.devic_airpods_pro2_right
|
||||
get() = R.drawable.device_airpods_pro2_right
|
||||
|
||||
override val batteryCasePercent: Float?
|
||||
get() = super.batteryCasePercent ?: cachedBatteryPercentage
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package eu.darken.capod.pods.core.apple.beats
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import eu.darken.capod.R
|
||||
import eu.darken.capod.common.bluetooth.BleScanResult
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
@@ -29,6 +31,18 @@ data class BeatsFitPro(
|
||||
|
||||
override val model: PodDevice.Model = PodDevice.Model.BEATS_FIT_PRO
|
||||
|
||||
@get:DrawableRes
|
||||
override val leftPodIcon: Int
|
||||
get() = R.drawable.device_beats_fitpro_left
|
||||
|
||||
@get:DrawableRes
|
||||
override val rightPodIcon: Int
|
||||
get() = R.drawable.device_beats_fitpro_right
|
||||
|
||||
@get:DrawableRes
|
||||
override val caseIcon: Int
|
||||
get() = R.drawable.device_beats_fitpro_case
|
||||
|
||||
override val batteryCasePercent: Float?
|
||||
get() = super.batteryCasePercent ?: cachedBatteryPercentage
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package eu.darken.capod.pods.core.apple.beats
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import eu.darken.capod.R
|
||||
import eu.darken.capod.common.bluetooth.BleScanResult
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
@@ -29,6 +31,18 @@ data class PowerBeatsPro(
|
||||
|
||||
override val model: PodDevice.Model = PodDevice.Model.POWERBEATS_PRO
|
||||
|
||||
@get:DrawableRes
|
||||
override val leftPodIcon: Int
|
||||
get() = R.drawable.device_powerbeats_pro_left
|
||||
|
||||
@get:DrawableRes
|
||||
override val rightPodIcon: Int
|
||||
get() = R.drawable.device_powerbeats_pro_right
|
||||
|
||||
@get:DrawableRes
|
||||
override val caseIcon: Int
|
||||
get() = R.drawable.device_powerbeats_pro_case
|
||||
|
||||
override val batteryCasePercent: Float?
|
||||
get() = super.batteryCasePercent ?: cachedBatteryPercentage
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package eu.darken.capod.pods.core.apple.beats
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import eu.darken.capod.R
|
||||
import eu.darken.capod.common.bluetooth.BleScanResult
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
@@ -29,6 +31,18 @@ data class PowerBeatsPro2(
|
||||
|
||||
override val model: PodDevice.Model = PodDevice.Model.POWERBEATS_PRO2
|
||||
|
||||
@get:DrawableRes
|
||||
override val leftPodIcon: Int
|
||||
get() = R.drawable.device_powerbeats_pro2_left
|
||||
|
||||
@get:DrawableRes
|
||||
override val rightPodIcon: Int
|
||||
get() = R.drawable.device_powerbeats_pro2_right
|
||||
|
||||
@get:DrawableRes
|
||||
override val caseIcon: Int
|
||||
get() = R.drawable.device_powerbeats_pro2_case
|
||||
|
||||
override val batteryCasePercent: Float?
|
||||
get() = super.batteryCasePercent ?: cachedBatteryPercentage
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package eu.darken.capod.pods.core.apple.misc
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import eu.darken.capod.R
|
||||
import eu.darken.capod.common.bluetooth.BleScanResult
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
import eu.darken.capod.pods.core.HasCase
|
||||
@@ -35,6 +37,18 @@ data class FakeAirPodsGen3(
|
||||
|
||||
override val model: PodDevice.Model = PodDevice.Model.FAKE_AIRPODS_GEN3
|
||||
|
||||
@get:DrawableRes
|
||||
override val leftPodIcon: Int
|
||||
get() = R.drawable.device_airpods_gen3_left
|
||||
|
||||
@get:DrawableRes
|
||||
override val rightPodIcon: Int
|
||||
get() = R.drawable.device_airpods_gen3_right
|
||||
|
||||
@get:DrawableRes
|
||||
override val caseIcon: Int
|
||||
get() = R.drawable.device_airpods_gen3_case
|
||||
|
||||
override val rssi: Int
|
||||
get() = rssiAverage ?: super<DualApplePods>.rssi
|
||||
|
||||
|
||||
@@ -39,19 +39,19 @@ data class FakeAirPodsPro(
|
||||
|
||||
@get:DrawableRes
|
||||
override val iconRes: Int
|
||||
get() = R.drawable.devic_airpods_pro2_both
|
||||
get() = R.drawable.device_airpods_pro2_both
|
||||
|
||||
@get:DrawableRes
|
||||
override val caseIcon: Int
|
||||
get() = R.drawable.devic_airpods_pro2_case
|
||||
get() = R.drawable.device_airpods_pro2_case
|
||||
|
||||
@get:DrawableRes
|
||||
override val leftPodIcon: Int
|
||||
get() = R.drawable.devic_airpods_pro2_left
|
||||
get() = R.drawable.device_airpods_pro2_left
|
||||
|
||||
@get:DrawableRes
|
||||
override val rightPodIcon: Int
|
||||
get() = R.drawable.devic_airpods_pro2_right
|
||||
get() = R.drawable.device_airpods_pro2_right
|
||||
|
||||
override val rssi: Int
|
||||
get() = rssiAverage ?: super<DualApplePods>.rssi
|
||||
|
||||
@@ -41,19 +41,19 @@ data class FakeAirPodsPro2(
|
||||
|
||||
@get:DrawableRes
|
||||
override val iconRes: Int
|
||||
get() = R.drawable.devic_airpods_pro2_both
|
||||
get() = R.drawable.device_airpods_pro2_both
|
||||
|
||||
@get:DrawableRes
|
||||
override val caseIcon: Int
|
||||
get() = R.drawable.devic_airpods_pro2_case
|
||||
get() = R.drawable.device_airpods_pro2_case
|
||||
|
||||
@get:DrawableRes
|
||||
override val leftPodIcon: Int
|
||||
get() = R.drawable.devic_airpods_pro2_left
|
||||
get() = R.drawable.device_airpods_pro2_left
|
||||
|
||||
@get:DrawableRes
|
||||
override val rightPodIcon: Int
|
||||
get() = R.drawable.devic_airpods_pro2_right
|
||||
get() = R.drawable.device_airpods_pro2_right
|
||||
|
||||
override val rssi: Int
|
||||
get() = rssiAverage ?: super<DualApplePods>.rssi
|
||||
|
||||
@@ -33,7 +33,7 @@ class PlayPause @Inject constructor(
|
||||
fun monitor() = combine(
|
||||
reactionSettings.autoPlay.flow,
|
||||
reactionSettings.autoPause.flow,
|
||||
reactionSettings.onePodMode.flow,
|
||||
reactionSettings.onePodMode.flow, // Included to restart pipeline when toggled; value read at decision time
|
||||
) { play, pause, _ -> play || pause }
|
||||
.flatMapLatest { if (it) bluetoothManager.connectedDevices else emptyFlow() }
|
||||
.flatMapLatest {
|
||||
@@ -57,46 +57,184 @@ class PlayPause @Inject constructor(
|
||||
.onEach { (previous, current) ->
|
||||
log(TAG, VERBOSE) { "Checking\nprevious=$previous\ncurrent=$current" }
|
||||
|
||||
val previousWorn: Boolean?
|
||||
val currentWorn: Boolean?
|
||||
// Convert to EarDetectionState based on device capabilities
|
||||
val prevState: EarDetectionState
|
||||
val currState: EarDetectionState
|
||||
|
||||
when {
|
||||
reactionSettings.onePodMode.value && previous is HasEarDetectionDual && current is HasEarDetectionDual -> {
|
||||
previousWorn = previous.isEitherPodInEar
|
||||
currentWorn = current.isEitherPodInEar
|
||||
log(TAG, VERBOSE) { "previous: left=${previous.isLeftPodInEar}, right=${previous.isRightPodInEar}" }
|
||||
log(TAG, VERBOSE) { "current: left${current.isLeftPodInEar}, right=${current.isRightPodInEar}" }
|
||||
previous is HasEarDetectionDual && current is HasEarDetectionDual -> {
|
||||
// Dual pod devices (AirPods, AirPods Pro, etc.)
|
||||
log(TAG, VERBOSE) {
|
||||
"Dual-pod device: left=${current.isLeftPodInEar}, right=${current.isRightPodInEar}"
|
||||
}
|
||||
prevState = EarDetectionState.fromDualPod(previous)
|
||||
currState = EarDetectionState.fromDualPod(current)
|
||||
}
|
||||
|
||||
previous is HasEarDetection && current is HasEarDetection -> {
|
||||
previousWorn = previous.isBeingWorn
|
||||
currentWorn = current.isBeingWorn
|
||||
log(TAG, VERBOSE) { "prev.isBeingWorn=${previousWorn}, cur.isBeingWorn=${currentWorn}" }
|
||||
// Single pod devices (AirPods Max, etc.)
|
||||
log(TAG, VERBOSE) { "Single-pod device: worn=${current.isBeingWorn}" }
|
||||
prevState = EarDetectionState.fromSinglePod(previous)
|
||||
currState = EarDetectionState.fromSinglePod(current)
|
||||
}
|
||||
|
||||
else -> {
|
||||
log(TAG, VERBOSE) { "Current devices don't support ear detection." }
|
||||
previousWorn = null
|
||||
currentWorn = null
|
||||
log(TAG, VERBOSE) { "Device doesn't support ear detection: $current" }
|
||||
return@onEach
|
||||
}
|
||||
}
|
||||
|
||||
if (previousWorn == false && currentWorn == true && !mediaControl.isPlaying) {
|
||||
if (reactionSettings.autoPlay.value) {
|
||||
log(TAG) { "autoPlay is triggered, sendPlay()" }
|
||||
// Evaluate what action to take
|
||||
val decision = evaluatePlayPauseAction(
|
||||
previous = prevState,
|
||||
current = currState,
|
||||
onePodMode = reactionSettings.onePodMode.value,
|
||||
isCurrentlyPlaying = mediaControl.isPlaying
|
||||
)
|
||||
|
||||
log(TAG, VERBOSE) { "Decision: ${decision.reason}" }
|
||||
|
||||
// Execute the decision
|
||||
when {
|
||||
decision.shouldPlay && reactionSettings.autoPlay.value -> {
|
||||
log(TAG) { "autoPlay is triggered, sendPlay() - ${decision.reason}" }
|
||||
mediaControl.sendPlay()
|
||||
} else {
|
||||
}
|
||||
|
||||
decision.shouldPlay && !reactionSettings.autoPlay.value -> {
|
||||
log(TAG, VERBOSE) { "autoPlay is disabled" }
|
||||
}
|
||||
} else if (previousWorn == true && currentWorn == false && mediaControl.isPlaying) {
|
||||
if (reactionSettings.autoPause.value) {
|
||||
log(TAG) { "autoPause is triggered, sendPause()" }
|
||||
|
||||
decision.shouldPause && reactionSettings.autoPause.value -> {
|
||||
log(TAG) { "autoPause is triggered, sendPause() - ${decision.reason}" }
|
||||
mediaControl.sendPause()
|
||||
} else {
|
||||
log(TAG) { "autoPause is disabled" }
|
||||
}
|
||||
|
||||
decision.shouldPause && !reactionSettings.autoPause.value -> {
|
||||
log(TAG, VERBOSE) { "autoPause is disabled" }
|
||||
}
|
||||
}
|
||||
}
|
||||
.setupCommonEventHandlers(TAG) { "monitor" }
|
||||
|
||||
internal fun evaluatePlayPauseAction(
|
||||
previous: EarDetectionState,
|
||||
current: EarDetectionState,
|
||||
onePodMode: Boolean,
|
||||
isCurrentlyPlaying: Boolean
|
||||
): PlayPauseDecision = if (onePodMode) {
|
||||
evaluateOnePodMode(previous, current, isCurrentlyPlaying)
|
||||
} else {
|
||||
evaluateNormalMode(previous, current, isCurrentlyPlaying)
|
||||
}
|
||||
|
||||
private fun evaluateOnePodMode(
|
||||
previous: EarDetectionState,
|
||||
current: EarDetectionState,
|
||||
isCurrentlyPlaying: Boolean
|
||||
): PlayPauseDecision {
|
||||
val netChange = current.podCount - previous.podCount
|
||||
|
||||
return when {
|
||||
// Net decrease: pod(s) removed → pause
|
||||
netChange < 0 && isCurrentlyPlaying -> PlayPauseDecision(
|
||||
shouldPlay = false,
|
||||
shouldPause = true,
|
||||
reason = "One-pod mode: pod(s) removed (net change: $netChange)"
|
||||
)
|
||||
|
||||
// Net increase: pod(s) inserted → play
|
||||
netChange > 0 && !isCurrentlyPlaying -> PlayPauseDecision(
|
||||
shouldPlay = true,
|
||||
shouldPause = false,
|
||||
reason = "One-pod mode: pod(s) inserted (net change: +$netChange)"
|
||||
)
|
||||
|
||||
// No net change, or action not appropriate for current playing state
|
||||
else -> PlayPauseDecision(
|
||||
shouldPlay = false,
|
||||
shouldPause = false,
|
||||
reason = "One-pod mode: no action (net change: $netChange, playing: $isCurrentlyPlaying)"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun evaluateNormalMode(
|
||||
previous: EarDetectionState,
|
||||
current: EarDetectionState,
|
||||
isCurrentlyPlaying: Boolean
|
||||
): PlayPauseDecision {
|
||||
val wasWorn = previous.bothInEar
|
||||
val isWorn = current.bothInEar
|
||||
|
||||
return when {
|
||||
// Transition: not worn → worn, and not playing → play
|
||||
!wasWorn && isWorn && !isCurrentlyPlaying -> PlayPauseDecision(
|
||||
shouldPlay = true,
|
||||
shouldPause = false,
|
||||
reason = "Normal mode: both pods in ear"
|
||||
)
|
||||
|
||||
// Transition: worn → not worn, and playing → pause
|
||||
wasWorn && !isWorn && isCurrentlyPlaying -> PlayPauseDecision(
|
||||
shouldPlay = false,
|
||||
shouldPause = true,
|
||||
reason = "Normal mode: not both pods in ear"
|
||||
)
|
||||
|
||||
// No action needed
|
||||
else -> PlayPauseDecision(
|
||||
shouldPlay = false,
|
||||
shouldPause = false,
|
||||
reason = "Normal mode: no action (wasWorn: $wasWorn, isWorn: $isWorn, playing: $isCurrentlyPlaying)"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class EarDetectionState(
|
||||
val leftInEar: Boolean?, // null for single pod devices
|
||||
val rightInEar: Boolean?, // null for single pod devices
|
||||
val isWorn: Boolean // Single-pod: device worn state. Dual-pod: equivalent to bothInEar (left && right).
|
||||
) {
|
||||
val isDualPod: Boolean get() = leftInEar != null && rightInEar != null
|
||||
val isSinglePod: Boolean get() = !isDualPod
|
||||
val eitherInEar: Boolean get() = if (isDualPod) leftInEar!! || rightInEar!! else isWorn
|
||||
val bothInEar: Boolean get() = if (isDualPod) leftInEar!! && rightInEar!! else isWorn
|
||||
val podCount: Int
|
||||
get() = if (isDualPod) {
|
||||
(if (leftInEar!!) 1 else 0) + (if (rightInEar!!) 1 else 0)
|
||||
} else {
|
||||
if (isWorn) 1 else 0
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun fromDualPod(device: HasEarDetectionDual) = fromDualPod(
|
||||
left = device.isLeftPodInEar,
|
||||
right = device.isRightPodInEar,
|
||||
)
|
||||
|
||||
fun fromDualPod(left: Boolean, right: Boolean) = EarDetectionState(
|
||||
leftInEar = left,
|
||||
rightInEar = right,
|
||||
isWorn = left && right
|
||||
)
|
||||
|
||||
fun fromSinglePod(device: HasEarDetection) = fromSinglePod(worn = device.isBeingWorn)
|
||||
|
||||
fun fromSinglePod(worn: Boolean) = EarDetectionState(
|
||||
leftInEar = null,
|
||||
rightInEar = null,
|
||||
isWorn = worn
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class PlayPauseDecision(
|
||||
val shouldPlay: Boolean,
|
||||
val shouldPause: Boolean,
|
||||
val reason: String
|
||||
)
|
||||
|
||||
companion object {
|
||||
private val TAG = logTag("Reaction", "PlayPause")
|
||||
}
|
||||
|
||||
@@ -5,11 +5,10 @@ import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.view.ContextThemeWrapper
|
||||
import androidx.core.view.isInvisible
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import eu.darken.capod.R
|
||||
import eu.darken.capod.common.debug.DebugSettings
|
||||
import eu.darken.capod.databinding.PopupNotificationDualPodsBinding
|
||||
import eu.darken.capod.databinding.PopupNotificationSinglePodsBinding
|
||||
import eu.darken.capod.pods.core.DualPodDevice
|
||||
@@ -24,7 +23,6 @@ import javax.inject.Inject
|
||||
|
||||
class PopUpPodViewFactory @Inject constructor(
|
||||
@ApplicationContext private val appContext: Context,
|
||||
private val debugSettings: DebugSettings,
|
||||
) {
|
||||
|
||||
private val context = ContextThemeWrapper(appContext, R.style.AppTheme)
|
||||
@@ -39,14 +37,15 @@ class PopUpPodViewFactory @Inject constructor(
|
||||
|
||||
private fun createDualPods(parent: ViewGroup, device: DualPodDevice): View =
|
||||
PopupNotificationDualPodsBinding.inflate(layoutInflater, parent, false).apply {
|
||||
podIcon.setImageResource(device.iconRes)
|
||||
podLabel.text = device.getLabel(context)
|
||||
signal.text = device.getSignalQuality(context)
|
||||
signal.isInvisible = debugSettings.isDebugModeEnabled.value
|
||||
signalIcon.isGone = signal.text.isNullOrBlank()
|
||||
signal.isGone = signal.text.isNullOrBlank()
|
||||
|
||||
// Left
|
||||
val leftPercent = device.batteryLeftPodPercent
|
||||
podLeftIcon.setImageResource(device.leftPodIcon)
|
||||
podLeftIcon.imageTintList = null
|
||||
podLeftBatteryIcon.setImageResource(getBatteryDrawable(leftPercent))
|
||||
podLeftBatteryLabel.text = formatBatteryPercent(context, leftPercent)
|
||||
|
||||
@@ -55,6 +54,7 @@ class PopUpPodViewFactory @Inject constructor(
|
||||
(device as? HasCase)?.let { case ->
|
||||
val casePercent = case.batteryCasePercent
|
||||
podCaseIcon.setImageResource(case.caseIcon)
|
||||
podCaseIcon.imageTintList = null
|
||||
podCaseBatteryIcon.setImageResource(getBatteryDrawable(casePercent))
|
||||
podCaseBatteryLabel.text = formatBatteryPercent(context, casePercent)
|
||||
}
|
||||
@@ -62,6 +62,7 @@ class PopUpPodViewFactory @Inject constructor(
|
||||
// Right
|
||||
val rightPercent = device.batteryRightPodPercent
|
||||
podRightIcon.setImageResource(device.rightPodIcon)
|
||||
podRightIcon.imageTintList = null
|
||||
podRightBatteryIcon.setImageResource(getBatteryDrawable(rightPercent))
|
||||
podRightBatteryLabel.text = formatBatteryPercent(context, rightPercent)
|
||||
}.root
|
||||
@@ -69,9 +70,11 @@ class PopUpPodViewFactory @Inject constructor(
|
||||
private fun createSinglePod(parent: ViewGroup, device: SinglePodDevice): View =
|
||||
PopupNotificationSinglePodsBinding.inflate(layoutInflater, parent, false).apply {
|
||||
headphonesIcon.setImageResource(device.iconRes)
|
||||
headphonesIcon.imageTintList = null
|
||||
headphonesLabel.text = device.getLabel(context)
|
||||
signal.text = device.getSignalQuality(context)
|
||||
signal.isInvisible = debugSettings.isDebugModeEnabled.value
|
||||
signalIcon.isGone = signal.text.isNullOrBlank()
|
||||
signal.isGone = signal.text.isNullOrBlank()
|
||||
|
||||
val headsetPercent = device.batteryHeadsetPercent
|
||||
headphonesBatteryIcon.setImageResource(getBatteryDrawable(headsetPercent))
|
||||
|
||||
@@ -32,13 +32,15 @@ class PopUpWindow @Inject constructor(
|
||||
private val windowManager = context.getSystemService(WINDOW_SERVICE) as WindowManager
|
||||
private val layoutParams = WindowManager.LayoutParams(
|
||||
WindowManager.LayoutParams.WRAP_CONTENT,
|
||||
WindowManager.LayoutParams.WRAP_CONTENT, // Display it on top of other application windows
|
||||
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, // Don't let it grab the input focus
|
||||
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, // Make the underlying application window visible
|
||||
|
||||
WindowManager.LayoutParams.WRAP_CONTENT,
|
||||
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
|
||||
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
|
||||
PixelFormat.TRANSLUCENT
|
||||
).apply {
|
||||
gravity = Gravity.BOTTOM
|
||||
gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL
|
||||
val dm = appContext.resources.displayMetrics
|
||||
val margin = (24 * dm.density).toInt()
|
||||
width = minOf(dm.widthPixels - margin * 2, (400 * dm.density).toInt())
|
||||
}
|
||||
private val popUpView: View = layoutInflater.inflate(R.layout.popup_window_container_layout, null).apply {
|
||||
findViewById<View>(R.id.close_action).setOnClickListener { close() }
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.4 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.9 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.9 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.9 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 4.8 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.0 KiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user