Compare commits

...
8 Commits
Author SHA1 Message Date
d4rken-org-releaser[bot] 8a57257f03 Release: 5.2.6-rc0 2026-09-10 14:31:40 +00:00
darken fc4d2674e9 General: Update app translations from Crowdin 2026-09-10 16:13:30 +02:00
darken 1a176e3df8 chore: Remove retired Claude Cafe plugin settings 2026-09-08 18:27:44 +02:00
Matthias Urhahn 1fff40b9d4 Merge pull request #707 from d4rken-org/chore/clanker-cafe-plugins
General: Move project assistant plugins to Clanker Cafe
2026-09-07 13:12:59 +02:00
darken 9a9d4452d1 chore: Migrate Claude plugins to Clanker Cafe
Enable the six migrated plugins at project scope and retain explicit disabled entries for the old marketplace. The local DebugBadger pilot has been promoted to a project installation.

Validation: a fresh Claude Code session loaded all six new plugin identities, their commands and agents, the migrated hooks and all four MCP services. Read-only schema discovery passed without device actions or service writes.
2026-09-07 13:06:08 +02:00
darken 43740d8b2e chore(build): Enable R8 obfuscation for the Google Play flavor
Google Play scores the obfuscation share of uploaded bundles and flags
listings below its threshold ("App optimisation is below our threshold",
fix by Feb 2027). Minify and shrink were already on; the shared
-dontobfuscate was the only thing keeping the score at 0%.

-dontobfuscate moves to a FOSS-only rule file; the Play rule file keeps
SourceFile/LineNumberTable for retracing and pins names only where they
reach users or logs: AapSetting/AapCommand subclasses (session logs),
ViewModel1 subclasses (log tag), Throwables (error dialog label). All
reflective targets (BuildConfig, ArtMirror, NeverCall, InvokeStub,
AncModeActionCallback) already carry @Keep or explicit keeps and are
identity-mapped in the gplayRelease mapping. The release workflow
archives the mapping next to the Play upload.
2026-09-06 10:41:37 +02:00
darken 83849efe80 refactor(logging): Replace receiver-derived log tags with explicit tags
The Any.log overload built its tag from this::class.java.name, which turns
into CAP:a once the class is obfuscated. Every call site now passes a
logTag(...) constant and the overload is gone so it cannot be reintroduced.
2026-09-06 10:41:37 +02:00
darken ed66731207 chore(fastlane): Roll out beta track releases to 100%
Both supply lanes staged uploads at 10% of the beta track, which left the
remaining 90% waiting on a manual Play Console rollout step for every
release. Beta-track users have already opted in, so staging within that
audience adds a manual gate without adding safety.

Promotion from the beta track to production stays manual.
2026-09-03 18:20:01 +02:00
34 changed files with 190 additions and 139 deletions
+6 -6
View File
@@ -7,12 +7,12 @@
"deny": []
},
"enabledPlugins": {
"android-translation@claude-code-cafe": true,
"debugbadger@claude-code-cafe": true,
"jvm-tools@claude-code-cafe": true,
"frontend-design@claude-plugins-official": true,
"support@claude-code-cafe": true,
"google-play@claude-code-cafe": true,
"devtools@claude-code-cafe": true
"android-translation@clanker-cafe": true,
"debugbadger@clanker-cafe": true,
"jvm-tools@clanker-cafe": true,
"support@clanker-cafe": true,
"google-play@clanker-cafe": true,
"devtools@clanker-cafe": true
}
}
+3 -3
View File
@@ -72,13 +72,13 @@ bats tools/release/bump.bats
| Tag suffix | FOSS APK | GitHub release | Fastlane lane | Play track | Rollout |
|---|---|---|---|---|---|
| `-beta*` | `assembleFossBeta` | pre-release | `beta` | `beta` | 10% |
| `-rc*` | `assembleFossRelease` | full release | `production` | **`beta`** | 10% |
| `-beta*` | `assembleFossBeta` | pre-release | `beta` | `beta` | 100% |
| `-rc*` | `assembleFossRelease` | full release | `production` | **`beta`** | 100% |
`release-tag.yml` accepts only `v<M.m.p>-rcN` or `v<M.m.p>-betaN` — any other suffix fails
`validate-tag` before a build starts. There is no third channel.
`lane :production` in `Fastfile` uploads to Play's **beta** track at 10% — manually promoted to production via Play Console.
`lane :production` in `Fastfile` uploads to Play's **beta** track at 100% — manually promoted to production via Play Console.
## Rollback
+12 -1
View File
@@ -193,4 +193,15 @@ jobs:
env:
STORE_PASSWORD: ${{ secrets.STORE_PASSWORD }}
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
# The Play flavor is obfuscated; the bundle embeds this mapping, but keep it reachable
# for retracing user-submitted logs without going through Play Console.
- name: Archive R8 mapping
if: always() && !(github.event_name == 'workflow_dispatch' && inputs.dry_run)
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a #v7.0.1
with:
name: r8-mapping-${{ github.ref_name }}
path: app/build/outputs/mapping/gplay*/mapping.txt
if-no-files-found: warn
overwrite: true
+1 -1
View File
@@ -1 +1 @@
5.2.5-rc0 50205000
5.2.6-rc0 50206000
+2
View File
@@ -55,10 +55,12 @@ android {
includeInApk = false
includeInBundle = false
}
proguardFiles("proguard-rules-foss.pro")
}
create("gplay") {
dimension = "version"
signingConfig = signingConfigs["releaseGplay"]
proguardFiles("proguard-rules-gplay.pro")
}
}
+3
View File
@@ -0,0 +1,3 @@
# The FOSS build is open source and ships on GitHub/F-Droid; readable stack traces are worth more
# than smaller identifiers there. Only the Google Play flavor obfuscates (proguard-rules-gplay.pro).
-dontobfuscate
+21
View File
@@ -0,0 +1,21 @@
# Google Play scores the obfuscation share of every uploaded bundle and restricts listings that
# stay below its threshold, so the Play flavor obfuscates. The FOSS flavor does not
# (proguard-rules-foss.pro).
# Keep stack traces retraceable with the mapping file that the bundle embeds.
-keepattributes SourceFile,LineNumberTable
-renamesourcefileattribute SourceFile
# AAP session logs identify settings and commands by their simple class name
# (AapSessionEngine, AapAncController, AapOutboundController). Names only; unused classes are
# still removed.
-keepnames class eu.darken.capod.pods.core.apple.aap.protocol.AapSetting
-keepnames class * extends eu.darken.capod.pods.core.apple.aap.protocol.AapSetting
-keepnames class eu.darken.capod.pods.core.apple.aap.protocol.AapCommand
-keepnames class * extends eu.darken.capod.pods.core.apple.aap.protocol.AapCommand
# ViewModel1 derives its log tag from the subclass name (VM:OverviewViewModel).
-keepnames class * extends eu.darken.capod.common.uix.ViewModel1
# Error dialogs and log summaries show exceptions by class name (LocalizedError, asLogSummary).
-keepnames class * extends java.lang.Throwable
-1
View File
@@ -1,5 +1,4 @@
-keep class eu.darken.capod.BuildConfig { *; }
-dontobfuscate
# work-runtime 2.7.1 (pulled by Glance) uses Class.newInstance() reflection throughout.
# R8 full mode strips no-arg constructors not reachable by static analysis.
@@ -10,6 +10,7 @@ import eu.darken.capod.common.debug.logging.Logging.Priority.ERROR
import eu.darken.capod.common.debug.logging.log
import eu.darken.capod.common.error.HasLocalizedError
import eu.darken.capod.common.error.LocalizedError
import eu.darken.capod.common.debug.logging.logTag
class GplayServiceUnavailableException(cause: Throwable) :
BillingException("Google Play services are unavailable.", cause), HasLocalizedError {
@@ -48,10 +49,11 @@ class GplayServiceUnavailableException(cause: Throwable) :
)
private fun onLaunchFailed(e: Exception) {
log(ERROR) { "Can't launch settings intent for Google Play: $e" }
log(TAG, ERROR) { "Can't launch settings intent for Google Play: $e" }
}
companion object {
private const val GPLAY_PKG = "com.android.vending"
private val TAG = logTag("Upgrade", "Gplay", "ServiceUnavailable")
}
}
+10 -13
View File
@@ -6,31 +6,30 @@
<string name="upgrades_gplay_already_owned_label">هل اشتريت فعلًا؟</string>
<string name="upgrades_gplay_already_owned_description">أفاد جوجل بلاي بأنّك تمتلك هذه الترقية فعلًا، ولكنْ تعذّرت استعادتها. تأكّد استخدام حساب جوجل الذي اشتريت به الترقية. قد تستغرق مزامنة سوق بلاي بعض الوقت — حاول إعادة تشغيل الجهاز، أو مسح ذاكرة التخزين المؤقّت لجوجل بلاي، أو الانتظار ببساطة.</string>
<string name="upgrade_badge_label">النسخة الاحترافية</string>
<!-- %1$s is the full localized brand ("CAPod Pro", composed from the app name + postfix) — translate the sentence around it, keep the placeholder. -->
<string name="upgrade_screen_title_template">احصل على %1$s</string>
<string name="upgrade_preamble">تمّ تطوير كابود بواسطة شخصٍ واحد. تتيح الترقية مزايا إضافية وتساعد في استمرار عمل التطبيق.</string>
<string name="upgrade_screen_subscription_trial_action">بدء المدّة التجريبية المجّانية</string>
<string name="upgrade_screen_subscription_action">الاشتراك سنويًّا</string>
<string name="upgrade_screen_iap_action">الاشتراء لمرّةٍ واحدة</string>
<string name="upgrade_screen_restore_purchase_action">استعادة المشتريات</string>
<string name="upgrade_preamble">تمّ تطوير كابود بواسطة شخصٍ واحد. تتيح الترقية مزايا إضافيةٍ وتساعد في استمرار عمل التطبيق.</string>
<string name="upgrade_screen_subscription_trial_action">ابدأ المدّة التجريبية المجّانية</string>
<string name="upgrade_screen_subscription_action">اشترك سنويًّا</string>
<string name="upgrade_screen_iap_action">اشترِ لمرّةٍ واحدة</string>
<string name="upgrade_screen_restore_purchase_action">استعِد المشتريات</string>
<string name="upgrade_screen_restore_sync_patience_hint">حاول مرّةً أخرى بعد بضع دقائق إذا لم يظهر طلبك.</string>
<string name="upgrade_screen_restore_multiaccount_hint">تأكّد تسجيل دخولك باستخدام حساب جوجل نفسه المُستخدَم في عملية الاشتراء.</string>
<string name="upgrade_screen_restore_banner_title">هل اشتريت النسخة الاحترافية فعلًا؟</string>
<string name="upgrade_screen_restore_banner_body">يبدو أنّك رقّيت إلى النسخة الاحترافية على هذا الجهاز سابقًا. استعِدْ عملية الاشتراء لفتحه مرّةً أخرى.</string>
<string name="upgrade_screen_restore_banner_body">يبدو أنّك رقّيت إلى النسخة الاحترافية على هذا الجهاز سابقًا. استعِد عملية الاشتراء لفتحه مرّةً أخرى.</string>
<string name="upgrade_screen_restore_success_message">تمّت استعادة عملية الاشتراء.</string>
<string name="upgrade_screen_restore_status_title">هل تبدو الحالة خاطئة؟</string>
<string name="upgrade_screen_restore_status_body">إذا كنت تمتلك ترقيةً غير معروضةٍ هنا، فاستعِدْ عملية الاشتراء الخاصّة بك.</string>
<string name="upgrade_screen_restore_status_body">إذا كنت تمتلك ترقيةً غير معروضةٍ هنا، فاستعِد عملية الاشتراء الخاصّة بك.</string>
<string name="upgrade_screen_owned_hero_title">لديك النسخة الاحترافية من كابود!</string>
<string name="upgrade_screen_owned_hero_sub_body">تمّ فتحها باشتراكك. شكرًا لدعمك لكابود!</string>
<string name="upgrade_screen_owned_hero_iap_body">تمّ فتحها باختيارك خيار الاشتراء لمرّةٍ واحدة. شكرًا لدعمك لكابود!</string>
<string name="upgrade_screen_owned_sub_renewing_body">اشتراكك فعّالٌ ومُعَدٌّ للتجديد. تتمّ إدارة الفواتير بواسطة جوجل بلاي.</string>
<string name="upgrade_screen_owned_sub_not_renewing_body">اشتراكك فعّالٌ، لكنّه غير مُعَدٍّ للتجديد. ستظلّ النسخة الاحترافية متاحةً حتّى نهاية المدّة الحالية.</string>
<string name="upgrade_screen_owned_sub_not_renewing_body">اشتراكك فعّالٌ، لكنّه غير مُعَدٍّ للتجديد. ستظلّ النسخة الاحترافية مُتاحةً حتّى نهاية المدّة الحالية.</string>
<string name="upgrade_screen_owned_iap_title">اشتراء لمرّةٍ واحدة</string>
<string name="upgrade_screen_owned_iap_body">ملكك للأبد. لا تجديدات، ولا تاريخ انتهاء.</string>
<string name="upgrade_screen_owned_both_renewing_warning">أنت تملك عملية الاشتراء لمرّةٍ واحدة، لكنّ اشتراكك لا يزال مُعَدًّا للتجديد التلقائي. ألغِه من جوجل بلاي لتجنّب الدفع مرّتين.</string>
<string name="upgrade_screen_manage_subscription_action">إدارة الاشتراك</string>
<string name="upgrade_screen_iap_offer_title">التبديل إلى الاشتراء لمرّةٍ واحدة</string>
<string name="upgrade_screen_owned_iap_locked_note">يصبح متاحًا بمجرد إلغاء ضبط اشتراكك على التجديد. ألغه في Google Play للتبديل.</string>
<string name="upgrade_screen_owned_iap_locked_note">يصبح هذا الخيار مُتاحًا بمجرّد إيقاف التجديد التلقائي لاشتراكك. ألغِ الاشتراك عبر جوجل بلاي لإجراء التبديل.</string>
<string name="upgrade_screen_owned_iap_purchase_note">هذا شراءٌ منفصل — لا يؤدّي إلى إلغاء اشتراكك أو استرداد قيمته. استخدم حساب جوجل نفسه.</string>
<string name="upgrade_screen_sub_still_renewing_title">الاشتراك لا يزال فعّالًا</string>
<string name="upgrade_screen_sub_still_renewing_message">لا يزال اشتراكك مُعَدًّا للتجديد التلقائي. ألغِه من جوجل بلاي أوّلًا، ثمّ بدّل إلى خيار الاشتراء لمرّةٍ واحدة. هل ألغيت للتوّ؟ انتظر قليلًا ثمّ حاول مرّةً أخرى من جوجل بلاي.</string>
@@ -41,7 +40,6 @@
<string name="upgrade_screen_grace_title">يجري تأكيد عملية الاشتراء</string>
<string name="upgrade_screen_grace_body_short">لم يؤكّد جوجل بلاي عملية الاشتراء بعد. لا تزال النسخة الاحترافية فعّالةً، فلا داعي لاتّخاذ أيّ إجراء.</string>
<string name="upgrade_screen_grace_body">لم يؤكّد جوجل بلاي عملية الاشتراء منذ مدّة. لا تزال النسخة الاحترافية فعّالةً. راجع اتّصالك بالإنترنت وتسجيل دخولك باستخدام حساب جوجل المُستخدَم في عملية الاشتراء، ثمّ حاول استعادة حسابك.</string>
<!-- Offercard layout -->
<string name="upgrade_screen_offers_title">خيارات الترقية</string>
<string name="upgrade_screen_offers_or">أو</string>
<string name="upgrade_screen_offers_body">نفس المزايا، ولكنّ نماذج تسعيرٍ مختلفة.</string>
@@ -58,7 +56,6 @@
<string name="upgrade_screen_contact_support_action">تواصَل مع الدعم</string>
<string name="settings_upgrade_status_description">حالة الترقية والاشتراء الخاصّة بك.</string>
<string name="upgrades_gplay_unavailable_error_description">لا يمكن لكابود الاتّصال بجوجل بلاي. هل جوجل بلاي مُثبَّتٌ ومُحدَّث؟ هل حساب جوجل الخاصّ بك مُسجَّل الدخول؟ حاول مسح ذاكرة التخزين المؤقّت لتطبيق جوجل بلاي وإعادة تشغيل جهازك.</string>
<!-- Toast shown when the "Google Play" button of an error dialog can't open Google Play's app info, e.g. Google Play is missing or blocked on this device. "Google Play" is a brand name, keep it untranslated. -->
<string name="upgrades_gplay_not_installed_message">تعذّر فتح جوجل بلاي. قد يكون غير موجودٍ أو مُعطَّلٍ أو مُقيَّدٍ على هذا الجهاز.</string>
<string name="upgrades_gplay_internal_error_title">خطأٌ في جوجل بلاي</string>
<string name="upgrades_gplay_internal_error_description">حدث خطأٌ داخليٌّ في جوجل بلاي. يُرجى تجربة ما يلي:\n\n• أعِد تشغيل جهازك\n• امسح ذاكرة التخزين المؤقّت لجوجل بلاي\n• حاول مرّةً أخرى لاحقًا</string>
@@ -66,5 +63,5 @@
<string name="upgrades_gplay_network_error_description">تعذّر الاتصال بجوجل بلاي. يُرجى التحقّق من اتّصالك بالإنترنت والمحاولة مرّةً أخرى.</string>
<string name="upgrades_gplay_offer_unavailable_title">العرض غير متوفّر</string>
<string name="upgrades_gplay_offer_unavailable_description">لا يقدّم جوجل بلاي حاليًّا خيار الترقية هذا. يُرجى المحاولة لاحقًا أو مراجعة سوق جوجل بلاي.</string>
<string name="upgrade_screen_restore_inconclusive_message">لم تكتمل مراجعة جوجل بلاي في الوقت المُحدَّد، لذا لا تزال حالة عملية الاشتراء غير معروفة. لم يُطرأ أيّ تغيير على حسابك.</string>
<string name="upgrade_screen_restore_inconclusive_message">لم تكتمل عملية التحقّق مع جوجل بلاي في الوقت المناسب، ولذلك لا تزال حالة عملية الاشتراء غير معروفة. لم يطرأ أيّ تغييرٍ على حسابك.</string>
</resources>
+1 -1
View File
@@ -35,7 +35,7 @@
<string name="upgrade_screen_sub_still_renewing_message">Tellimus uueneb ikka veel ise. Tühista see kõigepealt Google Plays ja lülitu seejärel ühekordsele ostule. Just tühistasid? Anna Google Playle hetk aega ja proovi uuesti.</string>
<string name="upgrade_screen_purchase_check_failed_message">Ei saanud Google Playga oma oste kontrollida. Kahekordse ostu vältimiseks proovi uuesti hetke pärast.</string>
<string name="upgrade_screen_pending_card_title">Makse ootel</string>
<string name="upgrade_screen_pending_card_body">Google Play töötleb ikka sinu makset. Pro avab automaatselt, kui makse läbi läheb. Mõned makseviisid võivad aega võtta. Pole vaja midagi muud teha.</string>
<string name="upgrade_screen_pending_card_body">Google Play töötleb ikka veel sinu makset. Pro avaneb ise, kui makse õnnestub. Mõne maksemeetodi puhul võib see aega võtta. Sul pole vaja midagi muud teha.</string>
<string name="upgrade_screen_pending_dialog_message">Google Play leidis ostu ootel maksega ja töötleb seda ikka. Pro algab, kui makse õnnestub.</string>
<string name="upgrade_screen_grace_title">Ostu kinnitamine</string>
<string name="upgrade_screen_grace_body_short">Google Play pole ostu veel kinnitanud. Pro on endiselt kasutatav — midagi ette võtta pole vaja.</string>
@@ -1,10 +1,13 @@
import android.content.BroadcastReceiver
import eu.darken.capod.common.debug.logging.log
import eu.darken.capod.common.debug.logging.logTag
fun BroadcastReceiver.PendingResult.finish2(): Boolean = try {
finish()
true
} catch (e: IllegalStateException) {
log { "BroadcastReceiver.PendingResult.finish() failed: $e" }
log(TAG) { "BroadcastReceiver.PendingResult.finish() failed: $e" }
false
}
}
private val TAG = logTag("Common", "BroadcastReceiver")
@@ -9,6 +9,7 @@ import eu.darken.capod.common.debug.logging.Logging.Priority.ERROR
import eu.darken.capod.common.debug.logging.asLog
import eu.darken.capod.common.debug.logging.log
import javax.inject.Inject
import eu.darken.capod.common.debug.logging.logTag
@Reusable
class WebpageTool @Inject constructor(
@@ -25,9 +26,12 @@ class WebpageTool @Inject constructor(
context.startActivity(intent)
true
} catch (e: Exception) {
log(ERROR) { "Failed to launch: ${e.asLog()}" }
log(TAG, ERROR) { "Failed to launch: ${e.asLog()}" }
false
}
}
companion object {
private val TAG = logTag("WebpageTool")
}
}
@@ -2,6 +2,7 @@ package eu.darken.capod.common.bluetooth
import eu.darken.capod.common.debug.logging.Logging.Priority.VERBOSE
import eu.darken.capod.common.debug.logging.log
import eu.darken.capod.common.debug.logging.logTag
suspend fun Collection<BleScanResult>.onlyNewAndUnique(): List<BleScanResult> = this
.groupBy { it.address }
@@ -10,7 +11,9 @@ suspend fun Collection<BleScanResult>.onlyNewAndUnique(): List<BleScanResult> =
// For each address we only want the newest result, upstream may batch data
val newest = sameAdrDevs.maxByOrNull { it.generatedAtNanos }!!
sameAdrDevs.minus(newest).let {
if (it.isNotEmpty()) log(VERBOSE) { "Discarding stale results: ${it.logSummary()}" }
if (it.isNotEmpty()) log(TAG, VERBOSE) { "Discarding stale results: ${it.logSummary()}" }
}
newest
}
private val TAG = logTag("Bluetooth", "Scanner")
@@ -6,6 +6,7 @@ import android.bluetooth.le.ScanResult
import android.os.ParcelUuid
import eu.darken.capod.common.debug.logging.asLog
import eu.darken.capod.common.debug.logging.log
import eu.darken.capod.common.debug.logging.logTag
fun BluetoothDevice.hasFeature(uuid: ParcelUuid): Boolean {
return uuids?.contains(uuid) ?: false
@@ -21,6 +22,8 @@ fun BluetoothDevice.hasFeature(uuid: ParcelUuid): Boolean {
fun ScanFilter.matchesSafe(scanResult: ScanResult): Boolean = try {
matches(scanResult)
} catch (e: NullPointerException) {
log { "AOSP error: ${e.asLog()}" }
log(TAG) { "AOSP error: ${e.asLog()}" }
false
}
}
private val TAG = logTag("Bluetooth", "Extensions")
@@ -15,6 +15,7 @@ import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.runBlocking
import eu.darken.capod.common.debug.logging.logTag
class DataStoreValue<T>(
private val dataStore: DataStore<Preferences>,
@@ -45,10 +46,14 @@ class DataStoreValue<T>(
prefs[key as Preferences.Key<Any>] = raw
}
}
log(VERBOSE) { "DataStoreValue($keyName) updated from $old to $new" }
log(TAG, VERBOSE) { "DataStoreValue($keyName) updated from $old to $new" }
@Suppress("UNCHECKED_CAST")
return Updated(old as T, new as T)
}
companion object {
private val TAG = logTag("DataStore", "Value")
}
}
suspend fun <T> DataStoreValue<T>.value(): T = flow.first()
@@ -34,6 +34,8 @@ object Logging {
)
}
private val TAG = logTag("Logging")
private val internalLoggers = mutableListOf<Logger>()
val loggers: List<Logger>
@@ -46,11 +48,11 @@ object Logging {
fun install(logger: Logger) {
synchronized(internalLoggers) { internalLoggers.add(logger) }
log { "Was installed $logger" }
log(TAG) { "Was installed $logger" }
}
fun remove(logger: Logger) {
log { "Removing: $logger" }
log(TAG) { "Removing: $logger" }
synchronized(internalLoggers) { internalLoggers.remove(logger) }
}
@@ -77,26 +79,11 @@ object Logging {
}
fun clearAll() {
log { "Clearing all loggers" }
log(TAG) { "Clearing all loggers" }
synchronized(internalLoggers) { internalLoggers.clear() }
}
}
inline fun Any.log(
priority: Logging.Priority = Logging.Priority.DEBUG,
metaData: Map<String, Any>? = null,
message: () -> String,
) {
if (Logging.hasReceivers) {
Logging.logInternal(
tag = "CAP:${logTagViaCallSite()}",
priority = priority,
metaData = metaData,
message = message(),
)
}
}
inline fun log(
tag: String,
priority: Logging.Priority = Logging.Priority.DEBUG,
@@ -135,15 +122,3 @@ fun Throwable.asLogSummary(): String {
private fun Throwable.safeMessage(): String? = runCatching { message }.getOrNull()
@PublishedApi
internal fun Any.logTagViaCallSite(): String {
val javaClass = this::class.java
val fullClassName = javaClass.name
val outerClassName = fullClassName.substringBefore('$')
val simplerOuterClassName = outerClassName.substringAfterLast('.')
return if (simplerOuterClassName.isEmpty()) {
fullClassName
} else {
simplerOuterClassName.removeSuffix("Kt")
}
}
@@ -25,6 +25,7 @@ import androidx.lifecycle.Observer
import eu.darken.capod.common.debug.logging.Logging.Priority.WARN
import eu.darken.capod.common.debug.logging.log
import java.util.concurrent.atomic.AtomicBoolean
import eu.darken.capod.common.debug.logging.logTag
/**
* A lifecycle-aware observable that sends only new updates after subscription, used for events like
@@ -46,7 +47,7 @@ class SingleLiveEvent<T> : MutableLiveData<T>() {
@MainThread
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
if (hasActiveObservers()) {
log(WARN) { "Multiple observers registered but only one will be notified of changes." }
log(TAG, WARN) { "Multiple observers registered but only one will be notified of changes." }
}
// Observe the internal MutableLiveData
@@ -73,4 +74,8 @@ class SingleLiveEvent<T> : MutableLiveData<T>() {
fun call() {
value = null
}
companion object {
private val TAG = logTag("SingleLiveEvent")
}
}
@@ -42,7 +42,7 @@ abstract class ViewModel2(
if (this is ErrorEventSource) {
return CoroutineExceptionHandler { _, ex ->
log(WARN) { "Error during launch: ${ex.asLog()}" }
log(TAG, WARN) { "Error during launch: ${ex.asLog()}" }
errorEvents.postValue(ex)
}
}
@@ -34,7 +34,7 @@ class BluetoothEventReceiver : BroadcastReceiver() {
log(TAG, WARN) { "Event without Bluetooth device association." }
return
} else {
log { "Event related to $bluetoothDevice" }
log(TAG) { "Event related to $bluetoothDevice" }
}
val supportedFeatures = try {
ContinuityProtocol.BLE_FEATURE_UUIDS.filter { bluetoothDevice.hasFeature(it) }
@@ -47,7 +47,7 @@ class BluetoothEventReceiver : BroadcastReceiver() {
log(TAG) { "Device has no features we support." }
return
} else {
log { "Device has the following we features we support $supportedFeatures" }
log(TAG) { "Device has the following we features we support $supportedFeatures" }
}
when (intent.action) {
@@ -8,6 +8,7 @@ import eu.darken.capod.pods.core.apple.ble.BATTERY_RESOLUTION_DECILE
import eu.darken.capod.pods.core.apple.ble.BATTERY_RESOLUTION_PERCENT
import eu.darken.capod.pods.core.apple.ble.DualBlePodSnapshot
import eu.darken.capod.pods.core.apple.ble.DualBlePodSnapshot.Pod
import eu.darken.capod.common.debug.logging.logTag
interface DualApplePods : ApplePods, HasChargeDetectionDual, DualBlePodSnapshot, HasEarDetectionDual, HasCase,
HasDualMicrophone, HasAppleColor {
@@ -38,7 +39,7 @@ interface DualApplePods : ApplePods, HasChargeDetectionDual, DualBlePodSnapshot,
return when (value) {
15 -> null
else -> if (value > 10) {
log { "Left pod: Above 100% battery: $value" }
log(TAG) { "Left pod: Above 100% battery: $value" }
1.0f
} else {
(value / 10f)
@@ -59,7 +60,7 @@ interface DualApplePods : ApplePods, HasChargeDetectionDual, DualBlePodSnapshot,
return when (value) {
15 -> null
else -> if (value > 10) {
log { "Right pod: Above 100% battery: $value" }
log(TAG) { "Right pod: Above 100% battery: $value" }
1.0f
} else {
value / 10f
@@ -131,7 +132,7 @@ interface DualApplePods : ApplePods, HasChargeDetectionDual, DualBlePodSnapshot,
return when (val value = pubCaseBattery.toInt()) {
15 -> null
else -> if (value > 10) {
log { "Case: Above 100% battery: $value" }
log(TAG) { "Case: Above 100% battery: $value" }
1.0f
} else {
value / 10f
@@ -206,3 +207,5 @@ interface DualApplePods : ApplePods, HasChargeDetectionDual, DualBlePodSnapshot,
}
}
private val TAG = logTag("Pod", "Apple", "DualApplePods")
@@ -3,6 +3,7 @@ package eu.darken.capod.pods.core.apple.ble.devices
import eu.darken.capod.common.debug.logging.log
import eu.darken.capod.common.lowerNibble
import eu.darken.capod.pods.core.apple.ble.SingleBlePodSnapshot
import eu.darken.capod.common.debug.logging.logTag
/**
* Devices that only present a single charge level, e.g. most Beats devices
@@ -17,11 +18,13 @@ interface SingleApplePods : ApplePods, SingleBlePodSnapshot, HasAppleColor {
return when (val value = pubPodsBattery.lowerNibble.toInt()) {
15 -> null
else -> if (value > 10) {
log { "Headset above 100% battery: $value" }
log(TAG) { "Headset above 100% battery: $value" }
1.0f
} else {
(value / 10f)
}
}
}
}
}
private val TAG = logTag("Pod", "Apple", "SingleApplePods")
@@ -14,7 +14,7 @@ object ProximityPairing {
class Decoder @Inject constructor() {
fun decode(message: ContinuityProtocol.Message): ProximityMessage? {
if (message.type != CONTINUITY_PROTOCOL_MESSAGE_TYPE_PROXIMITY_PAIRING) {
log { "Not a proximity pairing message: $this" }
log(TAG) { "Not a proximity pairing message: $message" }
return null
}
@@ -141,7 +141,7 @@ class DeviceProfilesRepo @Inject constructor(
if (addFirst) add(0, profile) else add(profile)
}.toList()
settings.profiles.valueBlocking = DeviceProfilesContainer(updatedProfiles)
log(VERBOSE) { "Added device profile: ${profile.label}" }
log(TAG, VERBOSE) { "Added device profile: ${profile.label}" }
}
suspend fun updateProfile(profile: DeviceProfile) = mutex.withLock {
@@ -152,7 +152,7 @@ class DeviceProfilesRepo @Inject constructor(
if (it.id == profile.id) profile else it
}
settings.profiles.valueBlocking = DeviceProfilesContainer(updatedProfiles)
log(VERBOSE) { "Updated device profile: ${profile.label}" }
log(TAG, VERBOSE) { "Updated device profile: ${profile.label}" }
}
/**
@@ -176,14 +176,14 @@ class DeviceProfilesRepo @Inject constructor(
checkAddressUniqueness(updated, otherProfiles)
val updatedProfiles = currentContainer.profiles.map { if (it.id == id) updated else it }
settings.profiles.valueBlocking = DeviceProfilesContainer(updatedProfiles)
log(VERBOSE) { "Updated apple device profile: ${updated.label}" }
log(TAG, VERBOSE) { "Updated apple device profile: ${updated.label}" }
}
suspend fun removeProfile(profileId: ProfileId) = mutex.withLock {
val currentContainer = settings.profiles.valueBlocking
val updatedProfiles = currentContainer.profiles.filter { it.id != profileId }
settings.profiles.valueBlocking = DeviceProfilesContainer(updatedProfiles)
log(VERBOSE) { "Removed device profile with ID: $profileId" }
log(TAG, VERBOSE) { "Removed device profile with ID: $profileId" }
deviceStateCache.delete(profileId)
batteryDrainStore.delete(profileId)
}
@@ -196,7 +196,7 @@ class DeviceProfilesRepo @Inject constructor(
val byId = current.associateBy { it.id }
val reordered = orderedIds.map { byId.getValue(it) }
settings.profiles.valueBlocking = DeviceProfilesContainer(reordered)
log(VERBOSE) { "Reordered ${reordered.size} device profiles by ID" }
log(TAG, VERBOSE) { "Reordered ${reordered.size} device profiles by ID" }
}
suspend fun clear() = mutex.withLock {
+18 -16
View File
@@ -51,7 +51,7 @@
<string name="settings_compat_offloaded_filtering_disabled_title">تعطيل تصفية الأجهزة</string>
<string name="settings_compat_offloaded_filtering_disabled_summary">جرّب هذا الحلّ إذا لم يتمّ اكتشاف سمّاعات أيربودز الخاصّة بك مُطلَقًا. سيبحث التطبيق في جميع إشارات البلوتوث القريبة بنفسه، بدلًا من ترك هاتفك يختارها مُسبَقًا.</string>
<string name="settings_compat_offloaded_batching_disabled_title">تعطيل تجميع الأجهزة</string>
<string name="settings_compat_offloaded_batching_disabled_summary">جرّب هذا إذا كانت مستويات البطارية تتحدّث فقط بتأخير أو دفعات. سيُرسل هاتفك كل إشارة بلوتوث فور وصولها، بدلًا من تجميعها أولًا.</string>
<string name="settings_compat_offloaded_batching_disabled_summary">جرّب هذا إذا كانت مستويات البطّارية تتحدّث بتأخيرٍ أو على دُفعات؛ حيث سيمرّر هاتفك كلّ إشارة بلوتوث فورًا بدلًا من تجميعها أوّلًا.</string>
<string name="settings_onepod_mode_label">وضع السمّاعة الواحدة</string>
<string name="settings_onepod_mode_description">تستجيب خاصّيتا التشغيل/الإيقاف التلقائي والاتّصال التلقائي لسمّاعةٍ واحدةٍ بدلًا من اشتراط كلتيهما.</string>
<string name="settings_popup_caseopen_label">أرسل إشعارًا منبثقًا عند فتح الحافظة</string>
@@ -177,8 +177,8 @@
<string name="overview_monitoring_active_label">تجري مراقبة الأجهزة</string>
<string name="overview_monitoring_active_description">تأكّد أنّ جهازك قريبٌ ونشيط.</string>
<string name="overview_monitoring_off_label">المراقبة الخلفية موقَفة</string>
<string name="overview_monitoring_off_description">لا يوجد جهاز بلوتوث مقرونٌ بأيّ ملفّ تعريفٍ، لذا يتمّ تحديث كابود فقط في أثناء فتح التطبيق. حدّد جهازًا واحدًا لتمكين المراقبة الخلفية والإشعارات والتفاعلات المستمرّة.</string>
<string name="overview_monitoring_off_action">حدّد جهازًا مقرونًا</string>
<string name="overview_monitoring_off_description">لا يوجد جهاز بلوتوث مُقرَنٌ بأيّ ملفّ تعريفٍ، لذا يتمّ تحديث كابود فقط في أثناء فتح التطبيق. حدّد جهازًا واحدًا لتمكين المراقبة الخلفية والإشعارات والتفاعلات المستمرّة.</string>
<string name="overview_monitoring_off_action">حدّد جهازًا مُقرَنًا</string>
<string name="overview_troubleshoot_suggestion_label">متّصلٌ، ولكنْ لا توجد بيانات</string>
<string name="overview_troubleshoot_suggestion_description">جهازك متّصلٌ، لكنّ كابود لا يستقبل أيّ بياناتٍ مباشِرةٍ منه. قد يحتاج هاتفك إلى خيار توافقٍ — يمكن لمستكشف الأخطاء ومصلحها محاولة عثور واحدٍ تلقائيًّا.</string>
<string name="overview_troubleshoot_suggestion_action">شغّل مستكشف الأخطاء ومصلحها</string>
@@ -186,7 +186,7 @@
<plurals name="overview_unmatched_devices_description">
<item quantity="zero">لم يُعثَر على أيّ أجهزةٍ قريبةٍ دون ملفّات تعريفٍ مطابقة.</item>
<item quantity="one">تمّ اكتشاف جهازٍ قريبٍ دون ملفّ تعريفٍ مطابق. اعرضه للاطّلاع على التفاصيل المُتاحة. إذا كان جهازك، فأضِف ملفّ تعريفٍ من قسم الأجهزة لكي يتمّ معرفته.</item>
<item quantity="two">تمّ اكتشاف جهازَين قريبَين دون ملفّات تعريفٍ مطابقة. اعرضهما للاطّلاع على التفاصيل المُتاحة. إذا كان أحدهما جهازك، فأضِف ملفّ تعريفٍ من قسم الأجهزة لكي يتمّ معرفته.</item>
<item quantity="two">تمّ اكتشاف جهازَيْن قريبَيْن دون ملفّات تعريفٍ مطابقة. اعرضهما للاطّلاع على التفاصيل المُتاحة. إذا كان أحدهما جهازك، فأضِف ملفّ تعريفٍ من قسم الأجهزة لكي يتمّ معرفته.</item>
<item quantity="few">تمّ اكتشاف %d أجهزةٍ قريبةٍ دون ملفّات تعريفٍ مطابقة. اعرضها للاطّلاع على التفاصيل المُتاحة. إذا كان أحدها جهازك، فأضِف ملفّ تعريفٍ من قسم الأجهزة لكي يتمّ معرفته.</item>
<item quantity="many">تمّ اكتشاف %d جهازًا قريبًا دون ملفّات تعريفٍ مطابقة. اعرضها للاطّلاع على التفاصيل المُتاحة. إذا كان أحدها جهازك، فأضِف ملفّ تعريفٍ من قسم الأجهزة لكي يتمّ معرفته.</item>
<item quantity="other">تمّ اكتشاف %d جهازٍ قريبٍ دون ملفّات تعريفٍ مطابقة. اعرضها للاطّلاع على التفاصيل المُتاحة. إذا كان أحدها جهازك، فأضِف ملفّ تعريفٍ من قسم الأجهزة لكي يتمّ معرفته.</item>
@@ -200,11 +200,11 @@
<item quantity="other">%d جهازٍ إضافي</item>
</plurals>
<string name="permission_bluetooth_connect_label">الاتّصال عبر البلوتوث</string>
<string name="permission_bluetooth_connect_description">يتطلّب هذا التطبيق إذن ”الاتّصال عبر البلوتوث“ للتفاعل مع الأجهزة المقرونة وبدء الاتّصالات.</string>
<string name="permission_bluetooth_connect_description">يتطلّب هذا التطبيق إذن ”الاتّصال عبر البلوتوث“ للتفاعل مع الأجهزة المُقرَنة وبدء الاتّصالات.</string>
<string name="permission_bluetooth_scan_label">البحث عن الأجهزة المستخدمة لتقنية البلوتوث</string>
<string name="permission_bluetooth_scan_description">يسمح إذن ”البحث عن الأجهزة المستخدمة لتقنية البلوتوث“ لهذا التطبيق باكتشاف واستقبال بيانات البلوتوث من الأجهزة القريبة مثل سمّاعات أيربودز الخاصّة بك.</string>
<string name="permission_bluetooth_label">البلوتوث</string>
<string name="permission_bluetooth_description">يتطلّب هذا التطبيق إذن ”البلوتوث“ للاتّصال بأجهزة البلوتوث المقرونة.</string>
<string name="permission_bluetooth_description">يتطلّب هذا التطبيق إذن ”البلوتوث“ للاتّصال بأجهزة البلوتوث المُقرَنة.</string>
<string name="permission_access_fine_location_label">الوصول إلى الموقع الدقيق</string>
<string name="permission_access_fine_location_description">يستخدم كابود إذن ”الموقع الدقيق“ لتلقّي بيانات تقنية بلوتوث منخفضة الطاقة (BLE). تستخدم سمّاعاتك تقنية بلوتوث منخفضة الطاقة لبثّ حالتها. لن يستخدم هذا التطبيق بيانات البلوتوث لتحديد موقعك.</string>
<string name="permission_background_location_label">الوصول إلى الموقع في الخلفية</string>
@@ -230,9 +230,9 @@
<item quantity="other">%d شَحنةٌ كاملةٌ إضافيةٌ تقريبًا لكلتا السمّاعتين</item>
</plurals>
<plurals name="battery_case_charges_at_least">
<item quantity="zero">%d شحنة كاملة إضافية على الأقل لكلتا السماعتين</item>
<item quantity="one">%d شحنة كاملة إضافية على الأقل لكلتا السماعتين</item>
<item quantity="two">%d شحنة كاملة إضافية على الأقل لكلتا السماعتين</item>
<item quantity="zero">في الأقلّ، لن تكون هناك حاجةٌ لشحنٍ كاملٍ لكلا السمّاعتين بعد الآن</item>
<item quantity="one">شَحنةٌ كاملةٌ إضافيةٌ واحدةٌ في الأقلّ لكلتا السمّاعتين</item>
<item quantity="two">شَحنتان كاملتان إضافيتان في الأقلّ لكلتا السمّاعتين</item>
<item quantity="few">%d شحنات كاملة إضافية على الأقل لكلتا السماعتين</item>
<item quantity="many">%d شحنة كاملة إضافية على الأقل لكلتا السماعتين</item>
<item quantity="other">%d شحنة كاملة إضافية على الأقل لكلتا السماعتين</item>
@@ -243,6 +243,8 @@
<string name="battery_case_charges_state_enough_cd">يكفي لشحنة كاملة</string>
<string name="battery_case_charges_state_not_enough_cd">لا يكفي لشحنة كاملة</string>
<string name="battery_case_charges_state_uncertain_cd">قد لا يكفي لشحنة كاملة</string>
<string name="battery_case_charges_short">%1$s من الشَّحنات</string>
<string name="battery_case_charges_short_at_least">%1$s من الشَّحنات أو أكثر</string>
<string name="pods_case_status_open_label">مفتوحة</string>
<string name="pods_case_status_closed_label">مُغلَقة</string>
<string name="pods_connection_state_disconnected_label">غير متصل بجهاز</string>
@@ -293,7 +295,7 @@
<string name="profiles_name_empty_error">لا يمكن أن يكون الاسم فارغاً</string>
<string name="profiles_name_default">سماعاتي</string>
<string name="profiles_model_label">طراز الجهاز</string>
<string name="profiles_paired_device_label">جهازٌ مقرون</string>
<string name="profiles_paired_device_label">الجهاز المُقرَن</string>
<string name="profiles_paired_device_none">لا شيء</string>
<string name="profiles_paired_device_none_description">لم يتم تحديد جهاز</string>
<string name="profiles_paired_device_used_by">مستخدم بواسطة %s</string>
@@ -442,9 +444,9 @@
<string name="device_settings_not_connected_connect_action">اتصال</string>
<string name="device_settings_not_connected_open_settings_action">فتح إعدادات البلوتوث</string>
<string name="device_settings_not_nearby_label">الجهاز غير قريب</string>
<string name="device_settings_not_nearby_description">الإعدادات متاحة فقط عندما يكون هذا الجهاز قريبًا ومتصلًا بهاتفك.</string>
<string name="device_settings_aap_unavailable_label">الإعدادات المتقدمة غير متاحة</string>
<string name="device_settings_aap_unavailable_description">تتطلب إعدادات AirPods المتقدمة ميزة بلوتوث غير متاحة على هذا الهاتف. قد يتم حل هذا بتحديث Android مستقبلي من الشركة المصنّعة لجهازك.</string>
<string name="device_settings_not_nearby_description">تتوفّر الإعدادات فقط عندما يكون هذا الجهاز قريبًا ومتّصلًا بهاتفك.</string>
<string name="device_settings_aap_unavailable_label">الإعدادات المتقدّمة غير مُتاحة</string>
<string name="device_settings_aap_unavailable_description">تتطلّب إعدادات أيربودز المتقدّمة مزيّة بلوتوث غير متوفّرةٍ في هذا الهاتف. قد يتمّ حلّ هذه المشكلة عبر تحديث أندرويد مُستقبَليٍّ من الشركة المُصنِّعة لجهازك.</string>
<string name="device_settings_aap_unavailable_action">عرض متتبع التوافق</string>
<string name="device_settings_category_sound_label">الصوت</string>
<string name="device_settings_category_controls_label">عناصر التحكم</string>
@@ -483,8 +485,8 @@
<string name="device_settings_end_call_mute_mic_option_b_title">ضغطة واحدة لكتم صوت الميكروفون</string>
<string name="device_settings_end_call_mute_mic_option_b_subtitle">ضغطة مزدوجة لإنهاء المكالمة</string>
<string name="device_settings_open_cd">فتح إعدادات الجهاز</string>
<string name="overview_card_missing_paired_device">لا يوجد جهاز بلوتوث مقرونٌ بملفّ التعريف هذا.</string>
<string name="overview_card_missing_paired_device_consequence">دونها، لن تتوفر إعدادات الجهاز والاتصال التلقائي.</string>
<string name="overview_card_missing_paired_device">لا يوجد جهاز بلوتوث مُقرَنٌ بملفّ التعريف هذا.</string>
<string name="overview_card_missing_paired_device_consequence">دونها، لن تكون إعدادات الجهاز ومزيّة الاتّصال التلقائي مُتاحة.</string>
<string name="overview_reactions_hint_title">ردود الفعل خاصة بكل جهاز</string>
<string name="overview_reactions_hint_body">يتم الآن تهيئة التشغيل التلقائي والإيقاف التلقائي والنوافذ المنبثقة لكل جهاز على حدة. انقر على أيقونة الإعدادات على البطاقة أثناء توصيل سماعاتك.</string>
<string name="review_app_review_action">مراجعة</string>
@@ -497,7 +499,7 @@
<string name="device_settings_microphone_mode_left">يسار</string>
<string name="device_settings_noise_control_current_mode_label">الوضع الحالي</string>
<string name="device_settings_listening_mode_cycle_label">دورة الضغط المطوّل</string>
<string name="device_settings_listening_mode_cycle_description">اختر الأوضاع المتاحة عند الضغط المطوّل على الذراع وفي المحوّل السريع.</string>
<string name="device_settings_listening_mode_cycle_description">اختَر الأوضاع المُتاحة عند الضغط مُطوَّلًا على الساق وفي المبدّل السريع.</string>
<string name="device_settings_listening_mode_cycle_dialog_title">تخصيص دورة الضغط المطوّل</string>
<string name="device_settings_listening_mode_cycle_summary_helper">يُستخدم عند الضغط المطوّل على الذراع وفي المحوّل السريع</string>
<string name="device_settings_listening_mode_cycle_summary_helper_override">يُستخدم في المحوّل السريع. يُلغي إجراء الضغط المطوّل المخصص للذراع تدوير AirPods.</string>
+13 -13
View File
@@ -101,11 +101,11 @@
<string name="settings_overview_hide_unmatched_label">Amaga els dispositius no coincidents</string>
<string name="settings_overview_hide_unmatched_description">No mostris els dispositius propers que no coincideixen amb cap dels perfils a la vista general. P. ex. els AirPods daltres persones.</string>
<string name="device_battery_estimate_toggle_label">Estimació de la bateria</string>
<string name="device_battery_estimate_card_desc">Aprèn com es carrega i es descarrega aquest dispositiu per estimar el temps restant, el temps fins a la càrrega i quant del temps d\'escolta nominal li queda.</string>
<string name="device_battery_estimate_card_desc">Apreneu com es carrega i es descarrega aquest dispositiu per estimar el temps restant, el temps fins a la càrrega completa i quant del temps d\'escolta nominal li queda.</string>
<string name="device_battery_estimate_reset_action">Restableix les dades apreses</string>
<string name="device_battery_estimate_reset_desc">Comença de nou des de la durada nominal de la bateria.</string>
<string name="device_battery_estimate_reset_confirm_title">Voleu restablir les dades apreses?</string>
<string name="device_battery_estimate_reset_confirm_message">CAPod oblidarà el consum mesurat d\'aquest dispositiu, la velocitat de càrrega i el temps d\'escolta, i tornarà a començar des de la durada nominal de la bateria.</string>
<string name="device_battery_estimate_reset_confirm_message">El CAPod oblidarà el consum mesurat d\'aquest dispositiu, la velocitat de càrrega i el temps d\'escolta, i tornarà a començar des de la durada nominal de la bateria.</string>
<string name="settings_acknowledgements_label">Agraïments</string>
<string name="help_translate_label">Traducció</string>
<string name="help_translate_description">Ajudeu a traduir aquesta aplicació al vostre idioma preferit.</string>
@@ -215,19 +215,19 @@
<string name="battery_state_low_cd">Bateria baixa</string>
<string name="battery_state_critical_cd">Bateria críticament baixa</string>
<plurals name="battery_case_charges_approx">
<item quantity="one">~%d més càrrega completa pels auriculars</item>
<item quantity="other">~%d més càrregues completes pels auriculars</item>
<item quantity="one">~%d càrrega més completa pels auriculars</item>
<item quantity="other">~%d càrregues més completes pels auriculars</item>
</plurals>
<plurals name="battery_case_charges_at_least">
<item quantity="one">Almenys %d més càrrega completa pels auriculars</item>
<item quantity="other">Almenys %d més càrregues completes pels auriculars</item>
<item quantity="one">Almenys %d càrrega més completa pels auriculars</item>
<item quantity="other">Almenys %d càrregues més completes pels auriculars</item>
</plurals>
<string name="battery_case_charges_less_than_one">Menys d\'una càrrega completa més pels auriculars</string>
<string name="battery_case_charges_empty">La caixa està buida, sense més càrregues pels auriculars</string>
<string name="battery_case_charges_uncertain">No és segur quantes càrregues completes hi queden pels auriculars</string>
<string name="battery_case_charges_state_enough_cd">Prou per a una càrrega completa</string>
<string name="battery_case_charges_empty">La funda està buida, sense més càrregues pels auriculars</string>
<string name="battery_case_charges_uncertain">No és segur quantes càrregues completes queden pels auriculars</string>
<string name="battery_case_charges_state_enough_cd">Suficient per a una càrrega completa</string>
<string name="battery_case_charges_state_not_enough_cd">Insuficient per a una càrrega completa</string>
<string name="battery_case_charges_state_uncertain_cd">Potser no és suficient per a una càrrega completa</string>
<string name="battery_case_charges_state_uncertain_cd">Potser no n\'hi ha prou per a una càrrega completa</string>
<string name="pods_case_status_open_label">Oberta</string>
<string name="pods_case_status_closed_label">Tancada</string>
<string name="pods_connection_state_disconnected_label">No està connectat a un dispositiu</string>
@@ -398,12 +398,12 @@
<string name="device_settings_info_left_bonded_label">Esquerre vinculat</string>
<string name="device_settings_info_right_bonded_label">Dret vinculat</string>
<string name="device_settings_info_battery_health_label">Temps d\'escolta estimat</string>
<string name="device_settings_info_battery_health_left_label">Temps d\'escolta del pod esquerre</string>
<string name="device_settings_info_battery_health_right_label">Temps d\'escolta del pod dret</string>
<string name="device_settings_info_battery_health_left_label">Temps d\'escolta de l\'auricular esquerre</string>
<string name="device_settings_info_battery_health_right_label">Temps d\'escolta de l\'auricular dret</string>
<string name="device_settings_info_battery_health_value">~%1$d%% del nominal</string>
<string name="device_settings_info_battery_health_pending">Encara s\'està determinant, torneu més tard</string>
<string name="device_settings_battery_runtime_warning_title">El temps d\'escolta és molt més curt que el nominal</string>
<string name="device_settings_battery_runtime_warning_description">%1$s: aproximadament %2$d%% del temps d\'escolta nominal. Toca per a detalls.</string>
<string name="device_settings_battery_runtime_warning_description">%1$s: aproximadament %2$d%% del temps d\'escolta nominal. Toqueu per obtenir més informació.</string>
<string name="device_settings_battery_runtime_warning_headset_label">Aquests auriculars</string>
<string name="device_settings_info_details_label">Detalls del dispositiu</string>
<string name="device_settings_info_details_action">Mostra els detalls del dispositiu</string>
+2
View File
@@ -227,6 +227,8 @@
<string name="battery_case_charges_state_enough_cd">Αρκετή για μία πλήρη φόρτιση</string>
<string name="battery_case_charges_state_not_enough_cd">Δεν αρκεί για μία πλήρη φόρτιση</string>
<string name="battery_case_charges_state_uncertain_cd">Μπορεί να μην αρκεί για μία πλήρη φόρτιση</string>
<string name="battery_case_charges_short">%1$s φορτίσεις</string>
<string name="battery_case_charges_short_at_least">%1$s+ φορτίσεις</string>
<string name="pods_case_status_open_label">Ανοιχτή</string>
<string name="pods_case_status_closed_label">Κλειστή</string>
<string name="pods_connection_state_disconnected_label">Δεν είναι συνδεδεμένο σε συσκευή</string>
+10 -8
View File
@@ -49,7 +49,7 @@
<string name="settings_category_compatibility_options_title">Ühilduvuse valikud</string>
<string name="settings_category_compatibility_options_description">Las jääda, kui kõik toimib ;)</string>
<string name="settings_compat_offloaded_filtering_disabled_title">Keela riistvara filtreerimine</string>
<string name="settings_compat_offloaded_filtering_disabled_summary">Proovi seda, kui AirPods\'i kunagi ei tuvastatakse. Rakendus uurib kõik lähedal olevad Bluetoothi signaalid ise, asemel et lasta telefonil neid enne valida.</string>
<string name="settings_compat_offloaded_filtering_disabled_summary">Proovige seda, kui AirPodse ei tuvastata kunagi. Rakendus otsib kõiki läheduses olevaid Bluetooth-signaale ise, selmet lasta seda teha telefonil.</string>
<string name="settings_compat_offloaded_batching_disabled_title">Keela riistvara rühmitamine</string>
<string name="settings_compat_offloaded_batching_disabled_summary">Proovi seda, kui akutasemed värskenduvad ainult hilja või lainete kaupa. Telefon edastab iga Bluetoothi signaali rakendusele kohe, selmet neid esmalt koguda.</string>
<string name="settings_onepod_mode_label">Ühe klapi režiim</string>
@@ -222,11 +222,13 @@
<item quantity="other">Vähemalt %d täielikku laadimiskorda mõlemale kõrvaklapile</item>
</plurals>
<string name="battery_case_charges_less_than_one">Vähem kui üks täielik laadimiskord mõlemale kõrvaklapile</string>
<string name="battery_case_charges_empty">Kest on tühi, laadimiskordaid kõrvaklapidele enam ei ole</string>
<string name="battery_case_charges_uncertain">Ei ole selge, kui palju täielikke laadimiskordade on jäänud mõlemale kõrvaklapile</string>
<string name="battery_case_charges_empty">Kest on tühi, laadimiskordasid kõrvaklappidele enam pole</string>
<string name="battery_case_charges_uncertain">Pole selge, kui palju täielikke laadimiskordi on jäänud mõlemale kõrvaklapile</string>
<string name="battery_case_charges_state_enough_cd">Piisavalt täielikuks laadimiseks</string>
<string name="battery_case_charges_state_not_enough_cd">Ei ole piisavalt täielikuks laadimiseks</string>
<string name="battery_case_charges_state_uncertain_cd">Võib-olla ei ole piisavalt täielikuks laadimiseks</string>
<string name="battery_case_charges_state_not_enough_cd">Pole piisavalt täielikuks laadimiseks</string>
<string name="battery_case_charges_state_uncertain_cd">Võib-olla pole piisavalt täielikuks laadimiseks</string>
<string name="battery_case_charges_short">%1$s laadimist</string>
<string name="battery_case_charges_short_at_least">%1$s+ laadimist</string>
<string name="pods_case_status_open_label">Avatud</string>
<string name="pods_case_status_closed_label">Suletud</string>
<string name="pods_connection_state_disconnected_label">Puudub ühendus seadmega</string>
@@ -397,12 +399,12 @@
<string name="device_settings_info_left_bonded_label">Vasak seotud</string>
<string name="device_settings_info_right_bonded_label">Parem seotud</string>
<string name="device_settings_info_battery_health_label">Hinnanguline kuulamisaeg</string>
<string name="device_settings_info_battery_health_left_label">Vasaku aku tervis (hinnanguline)</string>
<string name="device_settings_info_battery_health_right_label">Parema aku tervis (hinnanguline)</string>
<string name="device_settings_info_battery_health_left_label">Vasaku klapiga kuulamise aeg</string>
<string name="device_settings_info_battery_health_right_label">Parema klapiga kuulamise aeg</string>
<string name="device_settings_info_battery_health_value">~%1$d%% nimiväärtusest</string>
<string name="device_settings_info_battery_health_pending">Määratakse, vaata hiljem uuesti</string>
<string name="device_settings_battery_runtime_warning_title">Kuulamisaeg on palju lühem kui nimiväärtus</string>
<string name="device_settings_battery_runtime_warning_description">%1$s: umbes %2$d%% nimiväärtusest kuulamisajast. Puudutage üksikasjade saamiseks.</string>
<string name="device_settings_battery_runtime_warning_description">%1$s: umbes %2$d%% nimiväärtuse kuulamisajast. Puudutage andmete saamiseks.</string>
<string name="device_settings_battery_runtime_warning_headset_label">Need peakomplektid</string>
<string name="device_settings_info_details_label">Seadme andmed</string>
<string name="device_settings_info_details_action">Kuva seadme andmed</string>
+13 -13
View File
@@ -29,7 +29,7 @@
<string name="upgrade_benefit_support">개발자 지원</string>
<string name="upgrade_benefit_disclaimer">기능 사용 가능 여부는 헤드폰 및 기기에 따라 다릅니다.</string>
<string name="settings_monitor_connected_notification_label">추가 알림</string>
<string name="settings_monitor_connected_notification_description">기기가 연결되어 있을 때만 나타나는 두 번째 알림을 표시해요. 이 옵션을 켜면 \"기기 없음\"이라는 영구 알림을 숨길 수 있어요. CAPod의 Android 알림 설정에서 \"기기 상태\" 카테고리를 세요.</string>
<string name="settings_monitor_connected_notification_description">기기가 연결되어 있을 때만 나타나는 두 번째 알림을 표시합니다. 이 옵션을 켜면 \"기기 없음\"이라는 영구 알림을 숨길 수 있습니다. Android 설정에서 CAPod의 알림 설정 \"기기 상태\" 카테고리를 비활성화 하세요.</string>
<string name="settings_keep_notification_after_disconnect_label">연결 해제 후에도 알림 유지하기</string>
<string name="settings_keep_notification_after_disconnect_description">AirPods이 연결 해제된 후에도 마지막으로 알려진 배터리 잔량 계속 표시하기</string>
<string name="settings_autopause_label">자동 일시 중지</string>
@@ -40,7 +40,7 @@
<string name="settings_start_music_on_wear_description">수동으로 일시 정지한 후에도 이어팟을 착용하면 항상 음악 재생을 시도합니다.</string>
<string name="settings_eardetection_info_description">Apple의 제한으로 착용 감지가 마이크를 이용하는 데 사용되는 한쪽 유닛만 작동할 수 있습니다. Apple 기기에서 다음에 따라 설정할 수 있습니다. 설정 → 블루투스 → Airpods → 마이크</string>
<string name="settings_autoconnect_label">자동 연결</string>
<string name="settings_autoconnect_description">Android가 자동으로 연결하지 않는다면 앱이 대신 요청할 수 있어요. 이렇게 하면 CAPod가 항상 백그라운드에서 모니터링을 계속해요.</string>
<string name="settings_autoconnect_description">Android가 자동으로 연결하지 않는다면 앱이 대신 요청할 수 있습니다. 이렇게 하면 CAPod가 항상 백그라운드에서 모니터링을 계속합니다.</string>
<string name="settings_autoconnect_info_android12">자동 연결은 Android 12 이상에서 제한하는 시스템 기능에 의존합니다. 기기에서 작동하지 않을 수 있습니다.</string>
<string name="settings_autoconnect_condition_label">자동 연결 조건</string>
<string name="settings_devices_label">장치</string>
@@ -49,9 +49,9 @@
<string name="settings_category_compatibility_options_title">호환성 옵션</string>
<string name="settings_category_compatibility_options_description">모든 것이 작동하면 건드리지 마세요 ;)</string>
<string name="settings_compat_offloaded_filtering_disabled_title">하드웨어 필터링 비활성화</string>
<string name="settings_compat_offloaded_filtering_disabled_summary">AirPods가 감지되지 않는다면 이것을 시도해 보세요. 휴대폰이 미리 선택하는 대신 앱이 근처의 모든 Bluetooth 신호를 직접 확인해요.</string>
<string name="settings_compat_offloaded_filtering_disabled_summary">AirPods가 감지되지 않는다면 이것을 시도해 보세요. 휴대폰이 미리 선택하는 대신 앱이 근처의 모든 Bluetooth 신호를 직접 확인합니다.</string>
<string name="settings_compat_offloaded_batching_disabled_title">하드웨어 일괄 처리 비활성화</string>
<string name="settings_compat_offloaded_batching_disabled_summary">배터리 수준이 늦게 업데이트되거나 한 번에 업데이트된다면 이 옵션을 시도해 보세요. 휴대폰이 먼저 수집하는 대신 각 Bluetooth 신호를 즉시 전달해요.</string>
<string name="settings_compat_offloaded_batching_disabled_summary">배터리 상태가 늦게 업데이트되거나 한 번에 업데이트된다면 이 옵션을 시도해 보세요. 휴대폰이 먼저 수집하는 대신 각 Bluetooth 신호를 즉시 전달합니다.</string>
<string name="settings_onepod_mode_label">단일 유닛 모드</string>
<string name="settings_onepod_mode_description">자동 재생/일시 정지 및 자동 연결이 두 개 모두 필요하지 않고 단일 이어팟에도 반응합니다.</string>
<string name="settings_popup_caseopen_label">케이스 팝업 표시</string>
@@ -104,7 +104,7 @@
<string name="device_battery_estimate_reset_action">학습된 데이터 초기화</string>
<string name="device_battery_estimate_reset_desc">정격 배터리 수명부터 다시 시작됩니다.</string>
<string name="device_battery_estimate_reset_confirm_title">학습된 데이터를 초기화하시겠습니까?</string>
<string name="device_battery_estimate_reset_confirm_message">CAPod가 이 기기의 측정된 방전량, 충전 속도 및 청취 시간을 잊고 정격 배터리 수명부터 다시 시작합니다.</string>
<string name="device_battery_estimate_reset_confirm_message">CAPod가 이 기기의 측정된 방전량, 충전 속도 및 청취 시간을 잊고 정격 배터리 수명부터 다시 측정합니다.</string>
<string name="settings_acknowledgements_label">감사의 말</string>
<string name="help_translate_label">번역</string>
<string name="help_translate_description">이 앱을 즐겨 사용하는 언어로 번역하는 데 도움을 주세요.</string>
@@ -144,7 +144,7 @@
<string name="widget_config_preset_green">초록</string>
<string name="widget_config_preset_red">빨강</string>
<string name="settings_compat_indirectcallback_title">간접 데이터 전달</string>
<string name="settings_compat_indirectcallback_summary">다른 호환성 옵션이 도움이 되지 않았다면 이것을 시도해 보세요. 앱이 직접 수신하는 대신 Android가 백그라운드에서 Bluetooth 신호를 앱에 전달해요.</string>
<string name="settings_compat_indirectcallback_summary">다른 호환성 옵션이 도움이 되지 않았다면 이것을 시도해 보세요. 앱이 직접 수신하는 대신 Android가 백그라운드에서 Bluetooth 신호를 앱에 전달합니다.</string>
<string name="troubleshooter_title">문제 해결사</string>
<string name="troubleshooter_summary">Bluetooth 연결 문제를 진단하고 고칩니다.</string>
<string name="troubleshooter_ble_intro_title">Bluetooth Low Energy 브로드캐스트</string>
@@ -210,19 +210,19 @@
<string name="pods_dual_right_label">오른쪽 유닛</string>
<string name="battery_unavailable_label">배터리 사용 불가</string>
<string name="battery_state_low_cd">배터리 부족</string>
<string name="battery_state_critical_cd">배터리 극히 부족</string>
<string name="battery_state_critical_cd">배터리 매우 부족</string>
<plurals name="battery_case_charges_approx">
<item quantity="other">~%d회 추가 완전 충전</item>
<item quantity="other">~%d회 완전 충전 가능</item>
</plurals>
<plurals name="battery_case_charges_at_least">
<item quantity="other">최소 %d회 추가 완전 충전</item>
<item quantity="other">최소 %d회 완전 충전 가능</item>
</plurals>
<string name="battery_case_charges_less_than_one">추가 완전 충전이 1회 미만입니다</string>
<string name="battery_case_charges_empty">케이스가 비어 있고 더 이상 이어버드를 충전할 수 없습니다</string>
<string name="battery_case_charges_uncertain">남은 완전 충전 횟수를 확실히 알 수 없습니다</string>
<string name="battery_case_charges_less_than_one">1회 미만으로 완전 충전 가능</string>
<string name="battery_case_charges_empty">케이스 배터리가 떨어져서, 더 이상 이어버드를 충전할 수 없습니다.</string>
<string name="battery_case_charges_uncertain">남은 완전 충전 횟수를 확실히 알 수 없습니다.</string>
<string name="battery_case_charges_state_enough_cd">완전 충전 가능</string>
<string name="battery_case_charges_state_not_enough_cd">완전 충전 불가능</string>
<string name="battery_case_charges_state_uncertain_cd">완전 충전에 부족할 수 있습니다</string>
<string name="battery_case_charges_state_uncertain_cd">완전 충전에 부족할 수 있습니다.</string>
<string name="pods_case_status_open_label">열림</string>
<string name="pods_case_status_closed_label">닫힘</string>
<string name="pods_connection_state_disconnected_label">연결되지 않음</string>
+9 -7
View File
@@ -100,11 +100,11 @@
<string name="settings_overview_hide_unmatched_label">Niet-overeenkomende apparaten verbergen</string>
<string name="settings_overview_hide_unmatched_description">Verberg apparaten in het overzicht die niet overeenkomen met je profielen, bijv. AirPods van anderen.</string>
<string name="device_battery_estimate_toggle_label">Batterijschattingen</string>
<string name="device_battery_estimate_card_desc">Leer hoe dit apparaat oplaadt en verbruikt om de resterende tijd, laadtijd en hoeveel van de nominale luisterduur het nog bereikt in te schatten.</string>
<string name="device_battery_estimate_card_desc">Leer hoe dit apparaat oplaadt en ontlaadt om de resterende gebruiksduur, de tijd tot volledige oplading en het resterende deel van de nominale luistertijd te schatten.</string>
<string name="device_battery_estimate_reset_action">Leergegevens wissen</string>
<string name="device_battery_estimate_reset_desc">Opnieuw beginnen met de nominale batterijduur.</string>
<string name="device_battery_estimate_reset_confirm_title">Leergegevens wissen?</string>
<string name="device_battery_estimate_reset_confirm_message">CAPod zal het verbruik, de oplaadsnelheid en luisterduur van dit apparaat vergeten en opnieuw met de nominale batterijduur beginnen.</string>
<string name="device_battery_estimate_reset_confirm_message">CAPod vergeet dat dit apparaat het stroomverbruik, de laadsnelheid en de luistertijd heeft gemeten en begint opnieuw vanaf de opgegeven batterijduur.</string>
<string name="settings_acknowledgements_label">Dankbetuigingen</string>
<string name="help_translate_label">Vertaling</string>
<string name="help_translate_description">Help deze app te vertalen in je favoriete taal.</string>
@@ -212,7 +212,7 @@
<string name="pods_dual_right_label">Rechter pod</string>
<string name="battery_unavailable_label">Batterij niet beschikbaar</string>
<string name="battery_state_low_cd">Batterij laag</string>
<string name="battery_state_critical_cd">Batterij kritiek leeg</string>
<string name="battery_state_critical_cd">Batterij bijna leeg</string>
<plurals name="battery_case_charges_approx">
<item quantity="one">~%d meer volledige lading voor beide oordopjes</item>
<item quantity="other">~%d meer volledige ladingen voor beide oordopjes</item>
@@ -221,12 +221,14 @@
<item quantity="one">Minstens %d meer volledige lading voor beide oordopjes</item>
<item quantity="other">Minstens %d meer volledige ladingen voor beide oordopjes</item>
</plurals>
<string name="battery_case_charges_less_than_one">Minder dan één volledige lading voor beide oordopjes</string>
<string name="battery_case_charges_empty">Etui is leeg, geen ladingen meer voor je oordopjes</string>
<string name="battery_case_charges_uncertain">Niet zeker hoeveel volledige ladingen voor beide oordopjes overblijven</string>
<string name="battery_case_charges_state_enough_cd">Genoeg voor één volledige lading</string>
<string name="battery_case_charges_less_than_one">Nog minder dan één volledige oplaadbeurt voor beide oordopjes</string>
<string name="battery_case_charges_empty">Etui is leeg, de oordopjes zijn niet meer opgeladen</string>
<string name="battery_case_charges_uncertain">Ik weet niet precies hoeveel volledige oplaadbeurten beide oordopjes nog hebben</string>
<string name="battery_case_charges_state_enough_cd">Voldoende voor een volledige lading</string>
<string name="battery_case_charges_state_not_enough_cd">Niet genoeg voor één volledige lading</string>
<string name="battery_case_charges_state_uncertain_cd">Mogelijk niet genoeg voor één volledige lading</string>
<string name="battery_case_charges_short">%1$s opladen</string>
<string name="battery_case_charges_short_at_least">%1$s+ opladen</string>
<string name="pods_case_status_open_label">Open</string>
<string name="pods_case_status_closed_label">Gesloten</string>
<string name="pods_connection_state_disconnected_label">Niet verbonden met een apparaat</string>
+2
View File
@@ -237,6 +237,8 @@ K4r0lSz;</string>
<string name="battery_case_charges_state_enough_cd">Wystarczy do pełnego ładowania</string>
<string name="battery_case_charges_state_not_enough_cd">Za mało do pełnego ładowania</string>
<string name="battery_case_charges_state_uncertain_cd">Może być za mało do pełnego ładowania</string>
<string name="battery_case_charges_short">%1$s ładowania</string>
<string name="battery_case_charges_short_at_least">%1$s+ ładowania</string>
<string name="pods_case_status_open_label">Otwarte</string>
<string name="pods_case_status_closed_label">Zamknięte</string>
<string name="pods_connection_state_disconnected_label">Nie połączono z urządzeniem</string>
+2
View File
@@ -236,6 +236,8 @@ AL_Cool_T</string>
<string name="battery_case_charges_state_enough_cd">Достаточно для полной зарядки</string>
<string name="battery_case_charges_state_not_enough_cd">Недостаточно для полной зарядки</string>
<string name="battery_case_charges_state_uncertain_cd">Может быть недостаточно для полной зарядки</string>
<string name="battery_case_charges_short">%1$s зарядок</string>
<string name="battery_case_charges_short_at_least">%1$s+ зарядок</string>
<string name="pods_case_status_open_label">Открыто</string>
<string name="pods_case_status_closed_label">Закрыто</string>
<string name="pods_connection_state_disconnected_label">Не подключено к устройству</string>
+2 -2
View File
@@ -25,7 +25,7 @@ platform :android do
sh "bash ./remove_unsupported_languages.sh"
supply(
track: 'beta',
rollout: '0.10',
rollout: '1.0',
package_name: 'eu.darken.capod',
skip_upload_changelogs: 'false',
skip_upload_apk: 'true',
@@ -40,7 +40,7 @@ platform :android do
sh "bash ./remove_unsupported_languages.sh"
supply(
track: 'beta',
rollout: '0.10',
rollout: '1.0',
package_name: 'eu.darken.capod',
skip_upload_changelogs: 'false',
skip_upload_apk: 'true',
+1 -1
View File
@@ -1,7 +1,7 @@
### Updated by tools/release/bump.sh ###
project.versioning.major=5
project.versioning.minor=2
project.versioning.patch=5
project.versioning.patch=6
project.versioning.build=0
project.versioning.type=rc
#############################