diff --git a/FloconAndroid/qrscanner/qrscanner-no-op/src/commonMain/kotlin/io/github/openflocon/flocon/qrscanner/ConnectWithQrCode.kt b/FloconAndroid/qrscanner/qrscanner-no-op/src/commonMain/kotlin/io/github/openflocon/flocon/qrscanner/ConnectWithQrCode.kt deleted file mode 100644 index 76a3029dc..000000000 --- a/FloconAndroid/qrscanner/qrscanner-no-op/src/commonMain/kotlin/io/github/openflocon/flocon/qrscanner/ConnectWithQrCode.kt +++ /dev/null @@ -1,8 +0,0 @@ -package io.github.openflocon.flocon.qrscanner - -import io.github.openflocon.flocon.Flocon -import io.github.openflocon.flocon.FloconConnectResult - -/** Does nothing, always [FloconConnectResult.NotStarted]. */ -@Suppress("UNUSED_PARAMETER") -suspend fun Flocon.Companion.connectWithQrCode(content: String): FloconConnectResult = FloconConnectResult.NotStarted diff --git a/FloconAndroid/qrscanner/qrscanner/src/androidMain/kotlin/io/github/openflocon/flocon/qrscanner/ScanQrCodeAndConnect.kt b/FloconAndroid/qrscanner/qrscanner/src/androidMain/kotlin/io/github/openflocon/flocon/qrscanner/ScanQrCodeAndConnect.kt index ff74c9543..f3314495b 100644 --- a/FloconAndroid/qrscanner/qrscanner/src/androidMain/kotlin/io/github/openflocon/flocon/qrscanner/ScanQrCodeAndConnect.kt +++ b/FloconAndroid/qrscanner/qrscanner/src/androidMain/kotlin/io/github/openflocon/flocon/qrscanner/ScanQrCodeAndConnect.kt @@ -1,20 +1,25 @@ package io.github.openflocon.flocon.qrscanner import android.content.Context +import com.google.android.gms.common.ConnectionResult +import com.google.android.gms.common.GoogleApiAvailability import com.google.mlkit.vision.barcode.common.Barcode import com.google.mlkit.vision.codescanner.GmsBarcodeScannerOptions import com.google.mlkit.vision.codescanner.GmsBarcodeScanning import io.github.openflocon.flocon.Flocon import io.github.openflocon.flocon.FloconConnectResult +import io.github.openflocon.flocon.connect import kotlinx.coroutines.suspendCancellableCoroutine import kotlin.coroutines.resume /** * Opens the Google code scanner, then connects to the desktop app at the address held by the QR code - * it reads, see [connectWithQrCode]. + * it reads. The QR code of Flocon Desktop holds only the IP address of the computer, the same value as + * `Flocon.connect(address)`. * * The scanner is a screen of Google Play Services: the app needs no camera permission. It requires - * Google Play Services on the device, otherwise the result is [FloconConnectResult.Failed]. + * Google Play Services on the device, otherwise the result is [FloconConnectResult.Failed]: connect with + * `Flocon.connect(address)` instead. * * ``` * val result = Flocon.scanQrCodeAndConnect(context) @@ -23,12 +28,16 @@ import kotlin.coroutines.resume * @param context preferably an `Activity`. * @return the result of the connection, or `null` if the user closed the scanner without scanning. */ -suspend fun Flocon.Companion.scanQrCodeAndConnect(context: Context): FloconConnectResult? = - when (val scan = scanQrCode(context)) { +suspend fun Flocon.Companion.scanQrCodeAndConnect(context: Context): FloconConnectResult? { + if (GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context) != ConnectionResult.SUCCESS) { + return FloconConnectResult.Failed("Google Play Services is required to scan a QR code: connect with the IP address instead") + } + return when (val scan = scanQrCode(context)) { ScanQrCodeResult.Cancelled -> null is ScanQrCodeResult.Failed -> FloconConnectResult.Failed(scan.reason) - is ScanQrCodeResult.Code -> connectWithQrCode(scan.content) + is ScanQrCodeResult.Code -> connect(scan.content) } +} private sealed interface ScanQrCodeResult { data class Code(val content: String) : ScanQrCodeResult diff --git a/FloconAndroid/qrscanner/qrscanner/src/commonMain/kotlin/io/github/openflocon/flocon/qrscanner/ConnectWithQrCode.kt b/FloconAndroid/qrscanner/qrscanner/src/commonMain/kotlin/io/github/openflocon/flocon/qrscanner/ConnectWithQrCode.kt deleted file mode 100644 index 0f0cc3aaf..000000000 --- a/FloconAndroid/qrscanner/qrscanner/src/commonMain/kotlin/io/github/openflocon/flocon/qrscanner/ConnectWithQrCode.kt +++ /dev/null @@ -1,19 +0,0 @@ -package io.github.openflocon.flocon.qrscanner - -import io.github.openflocon.flocon.Flocon -import io.github.openflocon.flocon.FloconConnectResult -import io.github.openflocon.flocon.connect - -/** - * Connects to the desktop app at the address held by a scanned QR code. - * - * Same as `Flocon.connect(address)`, for the raw content of a QR code. The content is either the IP address - * (or host) of the computer, or a URL whose host is that address, such as `flocon://192.168.1.20`: the scheme, - * the port and the path are ignored. On Android, `Flocon.scanQrCodeAndConnect` scans the code and calls this. - * - * @param content the text read from the QR code. - * @return [FloconConnectResult.Connected], or why it could not connect. The messages are kept - * in every case, call it again to retry. - */ -suspend fun Flocon.Companion.connectWithQrCode(content: String): FloconConnectResult = - connect(addressFromQrCode(content)) diff --git a/FloconAndroid/qrscanner/qrscanner/src/commonMain/kotlin/io/github/openflocon/flocon/qrscanner/QrCodeAddress.kt b/FloconAndroid/qrscanner/qrscanner/src/commonMain/kotlin/io/github/openflocon/flocon/qrscanner/QrCodeAddress.kt deleted file mode 100644 index acb92e776..000000000 --- a/FloconAndroid/qrscanner/qrscanner/src/commonMain/kotlin/io/github/openflocon/flocon/qrscanner/QrCodeAddress.kt +++ /dev/null @@ -1,25 +0,0 @@ -package io.github.openflocon.flocon.qrscanner - -/** - * The address to connect to for the content of a QR code. - * - * The content is either the bare address of the computer (`192.168.1.20`), or a URL whose host is that - * address (`flocon://192.168.1.20`, `wss://my-mac.local:9023`): the scheme, the port and the path are - * ignored, as the port is always 9023. - * - * Anything that is not a URL is returned trimmed and unchanged, so that `Flocon.connect` reports it - * as an invalid address when it cannot be read. - */ -internal fun addressFromQrCode(content: String): String { - val text = content.trim() - val schemeEnd = text.indexOf("://") - if (schemeEnd <= 0) return text - val host = text - .substring(schemeEnd + 3) - .substringBefore('/') - .substringBefore('?') - .substringBefore('#') - .substringAfterLast('@') - .substringBefore(':') - return host.ifEmpty { text } -} diff --git a/FloconAndroid/qrscanner/qrscanner/src/jvmTest/kotlin/io/github/openflocon/flocon/qrscanner/QrCodeAddressTest.kt b/FloconAndroid/qrscanner/qrscanner/src/jvmTest/kotlin/io/github/openflocon/flocon/qrscanner/QrCodeAddressTest.kt deleted file mode 100644 index 7d5fc6f94..000000000 --- a/FloconAndroid/qrscanner/qrscanner/src/jvmTest/kotlin/io/github/openflocon/flocon/qrscanner/QrCodeAddressTest.kt +++ /dev/null @@ -1,30 +0,0 @@ -package io.github.openflocon.flocon.qrscanner - -import kotlin.test.Test -import kotlin.test.assertEquals - -class QrCodeAddressTest { - @Test - fun `a plain address is kept`() { - assertEquals("192.168.1.20", addressFromQrCode("192.168.1.20")) - } - - @Test - fun `whitespace around the address is removed`() { - assertEquals("my-mac.local", addressFromQrCode(" my-mac.local\n")) - } - - @Test - fun `a url gives its host`() { - assertEquals("192.168.1.20", addressFromQrCode("flocon://192.168.1.20")) - assertEquals("my-mac.local", addressFromQrCode("wss://my-mac.local:9023/path?token=1")) - assertEquals("192.168.1.20", addressFromQrCode("https://user@192.168.1.20:9023#top")) - } - - @Test - fun `text that is not an address is kept so it is reported as invalid`() { - assertEquals("hello world", addressFromQrCode("hello world")) - assertEquals("://", addressFromQrCode("://")) - assertEquals("flocon://", addressFromQrCode("flocon://")) - } -} diff --git a/docs/setup.md b/docs/setup.md index 10735c8ab..5bad587f6 100644 --- a/docs/setup.md +++ b/docs/setup.md @@ -127,7 +127,9 @@ Until a desktop is reachable, everything the plugins log is kept in memory. `Flo ### Connecting with a QR code -The desktop app has a **Connect** button in the top bar (QR code icon). It opens a dialog listing the IP addresses of the computer on the local network (one per network interface) and, for each one, a button that shows the address as a QR code. The QR code contains only the IP address, the same value as `Flocon.connect("192.168.1.20")`. Scanning it from the SDK is not available yet. +The desktop app has a **Connect** button in the top bar (QR code icon). It opens a dialog listing the IP addresses of the computer on the local network (one per network interface) and, for each one, a button that shows the address as a QR code. The QR code contains only the IP address, the same value as `Flocon.connect("192.168.1.20")`. + +To scan it from the app, add the optional `flocon-qr-scanner` module (and `flocon-qr-scanner-no-op` for release builds). `Flocon.scanQrCodeAndConnect(context)` opens the Google code scanner (a Google Play Services screen, no camera permission needed), then connects. It returns the `FloconConnectResult`, or `null` if the user closes the scanner. Without Google Play Services (an AOSP emulator, a Huawei device) it returns `Failed`: use `Flocon.connect("192.168.1.20")` instead. To use your own camera screen, pass the text it reads to `Flocon.connect(address)`. Up to 5000 messages are kept: above that, the oldest are dropped. `connect { bufferCapacity = ... }` changes this size.