mirror of
https://github.com/d4rken-org/capod.git
synced 2026-09-14 18:26:11 -04:00
Compare commits
52
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
513bd891af | ||
|
|
37cd753ab8 | ||
|
|
34bf2311b4 | ||
|
|
a86093f617 | ||
|
|
71f66e6004 | ||
|
|
2d1ab85e01 | ||
|
|
f029eb9102 | ||
|
|
471837ab88 | ||
|
|
1e80d742cc | ||
|
|
16b6273938 | ||
|
|
3407370563 | ||
|
|
3cda73a36c | ||
|
|
b604c423ad | ||
|
|
0c8b121c2c | ||
|
|
da11961611 | ||
|
|
48b9952fc0 | ||
|
|
99fa99eba5 | ||
|
|
06e67e04ac | ||
|
|
4a2f7c20a2 | ||
|
|
82e8769407 | ||
|
|
ad38702df1 | ||
|
|
3f98c62a8f | ||
|
|
ec7447992f | ||
|
|
99958e13eb | ||
|
|
b9fe5d4a14 | ||
|
|
1443fd5714 | ||
|
|
798083b235 | ||
|
|
18a7311089 | ||
|
|
9f49d62f12 | ||
|
|
0c3c4e7ea1 | ||
|
|
7cdf653842 | ||
|
|
32d99eada8 | ||
|
|
9e4adb16c6 | ||
|
|
4b5638e4f0 | ||
|
|
d8fb9cdd30 | ||
|
|
3920489dcd | ||
|
|
09d013588d | ||
|
|
d8bd03c65d | ||
|
|
8b2c6b9498 | ||
|
|
0f63d3a0d4 | ||
|
|
672eb333b0 | ||
|
|
de609b4d05 | ||
|
|
7e6e9eee36 | ||
|
|
a321bb733c | ||
|
|
490369a3e1 | ||
|
|
a2cce044ef | ||
|
|
8884bb88c7 | ||
|
|
313b0e9131 | ||
|
|
47048cf7e7 | ||
|
|
ef1c4615aa | ||
|
|
c9587b2bf0 | ||
|
|
669fa2fd22 |
@@ -48,7 +48,6 @@ jobs:
|
||||
STORE_PASSWORD: ${{ secrets.STORE_PASSWORD }}
|
||||
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
|
||||
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
|
||||
BUGSNAG_API_KEY: ${{ secrets.BUGSNAG_API_KEY }}
|
||||
|
||||
- name: Assemble production APK
|
||||
if: "!contains(steps.tagger.outputs.tag, '-beta')"
|
||||
@@ -58,7 +57,6 @@ jobs:
|
||||
STORE_PASSWORD: ${{ secrets.STORE_PASSWORD }}
|
||||
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
|
||||
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
|
||||
BUGSNAG_API_KEY: ${{ secrets.BUGSNAG_API_KEY }}
|
||||
|
||||
- name: Create pre-release
|
||||
if: contains(steps.tagger.outputs.tag, '-beta')
|
||||
@@ -138,6 +136,14 @@ jobs:
|
||||
ruby-version: 2.7.6
|
||||
bundler-cache: true
|
||||
|
||||
# - name: Assemble WearOS beta and upload to Google Play
|
||||
# if: contains(steps.tagger.outputs.tag, '-beta')
|
||||
# run: bundle exec fastlane beta_wearos
|
||||
# env:
|
||||
# STORE_PASSWORD: ${{ secrets.STORE_PASSWORD }}
|
||||
# KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
|
||||
# KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
|
||||
|
||||
- name: Assemble beta and upload to Google Play
|
||||
if: contains(steps.tagger.outputs.tag, '-beta')
|
||||
run: bundle exec fastlane beta
|
||||
@@ -145,7 +151,14 @@ jobs:
|
||||
STORE_PASSWORD: ${{ secrets.STORE_PASSWORD }}
|
||||
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
|
||||
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
|
||||
BUGSNAG_API_KEY: ${{ secrets.BUGSNAG_API_KEY }}
|
||||
|
||||
# - name: Assemble WearOS production and upload to Google Play
|
||||
# if: "!contains(steps.tagger.outputs.tag, '-beta')"
|
||||
# run: bundle exec fastlane production_wearos
|
||||
# env:
|
||||
# STORE_PASSWORD: ${{ secrets.STORE_PASSWORD }}
|
||||
# KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
|
||||
# KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
|
||||
|
||||
- name: Assemble production and upload to Google Play
|
||||
if: "!contains(steps.tagger.outputs.tag, '-beta')"
|
||||
@@ -153,5 +166,4 @@ jobs:
|
||||
env:
|
||||
STORE_PASSWORD: ${{ secrets.STORE_PASSWORD }}
|
||||
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
|
||||
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
|
||||
BUGSNAG_API_KEY: ${{ secrets.BUGSNAG_API_KEY }}
|
||||
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
|
||||
+42
-42
@@ -1,27 +1,27 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
CFPropertyList (3.0.5)
|
||||
CFPropertyList (3.0.6)
|
||||
rexml
|
||||
addressable (2.8.0)
|
||||
public_suffix (>= 2.0.2, < 5.0)
|
||||
addressable (2.8.4)
|
||||
public_suffix (>= 2.0.2, < 6.0)
|
||||
artifactory (3.0.15)
|
||||
atomos (0.1.3)
|
||||
aws-eventstream (1.2.0)
|
||||
aws-partitions (1.610.0)
|
||||
aws-sdk-core (3.131.3)
|
||||
aws-partitions (1.784.0)
|
||||
aws-sdk-core (3.177.0)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
aws-partitions (~> 1, >= 1.525.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-partitions (~> 1, >= 1.651.0)
|
||||
aws-sigv4 (~> 1.5)
|
||||
jmespath (~> 1, >= 1.6.1)
|
||||
aws-sdk-kms (1.58.0)
|
||||
aws-sdk-core (~> 3, >= 3.127.0)
|
||||
aws-sdk-kms (1.70.0)
|
||||
aws-sdk-core (~> 3, >= 3.177.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-s3 (1.114.0)
|
||||
aws-sdk-core (~> 3, >= 3.127.0)
|
||||
aws-sdk-s3 (1.128.0)
|
||||
aws-sdk-core (~> 3, >= 3.177.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.4)
|
||||
aws-sigv4 (1.5.1)
|
||||
aws-sigv4 (~> 1.6)
|
||||
aws-sigv4 (1.6.0)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
babosa (1.0.4)
|
||||
claide (1.1.0)
|
||||
@@ -30,14 +30,14 @@ GEM
|
||||
commander (4.6.0)
|
||||
highline (~> 2.0.0)
|
||||
declarative (0.0.20)
|
||||
digest-crc (0.6.4)
|
||||
digest-crc (0.6.5)
|
||||
rake (>= 12.0.0, < 14.0.0)
|
||||
domain_name (0.5.20190701)
|
||||
unf (>= 0.0.5, < 1.0.0)
|
||||
dotenv (2.7.6)
|
||||
dotenv (2.8.1)
|
||||
emoji_regex (3.2.3)
|
||||
excon (0.92.4)
|
||||
faraday (1.10.0)
|
||||
excon (0.100.0)
|
||||
faraday (1.10.3)
|
||||
faraday-em_http (~> 1.0)
|
||||
faraday-em_synchrony (~> 1.0)
|
||||
faraday-excon (~> 1.1)
|
||||
@@ -65,8 +65,8 @@ GEM
|
||||
faraday-retry (1.0.3)
|
||||
faraday_middleware (1.2.0)
|
||||
faraday (~> 1.0)
|
||||
fastimage (2.2.6)
|
||||
fastlane (2.208.0)
|
||||
fastimage (2.2.7)
|
||||
fastlane (2.213.0)
|
||||
CFPropertyList (>= 2.3, < 4.0.0)
|
||||
addressable (>= 2.8, < 3.0.0)
|
||||
artifactory (~> 3.0)
|
||||
@@ -90,7 +90,7 @@ GEM
|
||||
json (< 3.0.0)
|
||||
jwt (>= 2.1.0, < 3)
|
||||
mini_magick (>= 4.9.4, < 5.0.0)
|
||||
multipart-post (~> 2.0.0)
|
||||
multipart-post (>= 2.0.0, < 3.0.0)
|
||||
naturally (~> 2.2)
|
||||
optparse (~> 0.1.1)
|
||||
plist (>= 3.1.0, < 4.0.0)
|
||||
@@ -106,9 +106,9 @@ GEM
|
||||
xcpretty (~> 0.3.0)
|
||||
xcpretty-travis-formatter (>= 0.0.3)
|
||||
gh_inspector (1.1.3)
|
||||
google-apis-androidpublisher_v3 (0.25.0)
|
||||
google-apis-core (>= 0.7, < 2.a)
|
||||
google-apis-core (0.7.0)
|
||||
google-apis-androidpublisher_v3 (0.45.0)
|
||||
google-apis-core (>= 0.11.0, < 2.a)
|
||||
google-apis-core (0.11.0)
|
||||
addressable (~> 2.5, >= 2.5.1)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
httpclient (>= 2.8.1, < 3.a)
|
||||
@@ -117,27 +117,27 @@ GEM
|
||||
retriable (>= 2.0, < 4.a)
|
||||
rexml
|
||||
webrick
|
||||
google-apis-iamcredentials_v1 (0.13.0)
|
||||
google-apis-core (>= 0.7, < 2.a)
|
||||
google-apis-playcustomapp_v1 (0.10.0)
|
||||
google-apis-core (>= 0.7, < 2.a)
|
||||
google-apis-storage_v1 (0.18.0)
|
||||
google-apis-core (>= 0.7, < 2.a)
|
||||
google-apis-iamcredentials_v1 (0.17.0)
|
||||
google-apis-core (>= 0.11.0, < 2.a)
|
||||
google-apis-playcustomapp_v1 (0.13.0)
|
||||
google-apis-core (>= 0.11.0, < 2.a)
|
||||
google-apis-storage_v1 (0.19.0)
|
||||
google-apis-core (>= 0.9.0, < 2.a)
|
||||
google-cloud-core (1.6.0)
|
||||
google-cloud-env (~> 1.0)
|
||||
google-cloud-errors (~> 1.0)
|
||||
google-cloud-env (1.6.0)
|
||||
faraday (>= 0.17.3, < 3.0)
|
||||
google-cloud-errors (1.2.0)
|
||||
google-cloud-storage (1.37.0)
|
||||
google-cloud-errors (1.3.1)
|
||||
google-cloud-storage (1.44.0)
|
||||
addressable (~> 2.8)
|
||||
digest-crc (~> 0.4)
|
||||
google-apis-iamcredentials_v1 (~> 0.1)
|
||||
google-apis-storage_v1 (~> 0.1)
|
||||
google-apis-storage_v1 (~> 0.19.0)
|
||||
google-cloud-core (~> 1.6)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
mini_mime (~> 1.0)
|
||||
googleauth (1.2.0)
|
||||
googleauth (1.6.0)
|
||||
faraday (>= 0.17.3, < 3.a)
|
||||
jwt (>= 1.4, < 3.0)
|
||||
memoist (~> 0.16)
|
||||
@@ -148,20 +148,20 @@ GEM
|
||||
http-cookie (1.0.5)
|
||||
domain_name (~> 0.5)
|
||||
httpclient (2.8.3)
|
||||
jmespath (1.6.1)
|
||||
json (2.6.2)
|
||||
jwt (2.4.1)
|
||||
jmespath (1.6.2)
|
||||
json (2.6.3)
|
||||
jwt (2.7.1)
|
||||
memoist (0.16.2)
|
||||
mini_magick (4.11.0)
|
||||
mini_magick (4.12.0)
|
||||
mini_mime (1.1.2)
|
||||
multi_json (1.15.0)
|
||||
multipart-post (2.0.0)
|
||||
multipart-post (2.3.0)
|
||||
nanaimo (0.3.0)
|
||||
naturally (2.2.1)
|
||||
optparse (0.1.1)
|
||||
os (1.1.4)
|
||||
plist (3.6.0)
|
||||
public_suffix (4.0.7)
|
||||
plist (3.7.0)
|
||||
public_suffix (5.0.1)
|
||||
rake (13.0.6)
|
||||
representable (3.2.0)
|
||||
declarative (< 0.1.0)
|
||||
@@ -178,7 +178,7 @@ GEM
|
||||
faraday (>= 0.17.5, < 3.a)
|
||||
jwt (>= 1.5, < 3.0)
|
||||
multi_json (~> 1.10)
|
||||
simctl (1.6.8)
|
||||
simctl (1.6.10)
|
||||
CFPropertyList
|
||||
naturally
|
||||
terminal-notifier (2.0.0)
|
||||
@@ -194,7 +194,7 @@ GEM
|
||||
unf_ext
|
||||
unf_ext (0.0.8.2)
|
||||
unicode-display_width (1.8.0)
|
||||
webrick (1.7.0)
|
||||
webrick (1.8.1)
|
||||
word_wrap (1.0.0)
|
||||
xcodeproj (1.22.0)
|
||||
CFPropertyList (>= 2.3.3, < 4.0)
|
||||
|
||||
+28
-8
@@ -20,23 +20,43 @@ On Android 12+ the newer and more fine grained `BLUETOOTH_SCAN` permission is us
|
||||
|
||||
Bluetooth Low Energy is a technology that devices like AirPods use to communicate their status to nearby devices.
|
||||
CAPod requests location permissions because these permissions are required to work with Bluetooth Low Energy data.
|
||||
This is a privacy measure on Android's side because you could determine someones location by scanning for Bluetooth devices:
|
||||
If you know the physical location of a Bluetooth device (e.g. AirTags) you could use Bluetooth data to calculate your position.
|
||||
This is a privacy measure on Android's side because you could determine someones location by scanning for Bluetooth
|
||||
devices:
|
||||
If you know the physical location of a Bluetooth device (e.g. AirTags) you could use Bluetooth data to calculate your
|
||||
position.
|
||||
|
||||
### Location access in the background
|
||||
|
||||
CAPod uses the "location access in the background" permission (`ACCESS_BACKGROUND_LOCATION`) on Android 11 and older to receive Bluetooth Low Energy data while the app is in the background. This permission enables the "Show popup" and "Autoconnect" features and allows CAPod to react to nearby devices whil the app is closed.
|
||||
CAPod uses the "location access in the background" permission (`ACCESS_BACKGROUND_LOCATION`) on Android 11 and older to
|
||||
receive Bluetooth Low Energy data while the app is in the background. This permission enables the "Show popup" and "
|
||||
Autoconnect" features and allows CAPod to react to nearby devices whil the app is closed.
|
||||
|
||||
## Automatic crash reports
|
||||
## Automatic error reports
|
||||
|
||||
Anonymous device information may be collected in the event of an app crash or error.
|
||||
*This was removed in v2.11.0+*
|
||||
|
||||
To do this the app uses the service "Bugsnag":
|
||||
If an error occurs, an automated report may be sent to help me fix the issue.
|
||||
|
||||
This is optional and you can opt out of this in the settings.
|
||||
|
||||
Error reports are collected using "Bugsnag":
|
||||
https://www.bugsnag.com/
|
||||
|
||||
Bugsnag's privacy policy can be found here:
|
||||
https://docs.bugsnag.com/legal/privacy-policy/
|
||||
|
||||
Crash reports may contain device and app related information, e.g. your phone model, Android version and app version.
|
||||
Error reports contain device and app information related to the error that occured.
|
||||
Additional information about the error context may also be included, e.g. what this app did shortly before the error.
|
||||
|
||||
You can disable automatic reports in the app's settings.
|
||||
Additional details about the type of data that is collected by the error tracking SDK can be found here:
|
||||
https://docs.bugsnag.com/platforms/android/playstore-privacy/
|
||||
|
||||
Error reports are pseudonymous. Unless you tell me your install-ID, I don't know that an error report came from you.
|
||||
|
||||
Error reports are automatically deleted after 90 days.
|
||||
|
||||
## Debug logs
|
||||
|
||||
The app has a debug log feature that can be used to assist troubleshooting efforts. This feature creates a log file that contains verbose output of what the app is doing.
|
||||
|
||||
It is manually triggered by the user through an option in the app settings. The recorded log file can be shared through compatible apps (e.g. your email app) using the system's share dialog. As this log file may contain sensitive information (e.g. details about files or installed applications) it should only be shared with trusted parties.
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
[](https://github.com/d4rken-org/capod/releases/latest)
|
||||
[](https://github.com/d4rken/capod/actions/workflows/code-checks.yml)
|
||||
[](https://crowdin.com/project/capod)
|
||||
[](https://github.com/d4rken-org/capod/releases/latest)
|
||||
[](https://github.com/d4rken-org/capod/edit/main/README.md#download)
|
||||
[](https://github.com/d4rken-org/capod/edit/main/README.md#download)
|
||||
|
||||
A companion app that adds support for AirPod specific features to Android:
|
||||
|
||||
@@ -27,9 +28,11 @@ Currently supported models:
|
||||
* AirPods 3. Generation
|
||||
* AirPods Pro 1. Generation
|
||||
* AirPods Pro 2. Generation
|
||||
* AirPods Pro 2. Generation (USB-C)
|
||||
* AirPods Max
|
||||
* Power Beats Pro
|
||||
* Power Beats 3
|
||||
* Power Beats 4
|
||||
* Beats Solo 3
|
||||
* Beats Studio 3
|
||||
* Beats X
|
||||
|
||||
@@ -22,10 +22,6 @@ android {
|
||||
buildConfigField("String", "APPLICATION_ID", "\"${ProjectConfig.packageName}\"")
|
||||
buildConfigField("String", "GITSHA", "\"${lastCommitHash()}\"")
|
||||
buildConfigField("String", "BUILDTIME", "\"${buildTime()}\"")
|
||||
|
||||
manifestPlaceholders["bugsnagApiKey"] = getBugSnagApiKey(
|
||||
File(System.getProperty("user.home"), ".appconfig/${ProjectConfig.packageName}/bugsnag.properties")
|
||||
) ?: "bugsnag_apikey_placeholder"
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
@@ -100,7 +96,4 @@ dependencies {
|
||||
|
||||
addTesting()
|
||||
|
||||
"gplayImplementation"("com.bugsnag:bugsnag-android:5.9.2")
|
||||
"gplayImplementation"("com.getkeepsafe.relinker:relinker:1.4.3")
|
||||
|
||||
}
|
||||
@@ -1,10 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<manifest>
|
||||
|
||||
<application>
|
||||
<meta-data
|
||||
android:name="com.bugsnag.android.API_KEY"
|
||||
android:value="${bugsnagApiKey}" />
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -1,58 +0,0 @@
|
||||
package eu.darken.capod.debug.autoreport
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import com.bugsnag.android.Event
|
||||
import com.bugsnag.android.OnErrorCallback
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import eu.darken.capod.common.BuildConfigWrap
|
||||
import eu.darken.capod.common.debug.DebugSettings
|
||||
import eu.darken.capod.common.debug.logging.Logging.Priority.WARN
|
||||
import eu.darken.capod.common.debug.logging.asLog
|
||||
import eu.darken.capod.common.debug.logging.log
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class BugsnagErrorHandler @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
private val bugsnagLogger: BugsnagLogger,
|
||||
private val debugSettings: DebugSettings,
|
||||
) : OnErrorCallback {
|
||||
|
||||
override fun onError(event: Event): Boolean {
|
||||
bugsnagLogger.injectLog(event)
|
||||
|
||||
TAB_APP.also { tab ->
|
||||
event.addMetadata(tab, "gitSha", BuildConfigWrap.GIT_SHA)
|
||||
event.addMetadata(tab, "buildTime", BuildConfigWrap.BUILDTIME)
|
||||
|
||||
context.tryFormattedSignature()?.let { event.addMetadata(tab, "signatures", it) }
|
||||
}
|
||||
|
||||
return debugSettings.isAutoReportingEnabled.value && !eu.darken.capod.common.BuildConfigWrap.DEBUG
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAB_APP = "app"
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
@SuppressLint("PackageManagerGetSignatures")
|
||||
fun Context.tryFormattedSignature(): String? = try {
|
||||
packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES).signatures?.let { sigs ->
|
||||
val sb = StringBuilder("[")
|
||||
for (i in sigs.indices) {
|
||||
sb.append(sigs[i].hashCode())
|
||||
if (i + 1 != sigs.size) sb.append(", ")
|
||||
}
|
||||
sb.append("]")
|
||||
sb.toString()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
log(WARN) { e.asLog() }
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
package eu.darken.capod.debug.autoreport
|
||||
|
||||
import com.bugsnag.android.Event
|
||||
import eu.darken.capod.common.debug.logging.Logging
|
||||
import eu.darken.capod.common.debug.logging.asLog
|
||||
import java.lang.String.format
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class BugsnagLogger @Inject constructor() : Logging.Logger {
|
||||
|
||||
// Adding one to the initial size accounts for the add before remove.
|
||||
private val buffer: Deque<String> = ArrayDeque(BUFFER_SIZE + 1)
|
||||
|
||||
override fun log(priority: Logging.Priority, tag: String, message: String, metaData: Map<String, Any>?) {
|
||||
val line = "${System.currentTimeMillis()} ${priority.toLabel()}/$tag: $message"
|
||||
synchronized(buffer) {
|
||||
buffer.addLast(line)
|
||||
if (buffer.size > BUFFER_SIZE) {
|
||||
buffer.removeFirst()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun injectLog(event: Event) {
|
||||
synchronized(buffer) {
|
||||
var i = 100
|
||||
buffer.forEach { event.addMetadata("Log", format(Locale.ROOT, "%03d", i++), it) }
|
||||
event.addMetadata("Log", format(Locale.ROOT, "%03d", i), event.originalError?.asLog())
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val BUFFER_SIZE = 200
|
||||
|
||||
private fun Logging.Priority.toLabel(): String = when (this) {
|
||||
Logging.Priority.VERBOSE -> "V"
|
||||
Logging.Priority.DEBUG -> "D"
|
||||
Logging.Priority.INFO -> "I"
|
||||
Logging.Priority.WARN -> "W"
|
||||
Logging.Priority.ERROR -> "E"
|
||||
Logging.Priority.ASSERT -> "WTF"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,20 +2,15 @@ package eu.darken.capod.debug.autoreport
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import com.bugsnag.android.Bugsnag
|
||||
import com.bugsnag.android.Configuration
|
||||
import com.getkeepsafe.relinker.ReLinker
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import eu.darken.capod.common.BuildConfigWrap
|
||||
import eu.darken.capod.common.InstallId
|
||||
import eu.darken.capod.common.debug.Bugs
|
||||
import eu.darken.capod.common.debug.DebugSettings
|
||||
import eu.darken.capod.common.debug.autoreport.AutomaticBugReporter
|
||||
import eu.darken.capod.common.debug.logging.Logging
|
||||
import eu.darken.capod.common.debug.logging.Logging.Priority.WARN
|
||||
import eu.darken.capod.common.debug.logging.log
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Provider
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
@@ -23,9 +18,6 @@ class GplayAutoReporting @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
private val debugSettings: DebugSettings,
|
||||
private val installId: InstallId,
|
||||
private val bugsnagLogger: Provider<BugsnagLogger>,
|
||||
private val bugsnagErrorHandler: Provider<BugsnagErrorHandler>,
|
||||
private val nopBugsnagErrorHandler: Provider<NOPBugsnagErrorHandler>,
|
||||
) : AutomaticBugReporter {
|
||||
|
||||
override fun setup(application: Application) {
|
||||
@@ -34,35 +26,13 @@ class GplayAutoReporting @Inject constructor(
|
||||
|
||||
if (!isEnabled) return
|
||||
|
||||
ReLinker
|
||||
.log { message -> log(TAG) { "ReLinker: $message" } }
|
||||
.loadLibrary(application, "bugsnag-plugin-android-anr")
|
||||
// Currently no 3rd party bug tracking
|
||||
|
||||
try {
|
||||
val bugsnagConfig = Configuration.load(context).apply {
|
||||
if (debugSettings.isAutoReportingEnabled.value) {
|
||||
Logging.install(bugsnagLogger.get())
|
||||
setUser(installId.id, null, null)
|
||||
autoTrackSessions = true
|
||||
addOnError(bugsnagErrorHandler.get())
|
||||
addMetadata("App", "buildFlavor", BuildConfigWrap.FLAVOR)
|
||||
log(TAG) { "Bugsnag setup done!" }
|
||||
} else {
|
||||
autoTrackSessions = false
|
||||
addOnError(nopBugsnagErrorHandler.get())
|
||||
log(TAG) { "Installing Bugsnag NOP error handler due to user opt-out!" }
|
||||
}
|
||||
}
|
||||
|
||||
Bugsnag.start(context, bugsnagConfig)
|
||||
Bugs.reporter = this
|
||||
} catch (e: IllegalStateException) {
|
||||
log(TAG) { "Bugsnag API Key not configured." }
|
||||
}
|
||||
Bugs.reporter = this
|
||||
}
|
||||
|
||||
override fun notify(throwable: Throwable) {
|
||||
Bugsnag.notify(throwable)
|
||||
log(TAG, WARN) { "notify($throwable)" }
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
package eu.darken.capod.debug.autoreport
|
||||
|
||||
import com.bugsnag.android.Event
|
||||
import com.bugsnag.android.OnErrorCallback
|
||||
import eu.darken.capod.common.debug.logging.Logging.Priority.WARN
|
||||
import eu.darken.capod.common.debug.logging.asLog
|
||||
import eu.darken.capod.common.debug.logging.log
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class NOPBugsnagErrorHandler @Inject constructor() : OnErrorCallback {
|
||||
|
||||
override fun onError(event: Event): Boolean {
|
||||
log(WARN) { "Error, but skipping bugsnag due to user opt-out: ${event.originalError?.asLog()}" }
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package eu.darken.capod.common.bluetooth
|
||||
|
||||
import android.bluetooth.BluetoothDevice
|
||||
import java.time.Instant
|
||||
|
||||
data class BluetoothDevice2(
|
||||
internal val internal: BluetoothDevice,
|
||||
val seenFirstAt: Instant,
|
||||
) {
|
||||
val address: String
|
||||
get() = internal.address
|
||||
|
||||
val name: String?
|
||||
get() = internal.name
|
||||
}
|
||||
@@ -1,6 +1,10 @@
|
||||
package eu.darken.capod.common.bluetooth
|
||||
|
||||
import android.bluetooth.*
|
||||
import android.bluetooth.BluetoothAdapter
|
||||
import android.bluetooth.BluetoothDevice
|
||||
import android.bluetooth.BluetoothHeadset
|
||||
import android.bluetooth.BluetoothManager
|
||||
import android.bluetooth.BluetoothProfile
|
||||
import android.bluetooth.le.BluetoothLeScanner
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
@@ -12,14 +16,24 @@ import android.os.ParcelUuid
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import eu.darken.capod.common.coroutine.DispatcherProvider
|
||||
import eu.darken.capod.common.debug.Bugs
|
||||
import eu.darken.capod.common.debug.logging.Logging.Priority.*
|
||||
import eu.darken.capod.common.debug.logging.Logging.Priority.ERROR
|
||||
import eu.darken.capod.common.debug.logging.Logging.Priority.VERBOSE
|
||||
import eu.darken.capod.common.debug.logging.Logging.Priority.WARN
|
||||
import eu.darken.capod.common.debug.logging.log
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
import eu.darken.capod.pods.core.apple.protocol.ContinuityProtocol
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import java.io.IOException
|
||||
import java.time.Instant
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@@ -74,7 +88,7 @@ class BluetoothManager2 @Inject constructor(
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(profile: Int) {
|
||||
log(TAG, WARN) { "onServiceDisconnected(profile=$profile" }
|
||||
log(TAG, WARN) { "onServiceDisconnected(profile=$profile)" }
|
||||
close(IOException("BluetoothProfile service disconnected (profile=$profile)"))
|
||||
}
|
||||
|
||||
@@ -128,6 +142,7 @@ class BluetoothManager2 @Inject constructor(
|
||||
log(TAG) { "Adding $device to current devices $currentDevices" }
|
||||
trySend(currentDevices.plus(device))
|
||||
}
|
||||
|
||||
BluetoothDevice.ACTION_ACL_DISCONNECTED -> {
|
||||
log(TAG) { "Removing $device from current devices $currentDevices" }
|
||||
trySend(currentDevices.minus(device))
|
||||
@@ -145,23 +160,54 @@ class BluetoothManager2 @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private val seenDevicesLock = Mutex()
|
||||
private val seenDevicesCache = mutableMapOf<String, Instant>()
|
||||
|
||||
fun connectedDevices(
|
||||
featureFilter: Set<ParcelUuid> = ContinuityProtocol.BLE_FEATURE_UUIDS
|
||||
): Flow<List<BluetoothDevice>> = isBluetoothEnabled
|
||||
): Flow<List<BluetoothDevice2>> = isBluetoothEnabled
|
||||
.flatMapLatest { monitorDevicesForProfile(BluetoothProfile.HEADSET) }
|
||||
.map { devices ->
|
||||
devices.filter { device ->
|
||||
featureFilter.any { feature ->
|
||||
device.hasFeature(feature)
|
||||
}
|
||||
val currentAddresses = devices.map { it.address }
|
||||
|
||||
seenDevicesLock.withLock {
|
||||
val cleanedCache = seenDevicesCache.filterKeys { currentAddresses.contains(it) }
|
||||
seenDevicesCache.clear()
|
||||
seenDevicesCache.putAll(cleanedCache)
|
||||
}
|
||||
|
||||
devices
|
||||
.filter { device -> featureFilter.any { feature -> device.hasFeature(feature) } }
|
||||
.map { device ->
|
||||
BluetoothDevice2(
|
||||
internal = device,
|
||||
seenFirstAt = seenDevicesLock.withLock {
|
||||
seenDevicesCache[device.address] ?: Instant.now().also {
|
||||
seenDevicesCache[device.address] = it
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun bondedDevices(): Flow<Set<BluetoothDevice>> = flow {
|
||||
emit(adapter?.bondedDevices ?: throw IllegalStateException("Bluetooth adapter unavailable"))
|
||||
fun bondedDevices(): Flow<Set<BluetoothDevice2>> = flow {
|
||||
val rawDevices = adapter?.bondedDevices ?: throw IllegalStateException("Bluetooth adapter unavailable")
|
||||
val wrappedDevices = rawDevices.map { device ->
|
||||
|
||||
BluetoothDevice2(
|
||||
internal = device,
|
||||
seenFirstAt = seenDevicesLock.withLock {
|
||||
seenDevicesCache[device.address] ?: Instant.now().also {
|
||||
seenDevicesCache[device.address] = it
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
}.toSet()
|
||||
emit(wrappedDevices)
|
||||
}
|
||||
|
||||
suspend fun nudgeConnection(device: BluetoothDevice): Boolean = getBluetoothProfile().map { bluetoothProfile ->
|
||||
suspend fun nudgeConnection(device: BluetoothDevice2): Boolean = getBluetoothProfile().map { bluetoothProfile ->
|
||||
try {
|
||||
log(TAG) { "Nudging Android connection to $device" }
|
||||
|
||||
@@ -169,7 +215,7 @@ class BluetoothManager2 @Inject constructor(
|
||||
"connect", BluetoothDevice::class.java
|
||||
).apply { isAccessible = true }
|
||||
|
||||
connectMethod.invoke(bluetoothProfile.proxy, device)
|
||||
connectMethod.invoke(bluetoothProfile.proxy, device.internal)
|
||||
|
||||
log(TAG) { "Nudged connection to $device" }
|
||||
true
|
||||
|
||||
@@ -3,6 +3,7 @@ package eu.darken.capod.common.uix
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.lifecycle.LiveData
|
||||
import eu.darken.capod.common.debug.logging.Logging.Priority.VERBOSE
|
||||
import eu.darken.capod.common.debug.logging.log
|
||||
@@ -15,6 +16,8 @@ abstract class Activity2 : AppCompatActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
log(tag, VERBOSE) { "onCreate(savedInstanceState=$savedInstanceState)" }
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
WindowCompat.setDecorFitsSystemWindows( window, false )
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
|
||||
@@ -7,6 +7,8 @@ import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.LayoutRes
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import eu.darken.capod.common.debug.logging.Logging.Priority.VERBOSE
|
||||
import eu.darken.capod.common.debug.logging.log
|
||||
@@ -40,6 +42,17 @@ abstract class Fragment2(@LayoutRes val layoutRes: Int?) : Fragment(layoutRes ?:
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
log(tag, VERBOSE) { "onViewCreated(view=$view, savedInstanceState=$savedInstanceState)" }
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
ViewCompat.setOnApplyWindowInsetsListener(view) { v, insets ->
|
||||
val systemWindowInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
v.setPadding(
|
||||
v.paddingLeft,
|
||||
systemWindowInsets.top,
|
||||
v.paddingRight,
|
||||
0
|
||||
)
|
||||
insets
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
|
||||
@@ -8,6 +8,7 @@ import eu.darken.capod.common.error.ErrorEventSource
|
||||
import eu.darken.capod.common.flow.setupCommonEventHandlers
|
||||
import eu.darken.capod.common.livedata.SingleLiveEvent
|
||||
import eu.darken.capod.common.navigation.NavEventSource
|
||||
import eu.darken.capod.common.navigation.navVia
|
||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
@@ -31,4 +32,7 @@ abstract class ViewModel3(
|
||||
.setupCommonEventHandlers(TAG) { "launchInViewModel()" }
|
||||
.launchIn(vmScope)
|
||||
|
||||
fun NavDirections.navigate() {
|
||||
navVia(navEvents)
|
||||
}
|
||||
}
|
||||
@@ -40,6 +40,8 @@ class GeneralSettings @Inject constructor(
|
||||
val isOffloadedBatchingDisabled = preferences.createFlowPreference("core.compat.offloaded.batching.disabled", false)
|
||||
val useIndirectScanResultCallback = preferences.createFlowPreference("core.compat.indirectcallback.enabled", false)
|
||||
|
||||
val isOnboardingDone = preferences.createFlowPreference("core.onboarding.done", false)
|
||||
|
||||
override val preferenceDataStore: PreferenceDataStore = PreferenceStoreMapper(
|
||||
monitorMode,
|
||||
scannerMode,
|
||||
|
||||
@@ -10,7 +10,7 @@ import eu.darken.capod.common.debug.logging.Logging.Priority.VERBOSE
|
||||
import eu.darken.capod.common.debug.logging.log
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.util.*
|
||||
import java.util.UUID
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.max
|
||||
|
||||
@@ -91,6 +91,10 @@ interface PodDevice {
|
||||
"AirPods Pro 2",
|
||||
R.drawable.devic_airpods_pro2_both
|
||||
),
|
||||
@Json(name = "airpods.pro2.usbc") AIRPODS_PRO2_USBC(
|
||||
"AirPods Pro 2 USB-C",
|
||||
R.drawable.devic_airpods_pro2_both
|
||||
),
|
||||
@Json(name = "airpods.max") AIRPODS_MAX(
|
||||
"AirPods Max",
|
||||
R.drawable.devic_headphones_generic
|
||||
@@ -110,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"
|
||||
),
|
||||
|
||||
@@ -19,6 +19,7 @@ abstract class AppleFactoryModule {
|
||||
|
||||
@Binds @IntoSet abstract fun airPodsPro(factory: AirPodsPro.Factory): ApplePodsFactory<out ApplePods>
|
||||
@Binds @IntoSet abstract fun airPodsPro2(factory: AirPodsPro2.Factory): ApplePodsFactory<out ApplePods>
|
||||
@Binds @IntoSet abstract fun airPodsPro2Usbc(factory: AirPodsPro2Usbc.Factory): ApplePodsFactory<out ApplePods>
|
||||
@Binds @IntoSet abstract fun airPodsMax(factory: AirPodsMax.Factory): ApplePodsFactory<out ApplePods>
|
||||
|
||||
@Binds @IntoSet abstract fun beatsFlex(factory: BeatsFlex.Factory): ApplePodsFactory<out ApplePods>
|
||||
@@ -26,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,89 @@
|
||||
package eu.darken.capod.pods.core.apple.airpods
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import eu.darken.capod.common.R
|
||||
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.DualApplePods
|
||||
import eu.darken.capod.pods.core.apple.DualApplePods.LidState
|
||||
import eu.darken.capod.pods.core.apple.DualApplePodsFactory
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||
import java.time.Instant
|
||||
import javax.inject.Inject
|
||||
|
||||
data class AirPodsPro2Usbc(
|
||||
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,
|
||||
private val cachedBatteryPercentage: Float? = null,
|
||||
private val cachedCaseState: LidState? = null
|
||||
) : DualApplePods, HasStateDetectionAirPods {
|
||||
|
||||
override val model: PodDevice.Model = PodDevice.Model.AIRPODS_PRO2_USBC
|
||||
|
||||
@get:DrawableRes
|
||||
override val iconRes: Int
|
||||
get() = R.drawable.devic_airpods_pro2_both
|
||||
|
||||
@get:DrawableRes
|
||||
override val caseIcon: Int
|
||||
get() = R.drawable.devic_airpods_pro2_case
|
||||
|
||||
@get:DrawableRes
|
||||
override val leftPodIcon: Int
|
||||
get() = R.drawable.devic_airpods_pro2_left
|
||||
|
||||
@get:DrawableRes
|
||||
override val rightPodIcon: Int
|
||||
get() = R.drawable.devic_airpods_pro2_right
|
||||
|
||||
override val batteryCasePercent: Float?
|
||||
get() = super.batteryCasePercent ?: cachedBatteryPercentage
|
||||
|
||||
override val caseLidState: LidState
|
||||
get() = cachedCaseState ?: super.caseLidState
|
||||
|
||||
override val rssi: Int
|
||||
get() = rssiAverage ?: super<HasStateDetectionAirPods>.rssi
|
||||
|
||||
class Factory @Inject constructor() : DualApplePodsFactory(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 = AirPodsPro2Usbc(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,
|
||||
cachedBatteryPercentage = result.getLatestCaseBattery(),
|
||||
rssiAverage = result.rssiSmoothed(basic.rssi),
|
||||
cachedCaseState = result.getLatestCaseLidState(basic)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val DEVICE_CODE = 0x2420.toUShort()
|
||||
private val TAG = logTag("PodDevice", "Apple", "AirPods", "Pro2", "USBC")
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -47,6 +47,11 @@ class ReactionSettings @Inject constructor(
|
||||
false
|
||||
)
|
||||
|
||||
val showPopUpOnConnection = preferences.createFlowPreference(
|
||||
"reaction.popup.connected",
|
||||
false
|
||||
)
|
||||
|
||||
val onePodMode = preferences.createFlowPreference(
|
||||
"reaction.onepod.enabled",
|
||||
false
|
||||
@@ -58,6 +63,7 @@ class ReactionSettings @Inject constructor(
|
||||
autoConnect,
|
||||
autoConnectCondition,
|
||||
showPopUpOnCaseOpen,
|
||||
showPopUpOnConnection,
|
||||
onePodMode,
|
||||
)
|
||||
}
|
||||
@@ -1,15 +1,26 @@
|
||||
package eu.darken.capod.reaction.core.popup
|
||||
|
||||
import eu.darken.capod.common.debug.logging.Logging.Priority.*
|
||||
import eu.darken.capod.common.bluetooth.BluetoothManager2
|
||||
import eu.darken.capod.common.debug.logging.Logging.Priority.INFO
|
||||
import eu.darken.capod.common.debug.logging.Logging.Priority.VERBOSE
|
||||
import eu.darken.capod.common.debug.logging.Logging.Priority.WARN
|
||||
import eu.darken.capod.common.debug.logging.log
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
import eu.darken.capod.common.flow.setupCommonEventHandlers
|
||||
import eu.darken.capod.common.flow.withPrevious
|
||||
import eu.darken.capod.main.core.GeneralSettings
|
||||
import eu.darken.capod.monitor.core.PodMonitor
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
import eu.darken.capod.pods.core.apple.DualApplePods
|
||||
import eu.darken.capod.reaction.core.ReactionSettings
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.distinctUntilChangedBy
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.mapNotNull
|
||||
import kotlinx.coroutines.flow.merge
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import javax.inject.Inject
|
||||
@@ -19,11 +30,13 @@ import javax.inject.Singleton
|
||||
class PopUpReaction @Inject constructor(
|
||||
private val podMonitor: PodMonitor,
|
||||
private val reactionSettings: ReactionSettings,
|
||||
private val generalSettings: GeneralSettings,
|
||||
private val bluetoothManager: BluetoothManager2,
|
||||
) {
|
||||
|
||||
private val coolDowns = mutableMapOf<PodDevice.Id, Instant>()
|
||||
private val caseCoolDowns = mutableMapOf<PodDevice.Id, Instant>()
|
||||
|
||||
fun monitor(): Flow<Event> = reactionSettings.showPopUpOnCaseOpen.flow
|
||||
private fun monitorCase(): Flow<Event> = reactionSettings.showPopUpOnCaseOpen.flow
|
||||
.flatMapLatest { isEnabled ->
|
||||
if (isEnabled) {
|
||||
podMonitor.mainDevice.distinctUntilChangedBy { it?.rawDataHex }
|
||||
@@ -32,7 +45,7 @@ class PopUpReaction @Inject constructor(
|
||||
}
|
||||
}
|
||||
.withPrevious()
|
||||
.setupCommonEventHandlers(TAG) { "monitor" }
|
||||
.setupCommonEventHandlers(TAG) { "popUpCase" }
|
||||
.mapNotNull { (previous, current) ->
|
||||
if (previous !is DualApplePods? || current !is DualApplePods) {
|
||||
return@mapNotNull null
|
||||
@@ -54,35 +67,37 @@ class PopUpReaction @Inject constructor(
|
||||
}
|
||||
log(TAG) { "Case lid status changed for monitored device." }
|
||||
|
||||
tryPopWindow(current)
|
||||
throttleCasePopUps(current)
|
||||
}
|
||||
|
||||
private suspend fun tryPopWindow(current: DualApplePods): Event? = when {
|
||||
private fun throttleCasePopUps(current: DualApplePods): Event? = when {
|
||||
current.caseLidState == DualApplePods.LidState.OPEN -> {
|
||||
log(TAG, INFO) { "Show popup" }
|
||||
|
||||
val now = Instant.now()
|
||||
val lastShown = coolDowns[current.identifier] ?: Instant.MIN
|
||||
val lastShown = caseCoolDowns[current.identifier] ?: Instant.MIN
|
||||
val sinceLastPop = Duration.between(lastShown, now)
|
||||
log(TAG) { "Time since last popup: $sinceLastPop" }
|
||||
log(TAG) { "Time since last case popup: $sinceLastPop" }
|
||||
|
||||
if (sinceLastPop >= Duration.ofSeconds(10)) {
|
||||
coolDowns[current.identifier] = Instant.now()
|
||||
caseCoolDowns[current.identifier] = Instant.now()
|
||||
Event.PopupShow(device = current)
|
||||
} else {
|
||||
log(TAG, INFO) { "Popup is still on cooldown: $sinceLastPop" }
|
||||
log(TAG, INFO) { "Case popup is still on cooldown: $sinceLastPop" }
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
current.caseLidState != DualApplePods.LidState.OPEN -> {
|
||||
when (current.caseLidState) {
|
||||
DualApplePods.LidState.CLOSED -> {
|
||||
log(TAG, INFO) { "Lid was actively closed, resetting cooldown." }
|
||||
coolDowns.remove(current.identifier)
|
||||
caseCoolDowns.remove(current.identifier)
|
||||
}
|
||||
|
||||
else -> {
|
||||
log(TAG, WARN) { "Lid was was not actively closed, refreshing cooldown." }
|
||||
coolDowns[current.identifier] = Instant.now()
|
||||
caseCoolDowns[current.identifier] = Instant.now()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,9 +105,78 @@ class PopUpReaction @Inject constructor(
|
||||
|
||||
Event.PopupHide()
|
||||
}
|
||||
|
||||
else -> null
|
||||
}
|
||||
|
||||
private val connectionCoolDowns = mutableMapOf<String, Instant>()
|
||||
|
||||
private fun monitorConnection(): Flow<Event> = reactionSettings.showPopUpOnConnection.flow
|
||||
.flatMapLatest { isEnabled ->
|
||||
if (!isEnabled) return@flatMapLatest emptyFlow()
|
||||
|
||||
combine(
|
||||
generalSettings.mainDeviceAddress.flow,
|
||||
bluetoothManager.connectedDevices().distinctUntilChanged(),
|
||||
podMonitor.mainDevice.distinctUntilChangedBy { it?.rawDataHex },
|
||||
) { targetAddress, devices, broadcast ->
|
||||
log(TAG) { "$targetAddress $broadcast $devices " }
|
||||
val direct = devices.singleOrNull { it.address == targetAddress }.also {
|
||||
log(TAG, VERBOSE) { "Connected main device is $it" }
|
||||
}
|
||||
if (direct == null) {
|
||||
connectionCoolDowns.remove(targetAddress).also {
|
||||
if (it != null) log(TAG) { "Cleared connection cooldown for $targetAddress due to disconect" }
|
||||
}
|
||||
}
|
||||
if (direct != null && broadcast != null) direct to broadcast else null
|
||||
}
|
||||
}
|
||||
.withPrevious()
|
||||
.mapNotNull { (previouss, currents) ->
|
||||
val previousConnected = previouss?.first
|
||||
log(TAG, VERBOSE) { "previousConnected: $previousConnected" }
|
||||
val previousBroadcasted = previouss?.second
|
||||
log(TAG, VERBOSE) { "previousBroadcasted: $previousBroadcasted" }
|
||||
val currentConnected = currents?.first
|
||||
log(TAG, VERBOSE) { "currentConnected: $currentConnected" }
|
||||
val currentBroadcasted = currents?.second
|
||||
log(TAG, VERBOSE) { "currentBroadcasted: $currentBroadcasted" }
|
||||
|
||||
if (previousConnected != null && previousBroadcasted != null && currentConnected == null) {
|
||||
return@mapNotNull Event.PopupHide()
|
||||
}
|
||||
|
||||
if (currentConnected == null || currentBroadcasted == null) {
|
||||
// We need an active connection
|
||||
return@mapNotNull null
|
||||
}
|
||||
|
||||
val ageOfBroadcastedDevice = Duration.between(Instant.now(), currentBroadcasted.seenFirstAt)
|
||||
val ageOfConnectedDevice = Duration.between(Instant.now(), currentConnected.seenFirstAt)
|
||||
if (ageOfBroadcastedDevice > (ageOfConnectedDevice + Duration.ofSeconds(30))) {
|
||||
// This is likely a false positive, some random nearby device
|
||||
// We expect the first broadcasts to not be much older than the first connection
|
||||
log(TAG, VERBOSE) { "Current broadcasted main device is probably a false-positive" }
|
||||
return@mapNotNull null
|
||||
}
|
||||
|
||||
val now = Instant.now()
|
||||
val lastShown = connectionCoolDowns[currentConnected.address]
|
||||
val sinceLastPop = lastShown?.let { Duration.between(it, now) }
|
||||
log(TAG) { "Time since last connection popup: ${sinceLastPop?.seconds}s" }
|
||||
|
||||
if (lastShown == null) {
|
||||
connectionCoolDowns[currentConnected.address] = Instant.now()
|
||||
Event.PopupShow(device = currentBroadcasted)
|
||||
} else {
|
||||
log(TAG) { "Connection popup is still on cooldown: $sinceLastPop" }
|
||||
null
|
||||
}
|
||||
}
|
||||
.setupCommonEventHandlers(TAG) { "popUpConnection" }
|
||||
|
||||
fun monitor(): Flow<Event> = merge(monitorCase(), monitorConnection())
|
||||
|
||||
sealed class Event {
|
||||
data class PopupShow(
|
||||
|
||||
@@ -58,4 +58,6 @@
|
||||
<string name="pods_case_unknown_state">حالة غير معروفة</string>
|
||||
<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>
|
||||
|
||||
@@ -58,6 +58,6 @@
|
||||
<string name="pods_case_unknown_state">Невядомы стан</string>
|
||||
<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">"Дазволіць CAPod адлюстроўваць вам апавяшчэнні аб вашых AirPods: напрыклад, іх бягучы стан пры падключэнні."</string>
|
||||
<string name="permission_post_notifications_label">Паказваць апавяшчэнні</string>
|
||||
<string name="permission_post_notifications_description">"Дазволіць CAPod адлюстроўваць апавяшчэнні аб вашых AirPods: напрыклад, іх бягучы стан пры падключэнні."</string>
|
||||
</resources>
|
||||
|
||||
@@ -58,6 +58,6 @@
|
||||
<string name="pods_case_unknown_state">Estat desconegut</string>
|
||||
<string name="last_seen_x">Últim cop vist: %s</string>
|
||||
<string name="first_seen_x">Primera vegada vist: %s</string>
|
||||
<string name="permission_post_notifications_label">Publica notificacions</string>
|
||||
<string name="permission_post_notifications_description">"Permet que el CAPod us mostri notificacions sobre els vostres AirPods, p. ex. el seu estat actual mentre estan connectats."</string>
|
||||
<string name="permission_post_notifications_label">Mostra notificacions</string>
|
||||
<string name="permission_post_notifications_description">"Permet que el CAPod mostri notificacions sobre els vostres AirPods, p. ex. el seu estat actual mentre estan connectats."</string>
|
||||
</resources>
|
||||
|
||||
@@ -58,6 +58,6 @@
|
||||
<string name="pods_case_unknown_state">Neznámý stav</string>
|
||||
<string name="last_seen_x">Naposledy spatřeno: %s</string>
|
||||
<string name="first_seen_x">Poprvé spatřeno: %s</string>
|
||||
<string name="permission_post_notifications_label">Posílat zprávy</string>
|
||||
<string name="permission_post_notifications_label">Zobrazit oznámení</string>
|
||||
<string name="permission_post_notifications_description">"Povolte aplikaci CAPod zobrazovat oznámení o sluchátkách AirPods, např. o jejich aktuálním stavu během připojení."</string>
|
||||
</resources>
|
||||
|
||||
@@ -58,6 +58,6 @@
|
||||
<string name="pods_case_unknown_state">Unbekannter Zustand</string>
|
||||
<string name="last_seen_x">Zuletzt gesehen: %s</string>
|
||||
<string name="first_seen_x">Zum ersten Mal gesehen: %s</string>
|
||||
<string name="permission_post_notifications_label">Benachrichtigungen posten</string>
|
||||
<string name="permission_post_notifications_description">"Erlauben Sie CAPod, Ihnen Benachrichtigungen über Ihre AirPods anzuzeigen, z.b. ihren aktuellen Status, während sie verbunden sind."</string>
|
||||
<string name="permission_post_notifications_label">Zeige Benachrichtigungen</string>
|
||||
<string name="permission_post_notifications_description">"Erlaube CAPod, Benachrichtigungen über deine AirPods anzuzeigen, z. B. deren aktuellen Status, während die Verbindung besteht."</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_value_not_available_label">Δε διατίθεται</string>
|
||||
<string name="general_error_label">Σφάλμα</string>
|
||||
<string name="general_grant_permission_action">Παραχώρηση άδειας</string>
|
||||
@@ -56,4 +58,6 @@
|
||||
<string name="pods_case_unknown_state">Άγνωστη κατάσταση</string>
|
||||
<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">"Επιτρέψτε στην εφαρμογή να εμφανίζει ειδοποιήσεις σχετικά με τα AirPods σας, π.χ. την τρέχουσα κατάστασή τους ενώ είστε συνδεδεμένοι."</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,5 +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,6 +58,6 @@
|
||||
<string name="pods_case_unknown_state">Estado desconocido</string>
|
||||
<string name="last_seen_x">Visto por última vez: %s</string>
|
||||
<string name="first_seen_x">Visto por primera vez: %s</string>
|
||||
<string name="permission_post_notifications_label">Publicar las notificaciones</string>
|
||||
<string name="permission_post_notifications_description">"Permite que CAPod te muestre notificaciones sobre tus AirPods, por ejemplo, su estado actual mientras están conectados."</string>
|
||||
<string name="permission_post_notifications_label">Mostrar las notificaciones</string>
|
||||
<string name="permission_post_notifications_description">"Permite que CAPod muestre las notificaciones sobre tus AirPods, por ejemplo, su estado actual mientras están conectados."</string>
|
||||
</resources>
|
||||
|
||||
@@ -6,5 +6,6 @@
|
||||
<string name="general_value_not_available_label">Pole saadaval</string>
|
||||
<string name="pods_dual_left_label">Vasak klapp</string>
|
||||
<string name="pods_dual_right_label">Parem klapp</string>
|
||||
<string name="permission_post_notifications_label">Näita teateid</string>
|
||||
<string name="permission_post_notifications_description">"Luba CAPodil näidata AirPodsi teateid, nt praegust olekut ühendatud olles."</string>
|
||||
</resources>
|
||||
|
||||
@@ -19,12 +19,12 @@
|
||||
<string name="permission_access_fine_location_label">Accéder à la position précise</string>
|
||||
<string name="permission_access_fine_location_description">CAPod utilise l’autorisation « position précise » pour recevoir les données « faible énergie » de Bluetooth. Votre casque-micro utilise la technologie « basse énergie » de Bluetooth pour diffuser son état. Cette appli n’utilisera PAS les données Bluetooth pour déterminer votre position.</string>
|
||||
<string name="permission_background_location_label">Accès à la position en arrière-plan</string>
|
||||
<string name="permission_background_location_description">CAPod utilise « l’accès à la position en arrière-plan » afin de permettre des fonctions telles qu’« Afficher une notification » et « Connexion automatique » alors que l’appli est fermée. L’accès à la position en arrière-plan permet à l’appli de recevoir les données « basse énergie » de Bluetooth. Elles ne seront PAS utilisées pour déterminer votre position géographie.</string>
|
||||
<string name="permission_background_location_description">CAPod utilise « l’accès à la position en arrière-plan » afin de permettre des fonctions telles qu’« Afficher une fenêtre surgissante » et « Connexion automatique » alors que l’appli est fermée. L’accès à la position en arrière-plan permet à l’appli de recevoir les données « basse énergie » de Bluetooth. Elles ne seront PAS utilisées pour déterminer votre position géographie.</string>
|
||||
<string name="permission_ignore_battery_optimizations_label">Désactiver les optimisations de la pile/batterie</string>
|
||||
<string name="permission_ignore_battery_optimizations_description">Les optimisations de la pile/batterie empêchent l’appli de recevoir les données Bluetooth de manière fiable alors que l’appli est en arrière-plan.</string>
|
||||
<string name="permission_required_title">L’autorisation suivante est nécessaire :</string>
|
||||
<string name="permission_system_alert_window_label">Fenêtre d’alerte système</string>
|
||||
<string name="permission_system_alert_window_description">Permettre à CAPod d’apparaître par-dessus les autres applis pour rendre la fonction « Afficher une notification » possible.</string>
|
||||
<string name="permission_system_alert_window_description">Permettre à CAPod d’apparaître par-dessus les autres applis pour rendre la fonction « Afficher une fenêtre surgissante » possible.</string>
|
||||
<string name="settings_scanner_mode_lowpower_label">Faible puissance</string>
|
||||
<string name="settings_scanner_mode_balanced_label">Équilibré</string>
|
||||
<string name="settings_scanner_mode_lowlatency_label">Faible latence</string>
|
||||
|
||||
@@ -58,6 +58,4 @@
|
||||
<string name="pods_case_unknown_state">अज्ञात अवस्था</string>
|
||||
<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">"CAPod को अपने AirPods के बारे में सूचनाएं दिखाने की अनुमति दें, जैसे कि जुड़े रहने के दौरान उनकी वर्तमान स्थिति।"</string>
|
||||
</resources>
|
||||
|
||||
@@ -58,6 +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">Posting pemberitahuan</string>
|
||||
<string name="permission_post_notifications_description">"Izinkan CAPod menampilkan pemberitahuan tentang AirPods Anda, mis. status saat terhubung."</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>
|
||||
|
||||
@@ -21,26 +21,26 @@
|
||||
<string name="permission_background_location_label">Accesso in background alla tua posizione</string>
|
||||
<string name="permission_background_location_description">CAPod usa l\'accesso in background alla tua posizione per far funzionare opzioni come \"Mostra popup\" e \"AutoConnect\" mentre l\'applicazione é chiusa. L\'accesso in background abiliterà l\'applicazione nella ricezione di dati Bluetooth a basso consume mentre l\'app non é aperta. Questa applicazione non utilizza i dati Bluetooth per geolocalizzarti.</string>
|
||||
<string name="permission_ignore_battery_optimizations_label">Disabilita ottimizzazione batteria</string>
|
||||
<string name="permission_ignore_battery_optimizations_description">L\'ottimizzazione della batteria impedisce a questa applicazione di ricevere dati Bluetooth mentre l\'app é chiusa.</string>
|
||||
<string name="permission_ignore_battery_optimizations_description">L\'ottimizzazione della batteria impedisce a questa applicazione di ricevere dati Bluetooth mentre l\'app è in esecuzione in background.</string>
|
||||
<string name="permission_required_title">È richiesta la seguente autorizzazione:</string>
|
||||
<string name="permission_system_alert_window_label">Finestra di avviso di sistema</string>
|
||||
<string name="permission_system_alert_window_description">Permetti a CAPod di far apprarire overlay sopra le altre applicazioni per far funzionare \"Mostra PopUp\".</string>
|
||||
<string name="permission_system_alert_window_description">Concedi a CAPod la visualizzazione in primo piano sopra le altre applicazioni per la funzione \"Mostra PopUp\".</string>
|
||||
<string name="settings_scanner_mode_lowpower_label">Basso consumo</string>
|
||||
<string name="settings_scanner_mode_balanced_label">Bilanciato</string>
|
||||
<string name="settings_scanner_mode_lowlatency_label">Bassa latenza</string>
|
||||
<string name="settings_monitor_mode_manual_label">Quando l\'applicazione è aperta</string>
|
||||
<string name="settings_monitor_mode_automatic_label">Quando il device è connesso</string>
|
||||
<string name="settings_monitor_mode_automatic_label">Quando le cuffie sono connesse</string>
|
||||
<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 sinistra</string>
|
||||
<string name="pods_dual_right_label">Pod destra</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>
|
||||
@@ -48,14 +48,16 @@
|
||||
<string name="pods_connection_state_unknown_label">Stato di connessione sconosciuto</string>
|
||||
<string name="pods_unknown_raw_data_label">Dati non elaborati</string>
|
||||
<string name="pods_unknown_label">Dispositivo sconosciuto</string>
|
||||
<string name="pods_unknown_contact_dev">Questo é un dispositivo sconosciutom ma sta utilizzando un formato di comunicazione noto. Aggiungiamo il supporto per questo device, Contattami! :)</string>
|
||||
<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>
|
||||
<string name="pods_case_unknown_state">Stato sconosciuto</string>
|
||||
<string name="last_seen_x">Ultimo accesso: %s</string>
|
||||
<string name="first_seen_x">Primo accesso: %s</string>
|
||||
<string name="permission_post_notifications_label">Mostra notifiche</string>
|
||||
<string name="permission_post_notifications_description">"Concedi a CAPod di mostrare notifiche riguardanti le tue AirPods, ad esempio il loro stato di carica mentre sono connesse."</string>
|
||||
</resources>
|
||||
|
||||
@@ -58,6 +58,4 @@
|
||||
<string name="pods_case_unknown_state">מצב לא ידוע</string>
|
||||
<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">"לאשר ל-CAPod להראות לך התראות על ה-AirPods שלך, למשל. המצב הנוכחי שלהם בזמן שהם מחוברים."</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,5 +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_value_not_available_label">T/A</string>
|
||||
<string name="general_error_label">Ralat</string>
|
||||
<string name="general_grant_permission_action">Berikan kebenaran</string>
|
||||
@@ -55,6 +58,6 @@
|
||||
<string name="pods_case_unknown_state">Keadaan tidak diketahui</string>
|
||||
<string name="last_seen_x">Terakhir dilihat: %s</string>
|
||||
<string name="first_seen_x">Pertama dilihat: %s</string>
|
||||
<string name="permission_post_notifications_label">Siarkan pemberitahuan</string>
|
||||
<string name="permission_post_notifications_description">"Benarkan CAPod menunjukkan pemberitahuan kepada anda tentang AirPods anda, mis. status semasa mereka semasa disambungkan."</string>
|
||||
<string name="permission_post_notifications_label">Tunjukkan pemberitahuanm</string>
|
||||
<string name="permission_post_notifications_description">"Benarkan CAPod memaparkan pemberitahuan tentang AirPods anda, misalnya, status semasa mereka ketika disambungkan."</string>
|
||||
</resources>
|
||||
|
||||
@@ -58,6 +58,6 @@
|
||||
<string name="pods_case_unknown_state">Onbekende staat</string>
|
||||
<string name="last_seen_x">Laatst gezien: %s</string>
|
||||
<string name="first_seen_x">Eerst gezien: %s</string>
|
||||
<string name="permission_post_notifications_label">Post notificaties</string>
|
||||
<string name="permission_post_notifications_description">"Sta CAPod toe notificaties over je AirPods te tonen, b.v. de huidige verbindingsstatus."</string>
|
||||
<string name="permission_post_notifications_label">Toon meldingen</string>
|
||||
<string name="permission_post_notifications_description">"Sta CAPod toe om meldingen over je AirPods weer te geven, bijv. hun huidige status terwijl ze verbonden zijn."</string>
|
||||
</resources>
|
||||
|
||||
@@ -58,6 +58,6 @@
|
||||
<string name="pods_case_unknown_state">Nieznany stan</string>
|
||||
<string name="last_seen_x">Ostatnio widziane: %s</string>
|
||||
<string name="first_seen_x">Widziane pierwszy raz: %s</string>
|
||||
<string name="permission_post_notifications_label">Wysyłanie powiadomień</string>
|
||||
<string name="permission_post_notifications_label">Wyświetl powiadomienia</string>
|
||||
<string name="permission_post_notifications_description">"Zezwól CAPod na wyświetlanie powiadomień o słuchawkach AirPods, np. ich aktualnym statusie podczas połączenia."</string>
|
||||
</resources>
|
||||
|
||||
@@ -58,6 +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">Post de notificações</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>
|
||||
|
||||
@@ -58,6 +58,6 @@
|
||||
<string name="pods_case_unknown_state">Неизвестное состояние</string>
|
||||
<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">"Разрешить CAPod показывать уведомления о ваших AirPods, например текущий статус при подключении."</string>
|
||||
<string name="permission_post_notifications_label">Показывать уведомления</string>
|
||||
<string name="permission_post_notifications_description">"Разрешить CAPod показывать уведомления о Ваших AirPods, например текущий статус при подключении."</string>
|
||||
</resources>
|
||||
|
||||
@@ -59,5 +59,5 @@
|
||||
<string name="last_seen_x">Naposledy videné: %s</string>
|
||||
<string name="first_seen_x">Prvýkrát videné: %s</string>
|
||||
<string name="permission_post_notifications_label">Zobraziť oznámenia</string>
|
||||
<string name="permission_post_notifications_description">"Povoľte CAPod, aby vám zobrazoval upozornenia týkajúce sa vašich slúchadiel AirPods, napr. ich aktuálny stav počas pripojenia."</string>
|
||||
<string name="permission_post_notifications_description">"Povoľte CAPod, aby zobrazoval upozornenia týkajúce sa vašich slúchadiel AirPods, napr. ich aktuálny stav počas pripojenia."</string>
|
||||
</resources>
|
||||
|
||||
@@ -8,26 +8,26 @@
|
||||
<string name="general_grant_permission_action">İzin ver</string>
|
||||
<string name="overview_nomaindevice_label">Birincil aygıt yok</string>
|
||||
<string name="overview_nomaindevice_description">Tespit edilen tüm aygıtların sizin olması muhtemel değildir. Aygıtınızı açın ve bağlayın veya ayarları yapın.</string>
|
||||
<string name="overview_bluetooth_disabled_label">Bluetooth devre dışı</string>
|
||||
<string name="overview_bluetooth_disabled_description">Bluetooth devre dışı, lütfen etkinleştirin ;)</string>
|
||||
<string name="overview_bluetooth_disabled_label">Bluetooth kapalı</string>
|
||||
<string name="overview_bluetooth_disabled_description">Lütfen etkinleştirin ;)</string>
|
||||
<string name="permission_bluetooth_connect_label">Bluetooth bağlantısı</string>
|
||||
<string name="permission_bluetooth_connect_description">Bu uygulama, eşleştirilmiş aygıtlarla etkileşime geçmek ve bağlantıları başlatmak için \"Bluetooth bağlantısı\" izni gerektirir.</string>
|
||||
<string name="permission_bluetooth_scan_label">Bluetooth taraması</string>
|
||||
<string name="permission_bluetooth_scan_description">\"Bluetooth tarama\" izni, bu uygulamanın AirPod\'larınız gibi yakındaki aygıtlardan Bluetooth verilerini keşfetmesine ve almasına olanak tanır.</string>
|
||||
<string name="permission_bluetooth_scan_description">\"Bluetooth tarama\" izni, bu uygulamanın AirPod\'larınız gibi yakınınızdaki aygıtlardan Bluetooth verilerini keşfetmesine ve almasına olanak tanır.</string>
|
||||
<string name="permission_bluetooth_label">Bluetooth</string>
|
||||
<string name="permission_bluetooth_description">Bu uygulama, eşleştirilmiş bluetooth aygıtlarına bağlanmak için \"Bluetooth\" izni gerektirir.</string>
|
||||
<string name="permission_access_fine_location_label">Tam konuma erişim</string>
|
||||
<string name="permission_access_fine_location_description">CAPod, Bluetooth Düşük Enerji verilerini almak için \"tam konum\" iznini kullanır. Kulaklığınız, durumlarını yayınlamak için Bluetooth Düşük Enerji teknolojisini kullanır. Bu uygulama, konumunuzu belirlemek için Bluetooth verilerini KULLANMAZ.</string>
|
||||
<string name="permission_background_location_label">Arka planda konum erişimi</string>
|
||||
<string name="permission_background_location_description">CAPod, uygulama kapalıyken \"Açılır pencereyi göster\" ve \"Otomatik Bağlantı\" gibi özellikleri etkinleştirmek için \"arka planda konum erişimi\"ni kullanır. Arka planda konum erişimi, uygulamanın arka plandayken Bluetooth Düşük Enerji verilerini almasına olanak tanır. Bu uygulama, konumunuzu belirlemek için Bluetooth verilerini KULLANMAZ.</string>
|
||||
<string name="permission_ignore_battery_optimizations_label">Pil optimizasyonlarını devre dışı bırak</string>
|
||||
<string name="permission_ignore_battery_optimizations_label">Pil optimizasyonunu devre dışı bırak</string>
|
||||
<string name="permission_ignore_battery_optimizations_description">Pil optimizasyonları, uygulamanın arka planda çalışırken güvenilir bir şekilde Bluetooth verilerini almasını engeller.</string>
|
||||
<string name="permission_required_title">Aşağıdaki izin gereklidir:</string>
|
||||
<string name="permission_system_alert_window_label">Sistem Uyarı Penceresi</string>
|
||||
<string name="permission_system_alert_window_description">\"Açılır Pencere Göster\" özelliğine izin vermek için CAPod\'un diğer uygulamaların üzerinde gösterilmesine izin vermelisiniz.</string>
|
||||
<string name="permission_system_alert_window_description">\"Açılır pencereyi göster\" özelliğine izin vermek için lütfen CAPod uygulamasının diğer uygulamaların üzerinde gösterilmesine izin verin.</string>
|
||||
<string name="settings_scanner_mode_lowpower_label">Düşük güç</string>
|
||||
<string name="settings_scanner_mode_balanced_label">Dengeli</string>
|
||||
<string name="settings_scanner_mode_lowlatency_label">Düşük gecikme süresi</string>
|
||||
<string name="settings_scanner_mode_lowlatency_label">Düşük gecikme</string>
|
||||
<string name="settings_monitor_mode_manual_label">Uygulama açıkken</string>
|
||||
<string name="settings_monitor_mode_automatic_label">Aygıt bağlandığında</string>
|
||||
<string name="settings_monitor_mode_always_label">Her zaman</string>
|
||||
@@ -58,6 +58,6 @@
|
||||
<string name="pods_case_unknown_state">Bilinmeyen durum</string>
|
||||
<string name="last_seen_x">Son görülme: %s</string>
|
||||
<string name="first_seen_x">İlk görülme: %s</string>
|
||||
<string name="permission_post_notifications_label">Gönderi bildirimleri</string>
|
||||
<string name="permission_post_notifications_description">"CAPod'un size AirPod'larınız hakkında bildirimler göstermesine izin verin, ör. bağlıyken geçerli durumları."</string>
|
||||
<string name="permission_post_notifications_label">Bildirimleri göster</string>
|
||||
<string name="permission_post_notifications_description">"CAPod'un AirPods'larınız hakkında bildirimler göstermesine izin vermelisiniz, ör. cihaza bağlıyken mevcut durumları."</string>
|
||||
</resources>
|
||||
|
||||
@@ -58,6 +58,6 @@
|
||||
<string name="pods_case_unknown_state">Невідомий стан</string>
|
||||
<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">"Дозволити CAPod показувати вам сповіщення щодо ваших AirPods, наприклад, їх поточний стан при підключенні."</string>
|
||||
<string name="permission_post_notifications_label">Показувати сповіщення</string>
|
||||
<string name="permission_post_notifications_description">"Дозволити CAPod показ сповіщень щодо ваших AirPods, наприклад, їх поточний стан при підключенні."</string>
|
||||
</resources>
|
||||
|
||||
@@ -53,4 +53,6 @@
|
||||
<string name="pods_case_unknown_state">Trạng thái không xác định</string>
|
||||
<string name="last_seen_x">Nhìn thấy lần cuối: %s</string>
|
||||
<string name="first_seen_x">Nhìn thấy lần đầu: %s</string>
|
||||
<string name="permission_post_notifications_label">Hiển thị thông báo</string>
|
||||
<string name="permission_post_notifications_description">"Cho phép CAPod hiển thị thông báo về AirPods của bạn, ví dụ: tình trạng hiện tại của họ trong khi kết nối."</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,5 +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_value_not_available_label">不可用</string>
|
||||
<string name="general_error_label">错误</string>
|
||||
<string name="general_grant_permission_action">授予权限</string>
|
||||
@@ -55,4 +58,6 @@
|
||||
<string name="pods_case_unknown_state">未知状态</string>
|
||||
<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">"允许 CAPod 显示有关您 AirPods 的通知,例如其连接时的当前状态"</string>
|
||||
</resources>
|
||||
|
||||
@@ -58,6 +58,6 @@
|
||||
<string name="pods_case_unknown_state">未知狀態</string>
|
||||
<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">"允許 CAPod 為您顯示關於您 AirPods 的通知,例如在連線時顯示它們的目前狀態。"</string>
|
||||
<string name="permission_post_notifications_label">顯示通知</string>
|
||||
<string name="permission_post_notifications_description">"允許 CAPod 顯示關於您 AirPods 的通知,例如在連線時顯示它們的目前狀態。"</string>
|
||||
</resources>
|
||||
|
||||
@@ -58,6 +58,6 @@
|
||||
<string name="pods_case_unknown_state">未知狀態</string>
|
||||
<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">"允許 CAPod 為您顯示關於您 AirPods 的通知,例如在連線時顯示它們的目前狀態。"</string>
|
||||
<string name="permission_post_notifications_label">顯示通知</string>
|
||||
<string name="permission_post_notifications_description">"允許 CAPod 顯示關於您 AirPods 的通知,例如在連線時顯示它們的目前狀態。"</string>
|
||||
</resources>
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
package eu.darken.capod.pods.core.apple.airpods
|
||||
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
import eu.darken.capod.pods.core.apple.BaseAirPodsTest
|
||||
import eu.darken.capod.pods.core.apple.HasAppleColor
|
||||
import io.kotest.matchers.shouldBe
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class AirPodsPro2UsbcTest : BaseAirPodsTest() {
|
||||
|
||||
/**
|
||||
* https://github.com/d4rken-org/capod/issues/164
|
||||
*/
|
||||
@Test
|
||||
fun `AirPods Pro 2 with USB-C - via #164`() = runTest {
|
||||
create<AirPodsPro2Usbc>("07 19 01 24 20 0B 99 8F 11 00 04 BD A7 3B FF 2D 8A 3C AF 9B 1A 7C 74 B7 A9 D1 C3") {
|
||||
|
||||
rawPrefix shouldBe 0x01.toUByte()
|
||||
rawDeviceModel shouldBe 0x2420.toUShort()
|
||||
rawStatus shouldBe 0x0B.toUByte()
|
||||
rawPodsBattery shouldBe 0x99.toUByte()
|
||||
rawFlags shouldBe 0x8.toUShort()
|
||||
rawCaseBattery shouldBe 0xF.toUShort()
|
||||
rawCaseLidState shouldBe 0x11.toUByte()
|
||||
rawDeviceColor shouldBe 0x0.toUByte()
|
||||
rawSuffix shouldBe 0x04.toUByte()
|
||||
|
||||
isLeftPodMicrophone shouldBe false
|
||||
isRightPodMicrophone shouldBe true
|
||||
|
||||
isLeftPodInEar shouldBe true
|
||||
isRightPodInEar shouldBe true
|
||||
|
||||
batteryLeftPodPercent shouldBe 0.9f
|
||||
batteryRightPodPercent shouldBe 0.9f
|
||||
|
||||
isCaseCharging shouldBe false
|
||||
isRightPodCharging shouldBe false
|
||||
isLeftPodCharging shouldBe false
|
||||
batteryCasePercent shouldBe null
|
||||
|
||||
podStyle.identifier shouldBe HasAppleColor.DeviceColor.WHITE.name
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
@@ -45,12 +45,9 @@ android {
|
||||
create("gplay") {
|
||||
dimension = "version"
|
||||
signingConfig = signingConfigs["releaseGplay"]
|
||||
extra["useBugsnag"] = true
|
||||
}
|
||||
}
|
||||
|
||||
setupBugsnagPlugin()
|
||||
|
||||
buildTypes {
|
||||
val customProguardRules = fileTree(File("../proguard")) {
|
||||
include("*.pro")
|
||||
@@ -141,7 +138,4 @@ dependencies {
|
||||
|
||||
implementation("androidx.wear:wear:1.2.0")
|
||||
implementation("androidx.wear.tiles:tiles-material:1.1.0")
|
||||
|
||||
"gplayImplementation"("com.bugsnag:bugsnag-android:5.9.2")
|
||||
"gplayImplementation"("com.getkeepsafe.relinker:relinker:1.4.3")
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -42,12 +42,9 @@ android {
|
||||
create("gplay") {
|
||||
dimension = "version"
|
||||
signingConfig = signingConfigs["releaseGplay"]
|
||||
extra["useBugsnag"] = true
|
||||
}
|
||||
}
|
||||
|
||||
setupBugsnagPlugin()
|
||||
|
||||
buildTypes {
|
||||
val customProguardRules = fileTree(File(projectDir, "proguard")) {
|
||||
include("*.pro")
|
||||
@@ -128,7 +125,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()
|
||||
|
||||
@@ -148,9 +145,6 @@ dependencies {
|
||||
|
||||
addTesting()
|
||||
|
||||
"gplayImplementation"("com.android.billingclient:billing:5.1.0")
|
||||
"gplayImplementation"("com.android.billingclient:billing-ktx:5.1.0")
|
||||
|
||||
"gplayImplementation"("com.bugsnag:bugsnag-android:5.9.2")
|
||||
"gplayImplementation"("com.getkeepsafe.relinker:relinker:1.4.3")
|
||||
"gplayImplementation"("com.android.billingclient:billing:6.0.1")
|
||||
"gplayImplementation"("com.android.billingclient:billing-ktx:6.0.1")
|
||||
}
|
||||
+16
-6
@@ -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" }
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package eu.darken.capod.common.preferences
|
||||
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.util.AttributeSet
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.AttrRes
|
||||
import androidx.annotation.StyleRes
|
||||
import androidx.preference.Preference
|
||||
import eu.darken.capod.common.debug.logging.Logging.Priority.ERROR
|
||||
import eu.darken.capod.common.debug.logging.log
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
|
||||
|
||||
class IntentPreference @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
@AttrRes defStyleAttr: Int = androidx.preference.R.attr.preferenceStyle,
|
||||
@StyleRes defStyleRes: Int = 0,
|
||||
) : Preference(context, attrs, defStyleAttr, defStyleRes) {
|
||||
|
||||
override fun setIntent(_intent: Intent?) {
|
||||
super.setIntent(_intent)
|
||||
_intent?.let {
|
||||
intent
|
||||
setOnPreferenceClickListener {
|
||||
try {
|
||||
context.startActivity(intent)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
log(TAG, ERROR) { "Failed to launch $intent: $e" }
|
||||
Toast.makeText(context, e.toString(), Toast.LENGTH_LONG).show()
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = logTag("IntentPreference")
|
||||
}
|
||||
}
|
||||
@@ -53,7 +53,7 @@ abstract class PreferenceFragment2
|
||||
|
||||
}
|
||||
|
||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
|
||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package eu.darken.capod.main.ui.onboarding
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.fragment.app.viewModels
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import eu.darken.capod.R
|
||||
import eu.darken.capod.common.PrivacyPolicy
|
||||
import eu.darken.capod.common.WebpageTool
|
||||
import eu.darken.capod.common.uix.Fragment3
|
||||
import eu.darken.capod.common.viewbinding.viewBinding
|
||||
import eu.darken.capod.databinding.OnboardingFragmentBinding
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
@AndroidEntryPoint
|
||||
class OnboardingFragment : Fragment3(R.layout.onboarding_fragment) {
|
||||
|
||||
override val vm: OnboardingFragmentVM by viewModels()
|
||||
override val ui: OnboardingFragmentBinding by viewBinding()
|
||||
|
||||
@Inject lateinit var webpageTool: WebpageTool
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
ui.goPrivacyPolicy.setOnClickListener { webpageTool.open(PrivacyPolicy.URL) }
|
||||
ui.continueAction.setOnClickListener { vm.finishOnboarding() }
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package eu.darken.capod.main.ui.onboarding
|
||||
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import eu.darken.capod.common.coroutine.DispatcherProvider
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
import eu.darken.capod.common.uix.ViewModel3
|
||||
import eu.darken.capod.main.core.GeneralSettings
|
||||
import kotlinx.coroutines.flow.*
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class OnboardingFragmentVM @Inject constructor(
|
||||
@Suppress("UNUSED_PARAMETER") handle: SavedStateHandle,
|
||||
private val dispatcherProvider: DispatcherProvider,
|
||||
private val generalSettings: GeneralSettings,
|
||||
) : ViewModel3(dispatcherProvider = dispatcherProvider) {
|
||||
|
||||
fun finishOnboarding() = launch {
|
||||
generalSettings.isOnboardingDone.value = true
|
||||
OnboardingFragmentDirections.actionOnboardingFragmentToOverviewFragment().navigate()
|
||||
}
|
||||
|
||||
companion object {
|
||||
val TAG = logTag("Onboarding", "Fragment", "VM")
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,8 @@ import android.text.SpannableStringBuilder
|
||||
import android.view.View
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.fragment.app.viewModels
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import eu.darken.capod.BuildConfig
|
||||
@@ -147,6 +149,7 @@ class OverviewFragment : Fragment3(R.layout.main_fragment) {
|
||||
vm.launchUpgradeFlow.observe2 {
|
||||
it(requireActivity())
|
||||
}
|
||||
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ import eu.darken.capod.common.debug.logging.log
|
||||
import eu.darken.capod.common.flow.combine
|
||||
import eu.darken.capod.common.flow.throttleLatest
|
||||
import eu.darken.capod.common.livedata.SingleLiveEvent
|
||||
import eu.darken.capod.common.navigation.navVia
|
||||
import eu.darken.capod.common.permissions.Permission
|
||||
import eu.darken.capod.common.uix.ViewModel3
|
||||
import eu.darken.capod.common.upgrade.UpgradeRepo
|
||||
@@ -48,6 +47,12 @@ class OverviewFragmentVM @Inject constructor(
|
||||
private val bluetoothManager: BluetoothManager2,
|
||||
) : ViewModel3(dispatcherProvider = dispatcherProvider) {
|
||||
|
||||
init {
|
||||
if (!generalSettings.isOnboardingDone.value) {
|
||||
OverviewFragmentDirections.actionOverviewFragmentToOnboardingFragment().navigate()
|
||||
}
|
||||
}
|
||||
|
||||
val upgradeState = upgradeRepo.upgradeInfo.asLiveData2()
|
||||
val launchUpgradeFlow = SingleLiveEvent<(Activity) -> Unit>()
|
||||
|
||||
@@ -124,7 +129,7 @@ class OverviewFragmentVM @Inject constructor(
|
||||
items.add(0, BluetoothDisabledVH.Item)
|
||||
} else if (mainPod == null) {
|
||||
items.add(0, MissingMainDeviceVH.Item {
|
||||
OverviewFragmentDirections.actionOverviewFragmentToTroubleShooterFragment().navVia(this)
|
||||
OverviewFragmentDirections.actionOverviewFragmentToTroubleShooterFragment().navigate()
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -140,12 +145,14 @@ class OverviewFragmentVM @Inject constructor(
|
||||
showDebug = isDebugMode,
|
||||
isMainPod = isMainPod,
|
||||
)
|
||||
|
||||
is SinglePodDevice -> SinglePodsCardVH.Item(
|
||||
now = now,
|
||||
device = it,
|
||||
showDebug = isDebugMode,
|
||||
isMainPod = isMainPod,
|
||||
)
|
||||
|
||||
else -> UnknownPodDeviceCardVH.Item(
|
||||
now = now,
|
||||
device = it,
|
||||
@@ -166,7 +173,7 @@ class OverviewFragmentVM @Inject constructor(
|
||||
}
|
||||
|
||||
fun goToSettings() = launch {
|
||||
OverviewFragmentDirections.actionOverviewFragmentToSettingsFragment().navVia(this@OverviewFragmentVM)
|
||||
OverviewFragmentDirections.actionOverviewFragmentToSettingsFragment().navigate()
|
||||
}
|
||||
|
||||
fun onUpgrade() = launch {
|
||||
|
||||
@@ -2,7 +2,10 @@ package eu.darken.capod.main.ui.settings
|
||||
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ListView
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.preference.Preference
|
||||
@@ -15,6 +18,7 @@ import eu.darken.capod.common.viewbinding.viewBinding
|
||||
import eu.darken.capod.databinding.SettingsFragmentBinding
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
|
||||
@AndroidEntryPoint
|
||||
class SettingsFragment : Fragment2(R.layout.settings_fragment),
|
||||
PreferenceFragmentCompat.OnPreferenceStartFragmentCallback {
|
||||
|
||||
+4
-4
@@ -1,19 +1,19 @@
|
||||
package eu.darken.capod.main.ui.settings.general
|
||||
|
||||
import android.bluetooth.BluetoothDevice
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import eu.darken.capod.R
|
||||
import eu.darken.capod.common.bluetooth.BluetoothDevice2
|
||||
|
||||
|
||||
class DeviceSelectionDialogFactory constructor(private val context: Context) {
|
||||
|
||||
fun create(
|
||||
devices: List<BluetoothDevice>,
|
||||
current: BluetoothDevice?,
|
||||
callback: (BluetoothDevice?) -> Unit
|
||||
devices: List<BluetoothDevice2>,
|
||||
current: BluetoothDevice2?,
|
||||
callback: (BluetoothDevice2?) -> Unit
|
||||
): AlertDialog {
|
||||
return MaterialAlertDialogBuilder(context).apply {
|
||||
setTitle(R.string.settings_maindevice_address_label)
|
||||
|
||||
-1
@@ -30,7 +30,6 @@ class GeneralSettingsFragmentVM @Inject constructor(
|
||||
val events = SingleLiveEvent<GeneralSettingsEvents>()
|
||||
|
||||
init {
|
||||
|
||||
generalSettings.monitorMode.flow
|
||||
.withPrevious()
|
||||
.filter { (old, new) ->
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package eu.darken.capod.monitor.core.worker
|
||||
|
||||
import android.app.NotificationManager
|
||||
import android.bluetooth.BluetoothDevice
|
||||
import android.content.Context
|
||||
import androidx.hilt.work.HiltWorker
|
||||
import androidx.work.CoroutineWorker
|
||||
@@ -9,6 +8,7 @@ import androidx.work.ForegroundInfo
|
||||
import androidx.work.WorkerParameters
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import eu.darken.capod.common.bluetooth.BluetoothDevice2
|
||||
import eu.darken.capod.common.bluetooth.BluetoothManager2
|
||||
import eu.darken.capod.common.coroutine.DispatcherProvider
|
||||
import eu.darken.capod.common.debug.Bugs
|
||||
@@ -30,8 +30,20 @@ import eu.darken.capod.reaction.core.autoconnect.AutoConnect
|
||||
import eu.darken.capod.reaction.core.playpause.PlayPause
|
||||
import eu.darken.capod.reaction.core.popup.PopUpReaction
|
||||
import eu.darken.capod.reaction.ui.popup.PopUpWindow
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.cancelChildren
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
|
||||
@HiltWorker
|
||||
@@ -130,7 +142,7 @@ class MonitorWorker @AssistedInject constructor(
|
||||
.flatMapLatest { arguments ->
|
||||
val monitorMode = arguments[0] as MonitorMode
|
||||
val mainAddress = arguments[1] as String?
|
||||
val devices = arguments[2] as Collection<BluetoothDevice>
|
||||
val devices = arguments[2] as Collection<BluetoothDevice2>
|
||||
|
||||
log(TAG) { "Monitor mode: $monitorMode" }
|
||||
when (monitorMode) {
|
||||
@@ -145,7 +157,7 @@ class MonitorWorker @AssistedInject constructor(
|
||||
log(TAG, WARN) { "Main device address not set, staying alive while any is connected" }
|
||||
}
|
||||
devices.any { it.address == mainAddress } -> {
|
||||
log(TAG) { "MainDevice is connected ($mainAddress), aborting any timeout." }
|
||||
log(TAG) { "Main device is connected ($mainAddress), aborting any timeout." }
|
||||
}
|
||||
else -> {
|
||||
log(TAG) { "No known Pods are connected, canceling worker soon." }
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package eu.darken.capod.reaction.ui
|
||||
|
||||
import android.bluetooth.BluetoothDevice
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.annotation.Keep
|
||||
@@ -11,6 +10,7 @@ import androidx.preference.ListPreference
|
||||
import androidx.preference.Preference
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import eu.darken.capod.R
|
||||
import eu.darken.capod.common.bluetooth.BluetoothDevice2
|
||||
import eu.darken.capod.common.uix.PreferenceFragment3
|
||||
import eu.darken.capod.common.upgrade.UpgradeRepo
|
||||
import eu.darken.capod.main.core.GeneralSettings
|
||||
@@ -36,7 +36,7 @@ class ReactionSettingsFragment : PreferenceFragment3() {
|
||||
override val preferenceFile: Int = R.xml.preferences_reactions
|
||||
|
||||
private var isPro: Boolean = false
|
||||
private var bondedDevices: List<BluetoothDevice> = emptyList()
|
||||
private var bondedDevices: List<BluetoothDevice2> = emptyList()
|
||||
private val autoConnectConditionPref by lazy { findPreference<ListPreference>(settings.autoConnectCondition.key)!! }
|
||||
|
||||
override fun onPreferencesCreated() {
|
||||
@@ -83,6 +83,11 @@ class ReactionSettingsFragment : PreferenceFragment3() {
|
||||
upgradeRepo.launchBillingFlow(requireActivity())
|
||||
preference.isChecked = false
|
||||
return true
|
||||
} else if (preference.key == reactionSettings.showPopUpOnConnection.key && !isPro) {
|
||||
preference as CheckBoxPreference
|
||||
upgradeRepo.launchBillingFlow(requireActivity())
|
||||
preference.isChecked = false
|
||||
return true
|
||||
}
|
||||
|
||||
return super.onPreferenceTreeClick(preference)
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M20,2H4A2,2 0 0,0 2,4V22L6,18H20A2,2 0 0,0 22,16V4C22,2.89 21.1,2 20,2Z" />
|
||||
</vector>
|
||||
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M20 2H4C2.9 2 2 2.9 2 4V22L6 18H20C21.1 18 22 17.1 22 16V4C22 2.9 21.1 2 20 2M20 16H5.2L4 17.2V4H20V16Z" />
|
||||
</vector>
|
||||
@@ -22,6 +22,8 @@
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:paddingBottom="48dp"
|
||||
android:clipToPadding="false"
|
||||
tools:listitem="@layout/overview_pods_dual_item"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/toolbar" />
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="32dp">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/mascot"
|
||||
android:layout_width="96dp"
|
||||
android:layout_height="96dp"
|
||||
android:layout_marginTop="48dp"
|
||||
android:src="@drawable/splash_graphic2"
|
||||
app:layout_constraintEnd_toEndOf="@id/title"
|
||||
app:layout_constraintStart_toStartOf="@id/title"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/title"
|
||||
style="@style/TextAppearance.Material3.HeadlineLarge"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="@string/app_name"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/mascot" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/body1"
|
||||
style="@style/TextAppearance.MaterialComponents.Body1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="32dp"
|
||||
android:text="@string/onboarding_body1"
|
||||
app:layout_constraintTop_toBottomOf="@id/title" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/body2"
|
||||
style="@style/TextAppearance.MaterialComponents.Body1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/onboarding_body2"
|
||||
app:layout_constraintTop_toBottomOf="@id/body1" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/body3"
|
||||
style="@style/TextAppearance.MaterialComponents.Body1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/onboarding_body3"
|
||||
app:layout_constraintTop_toBottomOf="@id/body2" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/body4"
|
||||
style="@style/TextAppearance.MaterialComponents.Body1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/onboarding_body4"
|
||||
app:layout_constraintTop_toBottomOf="@id/body3" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/go_privacy_policy"
|
||||
style="@style/Widget.Material3.Button.TonalButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginVertical="32dp"
|
||||
android:text="@string/settings_privacy_policy_label"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/body4" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/continue_action"
|
||||
style="@style/Widget.Material3.Button"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="32dp"
|
||||
android:layout_marginTop="32dp"
|
||||
android:text="@string/general_continue_action"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/go_privacy_policy" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</ScrollView>
|
||||
@@ -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" />
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
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" />
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
<action
|
||||
android:id="@+id/action_overviewFragment_to_troubleShooterFragment"
|
||||
app:destination="@id/troubleShooterFragment" />
|
||||
<action
|
||||
android:id="@+id/action_overviewFragment_to_onboardingFragment"
|
||||
app:destination="@id/onboardingFragment" />
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
@@ -27,5 +30,13 @@
|
||||
android:id="@+id/troubleShooterFragment"
|
||||
android:name="eu.darken.capod.troubleshooter.ui.TroubleShooterFragment"
|
||||
tools:layout="@layout/troubleshooter_fragment" />
|
||||
<fragment
|
||||
android:id="@+id/onboardingFragment"
|
||||
android:name="eu.darken.capod.main.ui.onboarding.OnboardingFragment"
|
||||
tools:layout="@layout/onboarding_fragment">
|
||||
<action
|
||||
android:id="@+id/action_onboardingFragment_to_overviewFragment"
|
||||
app:destination="@id/overviewFragment" />
|
||||
</fragment>
|
||||
|
||||
</navigation>
|
||||
@@ -37,10 +37,9 @@
|
||||
<string name="settings_maindevice_address_none">غير موجود</string>
|
||||
<string name="settings_maindevice_model_label">طِراز جهازك</string>
|
||||
<string name="settings_maindevice_model_description">طِراز جهازك الرئيسي. يساعد هذا التطبيق في التعرف على جهازك عندما لا يكون متصلاً بهاتفك.</string>
|
||||
<string name="settings_popup_caseopen_label">عرض إشعار منبثق</string>
|
||||
<string name="settings_popup_caseopen_description">عرض إشعار منبثق عند فتح علبة الجهاز (تجريبي).</string>
|
||||
<string name="settings_onepod_mode_label">وضعية السماعة الواحدة</string>
|
||||
<string name="settings_onepod_mode_description">ليس بالضرورة ارتداء كِلتا السمَّاعتين، ارتداء سمَّاعة واحدة يكفي لتحريك ردود الفعل.</string>
|
||||
<string name="settings_popup_caseopen_description">عرض إشعار منبثق عند فتح علبة الجهاز (تجريبي).</string>
|
||||
<string name="notification_channel_device_status_label">حالة الجهاز</string>
|
||||
<string name="debug_debuglog_size_label">الحجم</string>
|
||||
<string name="debug_debuglog_size_compressed_label">الحجم المضغوط</string>
|
||||
|
||||
@@ -33,10 +33,9 @@
|
||||
<string name="settings_maindevice_address_none">Heç biri</string>
|
||||
<string name="settings_maindevice_model_label">Cihazınızın modeli</string>
|
||||
<string name="settings_maindevice_model_description">Əsas cihazının modeli. Bu, telefonunuzla bağlantı qurulmadığı vaxtlarda tətbiqin cihazınızı tanımasına kömək edir.</string>
|
||||
<string name="settings_popup_caseopen_label">Açılan pəncərəni göstər</string>
|
||||
<string name="settings_popup_caseopen_description">Cihazın qutusu açılanda bir açılan pəncərə göstər (təcrübi).</string>
|
||||
<string name="settings_onepod_mode_label">Tək tərəf rejimi</string>
|
||||
<string name="settings_onepod_mode_description">Hər iki tərəfi də taxmağa ehtiyac yoxdur, reaksiyaları tətikləmək üçün tək tərəfi taxmaq yetərlidir.</string>
|
||||
<string name="settings_popup_caseopen_description">Cihazın qutusu açılanda bir açılan pəncərə göstər (təcrübi).</string>
|
||||
<string name="notification_channel_device_status_label">Cihaz vəziyyəti</string>
|
||||
<string name="debug_debuglog_size_label">Həcm</string>
|
||||
<string name="debug_debuglog_size_compressed_label">Sıxışdırılmış həcm</string>
|
||||
|
||||
@@ -43,10 +43,12 @@
|
||||
<string name="settings_maindevice_address_none">Няма</string>
|
||||
<string name="settings_maindevice_model_label">Мадэль вашай прылады</string>
|
||||
<string name="settings_maindevice_model_description">Мадэль вашай прылады. Гэта дапамагае праграме апазнаць вашу прыладу, калі яна не падключана да вашага тэлефона.</string>
|
||||
<string name="settings_popup_caseopen_label">Паказваць апавяшчэнне</string>
|
||||
<string name="settings_popup_caseopen_description">Паказваць апавяшчэнне, калі футляр прылады адчынены (эксперыментальная функцыя).</string>
|
||||
<string name="settings_onepod_mode_label">Рэжым аднаго навушніка</string>
|
||||
<string name="settings_onepod_mode_description">Нашэнне абодвух навушнікаў не абавязкова, нашэнне аднаго навушніка цалкам дастаткова для запуску рэакцый.</string>
|
||||
<string name="settings_popup_caseopen_label">Паказваць апавяшчэнне пры адкрыцці футляра</string>
|
||||
<string name="settings_popup_caseopen_description">Паказваць апавяшчэнне, калі футляр прылады адчынены (эксперыментальная функцыя).</string>
|
||||
<string name="settings_popup_connected_label">Паказваць апавяшчэнне пры падключэнні</string>
|
||||
<string name="settings_popup_connected_description">Паказваць апавяшчэнне пры першапачатковым падключэнні прылады.</string>
|
||||
<string name="notification_channel_device_status_label">Статус прылады</string>
|
||||
<string name="debug_debuglog_size_label">Памер</string>
|
||||
<string name="debug_debuglog_size_compressed_label">Сціснуты памер</string>
|
||||
@@ -99,4 +101,9 @@
|
||||
<string name="troubleshooter_ble_result_failure_phone_headphones">Ваш тэлефон атрымаў даныя BLE, але яны паступаюць з прылады, якая не падтрымліваецца. Вашы навушнікі ўключаны? Ці падтрымлівае CAPod вашы навушнікі?</string>
|
||||
<string name="troubleshooter_ble_result_failure_action">Паспрабаваць яшчэ раз</string>
|
||||
<string name="troubleshoot_action">Выпраўленне непаладак</string>
|
||||
<string name="onboarding_body1">Гэта праграма дадае ў Android падтрымку спецыяльных функцый AirPod.</string>
|
||||
<string name="onboarding_body2">Не ўсе прылады Android цалкам падтрымліваюць дадатковыя функцыі AirPod. Ваша аперацыйная сістэма патрабуе карэктнай рэалізацыі функцыі Bluetooth Low Energy.</string>
|
||||
<string name="onboarding_body3">CAPod не змяшчае рэкламу і не збірае вашы даныя.</string>
|
||||
<string name="onboarding_body4">Вы можаце перайсці на CAPod Pro, каб атрымаць дадатковыя функцыі і такім чынам падтрымаць распрацоўку.</string>
|
||||
<string name="general_continue_action">Працягнуць</string>
|
||||
</resources>
|
||||
|
||||
@@ -43,10 +43,12 @@
|
||||
<string name="settings_maindevice_address_none">Cap</string>
|
||||
<string name="settings_maindevice_model_label">El vostre model de dispositiu</string>
|
||||
<string name="settings_maindevice_model_description">El model del vostre dispositiu principal. Això ajuda l\'aplicació a reconèixer el vostre dispositiu quan no està connectat al vostre telèfon.</string>
|
||||
<string name="settings_popup_caseopen_label">Mostra la finestra emergent</string>
|
||||
<string name="settings_popup_caseopen_description">Mostra una finestra emergent quan s\'obre la funda del dispositiu (experimental).</string>
|
||||
<string name="settings_onepod_mode_label">Mode d\'un AirPod</string>
|
||||
<string name="settings_onepod_mode_description">No cal portar els dos AirdPods, per provocar reaccions només cal portar-ne un.</string>
|
||||
<string name="settings_popup_caseopen_label">Mostra la finestra emergent de la funda</string>
|
||||
<string name="settings_popup_caseopen_description">Mostra una finestra emergent quan s\'obre la funda del dispositiu (experimental).</string>
|
||||
<string name="settings_popup_connected_label">Mostra la finestra emergent de connexió</string>
|
||||
<string name="settings_popup_connected_description">Mostra una finestra emergent quan el dispositiu es connecti per primera vegada.</string>
|
||||
<string name="notification_channel_device_status_label">Estat del dispositiu</string>
|
||||
<string name="debug_debuglog_size_label">Mida</string>
|
||||
<string name="debug_debuglog_size_compressed_label">Mida comprimida</string>
|
||||
@@ -95,8 +97,13 @@
|
||||
<string name="troubleshooter_ble_result_success_body">El CAPod està rebent emissions d\'anuncis BLE.</string>
|
||||
<string name="troubleshooter_ble_result_failure_title">Incorrecte</string>
|
||||
<string name="troubleshooter_ble_result_failure_body">La resolució de problemes ha fallat. No ha funcionat cap combinació d\'opcions de compatibilitat.</string>
|
||||
<string name="troubleshooter_ble_result_failure_phone_body">El vostre telèfon no ha rebut cap dada BLE. Podeu tornar a provar aquesta prova en una zona plena de gent per veure si es poden rebre fonts de dades (que no siguin els vostres auriculars). No es reben dades que indiquen un problema amb el sistema operatiu del vostre telèfon.</string>
|
||||
<string name="troubleshooter_ble_result_failure_phone_body">El vostres telèfon no ha rebut cap dada BLE. Podeu tornar a provar aquesta prova en una zona plena de gent per veure si es poden rebre fonts de dades (que no siguin els vostres auriculars). No rebre dades indica un problema amb el sistema operatiu del telèfon.</string>
|
||||
<string name="troubleshooter_ble_result_failure_phone_headphones">El vostre telèfon ha rebut dades BLE, però les dades no provenen de cap dispositiu compatible. Teniu els auriculars encesos? El CAPod és compatible amb els vostres auriculars?</string>
|
||||
<string name="troubleshooter_ble_result_failure_action">Torna-ho a provar</string>
|
||||
<string name="troubleshoot_action">Resolució de problemes</string>
|
||||
<string name="onboarding_body1">Aquesta aplicació afegeix suport per a funcions específiques d\'AirPod a l\'Android.</string>
|
||||
<string name="onboarding_body2">No tots els dispositius Android són compatibles completament amb les funcions addicionals d\'AirPod. El vostre sistema operatiu requereix una implementació de Bluetooth de baixa energia que funcioni correctament.</string>
|
||||
<string name="onboarding_body3">El CAPod no conté anuncis i no recull les vostres dades.</string>
|
||||
<string name="onboarding_body4">Podeu actualitzar a CAPod Pro per obtenir funcions addicionals i donar suport al desenvolupament.</string>
|
||||
<string name="general_continue_action">Continua</string>
|
||||
</resources>
|
||||
|
||||
@@ -43,10 +43,12 @@
|
||||
<string name="settings_maindevice_address_none">Žádná</string>
|
||||
<string name="settings_maindevice_model_label">Model vašeho zařízení</string>
|
||||
<string name="settings_maindevice_model_description">Model vašeho hlavního zařízení. To pomůže aplikaci rozpoznat vaše zařízení, když není připojeno k telefonu.</string>
|
||||
<string name="settings_popup_caseopen_label">Zobrazit vyskakovací okno</string>
|
||||
<string name="settings_popup_caseopen_description">Zobrazit vyskakovací okno, pokud je pouzdro zařízení otevřeno (experimentální).</string>
|
||||
<string name="settings_onepod_mode_label">Režim jednoho sluchátka</string>
|
||||
<string name="settings_onepod_mode_description">Není nutné nosit obě sluchátka, k vyvolání reakce stačí nosit jen jedno.</string>
|
||||
<string name="settings_popup_caseopen_label">Zobrazit vyskakovací okno pouzdra</string>
|
||||
<string name="settings_popup_caseopen_description">Zobrazit vyskakovací okno, pokud je pouzdro zařízení otevřeno (experimentální).</string>
|
||||
<string name="settings_popup_connected_label">Zobrazit vyskakovací okno při připojení</string>
|
||||
<string name="settings_popup_connected_description">Zobrazit vyskakovací okno při prvním připojení zařízení.</string>
|
||||
<string name="notification_channel_device_status_label">Stav zařízení</string>
|
||||
<string name="debug_debuglog_size_label">Velikost</string>
|
||||
<string name="debug_debuglog_size_compressed_label">Komprimovaná velikost</string>
|
||||
@@ -99,4 +101,9 @@
|
||||
<string name="troubleshooter_ble_result_failure_phone_headphones">Telefon přijal data BLE, ale data nepocházejí z žádného podporovaného zařízení. Jsou sluchátka zapnutá? Podporuje CAPod vaše sluchátka?</string>
|
||||
<string name="troubleshooter_ble_result_failure_action">Zkusit znovu</string>
|
||||
<string name="troubleshoot_action">Vyřešit problém</string>
|
||||
<string name="onboarding_body1">Tato aplikace přidává do Androidu podporu specifických funkcí AirPods.</string>
|
||||
<string name="onboarding_body2">Ne všechna zařízení s Androidem plně podporují speciální funkce AirPods.</string>
|
||||
<string name="onboarding_body3">CAPod neobsahuje žádné reklamy a neshromažďuje vaše údaje.</string>
|
||||
<string name="onboarding_body4">Chcete-li získat speciální funkce a podpořit vývoj, můžete přejít na verzi CAPod Pro.</string>
|
||||
<string name="general_continue_action">Pokračovat</string>
|
||||
</resources>
|
||||
|
||||
@@ -43,10 +43,12 @@
|
||||
<string name="settings_maindevice_address_none">Keine</string>
|
||||
<string name="settings_maindevice_model_label">Ihr Gerätemodell</string>
|
||||
<string name="settings_maindevice_model_description">Das Modell Ihres Hauptgeräts. Dies hilft der App, Ihr Gerät zu erkennen, wenn es nicht mit Ihrem Telefon verbunden ist.</string>
|
||||
<string name="settings_popup_caseopen_label">Popup zeigen</string>
|
||||
<string name="settings_popup_caseopen_description">Ein Popup anzeigen, wenn das Gerätegehäuse geöffnet wird (experimentell).</string>
|
||||
<string name="settings_onepod_mode_label">Ein-Pod-Modus</string>
|
||||
<string name="settings_onepod_mode_description">Das Tragen beider Pods ist nicht erforderlich. Das Tragen eines einzigen Pods reicht aus, um Reaktionen auszulösen.</string>
|
||||
<string name="settings_popup_caseopen_label">Hüllen popup anzeigen</string>
|
||||
<string name="settings_popup_caseopen_description">Ein Popup anzeigen, wenn das Gerätegehäuse geöffnet wird (experimentell).</string>
|
||||
<string name="settings_popup_connected_label">Verbindungs-Popup anzeigen</string>
|
||||
<string name="settings_popup_connected_description">Zeigt ein Popup an, wenn das Gerät zum ersten Mal eine Verbindung herstellt.</string>
|
||||
<string name="notification_channel_device_status_label">Geräte Status</string>
|
||||
<string name="debug_debuglog_size_label">Größe</string>
|
||||
<string name="debug_debuglog_size_compressed_label">Komprimierte Größe</string>
|
||||
@@ -68,10 +70,10 @@
|
||||
<string name="settings_licenses_label">Lizenzen</string>
|
||||
<string name="settings_category_other_label">Andere</string>
|
||||
<string name="settings_general_label">Einstellungen</string>
|
||||
<string name="settings_general_description">Allgemeine Optimierungen, die die gesamte App betreffen.</string>
|
||||
<string name="settings_general_description">Allgemeine Optimierungen, welche die gesamte App betreffen.</string>
|
||||
<string name="settings_acknowledgements_label">Danksagungen</string>
|
||||
<string name="settings_debug_autoreports_label">Automatische Fehlerberichte</string>
|
||||
<string name="settings_debug_autoreports_description">Meldet Probleme automatisch, z.B. Details zu einem App-Absturz, damit ich herausfinden kann, wie ich ihn beheben kann.</string>
|
||||
<string name="settings_debug_autoreports_description">Meldet Probleme automatisch, z.B. Details zu einem App-Absturz, welche dem Entwickler bei der Behebung unterstützen.</string>
|
||||
<string name="settings_debug_mode_label">Debug-Modus</string>
|
||||
<string name="settings_debug_mode_description">Zeige zusätzliche Informationen an, um Probleme zu beheben.</string>
|
||||
<string name="settings_blescanner_unfiltered_label">Ungefilterte BLE-Daten</string>
|
||||
@@ -95,8 +97,13 @@
|
||||
<string name="troubleshooter_ble_result_success_body">BLE-Werbesendungen werden von CAPod empfangen.</string>
|
||||
<string name="troubleshooter_ble_result_failure_title">Erfolglos</string>
|
||||
<string name="troubleshooter_ble_result_failure_body">Fehlerbehebung fehlgeschlagen. Keine Kombination von Kompatibilitätsoptionen hat geholfen.</string>
|
||||
<string name="troubleshooter_ble_result_failure_phone_body">Ihr Telefon hat überhaupt keine BLE-Daten empfangen. Sie können diesen Test in einem überfüllten Bereich wiederholen, um zu sehen, ob Datenquellen (außer Ihren Kopfhörern) empfangen werden können. Es werden keine Daten empfangen, was auf ein Problem mit dem Betriebssystem Ihres Telefons hindeutet.</string>
|
||||
<string name="troubleshooter_ble_result_failure_phone_body">Dein Telefon hat überhaupt keine BLE-Daten empfangen. Du könntest diesen Test in einem überfüllten Bereich wiederholen, um zu sehen, ob Datenquellen (außer Ihren Kopfhörern) empfangen werden können. Es werden keine Daten empfangen, was auf ein Problem mit dem Betriebssystem Ihres Telefons hindeutet.</string>
|
||||
<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 Funktionen 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>
|
||||
|
||||
@@ -32,15 +32,23 @@
|
||||
<string name="settings_reaction_label">Αντιδράσεις</string>
|
||||
<string name="settings_reaction_description">Αντίδραση σε συμβάντα και συμπεριφορές.</string>
|
||||
<string name="settings_category_yourdevice_label">Η συσκευή σας</string>
|
||||
<string name="settings_category_compatibility_options_title">Ρυθμίσεις συμβατότητας</string>
|
||||
<string name="settings_category_compatibility_options_description">Μην αγγίζεις άμα όλα δουλεύουν ;)</string>
|
||||
<string name="settings_compat_offloaded_filtering_disabled_title">Απενεργοποίηση φιλτραρίσματος υλικού</string>
|
||||
<string name="settings_compat_offloaded_filtering_disabled_summary">Μην μεταβιβάζετε φιλτράρισμα δεδομένων στο σύστημα, αλλά λάβετε όλα τα δεδομένα και φιλτράρετε τα μέσα στην εφαρμογή.</string>
|
||||
<string name="settings_compat_offloaded_batching_disabled_title">Απενεργοποίηση παρτίδας υλικού</string>
|
||||
<string name="settings_compat_offloaded_batching_disabled_summary">Μην αφήσετε την ομάδα συστήματος να συλλέξει δεδομένα BLE πριν τα προχωρήσει σε εμάς.</string>
|
||||
<string name="settings_maindevice_address_label">Διεύθυνση της συσκευής σας</string>
|
||||
<string name="settings_maindevice_address_description">Η διεύθυνση της αντιστοιχισμένης συσκευής σας. Η εφαρμογή τη χρησιμοποιεί για να προσδιορίσει εάν η συσκευή σας είναι συνδεδεμένη στο τηλέφωνό σας.</string>
|
||||
<string name="settings_maindevice_address_none">Καμία</string>
|
||||
<string name="settings_maindevice_model_label">Μοντέλο της συσκευής σας</string>
|
||||
<string name="settings_maindevice_model_description">Το μοντέλο της κύριας συσκευής σας. Αυτό βοηθά την εφαρμογή να αναγνωρίσει τη συσκευή σας όταν δεν είναι συνδεδεμένη στο τηλέφωνό σας.</string>
|
||||
<string name="settings_popup_caseopen_label">Εμφάνιση αναδυόμενου</string>
|
||||
<string name="settings_popup_caseopen_description">Εμφάνιση αναδυόμενου όταν η θήκη της συσκευής είναι ανοιχτή (πειραματικό).</string>
|
||||
<string name="settings_onepod_mode_label">Λειτουργία ενός pod</string>
|
||||
<string name="settings_onepod_mode_description">Δεν απαιτείται να φοράτε και τα δύο pods, αρκεί να φοράτε ένα μόνο pod για να ενεργοποιούνται αντιδράσεις.</string>
|
||||
<string name="settings_popup_caseopen_label">Εμφάνιση πληροφοριών θήκης</string>
|
||||
<string name="settings_popup_caseopen_description">Εμφάνιση αναδυόμενου όταν η θήκη της συσκευής είναι ανοιχτή (πειραματικό).</string>
|
||||
<string name="settings_popup_connected_label">Εμφάνιση παραθυράκι σύνδεσης</string>
|
||||
<string name="settings_popup_connected_description">Εμφάνιση παραθύρου όταν η συσκευή συνδέεται για πρώτη φορά.</string>
|
||||
<string name="notification_channel_device_status_label">Κατάσταση συσκευής</string>
|
||||
<string name="debug_debuglog_size_label">Μέγεθος</string>
|
||||
<string name="debug_debuglog_size_compressed_label">Συμπιεσμένο μέγεθος</string>
|
||||
@@ -53,6 +61,7 @@
|
||||
<string name="settings_support_description">Εάν χρειάζεστε κάποια βοήθεια.</string>
|
||||
<string name="issue_tracker_label">Εργαλείο παρακολούθησης θεμάτων</string>
|
||||
<string name="issue_tracker_description">Δημόσιο εργαλείο παρακολούθησης θεμάτων για αναφορές σφαλμάτων και αιτήματα για δυνατότητες (μόνο αγγλικά).</string>
|
||||
<string name="discord_label">Discord</string>
|
||||
<string name="discord_description">Ένα μέρος για να αράξετε και να κάνετε ερωτήσεις.</string>
|
||||
<string name="changelog_label">Αρχείο αλλαγών</string>
|
||||
<string name="settings_label">Ρυθμίσεις</string>
|
||||
@@ -74,5 +83,27 @@
|
||||
<string name="help_translate_label">Μετάφραση</string>
|
||||
<string name="help_translate_description">Βοηθήστε να μεταφραστεί αυτή η εφαρμογή στην αγαπημένη σας γλώσσα.</string>
|
||||
<string name="translators_thanks_title">Μεταφραστές</string>
|
||||
<string name="translators_thanks_description">Vagelis1608</string>
|
||||
<string name="translators_thanks_description">Vagelis1608;Theodoros Asimakopoulos;gogoboss_stampolidis;Matthias;Christos Chronopoulos</string>
|
||||
<string name="widget_description">Ένα γραφικό στοιχείο που δείχνει την τελευταία γνωστή κατάσταση συσκευής.</string>
|
||||
<string name="settings_compat_indirectcallback_title">Έμμεση παράδοση δεδομένων</string>
|
||||
<string name="settings_compat_indirectcallback_summary">Χρησιμοποιήστε εναλλακτική μέθοδο για να λάβετε δεδομένα BLE από το σύστημα (broadcast αντί για callback).</string>
|
||||
<string name="troubleshooter_title">Αντιμετώπιση προβλημάτων</string>
|
||||
<string name="troubleshooter_ble_intro_title">Εκπομπές Χαμηλής Ενέργειας Bluetooth</string>
|
||||
<string name="troubleshooter_ble_intro_body1">Τα AirPods (και παρόμοια ακουστικά) μεταδίδουν πληροφορίες κατάστασης χρησιμοποιώντας μια τεχνολογία BLE που ονομάζεται \"διαφημίσεις\". Ορισμένα τηλέφωνα δεν εφαρμόζουν σωστά αυτήν την τεχνολογία. Το CAPod μπορεί να προσπαθήσει να το διορθώσει δοκιμάζοντας διαφορετικές επιλογές συμβατότητας μέχρι να ληφθούν δεδομένα. Ξεκινήστε την αναπαραγωγή μουσικής στα ακουστικά σας και τοποθετήστε τα κοντά στο τηλέφωνό σας και, στη συνέχεια, ξεκινήστε τη διαδικασία.</string>
|
||||
<string name="troubleshooter_ble_intro_start_action">Ξεκινήστε την αντιμετώπιση προβλημάτων</string>
|
||||
<string name="troubleshooter_ble_process_title">Αντιμετώπιση προβλημάτων σε εξέλιξη</string>
|
||||
<string name="troubleshooter_ble_process_subtile">Κάθε βήμα διαρκεί 5-10 δευτερόλεπτα</string>
|
||||
<string name="troubleshooter_ble_result_success_title">Επιτυχία</string>
|
||||
<string name="troubleshooter_ble_result_success_body">Οι διαφημιστικές εκπομπές BLE λαμβάνονται από το CAPod.</string>
|
||||
<string name="troubleshooter_ble_result_failure_title">Ανεπιτυχής</string>
|
||||
<string name="troubleshooter_ble_result_failure_body">Η αντιμετώπιση προβλημάτων απέτυχε. Κανένας συνδυασμός επιλογών συμβατότητας δεν βοήθησε.</string>
|
||||
<string name="troubleshooter_ble_result_failure_phone_body">Το τηλέφωνό σας δεν έλαβε καθόλου δεδομένα BLE. Μπορείτε να δοκιμάσετε ξανά αυτήν τη δοκιμή σε μια πολυσύχναστη περιοχή για να δείτε εάν μπορούν να ληφθούν πηγές δεδομένων (εκτός από τα ακουστικά σας). Δε λαμβάνονται δεδομένα που δείχνουν ένα πρόβλημα με το λειτουργικό σύστημα του τηλεφώνου σας.</string>
|
||||
<string name="troubleshooter_ble_result_failure_phone_headphones">Το τηλέφωνό σας έλαβε δεδομένα BLE, αλλά τα δεδομένα δεν προέρχονται από καμία υποστηριζόμενη συσκευή. Είναι ενεργοποιημένα τα ακουστικά σας; Το CAPod υποστηρίζει τα ακουστικά σας;</string>
|
||||
<string name="troubleshooter_ble_result_failure_action">Δοκιμάστε ξανά</string>
|
||||
<string name="troubleshoot_action">Αντιμετώπιση</string>
|
||||
<string name="onboarding_body1">Αυτή η εφαρμογή προσθέτει υποστήριξη για συγκεκριμένες λειτουργίες του AirPod στο Android.</string>
|
||||
<string name="onboarding_body2">Δεν υποστηρίζουν πλήρως όλες οι συσκευές Android τις επιπλέον δυνατότητες του AirPod. Το λειτουργικό σας σύστημα απαιτεί μια σωστή εφαρμογή Bluetooth-χαμηλής ενέργειας.</string>
|
||||
<string name="onboarding_body3">Το CAPod δεν έχει διαφημίσεις και δε συλλέγει τα δεδομένα σας.</string>
|
||||
<string name="onboarding_body4">Μπορείτε να αναβαθμίσετε στο CAPod Pro για να αποκτήσετε επιπλέον λειτουργίες και να υποστηρίξετε την ανάπτυξη.</string>
|
||||
<string name="general_continue_action">Συνέχεια</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,2 +1,8 @@
|
||||
<?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="onboarding_body1">Esta aplicación agrega soporte para funciones específicas de los AirPods para Android.</string>
|
||||
<string name="onboarding_body2">No todos los dispositivos Android son totalmente compatibles con las funciones adicionales de los AirPods. Tu sistema operativo requiere una implementación de un Bluetooth de baja energía que funcione correctamente.</string>
|
||||
<string name="onboarding_body3">CAPod no contiene anuncios y no recopila tus datos.</string>
|
||||
<string name="onboarding_body4">Puedes actualizar a CAPod Pro para obtener funciones adicionales y apoyar el desarrollo.</string>
|
||||
<string name="general_continue_action">Sigue</string>
|
||||
</resources>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<string name="general_check_action">Verificar</string>
|
||||
<string name="general_close_action">Cerrar</string>
|
||||
<string name="upgrade_capod_label">Actualizar CAPod</string>
|
||||
<string name="upgrade_capod_description">Consigue características adicionales y apoya al desarrollador.</string>
|
||||
<string name="upgrade_capod_description">Consigue las funciones adicionales y apoya al desarrollador.</string>
|
||||
<string name="settings_monitor_mode_label">Modo monitor</string>
|
||||
<string name="settings_monitor_mode_description">Bajo qué circunstancias esta aplicación monitorea los datos Bluetooth.</string>
|
||||
<string name="settings_scanner_mode_label">Modo escáner</string>
|
||||
@@ -43,10 +43,12 @@
|
||||
<string name="settings_maindevice_address_none">Ninguno</string>
|
||||
<string name="settings_maindevice_model_label">El modelo de su dispositivo</string>
|
||||
<string name="settings_maindevice_model_description">El modelo de su dispositivo principal. Esto ayuda a que la aplicación reconozca su dispositivo cuando no está conectado a su teléfono.</string>
|
||||
<string name="settings_popup_caseopen_label">Mostrar ventanas emergentes</string>
|
||||
<string name="settings_popup_caseopen_description">Muestra una ventana emergente cuando se abre el estuche del dispositivo (experimental).</string>
|
||||
<string name="settings_onepod_mode_label">Modo un audífono</string>
|
||||
<string name="settings_onepod_mode_description">No es necesario usar ambos AirPods, usar un solo AirPod es suficiente para desencadenar reacciones.</string>
|
||||
<string name="settings_popup_caseopen_label">Mostrar una ventana emergente</string>
|
||||
<string name="settings_popup_caseopen_description">Muestra una ventana emergente cuando se abre el estuche del dispositivo (experimental).</string>
|
||||
<string name="settings_popup_connected_label">Mostrar una ventana emergente con la conexión</string>
|
||||
<string name="settings_popup_connected_description">Mostrar una ventana emergente cuando el dispositivo se conecta por primera vez.</string>
|
||||
<string name="notification_channel_device_status_label">Estado del dispositivo</string>
|
||||
<string name="debug_debuglog_size_label">Tamaño</string>
|
||||
<string name="debug_debuglog_size_compressed_label">Tamaño comprimido</string>
|
||||
@@ -95,8 +97,13 @@
|
||||
<string name="troubleshooter_ble_result_success_body">CAPod recibe las emisiones de anuncios BLE.</string>
|
||||
<string name="troubleshooter_ble_result_failure_title">Incorrecto</string>
|
||||
<string name="troubleshooter_ble_result_failure_body">La solución de problemas falló. Ninguna combinación de las opciones de compatibilidad ayudó.</string>
|
||||
<string name="troubleshooter_ble_result_failure_phone_body">Tu teléfono no ha recibido ningún dato BLE. Puedes volver a realizar esta prueba en una zona concurrida para ver si se pueden recibir otras fuentes de datos (distintas de los auriculares). El hecho de que no se reciban los datos apunta a un problema con el sistema operativo del teléfono.</string>
|
||||
<string name="troubleshooter_ble_result_failure_phone_body">Tu teléfono no ha recibido ningún dato BLE. Puedes volver a intentar esta prueba en una zona concurrida para ver si se pueden recibir fuentes de datos (distintas de los auriculares). El hecho de que no se reciban datos apunta a un problema con el sistema operativo del teléfono.</string>
|
||||
<string name="troubleshooter_ble_result_failure_phone_headphones">Tu teléfono recibió los datos BLE, pero los datos no proceden de ningún dispositivo compatible. ¿Están encendidos tus auriculares? ¿Es CAPod compatible con tus auriculares?</string>
|
||||
<string name="troubleshooter_ble_result_failure_action">Vuelve a intentarlo</string>
|
||||
<string name="troubleshoot_action">Reparador de los problemas</string>
|
||||
<string name="onboarding_body1">Esta aplicación agrega soporte para funciones específicas de los AirPods para Android.</string>
|
||||
<string name="onboarding_body2">No todos los dispositivos Android son totalmente compatibles con las funciones adicionales de los AirPods. Tu sistema operativo requiere una implementación en el Bluetooth de bajo consumo que funcione correctamente.</string>
|
||||
<string name="onboarding_body3">CAPod no contiene anuncios y no recopila tus datos.</string>
|
||||
<string name="onboarding_body4">Puedes actualizar a CAPod Pro para obtener funciones adicionales y apoyar el desarrollo.</string>
|
||||
<string name="general_continue_action">Seguir</string>
|
||||
</resources>
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
<string name="settings_compat_offloaded_filtering_disabled_summary">Süsteemis andmete filtreerimise asemel saate seda teha rakenduses.</string>
|
||||
<string name="settings_compat_offloaded_batching_disabled_title">Keela riistvara rühmitamine</string>
|
||||
<string name="settings_compat_offloaded_batching_disabled_summary">Keelake süsteemil rühmitada kogutud andmeid madalama energiatarbimisega Bluetoothi kohta enne meile saatmist.</string>
|
||||
<string name="settings_popup_caseopen_label">Näita kaane hüpikakent</string>
|
||||
<string name="settings_popup_connected_label">Näita ühenduse hüpikakent</string>
|
||||
<string name="settings_popup_connected_description">Näita seadme esmaühendumisel hüpikakent.</string>
|
||||
<string name="widget_description">Viimast teadaolevat seadme seisu näitab vidin.</string>
|
||||
<string name="settings_compat_indirectcallback_title">Kaudsete andmete edastamine</string>
|
||||
<string name="settings_compat_indirectcallback_summary">Süsteemis vähem energiat tarbiva Bluetoothi andmete saamine muul viisil (edasta tagasi kutsumise asemel).</string>
|
||||
@@ -23,4 +26,9 @@
|
||||
<string name="troubleshooter_ble_result_failure_phone_headphones">Telefoni saabusid andmed madalama energiakuluga Bluetoothi kaudu aga andmed ei pärine ühestki ühilduvast seadmest. Kas CAPod ühildub teie kõrvaklapiga?</string>
|
||||
<string name="troubleshooter_ble_result_failure_action">Proovi uuesti</string>
|
||||
<string name="troubleshoot_action">Lahendamine</string>
|
||||
<string name="onboarding_body1">See rakendus lisab Androidile AirPodi võimalused.</string>
|
||||
<string name="onboarding_body2">Vaid mõned Androidi seadmed toetavad kõiki AirPodi võimalusi. Operatsioonisüsteem vajab õigesti toimivat madalama energiatarbimisega Bluetoothi tuge.</string>
|
||||
<string name="onboarding_body3">CAPodis pole reklaame ja see ei kogu andmeid.</string>
|
||||
<string name="onboarding_body4">Lisavõimaluste saamiseks ja arendamise toetamiseks soetage CAPod Pro.</string>
|
||||
<string name="general_continue_action">Jätka</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
<?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">Share</string>
|
||||
</resources>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<string name="general_done_action">Terminé</string>
|
||||
<string name="general_copy_action">Copier</string>
|
||||
<string name="general_thank_you_label">Merci</string>
|
||||
<string name="general_upgrade_action">Surclasser vers Pro</string>
|
||||
<string name="general_upgrade_action">Passer Pro</string>
|
||||
<string name="general_check_action">Vérifier</string>
|
||||
<string name="general_close_action">Fermer</string>
|
||||
<string name="upgrade_capod_label">Surclasser CAPod</string>
|
||||
@@ -43,10 +43,12 @@
|
||||
<string name="settings_maindevice_address_none">Aucune</string>
|
||||
<string name="settings_maindevice_model_label">Le modèle de votre appareil</string>
|
||||
<string name="settings_maindevice_model_description">Le modèle de votre appareil principal. Facilite la reconnaissance de votre appareil par l’appli s’il n’est pas connecté à votre téléphone.</string>
|
||||
<string name="settings_popup_caseopen_label">Afficher une notification</string>
|
||||
<string name="settings_popup_caseopen_description">Afficher une notification à l’ouverture de l’étui de l\'appareil (expérimental).</string>
|
||||
<string name="settings_onepod_mode_label">Mode à un seul AirPod</string>
|
||||
<string name="settings_onepod_mode_description">Il n’est pas nécessaire de porter deux AirPods, en porter un seul suffit à déclencher les réactions.</string>
|
||||
<string name="settings_popup_caseopen_label">Afficher une fenêtre pour l’étui</string>
|
||||
<string name="settings_popup_caseopen_description">Afficher une fenêtre surgissante à l’ouverture de l’étui de l\'appareil (expérimental).</string>
|
||||
<string name="settings_popup_connected_label">Afficher une fenêtre de connection</string>
|
||||
<string name="settings_popup_connected_description">Afficher une fenêtre surgissante quand l’appareil se connecte pour la première fois.</string>
|
||||
<string name="notification_channel_device_status_label">État de l’appareil</string>
|
||||
<string name="debug_debuglog_size_label">Taille</string>
|
||||
<string name="debug_debuglog_size_compressed_label">Taille compressée</string>
|
||||
@@ -81,7 +83,7 @@
|
||||
<string name="help_translate_label">Traduction</string>
|
||||
<string name="help_translate_description">Aider à traduire cette appli dans votre langue préférée.</string>
|
||||
<string name="translators_thanks_title">Traducteurs</string>
|
||||
<string name="translators_thanks_description">AO</string>
|
||||
<string name="translators_thanks_description">yahoe-001</string>
|
||||
<string name="widget_description">Un widget qui affiche le dernier état connu de l’appareil.</string>
|
||||
<string name="settings_compat_indirectcallback_title">Transmission indirecte des données</string>
|
||||
<string name="settings_compat_indirectcallback_summary">Utiliser une méthode de remplacement pour recevoir les données BÉB du système (diffusion au lieu de rappel).</string>
|
||||
@@ -99,4 +101,9 @@
|
||||
<string name="troubleshooter_ble_result_failure_phone_headphones">Votre téléphone a reçu des données, mais les données ne proviennent pas d’un appareil pris en charge. Votre casque d’écoute est-il en fonction ? CAPod prend-il votre casque d’écoute en charge ?</string>
|
||||
<string name="troubleshooter_ble_result_failure_action">Réessayer</string>
|
||||
<string name="troubleshoot_action">Dépanner</string>
|
||||
<string name="onboarding_body1">Cette appli ajoute des fonctions propres aux AirPods à Android.</string>
|
||||
<string name="onboarding_body2">Tous les appareils Android ne prennent pas en charge les fonctions supplémentaires des AirPods. Votre système d’exploitation a besoin d’une mise en œuvre fonctionnelle du système à basse énergie Bluetooth.</string>
|
||||
<string name="onboarding_body3">CAPod ne présente aucune publicité et ne recueille pas vos données.</string>
|
||||
<string name="onboarding_body4">Vous pouvez passer à CAPod Pro pour obtenir des fonctions supplémentaires et soutenir le développement.</string>
|
||||
<string name="general_continue_action">Poursuivre</string>
|
||||
</resources>
|
||||
|
||||
@@ -37,10 +37,9 @@
|
||||
<string name="settings_maindevice_address_none">Semmi</string>
|
||||
<string name="settings_maindevice_model_label">Az Ön készülékének modellje</string>
|
||||
<string name="settings_maindevice_model_description">A fő eszköz modellje. Ez segít az alkalmazásnak felismerni az eszközt, amikor az nincs csatlakoztatva a telefonhoz.</string>
|
||||
<string name="settings_popup_caseopen_label">Előugró ablak megjelenítése</string>
|
||||
<string name="settings_popup_caseopen_description">Egy előugró ablak megjelenítése az eszköz házának kinyitásakor (kísérleti).</string>
|
||||
<string name="settings_onepod_mode_label">Egy fülhallgató mód</string>
|
||||
<string name="settings_onepod_mode_description">Nem szükséges mindkét fülhallgató viselése, elég egy, a működéshez.</string>
|
||||
<string name="settings_popup_caseopen_description">Egy előugró ablak megjelenítése az eszköz házának kinyitásakor (kísérleti).</string>
|
||||
<string name="notification_channel_device_status_label">Értesítési_állapot</string>
|
||||
<string name="debug_debuglog_size_label">Méret</string>
|
||||
<string name="debug_debuglog_size_compressed_label">Tömörített_méret</string>
|
||||
|
||||
@@ -43,10 +43,12 @@
|
||||
<string name="settings_maindevice_address_none">Tidak ada</string>
|
||||
<string name="settings_maindevice_model_label">Model perangkat anda</string>
|
||||
<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_popup_caseopen_label">Tampilkan jendela mengambang</string>
|
||||
<string name="settings_popup_caseopen_description">Tampilkan popup saat kasing perangkat dibuka (eksperimental).</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>
|
||||
@@ -95,8 +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 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_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>
|
||||
|
||||
@@ -7,55 +7,60 @@
|
||||
<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à monitor</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 al risparmio della 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, noi possiamo chiedergli di farlo. Questo imposterà la modalità di monitoramento su \'sempre\'.</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>
|
||||
<string name="settings_autoconnect_condition_description">Quando dovremmo provare a connetere il tuo dispositivo?</string>
|
||||
<string name="settings_autoconnect_condition_description">Quando dovremmo provare a connettere il tuo dispositivo?</string>
|
||||
<string name="settings_reaction_label">Reazioni</string>
|
||||
<string name="settings_reaction_description">Reazione ad eventi o comportamenti.</string>
|
||||
<string name="settings_reaction_description">Reazione a eventi o comportamenti.</string>
|
||||
<string name="settings_category_yourdevice_label">Il tuo dispositivo</string>
|
||||
<string name="settings_category_compatibility_options_title">Opzioni di compatibilità</string>
|
||||
<string name="settings_category_compatibility_options_description">Non toccare se funziona tutto ;)</string>
|
||||
<string name="settings_compat_offloaded_filtering_disabled_title">Disabilita il filtro hardware</string>
|
||||
<string name="settings_compat_offloaded_filtering_disabled_summary">Non delegare il filtraggio dei dati al sistema, invece ricevi tutti i dati e filtrali nell\'applicazione.</string>
|
||||
<string name="settings_compat_offloaded_batching_disabled_title">Disabilita il batching hardware</string>
|
||||
<string name="settings_compat_offloaded_batching_disabled_summary">Non concedere al sistema di raggruppare i dati BLE raccolti prima di inviarli a noi.</string>
|
||||
<string name="settings_maindevice_address_label">L\'indirizzo del tuo dispositivo</string>
|
||||
<string name="settings_maindevice_address_description">L\'indirizzo del dispositivo connesso. L\'applicazione usa questo per determinare quando il tuo dispositivo è connesso al telefono.</string>
|
||||
<string name="settings_maindevice_address_none">Niente</string>
|
||||
<string name="settings_maindevice_model_label">Il modello del tuo dispositivo</string>
|
||||
<string name="settings_maindevice_model_description">Il modello del tuo dispositivo principale. Questo aiuta la applicazione a riconoscere il tuo dispositivo quando non è connesso al tuo telefono.</string>
|
||||
<string name="settings_popup_caseopen_label">Mostra popup</string>
|
||||
<string name="settings_popup_caseopen_description">Mostra un popup quando la custodia del dispositivo è aperta (sperimentale).</string>
|
||||
<string name="settings_onepod_mode_label">Modalità singolo pod</string>
|
||||
<string name="settings_onepod_mode_description">Indossare entrambi i pod non è necessario, indossare un singolo pod è sufficiente per far funzionare le reazioni.</string>
|
||||
<string name="settings_popup_caseopen_label">Mostra popup della custodia</string>
|
||||
<string name="settings_popup_caseopen_description">Mostra un popup quando la custodia del dispositivo è aperta (sperimentale).</string>
|
||||
<string name="settings_popup_connected_label">Mostra popup di connessione</string>
|
||||
<string name="settings_popup_connected_description">Mostra un popup quando il dispositivo viene connesso per la prima volta.</string>
|
||||
<string name="notification_channel_device_status_label">Stato dispositivo</string>
|
||||
<string name="debug_debuglog_size_label">Dimensione</string>
|
||||
<string name="debug_debuglog_size_compressed_label">Dimensione compressa</string>
|
||||
<string name="debug_notification_channel_label">Notifiche di Debug</string>
|
||||
<string name="debug_debuglog_file_label">Log file salvato</string>
|
||||
<string name="debug_debuglog_file_label">File di log registrati</string>
|
||||
<string name="debug_debuglog_record_action">Log di debug</string>
|
||||
<string name="settings_support_installid_label">ID installazione</string>
|
||||
<string name="settings_support_installid_desc">Le segnalazioni automatiche degli errori sono anonime. Condividi il tuo ID di installazione con lo sviluppatore se necessità di trovare la tua segnalazione all\'interno del registro segnalazioni.</string>
|
||||
<string name="settings_support_installid_label">ID d\'installazione</string>
|
||||
<string name="settings_support_installid_desc">Le segnalazioni automatiche degli errori sono anonime. Condividi il tuo ID d\'installazione con lo sviluppatore se necessita di trovare la tua segnalazione all\'interno del registro segnalazioni.</string>
|
||||
<string name="settings_support_label">Supporto</string>
|
||||
<string name="settings_support_description">Se ti serve un aiuto.</string>
|
||||
<string name="issue_tracker_label">Tracker problemi</string>
|
||||
<string name="issue_tracker_description">Un issue tracker pubblico per segnalare i problemi e le richieste per aggiungere nuove funzionalità (solo in inglese).</string>
|
||||
<string name="settings_support_description">Se ti serve aiuto.</string>
|
||||
<string name="issue_tracker_label">Tracker di problemi</string>
|
||||
<string name="issue_tracker_description">Un tracker di problemi pubblico per segnalare problemi e richieste di nuove funzionalità (solo in inglese).</string>
|
||||
<string name="discord_label">Discord</string>
|
||||
<string name="discord_description">Un posto dove poter fare domande.</string>
|
||||
<string name="changelog_label">Changelog</string>
|
||||
@@ -78,11 +83,27 @@
|
||||
<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</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>
|
||||
<string name="troubleshooter_title">Risoluzione dei problemi</string>
|
||||
<string name="troubleshooter_ble_intro_title">Trasmissioni con Bluetooth Low Energy</string>
|
||||
<string name="troubleshooter_ble_intro_body1">Le AirPods (e cuffie simili) trasmettono le informazioni di stato utilizzando una tecnologia BLE chiamata \"annunci\". Alcuni smartphone non implementano questa tecnologia correttamente. CAPod può provare a risolvere ciò provando opzioni compatibili differenti fino a quando i dati vengono ricevuti. Riproduci della musica nelle cuffie e mettile vicino al tuo smartphone, successivamente avvia la procedura.</string>
|
||||
<string name="troubleshooter_ble_intro_start_action">Avvia la risoluzione dei problemi</string>
|
||||
<string name="troubleshooter_ble_process_title">Risoluzione dei problemi in corso</string>
|
||||
<string name="troubleshooter_ble_process_subtile">Ogni passaggio richiede 5-10 secondi</string>
|
||||
<string name="troubleshooter_ble_result_success_title">Eseguito</string>
|
||||
<string name="troubleshooter_ble_result_success_body">La trasmissione \"annunci\" BLE è stata ricevuta da CAPod.</string>
|
||||
<string name="troubleshooter_ble_result_failure_title">Fallito</string>
|
||||
<string name="troubleshooter_ble_result_failure_body">Diagnostica fallita. Nessuna combinazione di opzioni compatibili ha funzionato.</string>
|
||||
<string name="troubleshooter_ble_result_failure_phone_body">Il tuo smartphone non ha ricevuto dati BLE. Puoi riprovare questo test in un area affollata per vedere se le fonti dati (diverse dalle tue cuffie) vengono ricevuti. La mancata ricezione indica un possibile problema del sistema operativo del tuo smartphone.</string>
|
||||
<string name="troubleshooter_ble_result_failure_phone_headphones">Il tuo smartphone ha ricevuto i dati BLE, ma i dati non provengono da un dispositivo supportato. Le tue cuffie sono accese/cariche? CAPod supporta le tue cuffie?</string>
|
||||
<string name="troubleshooter_ble_result_failure_action">Riprova</string>
|
||||
<string name="troubleshoot_action">Risoluzione dei problemi</string>
|
||||
<string name="onboarding_body1">Questa applicazione fornisce supporto per le funzionalità specifiche dell AirPods su Android.</string>
|
||||
<string name="onboarding_body2">Non tutti i dispositivi Android supportano le funzionalità extra delle AirPods. Un prerequisito del tuo sistema operativo è l\'implementazione di Bluetooth-Low-Energy.</string>
|
||||
<string name="onboarding_body3">CAPod non contiene pubblicità e non raccoglie i tuoi dati.</string>
|
||||
<string name="onboarding_body4">Puoi aggiornare alla versione Pro per sbloccare funzioni aggiuntive e supportare lo sviluppo.</string>
|
||||
<string name="general_continue_action">Continua</string>
|
||||
</resources>
|
||||
|
||||
@@ -43,10 +43,9 @@
|
||||
<string name="settings_maindevice_address_none">אף אחד</string>
|
||||
<string name="settings_maindevice_model_label">דגם המכשיר שלך</string>
|
||||
<string name="settings_maindevice_model_description">הדגם של המכשיר הראשי שלך. זה עוזר לאפליקציה לזהות את המכשיר שלך כשהוא לא מחובר לטלפון שלך.</string>
|
||||
<string name="settings_popup_caseopen_label">הצג הודעה</string>
|
||||
<string name="settings_popup_caseopen_description">הצג חלון הודעה כאשר מארז המכשיר נפתח (ניסיוני).</string>
|
||||
<string name="settings_onepod_mode_label">מצב אוזניה בודדת</string>
|
||||
<string name="settings_onepod_mode_description">אין צורך לענוד את שני האוזניות, לבישת אוזניה בודדת מספיקה כדי לעורר תגובות.</string>
|
||||
<string name="settings_popup_caseopen_description">הצג חלון הודעה כאשר מארז המכשיר נפתח (ניסיוני).</string>
|
||||
<string name="notification_channel_device_status_label">מצב המכשיר</string>
|
||||
<string name="debug_debuglog_size_label">מידה</string>
|
||||
<string name="debug_debuglog_size_compressed_label">גודל דחיסה</string>
|
||||
@@ -95,7 +94,6 @@
|
||||
<string name="troubleshooter_ble_result_success_body">שידורי פרסומת BLE מתקבלים על ידי CAPod.</string>
|
||||
<string name="troubleshooter_ble_result_failure_title">לא מוצלח</string>
|
||||
<string name="troubleshooter_ble_result_failure_body">פתרון הבעיות נכשל. שום שילוב של אפשרויות תאימות לא עזר.</string>
|
||||
<string name="troubleshooter_ble_result_failure_phone_body">הטלפון שלך לא קיבל נתונים BLE כלל. באפשרותך לנסות שוב את הבדיקה הזו באזור אפוץ כדי לראות אם ניתן לקבל מקורות נתונים נוספים (מלבד האוזניות שלך). אין מידע שמתקבל מצביע על בעיה במערכת ההפעלה של הטלפון שלך.</string>
|
||||
<string name="troubleshooter_ble_result_failure_phone_headphones">טלפון שלך קיבל נתוני BLE, אך הנתונים אינם מגיעים ממכשיר אשר נתמך. האם האוזניות שלך מופעלות? האם CAPod תומך באוזניות שלך?</string>
|
||||
<string name="troubleshooter_ble_result_failure_action">נסה שנית</string>
|
||||
<string name="troubleshoot_action">פתרון בעיות</string>
|
||||
|
||||
@@ -41,10 +41,9 @@
|
||||
<string name="settings_maindevice_address_none">無し</string>
|
||||
<string name="settings_maindevice_model_label">あなたのデバイスのモデル</string>
|
||||
<string name="settings_maindevice_model_description">あなたが主に使うデバイスのモデルです。これにより、デバイスが機器に接続されていないときに、アプリがデバイスを認識しやすくなります。</string>
|
||||
<string name="settings_popup_caseopen_label">ポップアップ表示</string>
|
||||
<string name="settings_popup_caseopen_description">デバイスのケースが開いたときにポップアップを表示します (実験的)。</string>
|
||||
<string name="settings_onepod_mode_label">片耳モード</string>
|
||||
<string name="settings_onepod_mode_description">両方装着する必要はなく、1つだけを装着することで反応します。</string>
|
||||
<string name="settings_popup_caseopen_description">デバイスのケースが開いたときにポップアップを表示します (実験的)。</string>
|
||||
<string name="notification_channel_device_status_label">デバイスの状態</string>
|
||||
<string name="debug_debuglog_size_label">サイズ</string>
|
||||
<string name="debug_debuglog_size_compressed_label">圧縮サイズ</string>
|
||||
|
||||
@@ -37,10 +37,9 @@
|
||||
<string name="settings_maindevice_address_none">없음</string>
|
||||
<string name="settings_maindevice_model_label">내 기기 모델</string>
|
||||
<string name="settings_maindevice_model_description">사용하고 있는 기기의 모델입니다. 전화기에 기기가 연결되지 않았을 때 기기를 찾는 데 도움을 줍니다.</string>
|
||||
<string name="settings_popup_caseopen_label">팝업 보이기</string>
|
||||
<string name="settings_popup_caseopen_description">기기 케이스가 열렸을 때 팝업을 보여줍니다. (실험적)</string>
|
||||
<string name="settings_onepod_mode_label">한쪽 기기 모드</string>
|
||||
<string name="settings_onepod_mode_description">양쪽 기기를 모두 착용할 필요 없이, 앱에서 한 쪽만 있어도 반응하도록 합니다.</string>
|
||||
<string name="settings_popup_caseopen_description">기기 케이스가 열렸을 때 팝업을 보여줍니다. (실험적)</string>
|
||||
<string name="notification_channel_device_status_label">디바이스 상태</string>
|
||||
<string name="debug_debuglog_size_label">크기</string>
|
||||
<string name="debug_debuglog_size_compressed_label">압축된 크기</string>
|
||||
|
||||
@@ -43,10 +43,12 @@
|
||||
<string name="settings_maindevice_address_none">Tiada</string>
|
||||
<string name="settings_maindevice_model_label">Model peranti anda</string>
|
||||
<string name="settings_maindevice_model_description">Model peranti utama anda. Ini membantu apl mengenali peranti anda apabila ia tidak disambungkan ke telefon anda.</string>
|
||||
<string name="settings_popup_caseopen_label">Tunjukkan popup</string>
|
||||
<string name="settings_popup_caseopen_description">Tunjukkan popup apabila sarung peranti dibuka (percubaan).</string>
|
||||
<string name="settings_onepod_mode_label">Satu mod pod</string>
|
||||
<string name="settings_onepod_mode_description">Tidak perlu memakai kedua-dua pod, memakai satu pod sudah memadai untuk mencetuskan tindak balas.</string>
|
||||
<string name="settings_popup_caseopen_label">Tunjukkan pop timbul kes</string>
|
||||
<string name="settings_popup_caseopen_description">Tunjukkan popup apabila sarung peranti dibuka (percubaan).</string>
|
||||
<string name="settings_popup_connected_label">Tunjukkan pop timbul sambungan</string>
|
||||
<string name="settings_popup_connected_description">Tunjukkan pop timbul apabila peranti terhubung buat kali pertama.</string>
|
||||
<string name="notification_channel_device_status_label">Status peranti</string>
|
||||
<string name="debug_debuglog_size_label">Saiz</string>
|
||||
<string name="debug_debuglog_size_compressed_label">Saiz termampat</string>
|
||||
@@ -95,8 +97,13 @@
|
||||
<string name="troubleshooter_ble_result_success_body">Siaran iklan BLE sedang diterima oleh CAPod.</string>
|
||||
<string name="troubleshooter_ble_result_failure_title">Tidak berjaya</string>
|
||||
<string name="troubleshooter_ble_result_failure_body">Penyelesaian masalah gagal. Tiada gabungan pilihan keserasian dapat membantu.</string>
|
||||
<string name="troubleshooter_ble_result_failure_phone_body">Telefon anda tidak menerima sebarang data BLE sama sekali. Anda boleh mencuba semula ujian ini di kawasan yang sesak untuk melihat sama ada sumber data (selain fon kepala anda) boleh diterima. Tiada data yang diterima menunjukkan masalah dengan sistem pengendalian telefon anda.</string>
|
||||
<string name="troubleshooter_ble_result_failure_phone_body">Telefon anda tidak menerima sebarang data BLE sama sekali.. Anda boleh mencuba semula ujian ini di kawasan yang sesak untuk melihat sama ada sumber data (selain fon kepala anda) boleh diterima. Tiada data yang diterima menunjukkan masalah dengan sistem pengendalian telefon anda.</string>
|
||||
<string name="troubleshooter_ble_result_failure_phone_headphones">Telefon anda menerima data BLE, tetapi data itu tidak datang daripada mana-mana peranti yang disokong. Adakah fon kepala anda dihidupkan? Adakah CAPod menyokong fon kepala anda?</string>
|
||||
<string name="troubleshooter_ble_result_failure_action">Cuba semula</string>
|
||||
<string name="troubleshoot_action">Selesaikan masalah</string>
|
||||
<string name="onboarding_body1">Aplikasi ini menambah sokongan untuk ciri khusus AirPod pada Android.</string>
|
||||
<string name="onboarding_body2">Tidak semua peranti Android menyokong sepenuhnya ciri AirPod tambahan. Sistem pengendalian anda memerlukan pelaksanaan Bluetooth-Tenaga-Rendah yang berfungsi dengan betul.</string>
|
||||
<string name="onboarding_body3">CAPod tiada iklan serta tidak mengumpul data anda.</string>
|
||||
<string name="onboarding_body4">Anda boleh menaik taraf kepada CAPod Pro untuk mendapatkan ciri tambahan serta pembangunan sokongan.</string>
|
||||
<string name="general_continue_action">Teruskan</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<resources>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<style name="AppTheme" parent="Theme.Material3.Dark.NoActionBar">
|
||||
<item name="colorPrimary">@color/md_theme_dark_primary</item>
|
||||
@@ -27,6 +27,9 @@
|
||||
<item name="colorOnSurfaceInverse">@color/md_theme_dark_inverseOnSurface</item>
|
||||
<item name="colorSurfaceInverse">@color/md_theme_dark_inverseSurface</item>
|
||||
<item name="colorPrimaryInverse">@color/md_theme_dark_primaryInverse</item>
|
||||
|
||||
<item tools:targetApi="29" name="android:enforceNavigationBarContrast">true</item>
|
||||
<item tools:targetApi="29" name="android:navigationBarColor">@android:color/transparent</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
@@ -43,10 +43,12 @@
|
||||
<string name="settings_maindevice_address_none">Geen</string>
|
||||
<string name="settings_maindevice_model_label">Uw apparaatmodel</string>
|
||||
<string name="settings_maindevice_model_description">Het model van uw hoofdapparaat. Dit helpt de app uw apparaat te herkennen wanneer het niet is verbonden met uw telefoon.</string>
|
||||
<string name="settings_popup_caseopen_label">Pop-up weergeven</string>
|
||||
<string name="settings_popup_caseopen_description">Een pop-up weergeven wanneer de behuizing van het apparaat wordt geopend (experimenteel).</string>
|
||||
<string name="settings_onepod_mode_label">Eén pod-modus</string>
|
||||
<string name="settings_onepod_mode_description">Het dragen van beide pods is niet vereist, het dragen van een enkele pod is voldoende om reacties uit te lokken.</string>
|
||||
<string name="settings_popup_caseopen_label">Case-pop-up weergeven</string>
|
||||
<string name="settings_popup_caseopen_description">Een pop-up weergeven wanneer de behuizing van het apparaat wordt geopend (experimenteel).</string>
|
||||
<string name="settings_popup_connected_label">Verbindingspop-up weergeven</string>
|
||||
<string name="settings_popup_connected_description">Toon een pop-up wanneer het apparaat voor de eerste keer verbinding maakt.</string>
|
||||
<string name="notification_channel_device_status_label">Apparaatstatus</string>
|
||||
<string name="debug_debuglog_size_label">Grootte</string>
|
||||
<string name="debug_debuglog_size_compressed_label">Gecomprimeerde grootte</string>
|
||||
@@ -95,8 +97,13 @@
|
||||
<string name="troubleshooter_ble_result_success_body">BLE-advertentie-uitzendingen worden door CAPod ontvangen.</string>
|
||||
<string name="troubleshooter_ble_result_failure_title">Mislukt</string>
|
||||
<string name="troubleshooter_ble_result_failure_body">Problemen oplossen mislukt. Geen enkele combinatie van compatibiliteitsopties heeft geholpen.</string>
|
||||
<string name="troubleshooter_ble_result_failure_phone_body">Je telefoon heeft helemaal geen BLE-gegevens ontvangen. Je kunt deze test opnieuw proberen in een druk gebied om te zien of gegevensbronnen (anders dan je hoofdtelefoon) kunnen worden ontvangen. Er worden geen gegevens ontvangen die wijzen op een probleem met het besturingssysteem van je telefoon.</string>
|
||||
<string name="troubleshooter_ble_result_failure_phone_body">Je telefoon heeft helemaal geen BLE-gegevens ontvangen. Je kunt deze test opnieuw proberen in een druk gebied om te zien of gegevensbronnen (anders dan uw hoofdtelefoon) kunnen worden ontvangen. Er worden geen gegevens ontvangen die wijzen op een probleem met het besturingssysteem van uw telefoon.</string>
|
||||
<string name="troubleshooter_ble_result_failure_phone_headphones">Je telefoon heeft BLE-gegevens ontvangen, maar de gegevens zijn niet afkomstig van een ondersteund apparaat. Staat je koptelefoon aan? Ondersteunt CAPod je koptelefoon?</string>
|
||||
<string name="troubleshooter_ble_result_failure_action">Probeer opnieuw</string>
|
||||
<string name="troubleshoot_action">Problemen oplossen</string>
|
||||
<string name="onboarding_body1">Deze app voegt ondersteuning voor AirPod-specifieke functies toe aan Android.</string>
|
||||
<string name="onboarding_body2">Niet alle Android-apparaten ondersteunen de extra AirPod-functies volledig. Je besturingssysteem vereist een correct werkende Bluetooth-Low-Energy implementatie.</string>
|
||||
<string name="onboarding_body3">CAPod heeft geen advertenties en verzamelt je gegevens niet.</string>
|
||||
<string name="onboarding_body4">Je kunt upgraden naar CAPod Pro om extra functies te krijgen en de ontwikkeling te ondersteunen.</string>
|
||||
<string name="general_continue_action">Doorgaan</string>
|
||||
</resources>
|
||||
|
||||
@@ -43,10 +43,12 @@
|
||||
<string name="settings_maindevice_address_none">Ingen</string>
|
||||
<string name="settings_maindevice_model_label">Enhetens modell</string>
|
||||
<string name="settings_maindevice_model_description">Modellen til hovedenheten din. Dette hjelper appen med å gjenkjenne enheten din når den ikke er koblet til telefonen.</string>
|
||||
<string name="settings_popup_caseopen_label">Vis popup</string>
|
||||
<string name="settings_popup_caseopen_description">Vis en popup når enhetens deksel åpnes (eksperimentelt).</string>
|
||||
<string name="settings_onepod_mode_label">En pod modus</string>
|
||||
<string name="settings_onepod_mode_description">Du trenger kun ha på deg en pod for å utløse reaksjoner, i stedet for begge.</string>
|
||||
<string name="settings_popup_caseopen_label">Vis etui popup</string>
|
||||
<string name="settings_popup_caseopen_description">Vis en popup når enhetens deksel åpnes (eksperimentelt).</string>
|
||||
<string name="settings_popup_connected_label">Vis tilkobling popup</string>
|
||||
<string name="settings_popup_connected_description">Vis en popup første gang enheten kobles til.</string>
|
||||
<string name="notification_channel_device_status_label">Enhetsstatus</string>
|
||||
<string name="debug_debuglog_size_label">Størrelse</string>
|
||||
<string name="debug_debuglog_size_compressed_label">Komprimert størrelse</string>
|
||||
@@ -99,4 +101,9 @@
|
||||
<string name="troubleshooter_ble_result_failure_phone_headphones">Telefonen din mottok BLE-data, men dataene kommer ikke fra en støttet enhet. Er hodetelefonene dine slått på? Støtter CAPod din type hodetelefoner?</string>
|
||||
<string name="troubleshooter_ble_result_failure_action">Prøv igjen</string>
|
||||
<string name="troubleshoot_action">Feilsøk</string>
|
||||
<string name="onboarding_body1">Denne appen legger til støtte for AirPod-spesifikke funksjoner til Android.</string>
|
||||
<string name="onboarding_body2">Ikke alle Android-enheter støtter de ekstra AirPod-funksjonene fullt ut. Operativsystemet ditt krever en fungerende Bluetooth-Low-Energy-implementering.</string>
|
||||
<string name="onboarding_body3">CAPod har ingen annonser og samler ikke inn dataene dine.</string>
|
||||
<string name="onboarding_body4">Du kan oppgradere til CAPod Pro for å få ekstra funksjoner og støtte utviklingen.</string>
|
||||
<string name="general_continue_action">Fortsett</string>
|
||||
</resources>
|
||||
|
||||
@@ -43,10 +43,12 @@
|
||||
<string name="settings_maindevice_address_none">Brak</string>
|
||||
<string name="settings_maindevice_model_label">Model twojego urządzenia</string>
|
||||
<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_popup_caseopen_label">Wyświetlanie informacji</string>
|
||||
<string name="settings_popup_caseopen_description">Wyświetla informację, gdy obudowa urządzenia jest otwarta (eksperymentalnie).</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_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>
|
||||
<string name="notification_channel_device_status_label">Status urządzenia</string>
|
||||
<string name="debug_debuglog_size_label">Rozmiar</string>
|
||||
<string name="debug_debuglog_size_compressed_label">Rozmiar skompresowanego</string>
|
||||
@@ -99,4 +101,9 @@
|
||||
<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_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>
|
||||
<string name="onboarding_body2">Nie wszystkie urządzenia z Androidem w pełni wspierają dodatkowe funkcje AirPods. Twój system operacyjny wymaga dobrze działającej implementacji Bluetooth-Low-Energy.</string>
|
||||
<string name="onboarding_body3">CAPod nie wyświetla reklam i nie zbiera Twoich danych.</string>
|
||||
<string name="onboarding_body4">Możesz ulepszyć do CAPod Pro, by zyskać dodatkowe funkcje i wesprzeć rozwój.</string>
|
||||
<string name="general_continue_action">Kontynuuj</string>
|
||||
</resources>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user