diff --git a/kable-core/src/androidMain/kotlin/Bluetooth.kt b/kable-core/src/androidMain/kotlin/Bluetooth.kt index bde2ddfe2..ccb124f46 100644 --- a/kable-core/src/androidMain/kotlin/Bluetooth.kt +++ b/kable-core/src/androidMain/kotlin/Bluetooth.kt @@ -1,49 +1,19 @@ package com.juul.kable -import android.bluetooth.BluetoothAdapter.ERROR -import android.bluetooth.BluetoothAdapter.EXTRA_STATE -import android.bluetooth.BluetoothAdapter.STATE_OFF -import android.bluetooth.BluetoothAdapter.STATE_ON -import android.bluetooth.BluetoothAdapter.STATE_TURNING_OFF -import android.bluetooth.BluetoothAdapter.STATE_TURNING_ON import android.bluetooth.BluetoothManager -import android.content.Context -import android.content.IntentFilter -import android.location.LocationManager -import android.location.LocationManager.EXTRA_PROVIDER_ENABLED -import android.location.LocationManager.PROVIDERS_CHANGED_ACTION -import android.os.Build.VERSION.SDK_INT -import android.os.Build.VERSION_CODES.R -import androidx.core.content.ContextCompat -import androidx.core.location.LocationManagerCompat -import com.juul.kable.Bluetooth.Availability.Available -import com.juul.kable.Bluetooth.Availability.Unavailable -import com.juul.kable.Reason.AdapterNotAvailable -import com.juul.kable.Reason.LocationServicesDisabled -import com.juul.kable.Reason.Off -import com.juul.kable.Reason.TurningOff -import com.juul.kable.Reason.TurningOn -import com.juul.tuulbox.coroutines.flow.broadcastReceiverFlow -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.emitAll -import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onStart -import android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED as BLUETOOTH_STATE_CHANGED @Deprecated( message = "`Bluetooth.availability` has inconsistent behavior across platforms. " + "Will be removed in a future release. " + "See https://github.com/JuulLabs/kable/issues/737 for more details.", + level = DeprecationLevel.ERROR, ) public actual enum class Reason { @Deprecated( message = "`Bluetooth.availability` has inconsistent behavior across platforms. " + "Will be removed in a future release. " + "See https://github.com/JuulLabs/kable/issues/737 for more details.", + level = DeprecationLevel.ERROR, ) Off, // BluetoothAdapter.STATE_OFF @@ -51,6 +21,7 @@ public actual enum class Reason { message = "`Bluetooth.availability` has inconsistent behavior across platforms. " + "Will be removed in a future release. " + "See https://github.com/JuulLabs/kable/issues/737 for more details.", + level = DeprecationLevel.ERROR, ) TurningOff, // BluetoothAdapter.STATE_TURNING_OFF or BluetoothAdapter.STATE_BLE_TURNING_OFF @@ -58,6 +29,7 @@ public actual enum class Reason { message = "`Bluetooth.availability` has inconsistent behavior across platforms. " + "Will be removed in a future release. " + "See https://github.com/JuulLabs/kable/issues/737 for more details.", + level = DeprecationLevel.ERROR, ) TurningOn, // BluetoothAdapter.STATE_TURNING_ON or BluetoothAdapter.STATE_BLE_TURNING_ON @@ -69,6 +41,7 @@ public actual enum class Reason { message = "`Bluetooth.availability` has inconsistent behavior across platforms. " + "Will be removed in a future release. " + "See https://github.com/JuulLabs/kable/issues/737 for more details.", + level = DeprecationLevel.ERROR, ) AdapterNotAvailable, @@ -77,60 +50,7 @@ public actual enum class Reason { message = "`Bluetooth.availability` has inconsistent behavior across platforms. " + "Will be removed in a future release. " + "See https://github.com/JuulLabs/kable/issues/737 for more details.", + level = DeprecationLevel.ERROR, ) LocationServicesDisabled, } - -private fun Context.getLocationManagerOrNull() = - ContextCompat.getSystemService(this, LocationManager::class.java) - -private fun Context.isLocationEnabledOrNull(): Boolean? = - getLocationManagerOrNull()?.let(LocationManagerCompat::isLocationEnabled) - -private val locationEnabledOrNullFlow = when { - SDK_INT > R -> flowOf(true) - else -> broadcastReceiverFlow(IntentFilter(PROVIDERS_CHANGED_ACTION)) - .map { intent -> - if (SDK_INT == R) { - intent.getBooleanExtra(EXTRA_PROVIDER_ENABLED, false) - } else { - applicationContext.isLocationEnabledOrNull() - } - } - .onStart { emit(applicationContext.isLocationEnabledOrNull()) } - .distinctUntilChanged() -} - -private val bluetoothStateFlow = flow { - when (val adapter = getBluetoothAdapterOrNull()) { - null -> emit(Unavailable(reason = AdapterNotAvailable)) - else -> emitAll( - broadcastReceiverFlow(IntentFilter(BLUETOOTH_STATE_CHANGED)) - .map { intent -> intent.getIntExtra(EXTRA_STATE, ERROR) } - .onStart { - emit(if (adapter.isEnabled) STATE_ON else STATE_OFF) - } - .map { state -> - when (state) { - STATE_ON -> Available - STATE_OFF -> Unavailable(reason = Off) - STATE_TURNING_OFF -> Unavailable(reason = TurningOff) - STATE_TURNING_ON -> Unavailable(reason = TurningOn) - else -> error("Unexpected bluetooth state: $state") - } - }, - ) - } -} - -internal actual val bluetoothAvailability: Flow = - combine( - locationEnabledOrNullFlow, - bluetoothStateFlow, - ) { locationEnabled, bluetoothState -> - when (locationEnabled) { - true -> bluetoothState - false -> Unavailable(reason = LocationServicesDisabled) - null -> Unavailable(reason = AdapterNotAvailable) - } - } diff --git a/kable-core/src/appleMain/kotlin/Bluetooth.kt b/kable-core/src/appleMain/kotlin/Bluetooth.kt index a640ff11e..b62af7f8f 100644 --- a/kable-core/src/appleMain/kotlin/Bluetooth.kt +++ b/kable-core/src/appleMain/kotlin/Bluetooth.kt @@ -1,33 +1,18 @@ package com.juul.kable -import com.juul.kable.Bluetooth.Availability.Available -import com.juul.kable.Bluetooth.Availability.Unavailable -import com.juul.kable.Reason.Off -import com.juul.kable.Reason.Resetting -import com.juul.kable.Reason.Unauthorized -import com.juul.kable.Reason.Unknown -import com.juul.kable.Reason.Unsupported -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.emitAll -import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.flow.map -import platform.CoreBluetooth.CBCentralManagerStatePoweredOff -import platform.CoreBluetooth.CBCentralManagerStatePoweredOn -import platform.CoreBluetooth.CBCentralManagerStateResetting -import platform.CoreBluetooth.CBCentralManagerStateUnauthorized -import platform.CoreBluetooth.CBCentralManagerStateUnsupported - /** https://developer.apple.com/documentation/corebluetooth/cbmanagerstate */ @Deprecated( message = "`Bluetooth.availability` has inconsistent behavior across platforms. " + "Will be removed in a future release. " + "See https://github.com/JuulLabs/kable/issues/737 for more details.", + level = DeprecationLevel.ERROR, ) public actual enum class Reason { @Deprecated( message = "`Bluetooth.availability` has inconsistent behavior across platforms. " + "Will be removed in a future release. " + "See https://github.com/JuulLabs/kable/issues/737 for more details.", + level = DeprecationLevel.ERROR, ) Off, // CBManagerState.poweredOff @@ -35,6 +20,7 @@ public actual enum class Reason { message = "`Bluetooth.availability` has inconsistent behavior across platforms. " + "Will be removed in a future release. " + "See https://github.com/JuulLabs/kable/issues/737 for more details.", + level = DeprecationLevel.ERROR, ) Resetting, // CBManagerState.resetting @@ -42,6 +28,7 @@ public actual enum class Reason { message = "`Bluetooth.availability` has inconsistent behavior across platforms. " + "Will be removed in a future release. " + "See https://github.com/JuulLabs/kable/issues/737 for more details.", + level = DeprecationLevel.ERROR, ) Unauthorized, // CBManagerState.unauthorized @@ -49,6 +36,7 @@ public actual enum class Reason { message = "`Bluetooth.availability` has inconsistent behavior across platforms. " + "Will be removed in a future release. " + "See https://github.com/JuulLabs/kable/issues/737 for more details.", + level = DeprecationLevel.ERROR, ) Unsupported, // CBManagerState.unsupported @@ -56,20 +44,7 @@ public actual enum class Reason { message = "`Bluetooth.availability` has inconsistent behavior across platforms. " + "Will be removed in a future release. " + "See https://github.com/JuulLabs/kable/issues/737 for more details.", + level = DeprecationLevel.ERROR, ) Unknown, // CBManagerState.unknown } - -internal actual val bluetoothAvailability: Flow = flow { - // flow + emitAll dance so that lazy `CentralManager.Default` is not initialized until this flow is active. - emitAll(CentralManager.Default.delegate.state) -}.map { state -> - when (state) { - CBCentralManagerStatePoweredOn -> Available - CBCentralManagerStatePoweredOff -> Unavailable(reason = Off) - CBCentralManagerStateResetting -> Unavailable(reason = Resetting) - CBCentralManagerStateUnauthorized -> Unavailable(reason = Unauthorized) - CBCentralManagerStateUnsupported -> Unavailable(reason = Unsupported) - else -> Unavailable(reason = Unknown) - } -} diff --git a/kable-core/src/commonMain/kotlin/Bluetooth.kt b/kable-core/src/commonMain/kotlin/Bluetooth.kt index d51c7c230..66c3ae8ed 100644 --- a/kable-core/src/commonMain/kotlin/Bluetooth.kt +++ b/kable-core/src/commonMain/kotlin/Bluetooth.kt @@ -45,6 +45,7 @@ public object Bluetooth { message = "`Bluetooth.availability` has inconsistent behavior across platforms. " + "Will be removed in a future release. " + "See https://github.com/JuulLabs/kable/issues/737 for more details.", + level = DeprecationLevel.ERROR, ) public data object Available : Availability() @@ -52,6 +53,7 @@ public object Bluetooth { message = "`Bluetooth.availability` has inconsistent behavior across platforms. " + "Will be removed in a future release. " + "See https://github.com/JuulLabs/kable/issues/737 for more details.", + level = DeprecationLevel.ERROR, ) public data class Unavailable(val reason: Reason?) : Availability() } @@ -60,8 +62,10 @@ public object Bluetooth { message = "`Bluetooth.availability` has inconsistent behavior across platforms. " + "Will be removed in a future release. " + "See https://github.com/JuulLabs/kable/issues/737 for more details.", + level = DeprecationLevel.ERROR, ) - public val availability: Flow = bluetoothAvailability + public val availability: Flow + get() = error("Deprecated") /** * Checks if Bluetooth Low Energy is supported on the system. Being supported (a return of @@ -76,5 +80,3 @@ public object Bluetooth { @ExperimentalApi // Due to the inability to query Bluetooth support w/o showing a dialog on Apple, this function may be removed. public suspend fun isSupported(): Boolean = isBluetoothSupported() } - -internal expect val bluetoothAvailability: Flow diff --git a/kable-core/src/jsMain/kotlin/BluetoothAvailability.kt b/kable-core/src/jsMain/kotlin/BluetoothAvailability.kt index 2cd42f799..7743f6b1f 100644 --- a/kable-core/src/jsMain/kotlin/BluetoothAvailability.kt +++ b/kable-core/src/jsMain/kotlin/BluetoothAvailability.kt @@ -1,17 +1,5 @@ package com.juul.kable -import com.juul.kable.Bluetooth.Availability.Available -import com.juul.kable.Bluetooth.Availability.Unavailable -import com.juul.kable.Reason.BluetoothUndefined -import com.juul.kable.external.BluetoothAvailabilityChanged -import kotlinx.coroutines.await -import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.callbackFlow -import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.flow.onStart -import org.w3c.dom.events.Event - @Deprecated( message = "`Bluetooth.availability` has inconsistent behavior across platforms. " + "Will be removed in a future release. " + @@ -28,25 +16,3 @@ public actual enum class Reason { } private const val AVAILABILITY_CHANGED = "availabilitychanged" - -internal actual val bluetoothAvailability: Flow = - bluetoothOrNull()?.let { bluetooth -> - callbackFlow { - // https://developer.mozilla.org/en-US/docs/Web/API/Bluetooth/onavailabilitychanged - val listener: (Event) -> Unit = { event -> - val isAvailable = event.unsafeCast().value - trySend(if (isAvailable) Available else Unavailable(reason = null)) - } - - bluetooth.apply { - addEventListener(AVAILABILITY_CHANGED, listener) - awaitClose { - removeEventListener(AVAILABILITY_CHANGED, listener) - } - } - }.onStart { - val isAvailable = bluetooth.getAvailability().await() - val availability = if (isAvailable) Available else Unavailable(reason = null) - emit(availability) - } - } ?: flowOf(Unavailable(reason = BluetoothUndefined)) diff --git a/kable-core/src/jvmMain/kotlin/com/juul/kable/Bluetooth.kt b/kable-core/src/jvmMain/kotlin/com/juul/kable/Bluetooth.kt index c639ab190..a58f35a6a 100644 --- a/kable-core/src/jvmMain/kotlin/com/juul/kable/Bluetooth.kt +++ b/kable-core/src/jvmMain/kotlin/com/juul/kable/Bluetooth.kt @@ -1,11 +1,11 @@ package com.juul.kable -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flowOf - +@Deprecated( + message = "`Bluetooth.availability` has inconsistent behavior across platforms. " + + "Will be removed in a future release. " + + "See https://github.com/JuulLabs/kable/issues/737 for more details.", + level = DeprecationLevel.ERROR, +) public actual enum class Reason { // Not implemented. } - -// This is not a proper implementation, but this property is deprecated, so... -internal actual val bluetoothAvailability: Flow = flowOf(Bluetooth.Availability.Available)