Compare commits

...
49 Commits
Author SHA1 Message Date
darken 02f68821d2 Release: 2.14.0-rc0 2024-07-12 15:04:28 +02:00
darken 62886f00f1 Show battery values in notification content titles 2024-07-12 14:47:01 +02:00
darken f2eb45d00b Fix app crash on launch if permission are not granted
```java
12:49:29.433 AndroidRuntime                       E  FATAL EXCEPTION: main
                                                     Process: eu.darken.capod, PID: 15164
                                                     java.lang.SecurityException: Starting FGS with type connectedDevice callerApp=ProcessRecord{cb03698 15164:eu.darken.capod/u0a352} targetSDK=34 requires permissions: all of the permissions allOf=true [android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE] any of the permissions allOf=false [android.permission.BLUETOOTH_ADVERTISE, android.permission.BLUETOOTH_CONNECT, android.permission.BLUETOOTH_SCAN, android.permission.CHANGE_NETWORK_STATE, android.permission.CHANGE_WIFI_STATE, android.permission.CHANGE_WIFI_MULTICAST_STATE, android.permission.NFC, android.permission.TRANSMIT_IR, android.permission.UWB_RANGING, USB Device, USB Accessory]
                                                     	at android.os.Parcel.createExceptionOrNull(Parcel.java:3242)
                                                     	at android.os.Parcel.createException(Parcel.java:3226)
                                                     	at android.os.Parcel.readException(Parcel.java:3209)
                                                     	at android.os.Parcel.readException(Parcel.java:3151)
                                                     	at android.app.IActivityManager$Stub$Proxy.setServiceForeground(IActivityManager.java:7167)
                                                     	at android.app.Service.startForeground(Service.java:863)
                                                     	at androidx.work.impl.foreground.SystemForegroundService$Api31Impl.startForeground(SystemForegroundService.java:194)
                                                     	at androidx.work.impl.foreground.SystemForegroundService$1.run(SystemForegroundService.java:130)
                                                     	at android.os.Handler.handleCallback(Handler.java:959)
                                                     	at android.os.Handler.dispatchMessage(Handler.java:100)
                                                     	at android.os.Looper.loopOnce(Looper.java:232)
                                                     	at android.os.Looper.loop(Looper.java:317)
                                                     	at android.app.ActivityThread.main(ActivityThread.java:8699)
                                                     	at java.lang.reflect.Method.invoke(Native Method)
                                                     	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:580)
                                                     	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:886)
2024-07-12 13:44:57 +02:00
darken 2fca9097c2 Fix up-navigation behavior when doing onboarding
Back button shouldn't take you dashboard -> onboarding or onboarding -> dashboard
2024-07-12 13:44:44 +02:00
Ricky Cheung 051677e7dd Prevent return@apply duplicates
Signed-off-by: Ricky Cheung <rcheung844@gmail.com>
2024-07-12 12:18:24 +02:00
Ricky Cheung fbcd595b45 Add more spacing around the wide variant of dual pods widgets
Signed-off-by: Ricky Cheung <rcheung844@gmail.com>
2024-07-12 12:18:24 +02:00
Ricky Cheung aa38f58f62 setCustomContentView() -> setCustomBigContentView()
Signed-off-by: Ricky Cheung <rcheung844@gmail.com>
2024-07-12 12:18:24 +02:00
Ricky Cheung a3b01a02ee Update all checkbox preferences to SUW switches
Signed-off-by: Ricky Cheung <rcheung844@gmail.com>
2024-07-12 12:18:24 +02:00
Ricky Cheung 8544983984 Update com.google.android.material to 1.12.0
Signed-off-by: Ricky Cheung <rcheung844@gmail.com>
2024-07-12 12:18:24 +02:00
Ricky Cheung 07a11799b1 Implement support for different states in notification text
Fixes #198

Signed-off-by: Ricky Cheung <rcheung844@gmail.com>
2024-06-27 08:34:19 +02:00
Kavish Devar 65a7b1933a Add PUSH_NOTIFICATIONS permission to AndroidManifest.xml for wearos (#203)
* Add PUSH_NOTIFICATIONS permissions to AndroidManifest.xml for wearos

Fixes https://github.com/d4rken-org/capod/issues/154

* Fix typo in AndroidManifest.xml for wearos app
2024-06-26 12:41:47 +02:00
Ricky Cheung bbfb735124 Enlarge the icons on wide widgets
* Also move the case details to the centre and separate the text a little

Signed-off-by: Ricky Cheung <rcheung844@gmail.com>
2024-06-26 12:41:24 +02:00
Ricky Cheung 7b474fb0e1 Fix up comment
Signed-off-by: Ricky Cheung <rcheung844@gmail.com>
2024-06-26 12:41:24 +02:00
Ricky Cheung c6524d7cee Log layout ID in createDualPodLayout
Signed-off-by: Ricky Cheung <rcheung844@gmail.com>
2024-06-26 12:41:24 +02:00
Ricky Cheung 148cdf5109 Add more notes for the wide widgets
Signed-off-by: Ricky Cheung <rcheung844@gmail.com>
2024-06-26 12:41:24 +02:00
Ricky Cheung 60a3039415 Annotate layout argument as LayoutRes
Signed-off-by: Ricky Cheung <rcheung844@gmail.com>
2024-06-26 12:41:24 +02:00
Ricky Cheung 6f7b6e30c9 Fix status bar icons in light theme
Signed-off-by: Ricky Cheung <rcheung844@gmail.com>
2024-06-26 12:41:24 +02:00
Ricky Cheung 08c095eae4 Switch all MaterialComponents for Material3 for Wear
Signed-off-by: Ricky Cheung <rcheung844@gmail.com>
2024-06-26 12:41:24 +02:00
Ricky Cheung 9087be6282 Add wide variant of dual pod widget
* Wide variant activates when the widget is larger than 3 columns in length

Signed-off-by: Ricky Cheung <rcheung844@gmail.com>
2024-06-26 12:41:24 +02:00
Ricky Cheung d46fe35eab Make widgets consistent with each other
Going by 2f9185efa7, it appears the label is preferred to be at the bottom

Signed-off-by: Ricky Cheung <rcheung844@gmail.com>
2024-06-26 12:41:24 +02:00
Ricky Cheung 1afb77ee33 Make the status bar transparent
Signed-off-by: Ricky Cheung <rcheung844@gmail.com>
2024-06-26 12:41:24 +02:00
Ricky Cheung a9215fc11c Switch to everything Material3 components
This commit modifies all major elements of the UI for phones to use Material3 elements, including the toolbar.

Signed-off-by: Ricky Cheung <rcheung844@gmail.com>
2024-06-26 12:41:24 +02:00
darken d80a110280 Thank the MagicPods project 2024-04-30 16:10:06 +02:00
darken fdfc7f44fb Update translations 2024-04-30 15:37:18 +02:00
darken 26fbfc08f7 Remove proprietary Google Play info block in FOSS builds 2024-04-05 18:42:11 +02:00
hwlnx 89049ee3ab Update preferences_support.xml 2024-03-11 13:30:22 +01:00
hwlnx 68f81f900b Update strings.xml 2024-03-11 13:29:19 +01:00
darken 513bd891af Release: 2.13.0-rc0 2024-02-25 13:30:10 +01:00
darken 37cd753ab8 Update README 2024-02-25 13:29:13 +01:00
darken 34bf2311b4 Add support for PowerBeats4
Thanks to @docgalaxyblock
2024-02-25 13:29:13 +01:00
darken a86093f617 Update translations 2024-02-25 13:16:19 +01:00
Matthias Urhahn 71f66e6004 Merge pull request #182 from JerretQueen/main
Onboarding typo fixed, dual pods layout realigned.
2024-02-25 13:12:31 +01:00
Matthias Urhahn 2d1ab85e01 Update README.md 2024-02-25 13:11:46 +01:00
Matthias Urhahn f029eb9102 Merge pull request #179 from hwlnx/patch-1
Update strings.xml
2024-02-25 13:06:15 +01:00
Matthias Urhahn 471837ab88 Update gradle-wrapper.properties 2024-02-25 13:05:05 +01:00
Matthias Urhahn 1e80d742cc Update build.gradle.kts 2024-02-25 13:04:38 +01:00
Matthias Urhahn 16b6273938 Update build.gradle.kts 2024-02-25 13:04:08 +01:00
JerretQueen 3407370563 1. Fixed a typo in @string/onboarding_body2. The message now correctly says "implementation" instead of "implemtation".
2. Changed layout of overview_pods_dual_item.xml so that the left and right pod status are properly lined up with each other.
2023-12-30 00:14:24 -06:00
hwlnx 3cda73a36c Update strings.xml 2023-11-19 20:05:42 +01:00
darken b604c423ad Release: 2.12.1-rc0 2023-10-28 19:51:26 +02:00
darken 0c8b121c2c Request FOREGROUND_SERVICE_SPECIAL_USE for debug log service 2023-10-28 19:50:56 +02:00
darken da11961611 Release: 2.12.0-rc0 2023-10-28 18:36:11 +02:00
darken 48b9952fc0 Update translations 2023-10-28 18:21:50 +02:00
darken 99fa99eba5 Update billing client 2023-10-28 18:03:28 +02:00
darken 06e67e04ac Move permission 2023-10-28 17:58:09 +02:00
darken 4a2f7c20a2 Update unit test 2023-10-28 17:58:09 +02:00
darken 82e8769407 Bump dependencies and API targets. 2023-10-28 17:58:09 +02:00
darken ad38702df1 Widget: Center device label 2023-10-28 16:59:52 +02:00
darken 3f98c62a8f Extend test case for AirPods Pro 2 (USB-C)
Closes #164
2023-09-24 12:01:15 +02:00
83 changed files with 997 additions and 313 deletions
+5 -1
View File
@@ -4,7 +4,8 @@
[![GitHub release (latest SemVer including pre-releases)](https://img.shields.io/github/v/release/d4rken-org/capod?include_prereleases)](https://github.com/d4rken-org/capod/releases/latest)
[![Code tests & eval](https://github.com/d4rken-org/capod/actions/workflows/code-checks.yml/badge.svg)](https://github.com/d4rken/capod/actions/workflows/code-checks.yml)
[![Crowdin](https://badges.crowdin.net/capod/localized.svg)](https://crowdin.com/project/capod)
[![Github All Releases](https://img.shields.io/github/downloads/d4rken-org/capod/total.svg)](https://github.com/d4rken-org/capod/releases/latest)
[![Github Downloads](https://img.shields.io/github/downloads/d4rken-org/capod/total.svg?label=GitHub%20Downloads&logo=github)](https://github.com/d4rken-org/capod/edit/main/README.md#download)
[![Google Play Downloads](https://img.shields.io/endpoint?color=green&logo=google-play&logoColor=green&url=https%3A%2F%2Fplay.cuzi.workers.dev%2Fplay%3Fi%3Deu.darken.capod%26l%3DGoogle%2520Play%26m%3D%24totalinstalls)](https://github.com/d4rken-org/capod/edit/main/README.md#download)
A companion app that adds support for AirPod specific features to Android:
@@ -31,6 +32,7 @@ Currently supported models:
* AirPods Max
* Power Beats Pro
* Power Beats 3
* Power Beats 4
* Beats Solo 3
* Beats Studio 3
* Beats X
@@ -76,6 +78,8 @@ Currently supported models:
Travis & Rye, Erik & Sipes, Brandon & Teplov, Sam. (2019). Handoff All Your Privacy A Review of Apples Bluetooth
Low Energy Continuity Protocol. Proceedings on Privacy Enhancing Technologies. 2019. 34-53. 10.2478/popets-2019-0057.
* Celosia, Guillaume. (2020). Privacy Challenges in Wireless Communications of the Internet of Things.
* The [MagicPods](https://github.com/steam3d/MagicPods-Windows) project. If you are looking for "CAPod for Windows",
check it out.
## License
+1 -1
View File
@@ -1 +1 @@
2.11.1-rc0 21101000
2.14.0-rc0 21400000
@@ -0,0 +1,14 @@
package eu.darken.capod.common.preferences
import android.content.Context
import android.util.AttributeSet
import androidx.preference.SwitchPreferenceCompat
class MaterialSwitchPreference(context: Context, attrs: AttributeSet?) :
SwitchPreferenceCompat(context, attrs) {
init {
// Use material switch
widgetLayoutResource = eu.darken.capod.common.R.layout.preference_material_switch
}
}
@@ -114,6 +114,9 @@ interface PodDevice {
@Json(name = "beats.powerbeats.3") POWERBEATS_3(
"Power Beats 3"
),
@Json(name = "beats.powerbeats.4") POWERBEATS_4(
"Power Beats 4"
),
@Json(name = "beats.powerbeats.pro") POWERBEATS_PRO(
"Power Beats Pro"
),
@@ -27,6 +27,7 @@ abstract class AppleFactoryModule {
@Binds @IntoSet abstract fun beatsStudio3(factory: BeatsStudio3.Factory): ApplePodsFactory<out ApplePods>
@Binds @IntoSet abstract fun beatsX(factory: BeatsX.Factory): ApplePodsFactory<out ApplePods>
@Binds @IntoSet abstract fun powerBeats3(factory: PowerBeats3.Factory): ApplePodsFactory<out ApplePods>
@Binds @IntoSet abstract fun powerBeats4(factory: PowerBeats4.Factory): ApplePodsFactory<out ApplePods>
@Binds @IntoSet abstract fun powerBeatsPro(factory: PowerBeatsPro.Factory): ApplePodsFactory<out ApplePods>
@Binds @IntoSet abstract fun beatsFitPro(factory: BeatsFitPro.Factory): ApplePodsFactory<out ApplePods>
@@ -0,0 +1,60 @@
package eu.darken.capod.pods.core.apple.beats
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.apple.ApplePods
import eu.darken.capod.pods.core.apple.SingleApplePods
import eu.darken.capod.pods.core.apple.SingleApplePodsFactory
import eu.darken.capod.pods.core.apple.airpods.HasStateDetectionAirPods
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
import java.time.Instant
import javax.inject.Inject
data class PowerBeats4(
override val identifier: PodDevice.Id = PodDevice.Id(),
override val seenLastAt: Instant = Instant.now(),
override val seenFirstAt: Instant = Instant.now(),
override val seenCounter: Int = 1,
override val scanResult: BleScanResult,
override val proximityMessage: ProximityPairing.Message,
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
private val rssiAverage: Int? = null,
) : SingleApplePods, HasStateDetectionAirPods {
override val model: PodDevice.Model = PodDevice.Model.POWERBEATS_4
override val rssi: Int
get() = rssiAverage ?: super<SingleApplePods>.rssi
class Factory @Inject constructor() : SingleApplePodsFactory(TAG) {
override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run {
getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH
}
override fun create(scanResult: BleScanResult, message: ProximityPairing.Message): ApplePods {
var basic = PowerBeats4(scanResult = scanResult, proximityMessage = message)
val result = searchHistory(basic)
if (result != null) basic = basic.copy(identifier = result.id)
updateHistory(basic)
if (result == null) return basic
return basic.copy(
identifier = result.id,
seenFirstAt = result.seenFirstAt,
seenLastAt = scanResult.receivedAt,
seenCounter = result.seenCounter,
reliability = result.reliability,
rssiAverage = result.rssiSmoothed(basic.rssi),
)
}
}
companion object {
private val DEVICE_CODE = 0x0D20.toUShort()
private val TAG = logTag("PodDevice", "Beats", "PowerBeats", "4")
}
}
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.materialswitch.MaterialSwitch xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/switchWidget"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@null"
android:clickable="false"
android:focusable="false" />
@@ -59,4 +59,5 @@
<string name="last_seen_x">آخر ظهور: %s</string>
<string name="first_seen_x">أول ظهور:%s</string>
<string name="permission_post_notifications_label"></string>
<string name="permission_post_notifications_description">"اسمح لـلتطبيق بإظهار الإشعارات المتعلقة بسماعات الأذن الخاصة بك، على سبيل المثال. حالتهم الحالية خلال الاتصال."</string>
</resources>
@@ -1,6 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
<string name="app_name">CAPod</string>
<string name="app_name_pro">CAPod Pro</string>
<string name="app_name_foss">CAPod FOSS</string>
<string name="general_grant_permission_action">Conceder permiso</string>
<string name="permission_bluetooth_connect_label">Conexión Bluetooth</string>
<string name="permission_bluetooth_connect_description">Esta aplicacion requiere el permiso \"Conexión Bluetooth\" para interactuar con los dispositivos vinculados e iniciar las conexiones.</string>
@@ -58,4 +58,6 @@
<string name="pods_case_unknown_state">Status tidak diketahui</string>
<string name="last_seen_x">Terakhir terlihat: %s</string>
<string name="first_seen_x">Pertama kali terlihat: %s</string>
<string name="permission_post_notifications_label">Tampilkan notifikasi</string>
<string name="permission_post_notifications_description">"Izinkan CAPod untuk menampilkan notifikasi tentang AirPods anda, contoh. status saat terhubung."</string>
</resources>
@@ -33,14 +33,14 @@
<string name="settings_monitor_mode_always_label">Sempre</string>
<string name="settings_reaction_autoconnect_whenseen_label">Quando rilevato</string>
<string name="settings_reaction_autoconnect_caseopen_label">La custodia é aperta</string>
<string name="settings_reaction_autoconnect_inear_label">Nell\'orecchio</string>
<string name="pods_dual_left_label">Pod sinistro</string>
<string name="pods_dual_right_label">Pod destro</string>
<string name="settings_reaction_autoconnect_inear_label">Indossato</string>
<string name="pods_dual_left_label">Pod SX</string>
<string name="pods_dual_right_label">Pod DX</string>
<string name="pods_case_label">Custodia</string>
<string name="pods_case_status_open_label">Aperto</string>
<string name="pods_case_status_closed_label">Chiuso</string>
<string name="pods_connection_state_disconnected_label">Non connesso</string>
<string name="pods_connection_state_idle_label">Connesso ad un device, ma inattivo</string>
<string name="pods_connection_state_idle_label">Connesso al dispositivo, ma inattivo</string>
<string name="pods_connection_state_music_label">In modalità musica</string>
<string name="pods_connection_state_call_label">In modalità chiamata</string>
<string name="pods_connection_state_ringing_label">Squilla</string>
@@ -51,7 +51,7 @@
<string name="pods_unknown_contact_dev">Questo è un dispositivo sconosciuto, ma sta utilizzando un formato di comunicazione noto. Aggiungiamo il supporto per questo dispositivo, contattami :)</string>
<string name="pods_none_label_short">Nessun dispositivo</string>
<string name="pods_charging_label">In ricarica</string>
<string name="pods_inear_label">Nell\'orecchio</string>
<string name="pods_inear_label">Indossato</string>
<string name="pods_microphone_label">Microfono</string>
<string name="pods_yours">Tuoi</string>
<string name="headset_being_worn_label">Indossato</string>
@@ -58,4 +58,6 @@
<string name="pods_case_unknown_state">Estado desconhecido</string>
<string name="last_seen_x">Visto pela última vez: %s</string>
<string name="first_seen_x">Visto pela primeira vez: %s</string>
<string name="permission_post_notifications_label">Mostrar notificações</string>
<string name="permission_post_notifications_description">"Permita que o CAPod mostre notificações sobre seus AirPods, por exemplo. seu status atual enquanto conectado."</string>
</resources>
+62 -1
View File
@@ -1,2 +1,63 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation"></resources>
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
<string name="app_name">CAPod</string>
<string name="app_name_pro">CAPod Pro</string>
<string name="app_name_foss">CAPod FOSS</string>
<string name="general_value_not_available_label">N/A</string>
<string name="general_error_label">Eroare</string>
<string name="general_grant_permission_action">Acordați permisiuni</string>
<string name="overview_nomaindevice_label">Niciun dispozitiv principal</string>
<string name="overview_nomaindevice_description">Este puțin probabil ca toate dispozitivele detectate să fie ale tale. Porniți și conectați dispozitivul sau ajustați setările.</string>
<string name="overview_bluetooth_disabled_label">Bluetooth este dezactivat</string>
<string name="overview_bluetooth_disabled_description">Bluetooth este dezactivat, activați-l ;)</string>
<string name="permission_bluetooth_connect_label">Conectare Bluetooth</string>
<string name="permission_bluetooth_connect_description">Această aplicație necesită permisiunea „Conectare Bluetooth” pentru a interacționa cu dispozitivele asociate și a iniția conexiuni.</string>
<string name="permission_bluetooth_scan_label">Scanare Bluetooth</string>
<string name="permission_bluetooth_scan_description">Permisiunea „Scanare Bluetooth” permite acestei aplicații să descopere și să primească date Bluetooth de la dispozitivele din apropiere, cum ar fi AirPod-urile dvs.</string>
<string name="permission_bluetooth_label">Bluetooth</string>
<string name="permission_bluetooth_description">Această aplicație necesită permisiunea „Bluetooth” pentru a se conecta la dispozitive Bluetooth asociate.</string>
<string name="permission_access_fine_location_label">Permiteți locație exactă</string>
<string name="permission_access_fine_location_description">CAPod folosește permisiunea „locație exactă” pentru a primi date Bluetooth Energie Scăzută (BLE). Căștile dvs. folosesc tehnologia Bluetooth Energie Scăzută (BLE) pentru a-și transmite starea. Această aplicație NU va folosi date Bluetooth pentru a vă determina locația.</string>
<string name="permission_background_location_label">Permiteți acces la locație în fundal</string>
<string name="permission_background_location_description">CAPod folosește „accesul la locație în fundal” pentru a activa funcții precum „Afișare pop-up” și „Conectare Automată” în timp ce aplicația este închisă. Accesul la locație în fundal permite aplicației să primească date Bluetooth Energie Scăzută (BLE) în timp ce se află în fundal. Această aplicație NU va folosi date Bluetooth pentru a vă determina locația.</string>
<string name="permission_ignore_battery_optimizations_label">Dezactivați optimizări ale bateriei</string>
<string name="permission_ignore_battery_optimizations_description">Optimizările bateriei împiedică această aplicație să primească în mod fiabil date Bluetooth în timp ce aplicația este în fundal.</string>
<string name="permission_required_title">Este necesară următoarea permisiune:</string>
<string name="permission_system_alert_window_label">Fereastra de alertă de sistem</string>
<string name="permission_system_alert_window_description">Permiteți CAPod să apară peste alte aplicații pentru a face posibilă funcția „Afișați pop-up”.</string>
<string name="settings_scanner_mode_lowpower_label">Putere redusă</string>
<string name="settings_scanner_mode_balanced_label">Echilibrat</string>
<string name="settings_scanner_mode_lowlatency_label">Latență scăzută</string>
<string name="settings_monitor_mode_manual_label">Când aplicația este deschisă</string>
<string name="settings_monitor_mode_automatic_label">Când dispozitivul este conectat</string>
<string name="settings_monitor_mode_always_label">Mereu</string>
<string name="settings_reaction_autoconnect_whenseen_label">Când este detectat</string>
<string name="settings_reaction_autoconnect_caseopen_label">Carcasa este deschisă</string>
<string name="settings_reaction_autoconnect_inear_label">În ureche</string>
<string name="pods_dual_left_label">Casca stângă</string>
<string name="pods_dual_right_label">Casca dreaptă</string>
<string name="pods_case_label">Carcasă</string>
<string name="pods_case_status_open_label">Deschisă</string>
<string name="pods_case_status_closed_label">Închisă</string>
<string name="pods_connection_state_disconnected_label">Nu sunt conectate la un dispozitiv</string>
<string name="pods_connection_state_idle_label">Conectate la un dispozitiv, dar inactiv</string>
<string name="pods_connection_state_music_label">În modul muzică</string>
<string name="pods_connection_state_call_label">În modul apel</string>
<string name="pods_connection_state_ringing_label">Sună</string>
<string name="pods_connection_state_hanging_up_label">Închidere</string>
<string name="pods_connection_state_unknown_label">Starea conexiunii necunoscută</string>
<string name="pods_unknown_raw_data_label">Date neprelucrate</string>
<string name="pods_unknown_label">Dispozitiv necunoscut</string>
<string name="pods_unknown_contact_dev">Acesta este un dispozitiv necunoscut, dar folosește un format de mesaj similar. Să adăugăm suport pentru el, contactează-mă :)</string>
<string name="pods_none_label_short">Niciun dispozitiv</string>
<string name="pods_charging_label">Se încarcă</string>
<string name="pods_inear_label">În ureche</string>
<string name="pods_microphone_label">Microfon</string>
<string name="pods_yours">Ale tale</string>
<string name="headset_being_worn_label">Fiind purtate</string>
<string name="pods_case_unknown_state">Stare necunoscută</string>
<string name="last_seen_x">Vazute ultima dată: %s</string>
<string name="first_seen_x">Prima dată văzute: %s</string>
<string name="permission_post_notifications_label">Arată notificări</string>
<string name="permission_post_notifications_description">"Permiteți ca CAPod să afișeze notificări despre AirPod-urile dvs., de exemplu, starea lor actuală în timp ce sunt conectate."</string>
</resources>
@@ -61,6 +61,7 @@
<string name="pods_microphone_label">Microphone</string>
<string name="pods_yours">Yours</string>
<string name="headset_being_worn_label">Being worn</string>
<string name="headset_not_being_worn_label">Not being worn</string>
<string name="pods_case_unknown_state">Unknown state</string>
<string name="last_seen_x">Last seen: %s</string>
@@ -3,80 +3,69 @@ package eu.darken.capod.common.flow
import eu.darken.capod.common.collections.mutate
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.matchers.shouldBe
import io.kotest.matchers.types.instanceOf
import io.kotest.matchers.types.shouldBeInstanceOf
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.mockk
import kotlinx.coroutines.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.test.*
import kotlinx.coroutines.test.advanceUntilIdle
import org.junit.jupiter.api.Test
import testhelpers.BaseTest
import testhelpers.coroutine.runTest2
import testhelpers.flow.test
import java.io.IOException
import java.lang.Thread.sleep
import kotlin.concurrent.thread
import kotlin.coroutines.EmptyCoroutineContext
class DynamicStateFlowTest : BaseTest() {
// Without an init value, there isn't a way to keep using the flow
@Test
fun `exceptions on initialization are rethrown`() {
val testScope =
createTestCoroutineScope(TestCoroutineDispatcher() + TestCoroutineExceptionHandler() + EmptyCoroutineContext)
val hotData = eu.darken.capod.common.flow.DynamicStateFlow<String>(
fun `exceptions on initialization are rethrown`() = runTest2(expectedError = IOException::class) {
val testScope = this
val hotData = DynamicStateFlow<String>(
loggingTag = "tag",
parentScope = testScope,
coroutineContext = Dispatchers.Unconfined,
startValueProvider = { throw IOException() }
)
runBlocking {
withTimeoutOrNull(500) {
// This blocking scope gets the init exception as the first caller
hotData.flow.firstOrNull()
} shouldBe null
}
testScope.advanceUntilIdle()
testScope.uncaughtExceptions.single() shouldBe instanceOf(IOException::class)
// This blocking scope gets the init exception as the first caller
hotData.flow.firstOrNull()
}
@Test
fun `subscription doesn't end when no subscriber is collecting, mode Lazily`() {
val testScope =
createTestCoroutineScope(TestCoroutineDispatcher() + TestCoroutineExceptionHandler() + EmptyCoroutineContext)
fun `subscription doesn't end when no subscriber is collecting, mode Lazily`() = runTest2(autoCancel = true) {
val testScope = this
val valueProvider = mockk<suspend CoroutineScope.() -> String>()
coEvery { valueProvider.invoke(any()) } returns "Test"
val hotData = eu.darken.capod.common.flow.DynamicStateFlow(
val hotData = DynamicStateFlow(
loggingTag = "tag",
parentScope = testScope,
coroutineContext = Dispatchers.Unconfined,
startValueProvider = valueProvider,
)
testScope.apply {
runTest2(autoCancel = true) {
hotData.flow.first() shouldBe "Test"
hotData.flow.first() shouldBe "Test"
}
coVerify(exactly = 1) { valueProvider.invoke(any()) }
}
hotData.flow.first() shouldBe "Test"
hotData.flow.first() shouldBe "Test"
coVerify(exactly = 1) { valueProvider.invoke(any()) }
}
@Test
fun `value updates`() {
val testScope =
createTestCoroutineScope(TestCoroutineDispatcher() + TestCoroutineExceptionHandler() + EmptyCoroutineContext)
fun `value updates`() = runTest2(autoCancel = true) {
val testScope = this
val valueProvider = mockk<suspend CoroutineScope.() -> Long>()
coEvery { valueProvider.invoke(any()) } returns 1
val hotData = eu.darken.capod.common.flow.DynamicStateFlow(
val hotData = DynamicStateFlow(
loggingTag = "tag",
parentScope = testScope,
startValueProvider = valueProvider,
@@ -88,7 +77,6 @@ class DynamicStateFlowTest : BaseTest() {
(1..16).forEach { _ ->
thread {
(1..200).forEach { _ ->
sleep(10)
hotData.updateAsync(
onUpdate = { this + 1L },
onError = { throw it }
@@ -97,12 +85,16 @@ class DynamicStateFlowTest : BaseTest() {
}
}
runBlocking {
testCollector.await { list, _ -> list.size == 3201 }
testCollector.latestValues shouldBe (1..3201).toList()
}
advanceUntilIdle()
testCollector.await { list, _ -> list.size == 3201 }
testCollector.latestValues shouldBe (1..3201).toList()
advanceUntilIdle()
coVerify(exactly = 1) { valueProvider.invoke(any()) }
testCollector.cancelAndJoin()
}
data class TestData(
@@ -110,14 +102,13 @@ class DynamicStateFlowTest : BaseTest() {
)
@Test
fun `check multi threading value updates with more complex data`() {
val testScope =
createTestCoroutineScope(TestCoroutineDispatcher() + TestCoroutineExceptionHandler() + EmptyCoroutineContext)
val valueProvider =
mockk<suspend CoroutineScope.() -> Map<String, eu.darken.capod.common.flow.DynamicStateFlowTest.TestData>>()
coEvery { valueProvider.invoke(any()) } returns mapOf("data" to eu.darken.capod.common.flow.DynamicStateFlowTest.TestData())
fun `check multi threading value updates with more complex data`() = runTest2(autoCancel = true) {
val testScope = this
val hotData = eu.darken.capod.common.flow.DynamicStateFlow(
val valueProvider = mockk<suspend CoroutineScope.() -> Map<String, TestData>>()
coEvery { valueProvider.invoke(any()) } returns mapOf("data" to TestData())
val hotData = DynamicStateFlow(
loggingTag = "tag",
parentScope = testScope,
startValueProvider = valueProvider,
@@ -140,20 +131,23 @@ class DynamicStateFlowTest : BaseTest() {
}
}
runBlocking {
testCollector.await { list, _ -> list.size == 4001 }
testCollector.latestValues.map { it.values.single().number } shouldBe (1L..4001L).toList()
}
advanceUntilIdle()
testCollector.await { list, _ -> list.size == 4001 }
testCollector.latestValues.map { it.values.single().number } shouldBe (1L..4001L).toList()
advanceUntilIdle()
coVerify(exactly = 1) { valueProvider.invoke(any()) }
testCollector.cancelAndJoin()
}
@Test
fun `only emit new values if they actually changed updates`() {
val testScope =
createTestCoroutineScope(TestCoroutineDispatcher() + TestCoroutineExceptionHandler() + EmptyCoroutineContext)
fun `only emit new values if they actually changed updates`() = runTest2(autoCancel = true) {
val testScope = this
val hotData = eu.darken.capod.common.flow.DynamicStateFlow(
val hotData = DynamicStateFlow(
loggingTag = "tag",
parentScope = testScope,
startValueProvider = { "1" },
@@ -167,26 +161,28 @@ class DynamicStateFlowTest : BaseTest() {
hotData.updateAsync { "2" }
hotData.updateAsync { "1" }
runBlocking {
testCollector.await { list, _ -> list.size == 3 }
testCollector.latestValues shouldBe listOf("1", "2", "1")
}
advanceUntilIdle()
testCollector.await { list, _ -> list.size == 3 }
testCollector.latestValues shouldBe listOf("1", "2", "1")
}
@Test
fun `multiple subscribers share the flow`() = runTest2(autoCancel = true) {
val testScope = this
val valueProvider = mockk<suspend CoroutineScope.() -> String>()
coEvery { valueProvider.invoke(any()) } returns "Test"
val hotData = eu.darken.capod.common.flow.DynamicStateFlow(
val hotData = DynamicStateFlow(
loggingTag = "tag",
parentScope = this,
parentScope = testScope,
startValueProvider = valueProvider,
)
val sub1 = hotData.flow.test(tag = "sub1", scope = this)
val sub2 = hotData.flow.test(tag = "sub2", scope = this)
val sub3 = hotData.flow.test(tag = "sub3", scope = this)
val sub1 = hotData.flow.test(tag = "sub1", scope = testScope)
val sub2 = hotData.flow.test(tag = "sub2", scope = testScope)
val sub3 = hotData.flow.test(tag = "sub3", scope = testScope)
hotData.updateAsync { "A" }
hotData.updateAsync { "B" }
@@ -207,17 +203,19 @@ class DynamicStateFlowTest : BaseTest() {
@Test
fun `value is persisted between unsubscribes`() = runTest2(autoCancel = true) {
val testScope = this
val valueProvider = mockk<suspend CoroutineScope.() -> Long>()
coEvery { valueProvider.invoke(any()) } returns 1
val hotData = eu.darken.capod.common.flow.DynamicStateFlow(
val hotData = DynamicStateFlow(
loggingTag = "tag",
parentScope = this,
parentScope = testScope,
coroutineContext = this.coroutineContext,
startValueProvider = valueProvider,
)
val testCollector1 = hotData.flow.test(tag = "collector1", scope = this)
val testCollector1 = hotData.flow.test(tag = "collector1", scope = testScope)
testCollector1.silent = false
(1..10).forEach { _ ->
@@ -233,7 +231,7 @@ class DynamicStateFlowTest : BaseTest() {
testCollector1.cancelAndJoin()
val testCollector2 = hotData.flow.test(tag = "collector2", scope = this)
val testCollector2 = hotData.flow.test(tag = "collector2", scope = testScope)
testCollector2.silent = false
advanceUntilIdle()
@@ -246,10 +244,10 @@ class DynamicStateFlowTest : BaseTest() {
}
@Test
fun `blocking update is actually blocking`() = runBlocking {
val testScope =
createTestCoroutineScope(TestCoroutineDispatcher() + TestCoroutineExceptionHandler() + EmptyCoroutineContext)
val hotData = eu.darken.capod.common.flow.DynamicStateFlow(
fun `blocking update is actually blocking`() = runTest2(autoCancel = true) {
val testScope = this
val hotData = DynamicStateFlow(
loggingTag = "tag",
parentScope = testScope,
coroutineContext = testScope.coroutineContext,
@@ -270,6 +268,8 @@ class DynamicStateFlowTest : BaseTest() {
hotData.updateBlocking { this - 3 } shouldBe 0
advanceUntilIdle()
testCollector.await { _, i -> i == 3 }
testCollector.latestValues shouldBe listOf(2, 3, 0)
@@ -277,10 +277,10 @@ class DynamicStateFlowTest : BaseTest() {
}
@Test
fun `blocking update rethrows error`() = runBlocking {
val testScope =
createTestCoroutineScope(TestCoroutineDispatcher() + TestCoroutineExceptionHandler() + EmptyCoroutineContext)
val hotData = eu.darken.capod.common.flow.DynamicStateFlow(
fun `blocking update rethrows error`() = runTest2(autoCancel = true) {
val testScope = this
val hotData = DynamicStateFlow(
loggingTag = "tag",
parentScope = testScope,
coroutineContext = testScope.coroutineContext,
@@ -300,16 +300,17 @@ class DynamicStateFlowTest : BaseTest() {
hotData.flow.first() shouldBe 2
hotData.updateBlocking { 3 } shouldBe 3
hotData.flow.first() shouldBe 3
testScope.uncaughtExceptions.singleOrNull() shouldBe null
advanceUntilIdle()
hotData.flow.first() shouldBe 3
testCollector.cancelAndJoin()
}
@Test
fun `async updates error handler`() = runTest2(expectedError = IOException::class) {
val hotData = eu.darken.capod.common.flow.DynamicStateFlow(
val hotData = DynamicStateFlow(
loggingTag = "tag",
parentScope = this,
startValueProvider = { 1 },
@@ -321,14 +322,15 @@ class DynamicStateFlowTest : BaseTest() {
hotData.updateAsync { throw IOException("Surprise") }
advanceUntilIdle()
testCollector.cancelAndJoin()
}
@Test
fun `async updates rethrow errors on HotDataFlow scope if no error handler is set`() = runBlocking {
val testScope =
createTestCoroutineScope(TestCoroutineDispatcher() + TestCoroutineExceptionHandler() + EmptyCoroutineContext)
fun `async updates rethrow errors on HotDataFlow scope if no error handler is set`() = runTest2(autoCancel = true) {
val testScope = this
val hotData = eu.darken.capod.common.flow.DynamicStateFlow(
val hotData = DynamicStateFlow(
loggingTag = "tag",
parentScope = testScope,
startValueProvider = { 1 },
@@ -346,31 +348,29 @@ class DynamicStateFlowTest : BaseTest() {
testScope.advanceUntilIdle()
thrownError!!.shouldBeInstanceOf<IOException>()
testScope.uncaughtExceptions.singleOrNull() shouldBe null
testCollector.cancelAndJoin()
}
@Test
fun `clean up function is called when parent scope is cancelled`() = runTest {
val testScope =
createTestCoroutineScope(TestCoroutineDispatcher() + TestCoroutineExceptionHandler() + EmptyCoroutineContext)
fun `clean up function is called when parent scope is cancelled`() {
var onReleaseValue: String? = null
val hotData = eu.darken.capod.common.flow.DynamicStateFlow(
loggingTag = "tag",
parentScope = testScope,
coroutineContext = Dispatchers.Unconfined,
startValueProvider = { "Test" },
onRelease = {
onReleaseValue = it
}
)
runTest2(autoCancel = true) {
val testScope = this
hotData.flow.first() shouldBe "Test"
val hotData = DynamicStateFlow(
loggingTag = "tag",
parentScope = testScope,
coroutineContext = Dispatchers.Unconfined,
startValueProvider = { "Test" },
onRelease = {
onReleaseValue = it
}
)
testScope.cancel()
hotData.flow.first() shouldBe "Test"
}
onReleaseValue shouldBe "Test"
}
@@ -45,4 +45,28 @@ class AirPodsPro2UsbcTest : BaseAirPodsTest() {
model shouldBe PodDevice.Model.AIRPODS_PRO2_USBC
}
}
@Test
fun `AirPods Pro 2 with USB-C - via #164 - in case`() = runTest {
create<AirPodsPro2Usbc>("07 19 01 24 20 53 AA 98 32 00 05 49 0A B8 BF 8E 29 D8 70 12 0D A7 0C CE 77 56 00") {
isLeftPodMicrophone shouldBe true
isRightPodMicrophone shouldBe false
isLeftPodInEar shouldBe true
isRightPodInEar shouldBe false
batteryLeftPodPercent shouldBe 1f
batteryRightPodPercent shouldBe 1f
isCaseCharging shouldBe false
isRightPodCharging shouldBe true
isLeftPodCharging shouldBe false
batteryCasePercent shouldBe 0.8f
podStyle.identifier shouldBe HasAppleColor.DeviceColor.WHITE.name
model shouldBe PodDevice.Model.AIRPODS_PRO2_USBC
}
}
}
@@ -40,4 +40,33 @@ class BeatsFitProTest : BaseAirPodsTest() {
model shouldBe PodDevice.Model.BEATS_FIT_PRO
}
}
@Test
fun `extra rl test case`() = runTest {
create<BeatsFitPro>("07 19 01 12 20 04 FA 92 54 11 24 CE B1 DF 9D 8D F5 E3 37 60 B1 23 8B 90 3B 63 3F") {
rawPrefix shouldBe 0x01.toUByte()
rawDeviceModel shouldBe 0x1220.toUShort()
rawStatus shouldBe 0x04.toUByte()
rawPodsBattery shouldBe 0xFA.toUByte()
rawFlags shouldBe 0x9.toUShort()
rawCaseBattery shouldBe 0x2.toUShort()
rawCaseLidState shouldBe 0x54.toUByte()
rawDeviceColor shouldBe 0x11.toUByte()
rawSuffix shouldBe 0x24.toUByte()
isLeftPodMicrophone shouldBe false
isRightPodMicrophone shouldBe true
batteryLeftPodPercent shouldBe null
batteryRightPodPercent shouldBe 1.0f
isCaseCharging shouldBe false
isRightPodCharging shouldBe true
isLeftPodCharging shouldBe false
batteryCasePercent shouldBe 0.2f
podStyle.identifier shouldBe HasAppleColor.DeviceColor.UNKNOWN.name
model shouldBe PodDevice.Model.BEATS_FIT_PRO
}
}
}
@@ -0,0 +1,75 @@
package eu.darken.capod.pods.core.apple.beats
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.apple.BaseAirPodsTest
import eu.darken.capod.pods.core.apple.airpods.HasStateDetectionAirPods
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
class PowerBeats4Test : BaseAirPodsTest() {
@Test
fun `playing music`() = runTest {
create<PowerBeats4>("07 19 01 0D 20 00 02 80 01 00 05 07 C5 E1 9C BD 9A 05 0E AE 9E 56 53 2F F9 75 4A") {
rawPrefix shouldBe 0x01.toUByte()
rawDeviceModel shouldBe 0x0D20.toUShort()
rawStatus shouldBe 0x0.toUByte()
rawPodsBattery shouldBe 0x02.toUByte()
rawFlags shouldBe 0x8.toUShort()
rawCaseBattery shouldBe 0x0.toUShort()
rawCaseLidState shouldBe 0x01.toUByte()
rawDeviceColor shouldBe 0x00.toUByte()
rawSuffix shouldBe 0x05.toUByte()
batteryHeadsetPercent shouldBe 0.2f
model shouldBe PodDevice.Model.POWERBEATS_4
state shouldBe HasStateDetectionAirPods.ConnectionState.MUSIC
}
}
@Test
fun `extra test case`() = runTest {
create<PowerBeats4>("07 19 01 0D 20 00 02 80 02 00 04 80 90 60 77 32 C2 0C 75 7A 3D 7E D7 1C 0E B8 43") {
rawPrefix shouldBe 0x01.toUByte()
rawDeviceModel shouldBe 0x0D20.toUShort()
rawStatus shouldBe 0x0.toUByte()
rawPodsBattery shouldBe 0x02.toUByte()
rawFlags shouldBe 0x8.toUShort()
rawCaseBattery shouldBe 0x0.toUShort()
rawCaseLidState shouldBe 0x02.toUByte()
rawDeviceColor shouldBe 0x00.toUByte()
rawSuffix shouldBe 0x04.toUByte()
batteryHeadsetPercent shouldBe 0.2f
model shouldBe PodDevice.Model.POWERBEATS_4
state shouldBe HasStateDetectionAirPods.ConnectionState.IDLE
}
}
@Test
fun `disconnected state`() = runTest {
create<PowerBeats4>("07 19 01 0D 20 00 02 80 01 00 00 25 DF 66 40 AF 44 7B 77 95 8F D1 92 50 26 11 74") {
rawPrefix shouldBe 0x01.toUByte()
rawDeviceModel shouldBe 0x0D20.toUShort()
rawStatus shouldBe 0x0.toUByte()
rawPodsBattery shouldBe 0x02.toUByte()
rawFlags shouldBe 0x8.toUShort()
rawCaseBattery shouldBe 0x0.toUShort()
rawCaseLidState shouldBe 0x01.toUByte()
rawDeviceColor shouldBe 0x00.toUByte()
rawSuffix shouldBe 0x00.toUByte()
batteryHeadsetPercent shouldBe 0.2f
model shouldBe PodDevice.Model.POWERBEATS_4
state shouldBe HasStateDetectionAirPods.ConnectionState.DISCONNECTED
}
}
}
@@ -1,24 +0,0 @@
package testhelpers.coroutine
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.*
import org.junit.jupiter.api.extension.AfterEachCallback
import org.junit.jupiter.api.extension.BeforeEachCallback
import org.junit.jupiter.api.extension.ExtensionContext
@ExperimentalCoroutinesApi
class CoroutinesTestExtension(
private val dispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher()
) : BeforeEachCallback, AfterEachCallback,
TestCoroutineScope by createTestCoroutineScope(TestCoroutineDispatcher() + TestCoroutineExceptionHandler() + dispatcher) {
override fun beforeEach(context: ExtensionContext?) {
Dispatchers.setMain(dispatcher)
}
override fun afterEach(context: ExtensionContext?) {
cleanupTestCoroutines()
Dispatchers.resetMain()
}
}
+3 -2
View File
@@ -4,7 +4,8 @@
package="eu.darken.capod">
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-feature android:name="android.hardware.type.watch" />
<application
@@ -41,4 +42,4 @@
tools:node="remove" />
</application>
</manifest>
</manifest>
@@ -7,7 +7,7 @@
<com.google.android.material.textview.MaterialTextView
android:id="@+id/bluetooth_label"
style="@style/TextAppearance.MaterialComponents.Body1"
style="@style/TextAppearance.Material3.TitleMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
@@ -20,7 +20,7 @@
<com.google.android.material.textview.MaterialTextView
android:id="@+id/bluetooth_description"
style="@style/TextAppearance.MaterialComponents.Body2"
style="@style/TextAppearance.Material3.BodyMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
@@ -18,7 +18,7 @@
<com.google.android.material.textview.MaterialTextView
android:id="@+id/title"
style="@style/TextAppearance.MaterialComponents.Body1"
style="@style/TextAppearance.Material3.TitleMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
@@ -31,7 +31,7 @@
<com.google.android.material.textview.MaterialTextView
android:id="@+id/body"
style="@style/TextAppearance.MaterialComponents.Body2"
style="@style/TextAppearance.Material3.BodyMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
@@ -8,7 +8,7 @@
<com.google.android.material.textview.MaterialTextView
android:id="@+id/request_description"
style="@style/TextAppearance.MaterialComponents.Caption"
style="@style/TextAppearance.Material3.LabelMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
@@ -20,7 +20,7 @@
<com.google.android.material.textview.MaterialTextView
android:id="@+id/permission_label"
style="@style/TextAppearance.MaterialComponents.Body1"
style="@style/TextAppearance.Material3.BodyLarge"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
@@ -32,7 +32,7 @@
<com.google.android.material.textview.MaterialTextView
android:id="@+id/permission_description"
style="@style/TextAppearance.MaterialComponents.Body2"
style="@style/TextAppearance.Material3.BodyMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
@@ -44,11 +44,12 @@
<com.google.android.material.button.MaterialButton
android:id="@+id/grant_action"
style="@style/Widget.MaterialComponents.Button"
style="@style/Widget.Material3.Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="32dp"
android:text="@string/general_grant_permission_action"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/permission_description" />
@@ -69,11 +69,9 @@
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
app:layout_constraintBottom_toTopOf="@id/barrier_bottom"
app:layout_constraintEnd_toStartOf="@id/pod_case_container"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/barrier_top"
app:layout_constraintVertical_bias="0.2">
app:layout_constraintTop_toTopOf="@+id/pod_right_container">
<ImageView
android:id="@+id/pod_left_icon"
@@ -199,8 +197,7 @@
android:layout_marginEnd="4dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/pod_case_container"
app:layout_constraintTop_toBottomOf="@id/barrier_top"
app:layout_constraintVertical_bias="0.2">
app:layout_constraintTop_toBottomOf="@id/barrier_top">
<ImageView
android:id="@+id/pod_right_icon"
+8 -3
View File
@@ -38,6 +38,11 @@ android {
create("foss") {
dimension = "version"
signingConfig = signingConfigs["releaseFoss"]
// The info block is encrypted and can only be read by google
dependenciesInfo {
includeInApk = false
includeInBundle = false
}
}
create("gplay") {
dimension = "version"
@@ -125,7 +130,7 @@ android {
dependencies {
implementation(project(":app-common"))
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.5")
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.3")
addBaseKotlin()
@@ -145,6 +150,6 @@ dependencies {
addTesting()
"gplayImplementation"("com.android.billingclient:billing:5.1.0")
"gplayImplementation"("com.android.billingclient:billing-ktx:5.1.0")
"gplayImplementation"("com.android.billingclient:billing:6.0.1")
"gplayImplementation"("com.android.billingclient:billing-ktx:6.0.1")
}
+5 -1
View File
@@ -1,2 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources></resources>
<resources>
<string name="foss_upgrade_donate_label">Donează</string>
<string name="foss_upgrade_alreadydonated_label">Am donat deja</string>
<string name="foss_upgrade_no_money_label">Mi-am cheltuit toți banii pe AirPods</string>
</resources>
@@ -1,7 +1,13 @@
package eu.darken.capod.common.upgrade.core.client
import android.app.Activity
import com.android.billingclient.api.*
import com.android.billingclient.api.AcknowledgePurchaseParams
import com.android.billingclient.api.BillingClient
import com.android.billingclient.api.BillingFlowParams
import com.android.billingclient.api.BillingResult
import com.android.billingclient.api.ProductDetails
import com.android.billingclient.api.Purchase
import com.android.billingclient.api.QueryProductDetailsParams
import eu.darken.capod.common.debug.logging.Logging.Priority.INFO
import eu.darken.capod.common.debug.logging.Logging.Priority.WARN
import eu.darken.capod.common.debug.logging.log
@@ -20,11 +26,15 @@ data class BillingClientConnection(
) {
private val purchasesLocal = MutableStateFlow<Collection<Purchase>>(emptySet())
val purchases: Flow<Collection<Purchase>> = combine(purchasesGlobal, purchasesLocal) { global, local ->
val combined = mutableMapOf<String, Purchase>()
global.plus(local).toSet().sortedByDescending { it.purchaseTime }.forEach { purchase ->
combined[purchase.orderId] = purchase
}
combined.values
val combined = mutableSetOf<Purchase>()
combined.addAll(local)
global
.let { purchases -> purchases.filter { it.purchaseState == Purchase.PurchaseState.PURCHASED } }
.let { combined.addAll(it) }
combined.sortedByDescending { it.purchaseTime }
}
.setupCommonEventHandlers(TAG) { "purchases" }
+4 -1
View File
@@ -1,2 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources></resources>
<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>
</resources>
+12 -3
View File
@@ -9,6 +9,9 @@
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<uses-feature
android:name="android.hardware.bluetooth_le"
android:required="true" />
@@ -24,9 +27,9 @@
<activity
android:name=".main.ui.MainActivity"
android:exported="true"
android:theme="@style/AppThemeSplash"
android:label="@string/app_name"
android:launchMode="singleTop">
android:launchMode="singleTop"
android:theme="@style/AppThemeSplash">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -74,7 +77,13 @@
android:name=".common.debug.recording.ui.RecorderActivity"
android:theme="@style/AppThemeFloating" />
<service android:name=".common.debug.recording.core.RecorderService" />
<service
android:name=".common.debug.recording.core.RecorderService"
android:foregroundServiceType="specialUse">
<property
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="Users can record a debug log to help developers with bugs. The debug log recording is controlled via notifications and hosted in a foreground service." />
</service>
<!-- Worker stuff-->
<service
@@ -53,7 +53,7 @@ abstract class PreferenceFragment2
}
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) {
}
@@ -8,6 +8,7 @@ import android.content.Intent
import android.os.Bundle
import android.view.View
import android.widget.RemoteViews
import androidx.annotation.LayoutRes
import dagger.hilt.android.AndroidEntryPoint
import eu.darken.capod.R
import eu.darken.capod.common.coroutine.AppScope
@@ -41,6 +42,7 @@ import kotlinx.coroutines.withTimeout
import java.time.Duration
import javax.inject.Inject
@AndroidEntryPoint
class WidgetProvider : AppWidgetProvider() {
@@ -95,6 +97,27 @@ class WidgetProvider : AppWidgetProvider() {
}
}
/**
* Returns number of cells needed for given size of the widget.
*
* The value is determined in accordance with official guidelines
* for designing widgets, see:
* https://developer.android.com/guide/practices/ui_guidelines/widget_design
*
* Thanks to Jakub S. on Stackoverflow for this solution:
* https://stackoverflow.com/a/37522648/10866268
*
* @param size Widget size in dp.
* @return Size in number of cells.
*/
private fun getCellsForSize(size: Int): Int {
var n = 2
while (70 * n - 30 < size) {
++n
}
return n - 1
}
private suspend fun updateWidget(
context: Context,
widgetManager: AppWidgetManager,
@@ -107,7 +130,23 @@ class WidgetProvider : AppWidgetProvider() {
val layout = when {
!upgradeRepo.isPro() -> createUpgradeRequiredLayout(context)
device is DualPodDevice -> createDualPodLayout(context, device)
device is DualPodDevice -> {
val minWidth = widgetManager.getAppWidgetOptions(widgetId)
.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH)
val columns = getCellsForSize(minWidth)
/* Enable wide widgets only when we are provided with 5 or
* more columns of space.
* Although the minimum size is only 2x1, the safeguards are
* added if these restrictions will ever be loosened in the future.
*/
val layout = when (columns) {
in 1 .. 4 -> R.layout.widget_pod_dual_compact_layout
else -> R.layout.widget_pod_dual_wide_layout
}
createDualPodLayout(context, device, layout)
}
device is SinglePodDevice -> createSinglePodLayout(context, device)
device is PodDevice -> createUnknownPodLayout(context, device)
else -> createNoDeviceLayout(context)
@@ -169,8 +208,9 @@ class WidgetProvider : AppWidgetProvider() {
private fun createDualPodLayout(
context: Context,
podDevice: DualPodDevice,
): RemoteViews = RemoteViews(context.packageName, R.layout.widget_pod_dual_layout).apply {
log(TAG, VERBOSE) { "createSinglePodLayout(context=$context, podDevice=$podDevice)" }
@LayoutRes layout: Int
): RemoteViews = RemoteViews(context.packageName, layout).apply {
log(TAG, VERBOSE) { "createSinglePodLayout(context=$context, podDevice=$podDevice), layout=${layout}" }
val pendingIntent: PendingIntent = PendingIntent.getActivity(
context,
0,
@@ -80,8 +80,6 @@ class MonitorWorker @AssistedInject constructor(
val start = System.currentTimeMillis()
log(TAG, VERBOSE) { "Executing $inputData now (runAttemptCount=$runAttemptCount)" }
setForeground(monitorNotifications.getForegroundInfo(null))
doDoWork()
val duration = System.currentTimeMillis() - start
@@ -108,6 +106,8 @@ class MonitorWorker @AssistedInject constructor(
return
}
setForeground(monitorNotifications.getForegroundInfo(null))
val monitorJob = podMonitor.mainDevice
.setupCommonEventHandlers(TAG) { "PodMonitor" }
.distinctUntilChanged()
@@ -19,7 +19,16 @@ import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.common.hasApiLevel
import eu.darken.capod.common.notifications.PendingIntentCompat
import eu.darken.capod.main.ui.MainActivity
import eu.darken.capod.pods.core.DualPodDevice
import eu.darken.capod.pods.core.HasCase
import eu.darken.capod.pods.core.HasChargeDetection
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.getBatteryLevelCase
import eu.darken.capod.pods.core.getBatteryLevelHeadset
import eu.darken.capod.pods.core.getBatteryLevelLeftPod
import eu.darken.capod.pods.core.getBatteryLevelRightPod
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import javax.inject.Inject
@@ -71,9 +80,65 @@ class MonitorNotifications @Inject constructor(
}
return builder.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
// TODO: Implement a settings pane to allow user to customize this
val stateText = when {
// Pods charging state
// This goes first as pods should not be worn if it is still charging
device is HasChargeDetection && device.isHeadsetBeingCharged -> {
context.getString(eu.darken.capod.common.R.string.pods_charging_label)
}
// Pods wear state
device is HasEarDetection -> {
if (device.isBeingWorn) context.getString(eu.darken.capod.common.R.string.headset_being_worn_label)
else context.getString(eu.darken.capod.common.R.string.headset_not_being_worn_label)
}
// Case charge state
// This is under pods wear state as we don't want it conflicting with it
device is HasCase && device.isCaseCharging -> {
context.getString(eu.darken.capod.common.R.string.pods_charging_label)
}
else -> context.getString(eu.darken.capod.common.R.string.pods_case_unknown_state)
}
val batteryText = when (device) {
is DualPodDevice -> {
val left = device.getBatteryLevelLeftPod(context)
val right = device.getBatteryLevelRightPod(context)
when {
device is HasCase -> {
val case = device.getBatteryLevelCase(context)
"$left $case $right"
}
else -> "$left $right"
}
}
is SinglePodDevice -> {
val headset = device.getBatteryLevelHeadset(context)
when {
device is HasCase -> {
val case = device.getBatteryLevelCase(context)
"$headset $case"
}
else -> headset
}
}
else -> "?"
}
setStyle(NotificationCompat.DecoratedCustomViewStyle())
setCustomContentView(notificationViewFactory.createContentView(device))
setCustomBigContentView(notificationViewFactory.createContentView(device))
setSmallIcon(device.iconRes)
setContentTitle("$batteryText ~ $stateText")
setSubText(null)
log(TAG, VERBOSE) { "updatingNotification(): $device" }
}
@@ -13,7 +13,7 @@
<TextView
android:id="@+id/label_path"
style="@style/TextAppearance.MaterialComponents.Caption"
style="@style/TextAppearance.Material3.TitleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
@@ -30,7 +30,7 @@
<TextView
android:id="@+id/recording_path"
style="@style/TextAppearance.MaterialComponents.Body1"
style="@style/TextAppearance.Material3.BodyMedium"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
@@ -44,7 +44,7 @@
<TextView
android:id="@+id/label_size"
style="@style/TextAppearance.MaterialComponents.Caption"
style="@style/TextAppearance.Material3.TitleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="32dp"
@@ -58,7 +58,7 @@
<TextView
android:id="@+id/recording_size"
style="@style/TextAppearance.AppCompat.Body1"
style="@style/TextAppearance.Material3.BodyMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="32dp"
@@ -70,7 +70,7 @@
<TextView
android:id="@+id/label_compressed_size"
style="@style/TextAppearance.MaterialComponents.Caption"
style="@style/TextAppearance.Material3.TitleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
@@ -83,7 +83,7 @@
<TextView
android:id="@+id/recording_size_compressed"
style="@style/TextAppearance.AppCompat.Body1"
style="@style/TextAppearance.Material3.BodyMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="@+id/label_compressed_size"
+1 -1
View File
@@ -7,7 +7,7 @@
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
style="@style/Widget.MaterialComponents.Toolbar.Primary"
style="@style/Widget.Material3.Toolbar"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
@@ -32,7 +32,7 @@
<com.google.android.material.textview.MaterialTextView
android:id="@+id/body1"
style="@style/TextAppearance.MaterialComponents.Body1"
style="@style/TextAppearance.Material3.BodyLarge"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
@@ -41,7 +41,7 @@
<com.google.android.material.textview.MaterialTextView
android:id="@+id/body2"
style="@style/TextAppearance.MaterialComponents.Body1"
style="@style/TextAppearance.Material3.BodyLarge"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
@@ -50,7 +50,7 @@
<com.google.android.material.textview.MaterialTextView
android:id="@+id/body3"
style="@style/TextAppearance.MaterialComponents.Body1"
style="@style/TextAppearance.Material3.BodyLarge"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
@@ -59,7 +59,7 @@
<com.google.android.material.textview.MaterialTextView
android:id="@+id/body4"
style="@style/TextAppearance.MaterialComponents.Body1"
style="@style/TextAppearance.Material3.BodyLarge"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
@@ -15,7 +15,7 @@
<com.google.android.material.textview.MaterialTextView
android:id="@+id/bluetooth_label"
style="@style/TextAppearance.MaterialComponents.Body1"
style="@style/TextAppearance.Material3.TitleMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
@@ -27,7 +27,7 @@
<com.google.android.material.textview.MaterialTextView
android:id="@+id/bluetooth_description"
style="@style/TextAppearance.MaterialComponents.Body2"
style="@style/TextAppearance.Material3.BodyMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
@@ -15,7 +15,7 @@
<com.google.android.material.textview.MaterialTextView
android:id="@+id/permission_label"
style="@style/TextAppearance.MaterialComponents.Body1"
style="@style/TextAppearance.Material3.TitleMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
@@ -27,7 +27,7 @@
<com.google.android.material.textview.MaterialTextView
android:id="@+id/permission_description"
style="@style/TextAppearance.MaterialComponents.Body2"
style="@style/TextAppearance.Material3.BodyMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
@@ -15,7 +15,7 @@
<com.google.android.material.textview.MaterialTextView
android:id="@+id/request_description"
style="@style/TextAppearance.MaterialComponents.Caption"
style="@style/TextAppearance.Material3.LabelMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
@@ -27,7 +27,7 @@
<com.google.android.material.textview.MaterialTextView
android:id="@+id/permission_label"
style="@style/TextAppearance.MaterialComponents.Body1"
style="@style/TextAppearance.Material3.BodyLarge"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
@@ -39,7 +39,7 @@
<com.google.android.material.textview.MaterialTextView
android:id="@+id/permission_description"
style="@style/TextAppearance.MaterialComponents.Body2"
style="@style/TextAppearance.Material3.BodyMedium"
android:layout_width="match_parent"
android:layout_marginTop="4dp"
android:layout_marginHorizontal="16dp"
@@ -51,7 +51,7 @@
<com.google.android.material.textview.MaterialTextView
android:id="@+id/privacy_policy"
style="@style/TextAppearance.MaterialComponents.Tooltip"
style="@style/TextAppearance.Material3.BodySmall"
android:layout_width="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_height="wrap_content"
@@ -63,7 +63,7 @@
<com.google.android.material.button.MaterialButton
android:id="@+id/grant_action"
style="@style/Widget.MaterialComponents.Button"
style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_height="wrap_content"
@@ -28,7 +28,7 @@
<com.google.android.material.textview.MaterialTextView
android:id="@+id/name"
style="@style/TextAppearance.MaterialComponents.Body2"
style="@style/TextAppearance.Material3.TitleSmall"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
@@ -44,7 +44,7 @@
<com.google.android.material.textview.MaterialTextView
android:id="@+id/last_seen"
style="@style/TextAppearance.MaterialComponents.Caption"
style="@style/TextAppearance.Material3.LabelMedium"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toTopOf="@id/first_seen"
@@ -56,7 +56,7 @@
<com.google.android.material.textview.MaterialTextView
android:id="@+id/first_seen"
style="@style/TextAppearance.MaterialComponents.Caption"
style="@style/TextAppearance.Material3.LabelMedium"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
@@ -68,7 +68,7 @@
<com.google.android.material.textview.MaterialTextView
android:id="@+id/reception"
style="@style/TextAppearance.MaterialComponents.Caption"
style="@style/TextAppearance.Material3.LabelMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
@@ -82,7 +82,7 @@
<ImageView
android:id="@+id/reception_icon"
style="@style/TextAppearance.MaterialComponents.Caption"
style="@style/TextAppearance.Material3.LabelMedium"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginHorizontal="16dp"
@@ -432,7 +432,7 @@
<com.google.android.material.textview.MaterialTextView
android:id="@+id/status"
style="@style/TextAppearance.MaterialComponents.Body2"
style="@style/TextAppearance.Material3.BodyMedium"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
@@ -28,7 +28,7 @@
<com.google.android.material.textview.MaterialTextView
android:id="@+id/name"
style="@style/TextAppearance.MaterialComponents.Body2"
style="@style/TextAppearance.Material3.TitleSmall"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
@@ -44,7 +44,7 @@
<com.google.android.material.textview.MaterialTextView
android:id="@+id/last_seen"
style="@style/TextAppearance.MaterialComponents.Caption"
style="@style/TextAppearance.Material3.LabelMedium"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
@@ -56,7 +56,7 @@
<com.google.android.material.textview.MaterialTextView
android:id="@+id/reception"
style="@style/TextAppearance.MaterialComponents.Caption"
style="@style/TextAppearance.Material3.LabelMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
@@ -70,7 +70,7 @@
<ImageView
android:id="@+id/reception_icon"
style="@style/TextAppearance.MaterialComponents.Caption"
style="@style/TextAppearance.Material3.LabelMedium"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginHorizontal="16dp"
@@ -175,7 +175,7 @@
<com.google.android.material.textview.MaterialTextView
android:id="@+id/status"
style="@style/TextAppearance.MaterialComponents.Body2"
style="@style/TextAppearance.Material3.BodyMedium"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
@@ -15,7 +15,7 @@
<com.google.android.material.textview.MaterialTextView
android:id="@+id/name"
style="@style/TextAppearance.MaterialComponents.Body2"
style="@style/TextAppearance.Material3.TitleSmall"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
@@ -30,7 +30,7 @@
<com.google.android.material.textview.MaterialTextView
android:id="@+id/last_seen"
style="@style/TextAppearance.MaterialComponents.Caption"
style="@style/TextAppearance.Material3.LabelMedium"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
@@ -41,7 +41,7 @@
<com.google.android.material.textview.MaterialTextView
android:id="@+id/reception"
style="@style/TextAppearance.MaterialComponents.Caption"
style="@style/TextAppearance.Material3.LabelMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
@@ -55,7 +55,7 @@
<ImageView
android:id="@+id/reception_icon"
style="@style/TextAppearance.MaterialComponents.Caption"
style="@style/TextAppearance.Material3.LabelMedium"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginHorizontal="16dp"
@@ -71,7 +71,7 @@
<com.google.android.material.textview.MaterialTextView
android:id="@+id/details"
style="@style/TextAppearance.MaterialComponents.Body2"
style="@style/TextAppearance.Material3.BodyMedium"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
@@ -86,7 +86,7 @@
<com.google.android.material.textview.MaterialTextView
android:id="@+id/rawdata_label"
style="@style/TextAppearance.MaterialComponents.Caption"
style="@style/TextAppearance.Material3.LabelMedium"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
@@ -101,7 +101,7 @@
<com.google.android.material.textview.MaterialTextView
android:id="@+id/rawdata"
style="@style/TextAppearance.MaterialComponents.Body2"
style="@style/TextAppearance.Material3.BodyMedium"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
@@ -17,7 +17,7 @@
<com.google.android.material.textview.MaterialTextView
android:id="@+id/pod_label"
style="@style/TextAppearance.MaterialComponents.Body1"
style="@style/TextAppearance.Material3.BodyLarge"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
@@ -30,7 +30,7 @@
<com.google.android.material.textview.MaterialTextView
android:id="@+id/signal"
style="@style/TextAppearance.MaterialComponents.Caption"
style="@style/TextAppearance.Material3.TitleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableEnd="@drawable/ic_baseline_signal_cellular_alt_24"
@@ -72,7 +72,7 @@
<com.google.android.material.textview.MaterialTextView
android:id="@+id/pod_left_battery_label"
style="@style/TextAppearance.MaterialComponents.Body1"
style="@style/TextAppearance.Material3.BodyLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
@@ -116,7 +116,7 @@
<com.google.android.material.textview.MaterialTextView
android:id="@+id/pod_case_battery_label"
style="@style/TextAppearance.MaterialComponents.Body1"
style="@style/TextAppearance.Material3.BodyLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
@@ -159,7 +159,7 @@
<com.google.android.material.textview.MaterialTextView
android:id="@+id/pod_right_battery_label"
style="@style/TextAppearance.MaterialComponents.Body1"
style="@style/TextAppearance.Material3.BodyLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
@@ -17,7 +17,7 @@
<com.google.android.material.textview.MaterialTextView
android:id="@+id/headphones_label"
style="@style/TextAppearance.MaterialComponents.Body1"
style="@style/TextAppearance.Material3.BodyLarge"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
@@ -30,7 +30,7 @@
<com.google.android.material.textview.MaterialTextView
android:id="@+id/signal"
style="@style/TextAppearance.MaterialComponents.Caption"
style="@style/TextAppearance.Material3.TitleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableEnd="@drawable/ic_baseline_signal_cellular_alt_24"
@@ -63,7 +63,7 @@
<com.google.android.material.textview.MaterialTextView
android:id="@+id/headphones_battery_label"
style="@style/TextAppearance.MaterialComponents.Body1"
style="@style/TextAppearance.Material3.BodyLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
@@ -8,7 +8,7 @@
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
style="@style/Widget.MaterialComponents.Toolbar.Primary"
style="@style/Widget.Material3.Toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
@@ -7,7 +7,7 @@
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
style="@style/Widget.MaterialComponents.Toolbar.Primary"
style="@style/Widget.Material3.Toolbar"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
@@ -106,6 +106,7 @@
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textSize="12sp"
android:gravity="center"
android:textStyle="bold"
tools:text="AirPods Max" />
@@ -0,0 +1,130 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
style="@style/PodWidget.Container"
android:orientation="vertical">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="8dp"
android:layout_marginBottom="4dp"
android:orientation="horizontal">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginEnd="12dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:id="@+id/pod_left_icon"
style="@style/PodInfoItemIcon.Notification.Large"
android:src="@drawable/devic_airpods_pro2_left"
tools:ignore="ContentDescription" />
<TextView
android:id="@+id/pod_left_label"
style="@style/PodInfoItemText.Notification"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginHorizontal="4dp"
tools:text="100%" />
<ImageView
android:id="@+id/pod_left_charging"
style="@style/PodInfoItemIcon.Notification"
android:src="@drawable/ic_baseline_power_24"
tools:ignore="ContentDescription" />
<ImageView
android:id="@+id/pod_left_ear"
style="@style/PodInfoItemIcon.Notification"
android:src="@drawable/ic_baseline_hearing_24"
tools:ignore="ContentDescription" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginEnd="12dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:id="@+id/pod_case_icon"
style="@style/PodInfoItemIcon.Notification.Large"
android:src="@drawable/devic_airpods_gen1_case"
tools:ignore="ContentDescription" />
<TextView
android:id="@+id/pod_case_label"
style="@style/PodInfoItemText.Notification"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginHorizontal="4dp"
tools:text="100%" />
<ImageView
android:id="@+id/pod_case_charging"
style="@style/PodInfoItemIcon.Notification"
android:src="@drawable/ic_baseline_power_24" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:id="@+id/pod_right_icon"
style="@style/PodInfoItemIcon.Notification.Large"
android:src="@drawable/devic_airpods_pro2_right"
tools:ignore="ContentDescription" />
<TextView
android:id="@+id/pod_right_label"
style="@style/PodInfoItemText.Notification"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginHorizontal="4dp"
tools:text="100%" />
<ImageView
android:id="@+id/pod_right_charging"
style="@style/PodInfoItemIcon.Notification"
android:src="@drawable/ic_baseline_power_24"
tools:ignore="ContentDescription" />
<ImageView
android:id="@+id/pod_right_ear"
style="@style/PodInfoItemIcon.Notification"
android:src="@drawable/ic_baseline_hearing_24"
tools:ignore="ContentDescription" />
</LinearLayout>
</LinearLayout>
<TextView
android:id="@+id/headphones_label"
style="@style/PodWidget.TextPrimary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="4dp"
android:layout_marginBottom="8dp"
android:gravity="center"
android:textSize="12sp"
android:textStyle="bold"
tools:text="AirPods Max" />
</LinearLayout>
@@ -4,15 +4,6 @@
style="@style/PodWidget.Container"
android:orientation="vertical">
<TextView
android:id="@+id/headphones_label"
style="@style/PodWidget.TextPrimary"
android:layout_width="wrap_content"
android:textStyle="bold"
android:layout_height="wrap_content"
android:layout_gravity="center"
tools:text="AirPods Max" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -45,4 +36,14 @@
android:src="@drawable/ic_baseline_hearing_24"
tools:ignore="ContentDescription" />
</LinearLayout>
<TextView
android:id="@+id/headphones_label"
style="@style/PodWidget.TextPrimary"
android:layout_width="wrap_content"
android:textStyle="bold"
android:gravity="center"
android:layout_height="wrap_content"
android:layout_gravity="center"
tools:text="AirPods Max" />
</LinearLayout>
+5 -1
View File
@@ -18,6 +18,8 @@
app:destination="@id/troubleShooterFragment" />
<action
android:id="@+id/action_overviewFragment_to_onboardingFragment"
app:popUpTo="@id/overviewFragment"
app:popUpToInclusive="true"
app:destination="@id/onboardingFragment" />
</fragment>
@@ -36,7 +38,9 @@
tools:layout="@layout/onboarding_fragment">
<action
android:id="@+id/action_onboardingFragment_to_overviewFragment"
app:destination="@id/overviewFragment" />
app:destination="@id/overviewFragment"
app:popUpToInclusive="true"
app:popUpTo="@id/onboardingFragment" />
</fragment>
</navigation>
+5
View File
@@ -101,4 +101,9 @@
<string name="troubleshooter_ble_result_failure_phone_headphones">Ihr Telefon hat BLE-Daten empfangen, aber die Daten stammen von keinem unterstützten Gerät. Sind Ihre Kopfhörer eingeschaltet? Unterstützt CAPod Ihren Kopfhörer?</string>
<string name="troubleshooter_ble_result_failure_action">Versuchen Sie es erneut</string>
<string name="troubleshoot_action">Fehlerbehebung</string>
<string name="onboarding_body1">Diese App fügt Support für AirPod-Spezifische funkionen zu Android hinzu.</string>
<string name="onboarding_body2">Nicht alle Android-Geräte unterstützen die zusätzlichen AirPod-Funktionen vollständig. Ihr Betriebssystem erfordert eine korrekt funktionierende Bluetooth-Low-Energy-Implementierung.</string>
<string name="onboarding_body3">CAPod hat keine Werbung und sammelt Ihre Daten nicht.</string>
<string name="onboarding_body4">Sie können auf CAPod Pro upgraden, um zusätzliche Funktionen zu erhalten und die Entwicklung zu unterstützen.</string>
<string name="general_continue_action">Fortfahren</string>
</resources>
+9
View File
@@ -45,7 +45,10 @@
<string name="settings_maindevice_model_description">Model perangkat utama anda. Ini membantu aplikasi mengenali perangkat anda saat tidak terhubung ke ponsel anda.</string>
<string name="settings_onepod_mode_label">Mode satu pod</string>
<string name="settings_onepod_mode_description">Menggunakan kedua pod tidak diperlukan, memakai satu pod sudah cukup untuk memicu reaksi.</string>
<string name="settings_popup_caseopen_label">Tampilkan popup kasing</string>
<string name="settings_popup_caseopen_description">Tampilkan popup saat kasing perangkat dibuka (eksperimental).</string>
<string name="settings_popup_connected_label">Tampilkan popup saat terkoneksi</string>
<string name="settings_popup_connected_description">Tampilkan popup ketika perangkat terhubung pertama kalinya.</string>
<string name="notification_channel_device_status_label">Status perangkat</string>
<string name="debug_debuglog_size_label">Ukuran</string>
<string name="debug_debuglog_size_compressed_label">Ukuran terkompresi</string>
@@ -94,7 +97,13 @@
<string name="troubleshooter_ble_result_success_body">Siaran iklan BLE diterima oleh CAPod.</string>
<string name="troubleshooter_ble_result_failure_title">Gagal</string>
<string name="troubleshooter_ble_result_failure_body">Pemecahan masalah gagal. Tidak ada kombinasi opsi kompatibilitas yang membantu.</string>
<string name="troubleshooter_ble_result_failure_phone_body">Ponsel Anda tidak menerima data BLE sama sekali. Anda dapat mencoba lagi pengujian ini di area yang ramai untuk melihat apakah sumber data (selain headphone Anda) dapat diterima. Tidak ada data yang diterima menunjukkan masalah dengan sistem operasi ponsel Anda.</string>
<string name="troubleshooter_ble_result_failure_phone_headphones">Telepon anda menerima data BLE, tetapi data tersebut tidak berasal dari perangkat yang didukung. Apakah headphone Anda dihidupkan? Apakah CAPod mendukung headphone Anda?</string>
<string name="troubleshooter_ble_result_failure_action">Coba lagi</string>
<string name="troubleshoot_action">Memecahkan masalah</string>
<string name="onboarding_body1">Aplikasi ini menambahkan bantuan untuk fitur spesifik AirPod untuk Android.</string>
<string name="onboarding_body2">Tidak semua perangkat Android mendukung fitur AirPod ekstra. Sistem Os anda memerlukan implementasi Bluetooth-Low-Energy yang bekerja.</string>
<string name="onboarding_body3">CAPod tidak menampilkan iklan dan tidak mengambil data anda.</string>
<string name="onboarding_body4">Anda bisa membeli CAPod Pro agar bisa menggunakan fitur ekstra dan mendukung pengembangan aplikasi.</string>
<string name="general_continue_action">Lanjutkan</string>
</resources>
+9 -10
View File
@@ -7,24 +7,24 @@
<string name="general_upgrade_action">Aggiorna</string>
<string name="general_check_action">Controlla</string>
<string name="general_close_action">Chiudi</string>
<string name="upgrade_capod_label">Aggiorna CAPod</string>
<string name="upgrade_capod_label">Passa a CAPod PRO</string>
<string name="upgrade_capod_description">Ottieni funzionalità aggiuntive e supporta lo sviluppatore.</string>
<string name="settings_monitor_mode_label">Modalità monitoraggio</string>
<string name="settings_monitor_mode_description">Sotto quali circostanze questa applicazione monitora i dati Bluetooth.</string>
<string name="settings_monitor_mode_label">Modalità di monitoraggio</string>
<string name="settings_monitor_mode_description">Circostanze in cui questa applicazione monitora i dati Bluetooth.</string>
<string name="settings_scanner_mode_label">Modalità di scansione</string>
<string name="settings_scanner_mode_description">Il Bluetooth a basso consumo energetico dovrebbe dare priorità alle performance o risparmiare batteria?</string>
<string name="settings_scanner_mode_description">La scansione dei dati BLE deve dare priorità alle performance o al risparmio di batteria?</string>
<string name="settings_autopause_label">Pausa automatica</string>
<string name="settings_autopause_description">Metti in pausa l\'audio quando viene rimossa la cuffia da un orecchio.</string>
<string name="settings_showall_label">Mostra tutti i dispositivi</string>
<string name="settings_showall_description">Mostra i dispositivi delle altre persone vicine a te.</string>
<string name="settings_autopplay_label">Riproduzione automatica</string>
<string name="settings_autoplay_description">Avvia riproduzione audio quando il dispositivo è indossato.</string>
<string name="settings_fake_data_label">Dati immaginari</string>
<string name="settings_fake_data_label">Dati fittizzi</string>
<string name="settings_fake_data_description">Mostra dati fittizi, ovvero simula dispositivi che in realtà non esistono.</string>
<string name="settings_debug_label">Impostazioni di debug</string>
<string name="settings_debug_description">Impostazioni aggiuntive che possono aiutare a risolvere i problemi con l\'applicazione.</string>
<string name="settings_signal_minimum_label">Qualità del segnale minima</string>
<string name="settings_signal_minimum_description">La minima qualità del segnale che il dispositivo necessità per poter essere considerato tuo.</string>
<string name="settings_debug_description">Impostazioni aggiuntive che possono aiutare a risolvere i problemi dell\'app.</string>
<string name="settings_signal_minimum_label">Qualità minima di segnale</string>
<string name="settings_signal_minimum_description">La qualità minima di segnale che il dispositivo necessita per essere considerato tuo.</string>
<string name="settings_autoconnect_label">Connessione automatica</string>
<string name="settings_autoconnect_description">Se Android non si connette automaticamente, possiamo chiedergli di farlo. Questo imposterà la modalità di monitoraggio su \'Sempre\'.</string>
<string name="settings_autoconnect_condition_label">Condizione per la connessione automatica</string>
@@ -83,8 +83,7 @@
<string name="help_translate_label">Traduzione</string>
<string name="help_translate_description">Aiuta a tradurre l\'applicazione nella tua lingua preferita.</string>
<string name="translators_thanks_title">Traduttori</string>
<string name="translators_thanks_description">darken
Filippo Lolli</string>
<string name="translators_thanks_description">Filippo Lolli (IG: @lolli_filippo)</string>
<string name="widget_description">Un widget che mostra l\'ultimo stato del dispositivo.</string>
<string name="settings_compat_indirectcallback_title">Consegna indiretta dei dati</string>
<string name="settings_compat_indirectcallback_summary">Utilizza un metodo alternativo per ricevere dati BLE dal sistema (broadcast invece di callback).</string>
+1
View File
@@ -28,6 +28,7 @@
<item name="colorSurfaceInverse">@color/md_theme_dark_inverseSurface</item>
<item name="colorPrimaryInverse">@color/md_theme_dark_primaryInverse</item>
<item name="android:statusBarColor">@android:color/transparent</item>
<item tools:targetApi="29" name="android:enforceNavigationBarContrast">true</item>
<item tools:targetApi="29" name="android:navigationBarColor">@android:color/transparent</item>
</style>
+4 -4
View File
@@ -45,7 +45,7 @@
<string name="settings_maindevice_model_description">Model twojego głównego urządzenia. Pomaga aplikacji rozpoznać twoje urządzenie, gdy nie jest połączone z telefonem.</string>
<string name="settings_onepod_mode_label">Tryb jednego pod\'a</string>
<string name="settings_onepod_mode_description">Noszenie obu pod\'ów nie jest wymagane. Wystarczy jeden, aby aktywować reakcje.</string>
<string name="settings_popup_caseopen_label">Wyświetl okienko przybornika</string>
<string name="settings_popup_caseopen_label">Wyświetl okienko obudowy</string>
<string name="settings_popup_caseopen_description">Wyświetla informację, gdy obudowa urządzenia jest otwarta (eksperymentalnie).</string>
<string name="settings_popup_connected_label">Wyświetl okienko połączenia</string>
<string name="settings_popup_connected_description">Wyświetla okienko, gdy urządzenie łączy się po raz pierwszy.</string>
@@ -89,7 +89,7 @@
<string name="settings_compat_indirectcallback_summary">Użyj alternatywnej metody odbierania danych BLE z systemu (transmisja zamiast połączenia zwrotnego).</string>
<string name="troubleshooter_title">Rozwiązywanie problemów</string>
<string name="troubleshooter_ble_intro_title">Transmisja Bluetooth Low Energy</string>
<string name="troubleshooter_ble_intro_body1">AirPods (i podobne słuchawki) transmitują informacje o stanie za pomocą technologii BLE zwanej „reklamami”. Niektóre telefony nie obsługują tej technologii poprawnie. CAPod może spróbować to naprawić, wypróbowując różne opcje zgodności, dopóki dane nie zostaną odebrane. Rozpocznij odtwarzanie muzyki na słuchawkach i umieść je blisko telefonu, a następnie rozpocznij proces.</string>
<string name="troubleshooter_ble_intro_body1">AirPods (i podobne słuchawki) transmitują informacje o stanie za pomocą technologii BLE zwanej „reklamami”. Niektóre telefony nie obsługują tej technologii poprawnie. CAPod może spróbować to naprawić, testując różne opcje zgodności, dopóki dane nie zostaną odebrane. Rozpocznij odtwarzanie muzyki na słuchawkach i umieść je blisko telefonu, a następnie rozpocznij proces.</string>
<string name="troubleshooter_ble_intro_start_action">Rozpocznij rozwiązywanie problemów</string>
<string name="troubleshooter_ble_process_title">Rozwiązywanie problemów w toku</string>
<string name="troubleshooter_ble_process_subtile">Każdy etap zajmuje 510 sekund</string>
@@ -97,8 +97,8 @@
<string name="troubleshooter_ble_result_success_body">Audycje reklamowe BLE są odbierane przez CAPod.</string>
<string name="troubleshooter_ble_result_failure_title">Niepowodzenie</string>
<string name="troubleshooter_ble_result_failure_body">Rozwiązywanie problemów nie powiodło się. Nie pomogła żadna kombinacja opcji kompatybilności.</string>
<string name="troubleshooter_ble_result_failure_phone_body">Twój telefon nie odebrał żadnych danych BLE. Możesz ponowić test w zatłoczonym obszarze, aby sprawdzić, czy źródła danych (inne niż twoje słuchawki) mogą być odbierane. Brak odbioru danych wskazuje na problem z systemem operacyjnym telefonu.</string>
<string name="troubleshooter_ble_result_failure_phone_headphones">Twój telefon odebrał dane BLE, jednak nie pochodzą one od obsługiwanego urządzenia. Czy twoje słuchawki są włączone? Czy CAPod obsługuje twoje słuchawki?</string>
<string name="troubleshooter_ble_result_failure_phone_body">Twój telefon nie odebrał żadnych danych BLE. Możesz ponowić test w zatłoczonym obszarze, aby sprawdzić, czy źródła danych (inne niż Twoje słuchawki) mogą być odbierane. Brak odbioru danych wskazuje na problem z systemem operacyjnym telefonu.</string>
<string name="troubleshooter_ble_result_failure_phone_headphones">Twój telefon odebrał dane BLE, jednak nie pochodzą one z obsługiwanego urządzenia. Czy Twoje słuchawki są włączone? Czy CAPod obsługuje Twoje słuchawki?</string>
<string name="troubleshooter_ble_result_failure_action">Spróbuj ponownie</string>
<string name="troubleshoot_action">Rozwiązywanie problemów</string>
<string name="onboarding_body1">Ta aplikacja dodaje wsparcie dla funkcji AirPods na Androidzie.</string>
@@ -45,7 +45,10 @@
<string name="settings_maindevice_model_description">O modelo do seu dispositivo principal. Isso ajuda o aplicativo a reconhecer seu dispositivo quando ele não está conectado ao seu telefone.</string>
<string name="settings_onepod_mode_label">Modo de um airpod</string>
<string name="settings_onepod_mode_description">Não é necessário usar os dois airpods, usar um único airpod é suficiente para destravar reações.</string>
<string name="settings_popup_caseopen_label">Mostrar pop-up da case</string>
<string name="settings_popup_caseopen_description">Mostrar um pop-up quando o estojo do dispositivo for aberto (experimental).</string>
<string name="settings_popup_connected_label">Mostrar pop-up de conexão</string>
<string name="settings_popup_connected_description">Mostrar um pop-up quando o dispositivo se conectar pela primeira vez.</string>
<string name="notification_channel_device_status_label">Status do dispositivo</string>
<string name="debug_debuglog_size_label">Tamanho</string>
<string name="debug_debuglog_size_compressed_label">Tamanho compactado</string>
@@ -94,7 +97,13 @@
<string name="troubleshooter_ble_result_success_body">As transmissões de anúncios BLE estão sendo recebidas pelo CAPod.</string>
<string name="troubleshooter_ble_result_failure_title">Sem sucesso</string>
<string name="troubleshooter_ble_result_failure_body">A solução de problemas falhou. Nenhuma combinação de opções de compatibilidade ajudou.</string>
<string name="troubleshooter_ble_result_failure_phone_body">Seu telefone não recebeu nenhum dado BLE. Você pode tentar novamente este teste em uma área lotada para ver se fontes de dados (além dos fones de ouvido) podem ser recebidas. Nenhum dado recebido indica um problema no sistema operacional do seu telefone.</string>
<string name="troubleshooter_ble_result_failure_phone_headphones">Seu telefone recebeu dados BLE, mas os dados não vêm de nenhum dispositivo compatível. Seus fones de ouvido estão ligados? O CAPod suporta seu fone de ouvido?</string>
<string name="troubleshooter_ble_result_failure_action">Tentar novamente</string>
<string name="troubleshoot_action">Solucionar problemas</string>
<string name="onboarding_body1">Este aplicativo adiciona suporte para recursos específicos do AirPod ao Android.</string>
<string name="onboarding_body2">Nem todos os dispositivos Android oferecem suporte total aos recursos extras do AirPod. Seu sistema operacional requer uma implementação de Bluetooth-Low-Energy funcionando corretamente.</string>
<string name="onboarding_body3">CAPod não possui anúncios e não coleta seus dados.</string>
<string name="onboarding_body4">Você pode atualizar para o CAPod Pro para obter recursos extras e apoiar o desenvolvimento.</string>
<string name="general_continue_action">Continuar</string>
</resources>
+108 -1
View File
@@ -1,2 +1,109 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation"></resources>
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
<string name="general_share_action">Distribuie</string>
<string name="general_done_action">Efectuat</string>
<string name="general_copy_action">Copiază</string>
<string name="general_thank_you_label">Mulțumesc</string>
<string name="general_upgrade_action">Actualizare</string>
<string name="general_check_action">Verifică</string>
<string name="general_close_action">Închide</string>
<string name="upgrade_capod_label">Actualizați CaPod</string>
<string name="upgrade_capod_description">Obțineți funcții suplimentare și sprijiniți dezvoltatorul.</string>
<string name="settings_monitor_mode_label">Modul monitorizare</string>
<string name="settings_monitor_mode_description">În ce circumstanțe această aplicație monitorizează datele Bluetooth.</string>
<string name="settings_scanner_mode_label">Modul scanare</string>
<string name="settings_scanner_mode_description">Scanerul de date Bluetooth Energie Scăzută (BLE) ar trebui să prioritizeze performanța sau să economisească energia?</string>
<string name="settings_autopause_label">Pauză automată</string>
<string name="settings_autopause_description">Întrerupeți sunetul când scoateți dispozitivul din ureche.</string>
<string name="settings_showall_label">Arată toate dispozitivele</string>
<string name="settings_showall_description">Afișați dispozitivele altor persoane care sunt în apropierea dvs.</string>
<string name="settings_autopplay_label">Redare automată</string>
<string name="settings_autoplay_description">Porniți redarea audio când dispozitivul este purtat.</string>
<string name="settings_fake_data_label">Date false</string>
<string name="settings_fake_data_description">Afișați date false, ex: simulați dispozitive care nu există.</string>
<string name="settings_debug_label">Setări de depanare</string>
<string name="settings_debug_description">Setări suplimentare pentru a ajuta la depanarea problemelor cu aplicația.</string>
<string name="settings_signal_minimum_label">Calitatea minimă a semnalului</string>
<string name="settings_signal_minimum_description">Calitatea minimă a semnalului pe care trebuie să o aibă un dispozitiv trebuie să fie considerată a dvs.</string>
<string name="settings_autoconnect_label">Conectare automată</string>
<string name="settings_autoconnect_description">Dacă Android nu se conectează automat, îl putem întreba și noi. Aceasta va seta setarea modului monitorizare la „Întotdeauna”.</string>
<string name="settings_autoconnect_condition_label">Condiția de conectare automată</string>
<string name="settings_autoconnect_condition_description">Când ar trebui să încercăm să ne conectăm la dispozitivul dvs.?</string>
<string name="settings_reaction_label">Reacții</string>
<string name="settings_reaction_description">Reacționați la evenimente și comportamente.</string>
<string name="settings_category_yourdevice_label">Dispozitivul tău</string>
<string name="settings_category_compatibility_options_title">Opțiuni de compatibilitate</string>
<string name="settings_category_compatibility_options_description">Nu atingeți dacă totul funcționează ;)</string>
<string name="settings_compat_offloaded_filtering_disabled_title">Dezactivați filtrarea hardware-ului</string>
<string name="settings_compat_offloaded_filtering_disabled_summary">Nu delegați filtrarea datelor către sistem, în schimb, obțineți toate datele și filtrați în cadrul aplicației.</string>
<string name="settings_compat_offloaded_batching_disabled_title">Dezactivați lotizarea hardware</string>
<string name="settings_compat_offloaded_batching_disabled_summary">Nu lăsați sistemul să grupeze datele BLE colectate înainte de a ni le transmite.</string>
<string name="settings_maindevice_address_label">Adresa dispozitivului dvs.</string>
<string name="settings_maindevice_address_description">Adresa dispozitivului dvs. asociat. Aplicația utilizează aceasta pentru a determina când este conectată la telefonul dvs.</string>
<string name="settings_maindevice_address_none">Niciunul</string>
<string name="settings_maindevice_model_label">Modelul dispozitivului dvs.</string>
<string name="settings_maindevice_model_description">Modelul dispozitivului principal. Acest lucru ajută aplicația să recunoască dispozitivul dvs. atunci când nu este conectat la telefonul dvs.</string>
<string name="settings_onepod_mode_label">Modul de utilizare o singură cască</string>
<string name="settings_onepod_mode_description">Nu este necesar să purtați ambele caști, o singură cască este suficientă pentru a declanșa reacțiile.</string>
<string name="settings_popup_caseopen_label">Afișați pop-upul carcasei</string>
<string name="settings_popup_caseopen_description">Afișați un pop-up când este deschisă carcasa dispozitivului (experimental).</string>
<string name="settings_popup_connected_label">Afișați pop-upul conexiunii</string>
<string name="settings_popup_connected_description">Afișați un pop-up când dispozitivul se conectează pentru prima dată.</string>
<string name="notification_channel_device_status_label">Starea dispozitivului</string>
<string name="debug_debuglog_size_label">Dimensiune</string>
<string name="debug_debuglog_size_compressed_label">Dimensiune comprimată</string>
<string name="debug_notification_channel_label">Notificări de depanare</string>
<string name="debug_debuglog_file_label">Fișier jurnal înregistrat</string>
<string name="debug_debuglog_record_action">Înregistrați jurnalul de depanare</string>
<string name="settings_support_installid_label">ID de instalare</string>
<string name="settings_support_installid_desc">Rapoartele automate de eroare sunt anonime. Partajați ID-ul de instalare dacă dezvoltatorul trebuie să vă găsească rapoartele de eroare.</string>
<string name="settings_support_label">Suport</string>
<string name="settings_support_description">Dacă ai nevoie de ajutor.</string>
<string name="issue_tracker_label">Detector de probleme</string>
<string name="issue_tracker_description">Un instrument public de urmărire a problemelor pentru rapoartele de erori și solicitările de funcții (numai în limba engleză).</string>
<string name="discord_label">Discord</string>
<string name="discord_description">Un loc în care să stai și să pui întrebări.</string>
<string name="changelog_label">Jurnalul modificărilor</string>
<string name="settings_label">Setări</string>
<string name="settings_privacy_policy_label">Politica de confidențialitate</string>
<string name="settings_privacy_policy_desc">Responsabilitatea gestionării datelor.</string>
<string name="settings_licenses_label">Licențe</string>
<string name="settings_category_other_label">Altele</string>
<string name="settings_general_label">Setări</string>
<string name="settings_general_description">Ajustări generale care afectează întreaga aplicație.</string>
<string name="settings_acknowledgements_label">Mulțumiri</string>
<string name="settings_debug_autoreports_label">Rapoarte automate de erori</string>
<string name="settings_debug_autoreports_description">Raportează automat probleme, ex: detalii despre o blocare a aplicației, astfel încât să îmi dau seama cum să o rezolv.</string>
<string name="settings_debug_mode_label">Modul de depanare</string>
<string name="settings_debug_mode_description">Afișați informații suplimentare pentru a remedia problemele.</string>
<string name="settings_blescanner_unfiltered_label">Date BLE nefiltrate</string>
<string name="settings_blescanner_unfiltered_description">Eliminați toate filtrele din scanerul BLE pentru a afișa toate datele BLE difuzate. Util pentru a adăuga suport pentru noile tipuri de căști.</string>
<string name="settings_compatibility_mode_label">Mod de compatibilitate</string>
<string name="settings_compatibility_mode_description">Dezactivați optimizările pentru a îmbunătăți compatibilitatea. Încercați acest lucru dacă nu vedeți nicio dată.</string>
<string name="help_translate_label">Traducere</string>
<string name="help_translate_description">Ajută la traducerea acestei aplicații în limba ta preferată.</string>
<string name="translators_thanks_title">Traducători</string>
<string name="translators_thanks_description">Darken</string>
<string name="widget_description">Un widget care arată starea ultimului dispozitiv cunoscut.</string>
<string name="settings_compat_indirectcallback_title">Livrarea indirectă a datelor</string>
<string name="settings_compat_indirectcallback_summary">Utilizați o metodă alternativă pentru a primi date BLE de la sistem (difuzare în loc de apel invers).</string>
<string name="troubleshooter_title">Depanator</string>
<string name="troubleshooter_ble_intro_title">Transmisii Bluetooth Energie Scăzută (BLE)</string>
<string name="troubleshooter_ble_intro_body1">AirPod-urile (și căștile similare) transmit informații despre stare folosind o tehnologie BLE numită „reclame”. Unele telefoane nu implementează corect această tehnologie. CAPod poate încerca să remedieze acest lucru încercând diferite opțiuni de compatibilitate până la primirea datelor. Porniți redarea muzicii pe căști și plasați-le aproape de telefon, apoi începeți procesul.</string>
<string name="troubleshooter_ble_intro_start_action">Începeți depanarea</string>
<string name="troubleshooter_ble_process_title">Depanare în progres</string>
<string name="troubleshooter_ble_process_subtile">Fiecare pas durează 5-10 secunde</string>
<string name="troubleshooter_ble_result_success_title">Succes</string>
<string name="troubleshooter_ble_result_success_body">Emisiunile de reclame BLE sunt recepționate de CAPod.</string>
<string name="troubleshooter_ble_result_failure_title">Eșuat</string>
<string name="troubleshooter_ble_result_failure_body">Depanarea a eșuat. Nicio combinație de compatibilitate nu a ajutat.</string>
<string name="troubleshooter_ble_result_failure_phone_body">Telefonul dvs. nu a primit deloc date BLE. Puteți reîncerca acest test într-o zonă aglomerată pentru a vedea dacă sursele de date (altele decât căștile) pot fi recepționate. Nicio dată primită indică o problemă cu sistemul de operare al telefonului dvs.</string>
<string name="troubleshooter_ble_result_failure_phone_headphones">Telefonul dvs. a primit date BLE, dar datele nu provin de la niciun dispozitiv acceptat. Căștile tale sunt pornite? CAPod vă acceptă căștile?</string>
<string name="troubleshooter_ble_result_failure_action">Încearcă din nou</string>
<string name="troubleshoot_action">Depanare</string>
<string name="onboarding_body1">Această aplicație adaugă suport pentru caracteristicile specifice AirPod pentru Android.</string>
<string name="onboarding_body2">Nu toate dispozitivele Android acceptă pe deplin funcțiile suplimentare AirPod. Sistemul dvs. de operare necesită o implementare Bluetooth Energie Scăzută (BLE) care funcționează corect.</string>
<string name="onboarding_body3">CAPod nu are reclame și nu colectează datele dumneavoastră.</string>
<string name="onboarding_body4">Puteți face upgrade la CAPod Pro pentru a obține funcții suplimentare și pentru a sprijini dezvoltarea.</string>
<string name="general_continue_action">Continuă</string>
</resources>
+1 -1
View File
@@ -66,7 +66,7 @@
<string name="changelog_label">Sürüm günlüğü</string>
<string name="settings_label">Ayarlar</string>
<string name="settings_privacy_policy_label">Gizlilik politikası</string>
<string name="settings_privacy_policy_desc">Verileri sorumlu bir şekilde işler.</string>
<string name="settings_privacy_policy_desc">Verilerinizi sorumlu bir şekilde işliyoruz.</string>
<string name="settings_licenses_label">Lisanslar</string>
<string name="settings_category_other_label">Diğer</string>
<string name="settings_general_label">Ayarlar</string>
+1 -1
View File
@@ -122,7 +122,7 @@
<string name="onboarding_body1">This app adds support for AirPod specific features to Android.</string>
<string name="onboarding_body2">Not all Android devices fully support the extra AirPod features. Your operating system requires a correctly working Bluetooth-Low-Energy implemtation.</string>
<string name="onboarding_body2">Not all Android devices fully support the extra AirPod features. Your operating system requires a correctly working Bluetooth-Low-Energy implementation.</string>
<string name="onboarding_body3">CAPod has no ads and does not collect your data.</string>
<string name="onboarding_body4">You can upgrade to CAPod Pro to gain extra features and support development.</string>
+9 -4
View File
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="MyCardView" parent="Widget.MaterialComponents.CardView">
<style name="MyCardView" parent="Widget.Material3.CardView.Outlined">
<item name="android:layout_marginTop">8dp</item>
<item name="android:layout_marginStart">8dp</item>
<item name="android:layout_marginEnd">8dp</item>
@@ -24,13 +24,18 @@
<item name="tint">?android:attr/colorAccent</item>
</style>
<style name="PodInfoItemIcon.Notification.Large" parent="PodInfoItemIcon.Notification">
<item name="android:layout_height">40dp</item>
<item name="android:layout_width">40dp</item>
</style>
<style name="PodInfoItemText.Notification" parent="TextAppearance.Compat.Notification.Title">
<item name="android:singleLine">true</item>
<item name="android:ellipsize">end</item>
<item name="android:textColor">?android:attr/textColorPrimary</item>
</style>
<style name="PodInfoItemIcon" parent="TextAppearance.MaterialComponents.Body2">
<style name="PodInfoItemIcon" parent="TextAppearance.Material3.BodyMedium">
<item name="android:layout_height">20dp</item>
<item name="android:layout_width">20dp</item>
<item name="android:tintMode">multiply</item>
@@ -43,7 +48,7 @@
<item name="android:layout_width">24dp</item>
</style>
<style name="PodInfoItemText" parent="TextAppearance.MaterialComponents.Body2">
<style name="PodInfoItemText" parent="TextAppearance.Material3.BodyMedium">
<item name="android:singleLine">true</item>
<item name="android:ellipsize">end</item>
</style>
@@ -72,4 +77,4 @@
<style name="PodWidget.TextSecondary">
<item name="android:textColor">?android:attr/textColorSecondary</item>
</style>
</resources>
</resources>
+2 -1
View File
@@ -28,7 +28,8 @@
<item name="colorSurfaceInverse">@color/md_theme_light_inverseSurface</item>
<item name="colorPrimaryInverse">@color/md_theme_light_primaryInverse</item>
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:windowLightStatusBar">true</item>
<item tools:targetApi="29" name="android:enforceNavigationBarContrast">true</item>
<item tools:targetApi="29" name="android:navigationBarColor">@android:color/transparent</item>
@@ -31,6 +31,15 @@
android:action="android.intent.action.VIEW"
android:data="https://github.com/adolfintel/OpenPods" />
</eu.darken.capod.common.preferences.IntentPreference>
<eu.darken.capod.common.preferences.IntentPreference
android:key="thanks.magicpods"
android:summary="Pioneering AirPod support on Windows"
android:title="MagicPods project"
app:iconSpaceReserved="false">
<intent
android:action="android.intent.action.VIEW"
android:data="https://github.com/steam3d/MagicPods-Windows" />
</eu.darken.capod.common.preferences.IntentPreference>
<eu.darken.capod.common.preferences.IntentPreference
android:key="thanks.furiousmac"
android:summary="Research on the continuity protocol"
+3 -3
View File
@@ -6,19 +6,19 @@
android:key="debug.log.record"
android:title="@string/debug_debuglog_record_action" />
<CheckBoxPreference
<eu.darken.capod.common.preferences.MaterialSwitchPreference
android:icon="@drawable/ic_baseline_bug_report_24"
android:key="debug.mode.enabled"
android:summary="@string/settings_debug_mode_description"
android:title="@string/settings_debug_mode_label" />
<CheckBoxPreference
<eu.darken.capod.common.preferences.MaterialSwitchPreference
android:icon="@drawable/ic_baseline_data_array_24"
android:key="debug.fakedata.enabled"
android:summary="@string/settings_fake_data_description"
android:title="@string/settings_fake_data_label" />
<CheckBoxPreference
<eu.darken.capod.common.preferences.MaterialSwitchPreference
android:icon="@drawable/ic_baseline_devices_other_24"
android:key="debug.blescanner.unfiltered.enabled"
android:summary="@string/settings_blescanner_unfiltered_description"
+4 -4
View File
@@ -14,7 +14,7 @@
android:summary="@string/settings_scanner_mode_description"
android:title="@string/settings_scanner_mode_label" />
<CheckBoxPreference
<eu.darken.capod.common.preferences.MaterialSwitchPreference
android:icon="@drawable/ic_baseline_devices_other_24"
android:key="core.showall.enabled"
android:summary="@string/settings_showall_description"
@@ -49,19 +49,19 @@
android:title="@string/settings_category_compatibility_options_title"
app:icon="@drawable/ic_chip_24">
<CheckBoxPreference
<eu.darken.capod.common.preferences.MaterialSwitchPreference
android:icon="@drawable/ic_filter_cog_outline_24"
android:key="core.compat.offloaded.filtering.disabled"
android:summary="@string/settings_compat_offloaded_filtering_disabled_summary"
android:title="@string/settings_compat_offloaded_filtering_disabled_title" />
<CheckBoxPreference
<eu.darken.capod.common.preferences.MaterialSwitchPreference
android:icon="@drawable/ic_format_list_group_24"
android:key="core.compat.offloaded.batching.disabled"
android:summary="@string/settings_compat_offloaded_batching_disabled_summary"
android:title="@string/settings_compat_offloaded_batching_disabled_title" />
<CheckBoxPreference
<eu.darken.capod.common.preferences.MaterialSwitchPreference
android:icon="@drawable/ic_strategy_24"
android:key="core.compat.indirectcallback.enabled"
android:summary="@string/settings_compat_indirectcallback_summary"
@@ -1,26 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<CheckBoxPreference
<eu.darken.capod.common.preferences.MaterialSwitchPreference
android:icon="@drawable/ic_baseline_looks_one_24"
android:key="reaction.onepod.enabled"
android:summary="@string/settings_onepod_mode_description"
android:title="@string/settings_onepod_mode_label" />
<CheckBoxPreference
<eu.darken.capod.common.preferences.MaterialSwitchPreference
android:icon="@drawable/ic_baseline_play_circle_24"
android:key="reaction.autoplay.enabled"
android:summary="@string/settings_autoplay_description"
android:title="@string/settings_autopplay_label" />
<CheckBoxPreference
<eu.darken.capod.common.preferences.MaterialSwitchPreference
android:icon="@drawable/ic_baseline_pause_circle_24"
android:key="reaction.autopause.enabled"
android:summary="@string/settings_autopause_description"
android:title="@string/settings_autopause_label" />
<CheckBoxPreference
<eu.darken.capod.common.preferences.MaterialSwitchPreference
android:icon="@drawable/ic_baseline_bluetooth_connected_24"
android:key="reaction.autoconnect.enabled"
android:summary="@string/settings_autoconnect_description"
@@ -32,13 +32,13 @@
android:summary="@string/settings_autoconnect_condition_description"
android:title="@string/settings_autoconnect_condition_label" />
<CheckBoxPreference
<eu.darken.capod.common.preferences.MaterialSwitchPreference
android:icon="@drawable/ic_message_outline_24"
android:key="reaction.popup.caseopen"
android:summary="@string/settings_popup_caseopen_description"
android:title="@string/settings_popup_caseopen_label" />
<CheckBoxPreference
<eu.darken.capod.common.preferences.MaterialSwitchPreference
android:icon="@drawable/ic_message_24"
android:key="reaction.popup.connected"
android:summary="@string/settings_popup_connected_description"
+4 -4
View File
@@ -18,19 +18,19 @@
android:data="https://github.com/d4rken-org/capod/issues" />
</eu.darken.capod.common.preferences.IntentPreference>
<!-- Hide Install-ID and bug reporting settings, but keep for possible use in the future.
<PreferenceCategory app:title="@string/settings_category_other_label">
<Preference
android:icon="@drawable/ic_id_onsurface"
android:key="support.installid"
android:summary="@string/settings_support_installid_desc"
android:title="@string/settings_support_installid_label"
app:isPreferenceVisible="false" />
android:title="@string/settings_support_installid_label" />
<CheckBoxPreference
android:icon="@drawable/ic_spider_thread_onsurface"
android:key="debug.bugreport.automatic.enabled"
app:isPreferenceVisible="false"
android:summary="@string/settings_debug_autoreports_description"
android:title="@string/settings_debug_autoreports_label" />
</PreferenceCategory>
</PreferenceScreen>
-->
</PreferenceScreen>
+2 -2
View File
@@ -4,7 +4,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath("com.android.tools.build:gradle:8.0.2")
classpath("com.android.tools.build:gradle:8.1.2")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${Versions.Kotlin.core}")
classpath("com.google.dagger:hilt-android-gradle-plugin:${Versions.Dagger.core}")
classpath("androidx.navigation:navigation-safe-args-gradle-plugin:${Versions.AndroidX.Navigation.core}")
@@ -20,4 +20,4 @@ allprojects {
tasks.register("clean").configure {
delete("build")
}
}
+2 -2
View File
@@ -8,7 +8,7 @@ repositories {
mavenCentral()
}
dependencies {
implementation("com.android.tools.build:gradle:8.0.2")
implementation("com.android.tools.build:gradle:8.1.2")
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.0")
implementation("com.squareup:javapoet:1.13.0")
}
}
+6 -6
View File
@@ -84,8 +84,8 @@ fun DependencyHandlerScope.addBaseWorkManager() {
}
fun DependencyHandlerScope.addBaseAndroid() {
implementation("androidx.core:core-ktx:1.10.0-rc01")
implementation("androidx.annotation:annotation:1.4.0")
implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.annotation:annotation:1.7.0")
implementation("androidx.collection:collection-ktx:1.2.0")
implementation("androidx.preference:preference-ktx:1.2.0")
}
@@ -95,12 +95,12 @@ fun DependencyHandlerScope.addBaseAndroidUi() {
implementation("androidx.constraintlayout:constraintlayout:2.1.3")
implementation("androidx.fragment:fragment-ktx:1.4.1")
implementation("androidx.activity:activity-ktx:1.6.0-rc01")
implementation("androidx.fragment:fragment-ktx:1.5.0")
implementation("androidx.activity:activity-ktx:1.8.0")
implementation("androidx.fragment:fragment-ktx:1.6.1")
implementation("com.google.android.material:material:1.5.0-rc01")
implementation("com.google.android.material:material:1.12.0")
val lifecycleVers = "2.5.0"
val lifecycleVers = "2.6.2"
implementation("androidx.lifecycle:lifecycle-extensions:2.2.0")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVers")
implementation("androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycleVers")
+2 -2
View File
@@ -14,8 +14,8 @@ object ProjectConfig {
const val packageName = "eu.darken.capod"
const val minSdk = 26
const val compileSdk = 33
const val targetSdk = 33
const val compileSdk = 34
const val targetSdk = 34
object Version {
val versionProperties = Properties().apply {
+4 -8
View File
@@ -1,11 +1,7 @@
object Versions {
object Gradle {
const val buildTools = "7.1.2"
}
object Kotlin {
const val core = "1.8.0"
const val coroutines = "1.6.2"
const val core = "1.9.10"
const val coroutines = "1.7.3"
}
object Dokka {
@@ -13,7 +9,7 @@ object Versions {
}
object Dagger {
const val core = "2.45"
const val core = "2.48.1"
}
object Moshi {
@@ -24,7 +20,7 @@ object Versions {
const val core = ""
object Navigation {
const val core = "2.5.0"
const val core = "2.7.3"
}
object Testing {
@@ -0,0 +1 @@
Support for AirPods Pro 2 with USB-C
@@ -0,0 +1,2 @@
Bugfixes, performance improvements and maybe new features.
¯\_(ツ)_/¯
@@ -0,0 +1,2 @@
Bugfixes, performance improvements and maybe new features.
¯\_(ツ)_/¯
@@ -0,0 +1,2 @@
Bugfixes, performance improvements and maybe new features.
¯\_(ツ)_/¯
@@ -0,0 +1,2 @@
Bugfixes, performance improvements and maybe new features.
¯\_(ツ)_/¯
@@ -1,4 +1,4 @@
O CAPod é um aplicativo complementar para AirPods.
CAPod é uma aplicação complementar aos AirPods.
Recursos:
@@ -1,18 +1,18 @@
CAPod is a companion app for AirPods.
CAPod este o aplicație însoțitoare pentru AirPods.
Features:
Caracteristici:
* Battery level for pods and cases.
* Charging status for pods and case.
* Additional infos about connection, microphone and case.
* Can receive and show all nearby devices.
* Ear detection with automatic play/pause.
* Automatically connect phone and AirPods.
* Show popup when case is opened.
* Nivelul bateriei pentru căști și carcasă.
* Starea de încărcare pentru căști și carcasă.
* Informații suplimentare despre conexiune, microfon și carcasă.
* Poate primi și afișa toate dispozitivele din apropiere.
* Detectare urechi cu redare/pauză automată.
* Conectați automat telefonul și AirPod-urile.
* Afișează pop-up când este deschisă carcasa.
CAPod is ad-free. Some features require an in-app purchase.
CAPod este fără reclame. Unele funcții necesită o achiziție în aplicație.
Most popular AirPods and Beats devices are supported.
If your device is similar to AirPods but not yet supported, send me a short mail.
Cele mai populare dispozitive AirPods și Beats sunt acceptate.
Dacă dispozitivul dvs. este similar cu AirPods, dar nu este încă acceptat, trimiteți-mi un scurt e-mail.
Got a cool idea for a new feature? Reach out!
Aveți o idee grozavă pentru o funcție nouă? Contactați!
@@ -1 +1 @@
CAPod is a companion app for AirPods on Android.
CAPod este o aplicație însoțitoare pentru AirPods pe Android.
+1 -1
View File
@@ -1 +1 @@
CAPod - Companion for AirPods
CaPod - Însoțitorul pentru AirPods
@@ -1,4 +1,4 @@
CAPod - програма-компаньйон для AirPods на Android.
CAPod is a companion app for AirPods.
Функції:
@@ -1,14 +1,14 @@
CAPod 是为 AirPods 提供配套功能的一个应用
CAPod 是一款为 AirPods 提供配套功能的应用
功能:
功能包括
* 耳机和充电仓的电量显示
* 耳机和充电仓的充电状态
* 有关连接、麦克风和充电仓的附加信息
* 能够接收和显示位于附近的全部设备
* 佩戴/取下时自动开始/暂停音乐播放。
* 自动连接手机与 AirPods
* 充电仓打开时显示弹窗
* 耳机和充电仓的电量显示
* 耳机和充电仓的充电状态
* 有关连接、麦克风和充电仓的附加信息
* 能够接收和显示所有附近的设备
* 佩戴/取下耳机时自动播放/暂停
* 自动连接手机与 AirPods
* 充电仓打开时显示弹窗
CAPod 是一款免费共享软件。 某些功能需在应用内购买。
+2 -2
View File
@@ -1,6 +1,6 @@
### Updated by release.sh ###
project.versioning.major=2
project.versioning.minor=11
project.versioning.patch=1
project.versioning.minor=14
project.versioning.patch=0
project.versioning.build=0
#############################