mirror of
https://github.com/d4rken-org/capod.git
synced 2026-09-14 18:26:11 -04:00
Compare commits
6
Commits
v4.0.2-rc0
...
v4.0.3-rc0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9d6ec2b6bb | ||
|
|
9a85b23d91 | ||
|
|
217d9be177 | ||
|
|
53447f3fb9 | ||
|
|
db759bac7c | ||
|
|
6837db5b84 |
+12
-11
@@ -1,16 +1,17 @@
|
|||||||
{
|
{
|
||||||
"permissions": {
|
"permissions": {
|
||||||
"allow": [
|
"allow": [
|
||||||
"mcp__ide__getDiagnostics",
|
"mcp__ide__getDiagnostics",
|
||||||
"WebSearch"
|
"WebSearch"
|
||||||
],
|
],
|
||||||
"deny": []
|
"deny": []
|
||||||
},
|
},
|
||||||
"enabledPlugins": {
|
"enabledPlugins": {
|
||||||
"android-translation@claude-code-cafe": true,
|
"android-translation@claude-code-cafe": true,
|
||||||
"debugbadger@claude-code-cafe": true,
|
"debugbadger@claude-code-cafe": true,
|
||||||
"jvm-tools@claude-code-cafe": true,
|
"jvm-tools@claude-code-cafe": true,
|
||||||
"frontend-design@claude-plugins-official": true,
|
"frontend-design@claude-plugins-official": true,
|
||||||
"kotlin-lsp@claude-plugins-official": true
|
"kotlin-lsp@claude-plugins-official": true,
|
||||||
|
"support-investigator@claude-code-cafe": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -304,6 +304,9 @@ class BluetoothManager2 @Inject constructor(
|
|||||||
emit(wrappedDevices)
|
emit(wrappedDevices)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var _isNudgeAvailable: Boolean = true
|
||||||
|
val isNudgeAvailable: Boolean get() = _isNudgeAvailable
|
||||||
|
|
||||||
suspend fun nudgeConnection(device: BluetoothDevice2): Boolean = getBluetoothProfile().map { bluetoothProfile ->
|
suspend fun nudgeConnection(device: BluetoothDevice2): Boolean = getBluetoothProfile().map { bluetoothProfile ->
|
||||||
try {
|
try {
|
||||||
log(TAG) { "Nudging Android connection to $device" }
|
log(TAG) { "Nudging Android connection to $device" }
|
||||||
@@ -317,6 +320,12 @@ class BluetoothManager2 @Inject constructor(
|
|||||||
log(TAG) { "Nudged connection to $device" }
|
log(TAG) { "Nudged connection to $device" }
|
||||||
true
|
true
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
val isSecurityException = e is SecurityException ||
|
||||||
|
(e is java.lang.reflect.InvocationTargetException && e.cause is SecurityException)
|
||||||
|
if (isSecurityException) {
|
||||||
|
log(TAG, ERROR) { "nudgeConnection is permanently unavailable: missing MODIFY_PHONE_STATE permission" }
|
||||||
|
_isNudgeAvailable = false
|
||||||
|
}
|
||||||
Bugs.report(tag = TAG, "BluetoothHeadset.connect(device) is unavailable", exception = e)
|
Bugs.report(tag = TAG, "BluetoothHeadset.connect(device) is unavailable", exception = e)
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ import eu.darken.capod.pods.core.PodDevice
|
|||||||
import eu.darken.capod.pods.core.SinglePodDevice
|
import eu.darken.capod.pods.core.SinglePodDevice
|
||||||
import eu.darken.capod.pods.core.unknown.UnknownDevice
|
import eu.darken.capod.pods.core.unknown.UnknownDevice
|
||||||
import eu.darken.capod.profiles.core.AppleDeviceProfile
|
import eu.darken.capod.profiles.core.AppleDeviceProfile
|
||||||
|
import eu.darken.capod.pods.core.apple.ApplePods
|
||||||
|
import eu.darken.capod.pods.core.apple.protocol.ProximityPayload
|
||||||
import eu.darken.capod.profiles.core.DeviceProfile
|
import eu.darken.capod.profiles.core.DeviceProfile
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
@@ -114,11 +116,13 @@ object MockPodDataProvider {
|
|||||||
_isBeingWorn = true,
|
_isBeingWorn = true,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun airPodsMaxCharging(): SinglePodDevice = MockSinglePodDevice(
|
fun airPodsMaxCharging(): SinglePodDevice = MockSingleApplePodDevice(
|
||||||
_model = PodDevice.Model.AIRPODS_MAX,
|
_model = PodDevice.Model.AIRPODS_MAX,
|
||||||
_label = "AirPods Max",
|
_label = "AirPods Max",
|
||||||
batteryHeadsetPercent = 0.40f,
|
batteryHeadsetPercent = 0.40f,
|
||||||
_isHeadsetBeingCharged = true,
|
_isHeadsetBeingCharged = true,
|
||||||
|
_isIRKMatch = true,
|
||||||
|
_hasPrivatePayload = true,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun beatsSolo3(): SinglePodDevice = MockSinglePodDevice(
|
fun beatsSolo3(): SinglePodDevice = MockSinglePodDevice(
|
||||||
@@ -273,3 +277,41 @@ private class MockSinglePodDevice(
|
|||||||
// HasEarDetection
|
// HasEarDetection
|
||||||
override val isBeingWorn: Boolean = _isBeingWorn
|
override val isBeingWorn: Boolean = _isBeingWorn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("PropertyName")
|
||||||
|
private class MockSingleApplePodDevice(
|
||||||
|
private val _model: PodDevice.Model,
|
||||||
|
private val _label: String,
|
||||||
|
override val batteryHeadsetPercent: Float?,
|
||||||
|
private val _isHeadsetBeingCharged: Boolean = false,
|
||||||
|
private val _isBeingWorn: Boolean = false,
|
||||||
|
private val _isIRKMatch: Boolean = false,
|
||||||
|
private val _hasPrivatePayload: Boolean = false,
|
||||||
|
rssi: Int = -50,
|
||||||
|
) : SinglePodDevice, ApplePods, HasChargeDetection, HasEarDetection {
|
||||||
|
override val identifier: PodDevice.Id = PodDevice.Id()
|
||||||
|
override val model: PodDevice.Model = _model
|
||||||
|
override val seenLastAt: Instant = MOCK_NOW
|
||||||
|
override val seenFirstAt: Instant = MOCK_NOW
|
||||||
|
override val seenCounter: Int = 5
|
||||||
|
override val scanResult: BleScanResult = MockPodDataProvider.dummyScanResult(rssi)
|
||||||
|
override val reliability: Float = 1.0f
|
||||||
|
override val signalQuality: Float = 0.80f
|
||||||
|
override val iconRes: Int = _model.iconRes
|
||||||
|
override val meta: ApplePods.AppleMeta = ApplePods.AppleMeta(
|
||||||
|
isIRKMatch = _isIRKMatch,
|
||||||
|
profile = AppleDeviceProfile(label = _label, model = _model),
|
||||||
|
)
|
||||||
|
override val payload: ProximityPayload = ProximityPayload(
|
||||||
|
public = ProximityPayload.Public(UByteArray(9)),
|
||||||
|
private = if (_hasPrivatePayload) ProximityPayload.Private(UByteArray(8)) else null,
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun getLabel(context: Context): String = _model.label
|
||||||
|
|
||||||
|
// HasChargeDetection
|
||||||
|
override val isHeadsetBeingCharged: Boolean = _isHeadsetBeingCharged
|
||||||
|
|
||||||
|
// HasEarDetection
|
||||||
|
override val isBeingWorn: Boolean = _isBeingWorn
|
||||||
|
}
|
||||||
|
|||||||
@@ -19,9 +19,12 @@ import androidx.compose.foundation.layout.size
|
|||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.Key
|
||||||
import androidx.compose.material.icons.twotone.BatteryChargingFull
|
import androidx.compose.material.icons.twotone.BatteryChargingFull
|
||||||
import androidx.compose.material.icons.twotone.Hearing
|
import androidx.compose.material.icons.twotone.Hearing
|
||||||
|
import androidx.compose.material.icons.twotone.Key
|
||||||
import androidx.compose.material3.CardDefaults
|
import androidx.compose.material3.CardDefaults
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.material3.ElevatedCard
|
import androidx.compose.material3.ElevatedCard
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
@@ -44,6 +47,7 @@ import eu.darken.capod.common.compose.preview.MockPodDataProvider
|
|||||||
import eu.darken.capod.pods.core.HasChargeDetection
|
import eu.darken.capod.pods.core.HasChargeDetection
|
||||||
import eu.darken.capod.pods.core.HasEarDetection
|
import eu.darken.capod.pods.core.HasEarDetection
|
||||||
import eu.darken.capod.pods.core.SinglePodDevice
|
import eu.darken.capod.pods.core.SinglePodDevice
|
||||||
|
import eu.darken.capod.pods.core.apple.ApplePods
|
||||||
import eu.darken.capod.pods.core.firstSeenFormatted
|
import eu.darken.capod.pods.core.firstSeenFormatted
|
||||||
import eu.darken.capod.pods.core.formatBatteryPercent
|
import eu.darken.capod.pods.core.formatBatteryPercent
|
||||||
import eu.darken.capod.pods.core.getSignalQuality
|
import eu.darken.capod.pods.core.getSignalQuality
|
||||||
@@ -98,12 +102,23 @@ fun SinglePodsCard(
|
|||||||
Spacer(modifier = Modifier.width(12.dp))
|
Spacer(modifier = Modifier.width(12.dp))
|
||||||
|
|
||||||
Column(modifier = Modifier.weight(1f)) {
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
Text(
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
text = device.meta.profile?.label ?: "?",
|
Text(
|
||||||
style = MaterialTheme.typography.titleMedium,
|
text = device.meta.profile?.label ?: "?",
|
||||||
maxLines = 1,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
overflow = TextOverflow.Ellipsis,
|
maxLines = 1,
|
||||||
)
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
)
|
||||||
|
if (device is ApplePods && device.meta.isIRKMatch) {
|
||||||
|
Spacer(modifier = Modifier.width(6.dp))
|
||||||
|
Icon(
|
||||||
|
imageVector = if (device.payload.private != null) Icons.TwoTone.Key else Icons.Outlined.Key,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(14.dp),
|
||||||
|
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
Text(
|
Text(
|
||||||
text = device.getLabel(context),
|
text = device.getLabel(context),
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
|||||||
@@ -99,6 +99,12 @@ class AutoConnect @Inject constructor(
|
|||||||
log(TAG) { "Auto connect condition ($condition) is not fullfilled: ${decision.reason}" }
|
log(TAG) { "Auto connect condition ($condition) is not fullfilled: ${decision.reason}" }
|
||||||
return@map
|
return@map
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!bluetoothManager.isNudgeAvailable) {
|
||||||
|
log(TAG, WARN) { "nudgeConnection is not available on this device, skipping" }
|
||||||
|
return@map
|
||||||
|
}
|
||||||
|
|
||||||
val result = bluetoothManager.nudgeConnection(bondedDevice)
|
val result = bluetoothManager.nudgeConnection(bondedDevice)
|
||||||
log(TAG) { "nudgeConnection($bondedDevice) returned $result" }
|
log(TAG) { "nudgeConnection($bondedDevice) returned $result" }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,7 +61,7 @@
|
|||||||
<string name="settings_signal_minimum_label">Minimum signal quality</string>
|
<string name="settings_signal_minimum_label">Minimum signal quality</string>
|
||||||
<string name="settings_signal_minimum_description">The minimum signal quality that a device needs to have to be considered yours.</string>
|
<string name="settings_signal_minimum_description">The minimum signal quality that a device needs to have to be considered yours.</string>
|
||||||
<string name="settings_autoconnect_label">Auto connect</string>
|
<string name="settings_autoconnect_label">Auto connect</string>
|
||||||
<string name="settings_autoconnect_description">If Android does not automatically connect, we can ask it too. This will set the monitor mode setting to \'Always\'.</string>
|
<string name="settings_autoconnect_description">If Android does not automatically connect, we can ask it too. This will set the monitor mode setting to \'Always\'. May not work on newer Android versions.</string>
|
||||||
<string name="settings_autoconnect_condition_label">Auto connect condition</string>
|
<string name="settings_autoconnect_condition_label">Auto connect condition</string>
|
||||||
<string name="settings_autoconnect_condition_description">When should we try to connect to your device?</string>
|
<string name="settings_autoconnect_condition_description">When should we try to connect to your device?</string>
|
||||||
<string name="settings_devices_label">Devices</string>
|
<string name="settings_devices_label">Devices</string>
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ platform :android do
|
|||||||
track: 'beta',
|
track: 'beta',
|
||||||
rollout: '0.10',
|
rollout: '0.10',
|
||||||
package_name: 'eu.darken.capod',
|
package_name: 'eu.darken.capod',
|
||||||
mapping: '../app/build/outputs/mapping/gplayBeta/mapping.txt',
|
|
||||||
skip_upload_changelogs: 'false',
|
skip_upload_changelogs: 'false',
|
||||||
skip_upload_apk: 'true',
|
skip_upload_apk: 'true',
|
||||||
skip_upload_images: 'true',
|
skip_upload_images: 'true',
|
||||||
@@ -43,7 +42,6 @@ platform :android do
|
|||||||
track: 'beta',
|
track: 'beta',
|
||||||
rollout: '0.10',
|
rollout: '0.10',
|
||||||
package_name: 'eu.darken.capod',
|
package_name: 'eu.darken.capod',
|
||||||
mapping: '../app/build/outputs/mapping/gplayRelease/mapping.txt',
|
|
||||||
skip_upload_changelogs: 'false',
|
skip_upload_changelogs: 'false',
|
||||||
skip_upload_apk: 'true',
|
skip_upload_apk: 'true',
|
||||||
skip_upload_images: 'true',
|
skip_upload_images: 'true',
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
### Updated by release.sh ###
|
### Updated by release.sh ###
|
||||||
project.versioning.major=4
|
project.versioning.major=4
|
||||||
project.versioning.minor=0
|
project.versioning.minor=0
|
||||||
project.versioning.patch=2
|
project.versioning.patch=3
|
||||||
project.versioning.build=0
|
project.versioning.build=0
|
||||||
project.versioning.type=rc
|
project.versioning.type=rc
|
||||||
#############################
|
#############################
|
||||||
|
|||||||
Reference in New Issue
Block a user