Getting ready for initial release
This commit is contained in:
parent
2e86e0d052
commit
156f97bb9d
8 changed files with 249 additions and 210 deletions
|
@ -67,20 +67,25 @@ android {
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("androidx.core:core-ktx:1.9.0")
|
implementation("androidx.core:core-ktx:1.9.0")
|
||||||
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.5.1")
|
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.1")
|
||||||
implementation("androidx.activity:activity-compose:1.6.1")
|
implementation("androidx.activity:activity-compose:1.7.0")
|
||||||
|
implementation("androidx.compose.material3:material3:1.1.0-beta01")
|
||||||
|
|
||||||
implementation("androidx.compose.ui:ui:1.3.3")
|
val accompanistVersion = "0.30.0"
|
||||||
implementation("androidx.compose.ui:ui-tooling-preview:1.3.3")
|
implementation("com.google.accompanist:accompanist-permissions:$accompanistVersion")
|
||||||
implementation("androidx.compose.material3:material3:1.1.0-alpha06")
|
|
||||||
implementation("androidx.compose.material:material-icons-extended:1.3.1")
|
|
||||||
|
|
||||||
implementation("androidx.camera:camera-camera2:1.2.1")
|
val composeVersion = "1.4.0"
|
||||||
implementation("androidx.camera:camera-lifecycle:1.2.1")
|
implementation("androidx.compose.ui:ui:$composeVersion")
|
||||||
implementation("androidx.camera:camera-view:1.2.1")
|
implementation("androidx.compose.ui:ui-tooling-preview:$composeVersion")
|
||||||
|
implementation("androidx.compose.material:material-icons-extended:$composeVersion")
|
||||||
|
debugImplementation("androidx.compose.ui:ui-tooling:$composeVersion")
|
||||||
|
debugImplementation("androidx.compose.ui:ui-test-manifest:$composeVersion")
|
||||||
|
|
||||||
|
val cameraVersion = "1.2.2"
|
||||||
|
implementation("androidx.camera:camera-camera2:$cameraVersion")
|
||||||
|
implementation("androidx.camera:camera-lifecycle:$cameraVersion")
|
||||||
|
implementation("androidx.camera:camera-view:$cameraVersion")
|
||||||
|
|
||||||
implementation("com.google.zxing:core:3.3.3")
|
implementation("com.google.zxing:core:3.3.3")
|
||||||
|
|
||||||
debugImplementation("androidx.compose.ui:ui-tooling:1.3.3")
|
|
||||||
debugImplementation("androidx.compose.ui:ui-test-manifest:1.3.3")
|
|
||||||
}
|
}
|
|
@ -1,231 +1,89 @@
|
||||||
@file:OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
|
|
||||||
package com.henryhiles.qscan
|
package com.henryhiles.qscan
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.pm.PackageManager
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.webkit.URLUtil
|
import android.webkit.URLUtil
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.camera.core.CameraSelector
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.camera.core.ImageAnalysis
|
import androidx.compose.material3.Surface
|
||||||
import androidx.camera.core.ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST
|
|
||||||
import androidx.camera.lifecycle.ProcessCameraProvider
|
|
||||||
import androidx.camera.view.PreviewView
|
|
||||||
import androidx.compose.foundation.layout.*
|
|
||||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
|
||||||
import androidx.compose.material3.*
|
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Alignment.Companion.CenterHorizontally
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
|
||||||
import androidx.compose.ui.platform.LocalUriHandler
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
import androidx.compose.ui.unit.dp
|
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
import com.google.accompanist.permissions.isGranted
|
||||||
import androidx.core.content.ContextCompat
|
import com.google.accompanist.permissions.rememberPermissionState
|
||||||
|
import com.google.accompanist.permissions.shouldShowRationale
|
||||||
|
import com.henryhiles.qscan.components.Camera
|
||||||
|
import com.henryhiles.qscan.components.alerts.PermissionAlert
|
||||||
|
import com.henryhiles.qscan.components.alerts.ScannedAlert
|
||||||
import com.henryhiles.qscan.ui.theme.QScanTheme
|
import com.henryhiles.qscan.ui.theme.QScanTheme
|
||||||
|
import com.henryhiles.qscan.utils.Helpers.isURL
|
||||||
|
|
||||||
|
const val autoOpenKey = "AUTO_OPEN"
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
|
@OptIn(ExperimentalPermissionsApi::class)
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContent {
|
setContent {
|
||||||
QScanTheme {
|
QScanTheme {
|
||||||
// A surface container using the 'background' color from the theme
|
|
||||||
Surface(
|
Surface(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize()
|
||||||
color = MaterialTheme.colorScheme.background
|
|
||||||
) {
|
) {
|
||||||
Screen()
|
var code by remember { mutableStateOf("") }
|
||||||
}
|
val context = LocalContext.current
|
||||||
}
|
val activity = context as Activity
|
||||||
}
|
val sharedPref = activity.getPreferences(Context.MODE_PRIVATE)
|
||||||
}
|
var doNotAsk by remember {
|
||||||
}
|
mutableStateOf(
|
||||||
|
sharedPref.getBoolean(
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
autoOpenKey,
|
||||||
@Composable
|
false
|
||||||
fun Screen() {
|
|
||||||
var code by remember { mutableStateOf("") }
|
|
||||||
|
|
||||||
val context = LocalContext.current
|
|
||||||
val lifeCycleOwner = LocalLifecycleOwner.current
|
|
||||||
val activity = lifeCycleOwner as Activity
|
|
||||||
val sharedPref = activity.getPreferences(Context.MODE_PRIVATE)
|
|
||||||
var doNotAsk by remember {
|
|
||||||
mutableStateOf(
|
|
||||||
sharedPref.getBoolean(
|
|
||||||
R.string.should_auto_open_key.toString(),
|
|
||||||
false
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
var prompted by remember {
|
|
||||||
mutableStateOf(false)
|
|
||||||
}
|
|
||||||
val uriHandler = LocalUriHandler.current
|
|
||||||
val cameraProviderFuture = remember {
|
|
||||||
ProcessCameraProvider.getInstance(context)
|
|
||||||
}
|
|
||||||
var hasCamPermission by remember {
|
|
||||||
mutableStateOf(
|
|
||||||
ContextCompat.checkSelfPermission(
|
|
||||||
context,
|
|
||||||
Manifest.permission.CAMERA
|
|
||||||
) == PackageManager.PERMISSION_GRANTED
|
|
||||||
)
|
|
||||||
}
|
|
||||||
val launcher = rememberLauncherForActivityResult(
|
|
||||||
contract = ActivityResultContracts.RequestPermission(),
|
|
||||||
onResult = { granted ->
|
|
||||||
hasCamPermission = granted
|
|
||||||
prompted = true
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
LaunchedEffect(key1 = true) {
|
|
||||||
launcher.launch(Manifest.permission.CAMERA)
|
|
||||||
}
|
|
||||||
|
|
||||||
LaunchedEffect(key1 = code) {
|
|
||||||
if (doNotAsk && URLUtil.isValidUrl(code)) {
|
|
||||||
uriHandler.openUri(code)
|
|
||||||
code = ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LaunchedEffect(key1 = doNotAsk) {
|
|
||||||
with(sharedPref.edit()) {
|
|
||||||
putBoolean(R.string.should_auto_open_key.toString(), doNotAsk)
|
|
||||||
apply()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(modifier = Modifier.fillMaxSize()) {
|
|
||||||
if (hasCamPermission) {
|
|
||||||
AndroidView(
|
|
||||||
factory = { context ->
|
|
||||||
val previewView = PreviewView(context)
|
|
||||||
val preview = androidx.camera.core.Preview.Builder().build()
|
|
||||||
val selector =
|
|
||||||
CameraSelector.Builder()
|
|
||||||
.requireLensFacing(CameraSelector.LENS_FACING_BACK)
|
|
||||||
.build()
|
|
||||||
preview.setSurfaceProvider(previewView.surfaceProvider)
|
|
||||||
val imageAnalysis = ImageAnalysis.Builder()
|
|
||||||
.setBackpressureStrategy(STRATEGY_KEEP_ONLY_LATEST)
|
|
||||||
.build()
|
|
||||||
imageAnalysis.setAnalyzer(
|
|
||||||
ContextCompat.getMainExecutor(context),
|
|
||||||
QrCodeAnalyzer { result -> code = result })
|
|
||||||
|
|
||||||
try {
|
|
||||||
cameraProviderFuture.get()
|
|
||||||
.bindToLifecycle(lifeCycleOwner, selector, preview, imageAnalysis)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
previewView
|
|
||||||
}, modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
)
|
|
||||||
if (code != "" && !doNotAsk) {
|
|
||||||
val isURL = URLUtil.isValidUrl(code)
|
|
||||||
|
|
||||||
var tempDoNotAsk by remember { mutableStateOf(false) }
|
|
||||||
|
|
||||||
AlertDialog(onDismissRequest = { code = "" }) {
|
|
||||||
Surface(
|
|
||||||
shape = MaterialTheme.shapes.large
|
|
||||||
) {
|
|
||||||
Column(modifier = Modifier.padding(16.dp)) {
|
|
||||||
Text(
|
|
||||||
text = "QR Code Scanned",
|
|
||||||
style = MaterialTheme.typography.headlineSmall,
|
|
||||||
modifier = Modifier.align(CenterHorizontally)
|
|
||||||
)
|
)
|
||||||
SelectionContainer {
|
|
||||||
Text(
|
|
||||||
text = if (isURL) "This QR code will take you to $code, are you sure you want to go there?" else "The content of that QR Code is \"$code\"",
|
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
|
||||||
modifier = Modifier.padding(16.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
|
||||||
LabelledCheckBox(
|
|
||||||
checked = tempDoNotAsk,
|
|
||||||
onCheckedChange = { tempDoNotAsk = it },
|
|
||||||
label = "Don't ask again"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.End
|
|
||||||
) {
|
|
||||||
TextButton(onClick = { code = "" }) {
|
|
||||||
Text(
|
|
||||||
text = "Cancel",
|
|
||||||
style = MaterialTheme.typography.labelLarge
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (isURL)
|
|
||||||
TextButton(onClick = {
|
|
||||||
uriHandler.openUri(code)
|
|
||||||
doNotAsk = tempDoNotAsk
|
|
||||||
code = ""
|
|
||||||
}) {
|
|
||||||
Text(
|
|
||||||
text = "Open URL",
|
|
||||||
style = MaterialTheme.typography.labelLarge
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (prompted) AlertDialog(onDismissRequest = {}) {
|
|
||||||
Surface(
|
|
||||||
shape = MaterialTheme.shapes.large
|
|
||||||
) {
|
|
||||||
Column(modifier = Modifier.padding(16.dp)) {
|
|
||||||
Text(
|
|
||||||
text = "No camera permission",
|
|
||||||
style = MaterialTheme.typography.headlineSmall,
|
|
||||||
modifier = Modifier.align(CenterHorizontally)
|
|
||||||
)
|
|
||||||
SelectionContainer {
|
|
||||||
Text(
|
|
||||||
text = "Camera permission is needed for this app to function.",
|
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
|
||||||
modifier = Modifier.padding(16.dp)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
val uriHandler = LocalUriHandler.current
|
||||||
|
val cameraPermissionState = rememberPermissionState(
|
||||||
|
Manifest.permission.CAMERA
|
||||||
|
)
|
||||||
|
|
||||||
Row(
|
LaunchedEffect(key1 = code) {
|
||||||
modifier = Modifier.fillMaxWidth(),
|
if (doNotAsk && URLUtil.isValidUrl(code)) {
|
||||||
horizontalArrangement = Arrangement.End
|
uriHandler.openUri(code)
|
||||||
) {
|
code = ""
|
||||||
TextButton(onClick = {
|
}
|
||||||
launcher.launch(Manifest.permission.CAMERA)
|
}
|
||||||
}) {
|
|
||||||
Text(
|
LaunchedEffect(key1 = doNotAsk) {
|
||||||
text = "Grant Permission",
|
with(sharedPref.edit()) {
|
||||||
style = MaterialTheme.typography.labelLarge
|
putBoolean(autoOpenKey, doNotAsk)
|
||||||
)
|
apply()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(modifier = Modifier.fillMaxSize()) {
|
||||||
|
if (cameraPermissionState.status.isGranted) {
|
||||||
|
Camera(onScan = { code = it })
|
||||||
|
if (code != "" && !(doNotAsk && isURL(code)))
|
||||||
|
ScannedAlert(
|
||||||
|
onDismiss = { code = "" },
|
||||||
|
code = code
|
||||||
|
) { doNotAsk = it }
|
||||||
|
} else PermissionAlert(
|
||||||
|
textToShow = if (cameraPermissionState.status.shouldShowRationale) "The camera is important for this app. Please grant the permission."
|
||||||
|
else "Camera permission is required for this feature to be available. Please grant the permission.",
|
||||||
|
permissionName = "camera"
|
||||||
|
) {
|
||||||
|
cameraPermissionState.launchPermissionRequest()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
53
app/src/main/java/com/henryhiles/qscan/components/Camera.kt
Normal file
53
app/src/main/java/com/henryhiles/qscan/components/Camera.kt
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
package com.henryhiles.qscan.components
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.camera.core.CameraSelector
|
||||||
|
import androidx.camera.core.ImageAnalysis
|
||||||
|
import androidx.camera.core.Preview
|
||||||
|
import androidx.camera.lifecycle.ProcessCameraProvider
|
||||||
|
import androidx.camera.view.PreviewView
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||||
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import com.henryhiles.qscan.QrCodeAnalyzer
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Camera(onScan: (result: String) -> Unit) {
|
||||||
|
val lifeCycleOwner = LocalLifecycleOwner.current
|
||||||
|
val cameraProviderFuture = remember {
|
||||||
|
ProcessCameraProvider.getInstance(lifeCycleOwner as Context)
|
||||||
|
}
|
||||||
|
|
||||||
|
AndroidView(
|
||||||
|
factory = { context ->
|
||||||
|
|
||||||
|
val previewView = PreviewView(context)
|
||||||
|
val preview = Preview.Builder().build()
|
||||||
|
val selector =
|
||||||
|
CameraSelector.Builder()
|
||||||
|
.requireLensFacing(CameraSelector.LENS_FACING_BACK)
|
||||||
|
.build()
|
||||||
|
preview.setSurfaceProvider(previewView.surfaceProvider)
|
||||||
|
val imageAnalysis = ImageAnalysis.Builder()
|
||||||
|
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
|
||||||
|
.build()
|
||||||
|
imageAnalysis.setAnalyzer(
|
||||||
|
ContextCompat.getMainExecutor(context),
|
||||||
|
QrCodeAnalyzer(onScan)
|
||||||
|
)
|
||||||
|
|
||||||
|
try {
|
||||||
|
cameraProviderFuture.get()
|
||||||
|
.bindToLifecycle(lifeCycleOwner, selector, preview, imageAnalysis)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
previewView
|
||||||
|
}, modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
package com.henryhiles.qscan.components.alerts
|
||||||
|
|
||||||
|
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun PermissionAlert(textToShow: String, permissionName: String, onGrantRequest: () -> Unit) {
|
||||||
|
AlertDialog(
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
text = "${permissionName.replaceFirstChar { it.uppercaseChar() }} permission required",
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onDismissRequest = {},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = onGrantRequest) {
|
||||||
|
Text(
|
||||||
|
text = "Grant Permission",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
SelectionContainer {
|
||||||
|
Text(
|
||||||
|
text = textToShow,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//Surface(
|
||||||
|
//shape = MaterialTheme.shapes.large
|
||||||
|
//) {
|
||||||
|
// Column(modifier = Modifier.padding(16.dp)) {
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// Row(
|
||||||
|
// modifier = Modifier.fillMaxWidth(),
|
||||||
|
// horizontalArrangement = Arrangement.End
|
||||||
|
// ) {
|
||||||
|
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//}
|
|
@ -0,0 +1,65 @@
|
||||||
|
package com.henryhiles.qscan.components.alerts
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.henryhiles.qscan.LabelledCheckBox
|
||||||
|
import com.henryhiles.qscan.utils.Helpers.isURL
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ScannedAlert(onDismiss: () -> Unit, code: String, onChangeDoNotAsk: (Boolean) -> Unit) {
|
||||||
|
val uriHandler = LocalUriHandler.current
|
||||||
|
var tempDoNotAsk by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
AlertDialog(
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
text = "QR Code Scanned",
|
||||||
|
)
|
||||||
|
}, onDismissRequest = onDismiss, dismissButton = {
|
||||||
|
TextButton(onClick = onDismiss) {
|
||||||
|
Text(
|
||||||
|
text = "Cancel",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Column {
|
||||||
|
SelectionContainer {
|
||||||
|
Text(
|
||||||
|
text = if (isURL(code)) "This QR code will take you to $code, are you sure you want to go there?" else "The content of that QR Code is \"$code\"",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
LabelledCheckBox(
|
||||||
|
checked = tempDoNotAsk,
|
||||||
|
onCheckedChange = { tempDoNotAsk = it },
|
||||||
|
label = "Don't ask again"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
if (isURL(code))
|
||||||
|
TextButton(onClick = {
|
||||||
|
uriHandler.openUri(code)
|
||||||
|
onChangeDoNotAsk(tempDoNotAsk)
|
||||||
|
onDismiss()
|
||||||
|
}) {
|
||||||
|
Text(
|
||||||
|
text = "Open URL",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
9
app/src/main/java/com/henryhiles/qscan/utils/helpers.kt
Normal file
9
app/src/main/java/com/henryhiles/qscan/utils/helpers.kt
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
package com.henryhiles.qscan.utils
|
||||||
|
|
||||||
|
import android.webkit.URLUtil
|
||||||
|
|
||||||
|
object Helpers {
|
||||||
|
fun isURL(value: String): Boolean {
|
||||||
|
return URLUtil.isValidUrl(value)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,3 @@
|
||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">QScan</string>
|
<string name="app_name">QScan</string>
|
||||||
<string name="should_auto_open_key">AUTO_OPEN</string>
|
|
||||||
</resources>
|
</resources>
|
Reference in a new issue