Skip to content

Commit 9969ac9

Browse files
[Feature] Added QR code scanning functionality to add deeplink (#39)
* added QR code scanner to add deeplink * lint fix * lint fix * lint fix * lint fix --------- Co-authored-by: anas shikoh <[email protected]>
1 parent f730208 commit 9969ac9

File tree

8 files changed

+75
-17
lines changed

8 files changed

+75
-17
lines changed

app/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ dependencies {
9898
implementation(libs.accompanist)
9999
implementation(libs.opencsv)
100100
implementation(libs.koin.compose)
101+
implementation(libs.zxing.scanner)
101102
ktlint(libs.ktlint)
102103
implementation("dev.chrisbanes.haze:haze:1.6.9")
103104
implementation("dev.chrisbanes.haze:haze-materials:1.6.9")

app/src/main/AndroidManifest.xml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<?xml version="1.0" encoding="utf-8"?>
2-
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
2+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
3+
xmlns:tools="http://schemas.android.com/tools">
34

45
<application
56
android:name=".DeeprApplication"
@@ -23,6 +24,10 @@
2324
<category android:name="android.intent.category.LAUNCHER" />
2425
</intent-filter>
2526
</activity>
27+
<activity
28+
android:name="com.journeyapps.barcodescanner.CaptureActivity"
29+
android:screenOrientation="portrait"
30+
tools:replace="screenOrientation" />
2631
</application>
2732

2833
</manifest>

app/src/main/java/com/yogeshpaliyal/deepr/ui/screens/Home.kt

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import android.content.ClipData
44
import android.content.ClipboardManager
55
import android.content.Context
66
import android.widget.Toast
7+
import androidx.activity.compose.rememberLauncherForActivityResult
78
import androidx.compose.animation.AnimatedVisibility
89
import androidx.compose.foundation.combinedClickable
910
import androidx.compose.foundation.layout.Arrangement
@@ -53,11 +54,12 @@ import androidx.compose.ui.platform.LocalContext
5354
import androidx.compose.ui.text.style.TextAlign
5455
import androidx.compose.ui.text.style.TextOverflow
5556
import androidx.compose.ui.unit.dp
56-
import androidx.lifecycle.viewmodel.compose.viewModel
57+
import com.journeyapps.barcodescanner.ScanOptions
5758
import com.yogeshpaliyal.deepr.Deepr
5859
import com.yogeshpaliyal.deepr.ui.components.CreateShortcutDialog
5960
import com.yogeshpaliyal.deepr.ui.components.EditDeeplinkDialog
6061
import com.yogeshpaliyal.deepr.ui.components.QrCodeDialog
62+
import com.yogeshpaliyal.deepr.util.QRScanner
6163
import com.yogeshpaliyal.deepr.util.hasShortcut
6264
import com.yogeshpaliyal.deepr.util.isShortcutSupported
6365
import com.yogeshpaliyal.deepr.util.isValidDeeplink
@@ -100,6 +102,22 @@ fun HomeScreen(
100102
var isSearchActive by remember { mutableStateOf(false) }
101103
var searchQuery by remember { mutableStateOf("") }
102104
val hazeState = rememberHazeState()
105+
val context = LocalContext.current
106+
val qrScanner =
107+
rememberLauncherForActivityResult(
108+
QRScanner(),
109+
) { result ->
110+
if (result.contents == null) {
111+
Toast.makeText(context, "No Data found", Toast.LENGTH_SHORT).show()
112+
} else {
113+
if (isValidDeeplink(result.contents)) {
114+
viewModel.insertAccount(result.contents, false)
115+
Toast.makeText(context, "Saved", Toast.LENGTH_SHORT).show()
116+
} else {
117+
Toast.makeText(context, "Invalid deeplink", Toast.LENGTH_SHORT).show()
118+
}
119+
}
120+
}
103121

104122
Scaffold(
105123
modifier = modifier.fillMaxSize(),
@@ -130,10 +148,17 @@ fun HomeScreen(
130148
contentDescription = if (isSearchActive) "Close search" else "Search",
131149
)
132150
}
151+
IconButton(onClick = {
152+
qrScanner.launch(ScanOptions())
153+
}) {
154+
Icon(
155+
TablerIcons.Qrcode,
156+
contentDescription = "QR Scanner",
157+
)
158+
}
133159
FilterMenu(onSortOrderChange = {
134160
viewModel.setSortOrder(it)
135161
})
136-
137162
IconButton(onClick = {
138163
backStack.add(Settings)
139164
}) {
@@ -174,6 +199,7 @@ fun HomeScreen(
174199
}
175200
}
176201

202+
@OptIn(ExperimentalHazeMaterialsApi::class)
177203
@Composable
178204
fun BottomContent(
179205
hazeState: HazeState,
@@ -191,8 +217,10 @@ fun BottomContent(
191217
RoundedCornerShape(
192218
topStart = 12.dp,
193219
),
194-
).hazeEffect(state = hazeState, style = HazeMaterials.thin())
195-
.fillMaxWidth(),
220+
).hazeEffect(
221+
state = hazeState,
222+
style = HazeMaterials.thin(),
223+
).fillMaxWidth(),
196224
) {
197225
Column(
198226
modifier =
@@ -229,8 +257,11 @@ fun BottomContent(
229257
if (isValidDeeplink(inputText.value)) {
230258
viewModel.insertAccount(inputText.value, false)
231259
Toast
232-
.makeText(context, "Saved", Toast.LENGTH_SHORT)
233-
.show()
260+
.makeText(
261+
context,
262+
"Saved",
263+
Toast.LENGTH_SHORT,
264+
).show()
234265
inputText.value = ""
235266
} else {
236267
isError = true
@@ -527,9 +558,10 @@ fun DeeprItem(
527558
modifier
528559
.fillMaxWidth()
529560
.padding(vertical = 4.dp)
530-
.combinedClickable(onClick = { onItemClick?.invoke(account) }, onLongClick = {
531-
onItemLongClick?.invoke(account)
532-
}),
561+
.combinedClickable(
562+
onClick = { onItemClick?.invoke(account) },
563+
onLongClick = { onItemLongClick?.invoke(account) },
564+
),
533565
) {
534566
Row(
535567
modifier =

app/src/main/java/com/yogeshpaliyal/deepr/ui/screens/Settings.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ import androidx.compose.ui.Modifier
3434
import androidx.compose.ui.platform.LocalContext
3535
import androidx.compose.ui.text.style.TextAlign
3636
import androidx.compose.ui.unit.dp
37-
import androidx.lifecycle.viewmodel.compose.viewModel
3837
import com.google.accompanist.permissions.ExperimentalPermissionsApi
3938
import com.google.accompanist.permissions.isGranted
4039
import com.google.accompanist.permissions.rememberPermissionState
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package com.yogeshpaliyal.deepr.util
2+
3+
import android.content.Context
4+
import android.content.Intent
5+
import androidx.activity.result.contract.ActivityResultContract
6+
import com.journeyapps.barcodescanner.CaptureActivity
7+
import com.journeyapps.barcodescanner.ScanIntentResult
8+
import com.journeyapps.barcodescanner.ScanOptions
9+
10+
class QRScanner : ActivityResultContract<ScanOptions, ScanIntentResult>() {
11+
override fun createIntent(
12+
context: Context,
13+
input: ScanOptions,
14+
): Intent = Intent(context, CaptureActivity::class.java)
15+
16+
override fun parseResult(
17+
resultCode: Int,
18+
intent: Intent?,
19+
): ScanIntentResult = ScanIntentResult.parseActivityResult(resultCode, intent)
20+
}

app/src/main/java/com/yogeshpaliyal/deepr/util/Utils.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ fun openDeeplink(
2525
fun isValidDeeplink(link: String): Boolean {
2626
if (link.isBlank()) return false
2727
return try {
28-
link.toUri()
29-
return true
30-
} catch (e: Exception) {
28+
val uri = link.toUri()
29+
uri.scheme != null && uri.scheme!!.isNotBlank()
30+
} catch (_: Exception) {
3131
false
3232
}
3333
}

app/src/main/java/com/yogeshpaliyal/deepr/viewmodel/AccountViewModel.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import com.yogeshpaliyal.deepr.Deepr
99
import com.yogeshpaliyal.deepr.DeeprQueries
1010
import com.yogeshpaliyal.deepr.backup.ExportRepository
1111
import com.yogeshpaliyal.deepr.backup.ImportRepository
12-
import com.yogeshpaliyal.deepr.backup.ImportResult
1312
import com.yogeshpaliyal.deepr.preference.AppPreferenceDataStore
1413
import com.yogeshpaliyal.deepr.util.RequestResult
1514
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -119,7 +118,7 @@ class AccountViewModel(
119118
viewModelScope.launch {
120119
val result = exportRepository.exportToCsv()
121120
when (result) {
122-
is RequestResult.Success<String> -> {
121+
is RequestResult.Success -> {
123122
exportResultChannel.send("Export completed: ${result.data}")
124123
}
125124

@@ -136,7 +135,7 @@ class AccountViewModel(
136135
val result = importRepository.importFromCsv(uri)
137136

138137
when (result) {
139-
is RequestResult.Success<ImportResult> -> {
138+
is RequestResult.Success -> {
140139
importResultChannel.send(
141140
"Import complete! Added: ${result.data.importedCount}, Skipped (duplicates): ${result.data.skippedCount}",
142141
)

gradle/libs.versions.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ lifecycleViewmodelNav3 = "1.0.0-alpha01"
2020
dataStore = "1.1.7"
2121
accompanist = "0.34.0"
2222
opencsv = "4.6"
23+
zxing = "4.3.0"
2324

2425
[libraries]
2526
android-driver = { module = "app.cash.sqldelight:android-driver", version.ref = "androidDriver" }
@@ -53,6 +54,7 @@ accompanist = { group = "com.google.accompanist", name = "accompanist-permission
5354
androidx-navigation3-runtime = { module = "androidx.navigation3:navigation3-runtime", version.ref = "nav3Core" }
5455
androidx-navigation3-ui = { module = "androidx.navigation3:navigation3-ui", version.ref = "nav3Core" }
5556
androidx-lifecycle-viewmodel-navigation3 = { module = "androidx.lifecycle:lifecycle-viewmodel-navigation3", version.ref = "lifecycleViewmodelNav3" }
57+
zxing-scanner = { group = "com.journeyapps", name = "zxing-android-embedded", version.ref = "zxing" }
5658

5759
[plugins]
5860
android-application = { id = "com.android.application", version.ref = "agp" }

0 commit comments

Comments
 (0)