Repository navigation
feat: [QRSCANNER] Connect by scanning the desktop QR code #748
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
a0d3eae
feat: [QRSCANNER] qr-scanner module to connect by scanning the deskto…
claude 275d480
feat(sample): scan the desktop QR code from the connection screen
claude 9359ac7
renamed
56b9ebd
renamed
98abc4e
fix
ed76423
Delete .idea/.gitignore
florent37 3704f73
Merge branch 'main' into feat/qr-code-scanner
florent37 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| plugins { | ||
| id("flocon.kotlin.library") | ||
| id("flocon.android.library") | ||
| id("flocon.jvm.library") | ||
| id("flocon.publish") | ||
| } | ||
|
|
||
| kotlin { | ||
| android { | ||
| namespace = "io.github.openflocon.flocon.qrscanner.noop" | ||
| } | ||
|
|
||
| sourceSets { | ||
| commonMain.dependencies { | ||
| api(projects.floconNoOp) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| mavenPublishing { | ||
| coordinates( | ||
| groupId = project.property("floconGroupId") as String, | ||
| artifactId = "flocon-qr-scanner-no-op", | ||
| version = System.getenv("PROJECT_VERSION_NAME") ?: project.property("floconVersion") as String | ||
| ) | ||
|
|
||
|
|
||
| pom { | ||
| name = "Flocon QR Scanner No-Op" | ||
| } | ||
| } |
9 changes: 9 additions & 0 deletions
9
...o-op/src/androidMain/kotlin/io/github/openflocon/flocon/qrscanner/ScanQrCodeAndConnect.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| package io.github.openflocon.flocon.qrscanner | ||
|
|
||
| import android.content.Context | ||
| import io.github.openflocon.flocon.Flocon | ||
| import io.github.openflocon.flocon.FloconConnectResult | ||
|
|
||
| /** Does nothing, no scanner is opened: always `null`. */ | ||
| @Suppress("UNUSED_PARAMETER") | ||
| suspend fun Flocon.Companion.scanQrCodeAndConnect(context: Context): FloconConnectResult? = null |
8 changes: 8 additions & 0 deletions
8
...er-no-op/src/commonMain/kotlin/io/github/openflocon/flocon/qrscanner/ConnectWithQrCode.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| plugins { | ||
| id("flocon.kotlin.library") | ||
| id("flocon.android.library") | ||
| id("flocon.jvm.library") | ||
| id("flocon.publish") | ||
| } | ||
|
|
||
| kotlin { | ||
| android { | ||
| namespace = "io.github.openflocon.flocon.qrscanner" | ||
| } | ||
|
|
||
| sourceSets { | ||
| commonMain.dependencies { | ||
| implementation(projects.flocon) | ||
| implementation(libs.kotlinx.coroutines.core) | ||
| } | ||
|
|
||
| androidMain.dependencies { | ||
| implementation(libs.google.play.services.code.scanner) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| mavenPublishing { | ||
| coordinates( | ||
| groupId = project.property("floconGroupId") as String, | ||
| artifactId = "flocon-qr-scanner", | ||
| version = System.getenv("PROJECT_VERSION_NAME") ?: project.property("floconVersion") as String | ||
| ) | ||
|
|
||
|
|
||
| pom { | ||
| name = "Flocon QR Scanner" | ||
| } | ||
| } |
11 changes: 11 additions & 0 deletions
11
FloconAndroid/qrscanner/src/androidMain/AndroidManifest.xml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| <?xml version="1.0" encoding="utf-8"?> | ||
| <manifest xmlns:android="http://schemas.android.com/apk/res/android"> | ||
|
|
||
| <application> | ||
| <!-- Downloads the scanner module when the app is installed, so that the first scan is not delayed --> | ||
| <meta-data | ||
| android:name="com.google.mlkit.vision.DEPENDENCIES" | ||
| android:value="barcode_ui" /> | ||
| </application> | ||
|
|
||
| </manifest> |
62 changes: 62 additions & 0 deletions
62
...nner/src/androidMain/kotlin/io/github/openflocon/flocon/qrscanner/ScanQrCodeAndConnect.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| package io.github.openflocon.flocon.qrscanner | ||
|
|
||
| import android.content.Context | ||
| 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 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]. | ||
| * | ||
| * 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]. | ||
| * | ||
| * ``` | ||
| * val result = Flocon.scanQrCodeAndConnect(context) | ||
| * ``` | ||
| * | ||
| * @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)) { | ||
| ScanQrCodeResult.Cancelled -> null | ||
| is ScanQrCodeResult.Failed -> FloconConnectResult.Failed(scan.reason) | ||
| is ScanQrCodeResult.Code -> connectWithQrCode(scan.content) | ||
| } | ||
|
|
||
| private sealed interface ScanQrCodeResult { | ||
| data class Code(val content: String) : ScanQrCodeResult | ||
| data object Cancelled : ScanQrCodeResult | ||
| data class Failed(val reason: String) : ScanQrCodeResult | ||
| } | ||
|
|
||
| private suspend fun scanQrCode(context: Context): ScanQrCodeResult = | ||
| suspendCancellableCoroutine { continuation -> | ||
| val options = GmsBarcodeScannerOptions.Builder() | ||
| .setBarcodeFormats(Barcode.FORMAT_QR_CODE) | ||
| .build() | ||
| GmsBarcodeScanning.getClient(context, options) | ||
| .startScan() | ||
| .addOnSuccessListener { barcode -> | ||
| val content = barcode.rawValue | ||
| continuation.resume( | ||
| if (content == null) { | ||
| ScanQrCodeResult.Failed("The QR code is empty") | ||
| } else { | ||
| ScanQrCodeResult.Code(content) | ||
| } | ||
| ) | ||
| } | ||
| .addOnCanceledListener { | ||
| continuation.resume(ScanQrCodeResult.Cancelled) | ||
| } | ||
| .addOnFailureListener { error -> | ||
| continuation.resume(ScanQrCodeResult.Failed(error.message ?: "The scanner failed")) | ||
| } | ||
| } |
19 changes: 19 additions & 0 deletions
19
...rscanner/src/commonMain/kotlin/io/github/openflocon/flocon/qrscanner/ConnectWithQrCode.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| 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)) |
25 changes: 25 additions & 0 deletions
25
...id/qrscanner/src/commonMain/kotlin/io/github/openflocon/flocon/qrscanner/QrCodeAddress.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| 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 } | ||
| } |
30 changes: 30 additions & 0 deletions
30
...d/qrscanner/src/jvmTest/kotlin/io/github/openflocon/flocon/qrscanner/QrCodeAddressTest.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| 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://")) | ||
| } | ||
| } |
12 changes: 12 additions & 0 deletions
12
...c/commonMain/kotlin/io/github/openflocon/flocon/myapplication/multi/ui/SampleQrScanner.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| package io.github.openflocon.flocon.myapplication.multi.ui | ||
|
|
||
| import io.github.openflocon.flocon.FloconConnectResult | ||
|
|
||
| /** | ||
| * Scans the QR code of Flocon Desktop and connects to it, on the platforms that can: the Android app sets it. | ||
| * The connection screen shows its button only when it is set. | ||
| */ | ||
| object SampleQrScanner { | ||
| /** Returns the result of the connection, or `null` if the scan was cancelled. */ | ||
| var scan: (suspend () -> FloconConnectResult?)? = null | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.