From 2e88364b39159e73af036eb901a51dcc69a09c4f Mon Sep 17 00:00:00 2001 From: Mircea Bucerzan Date: Thu, 14 Aug 2025 12:50:26 +0300 Subject: [PATCH 01/11] Update webapp compile and target SDK version to 34 Related to YD-6940 --- webapp/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webapp/build.gradle b/webapp/build.gradle index 2a02abe..44e4e3d 100644 --- a/webapp/build.gradle +++ b/webapp/build.gradle @@ -4,12 +4,12 @@ apply plugin: 'kotlin-android' android { namespace "com.yoti.mobile.android.sdk.yotidocscan.websample" - compileSdk 33 + compileSdk 34 defaultConfig { applicationId "com.yoti.mobile.android.sdk.yotidocscan.websample" minSdkVersion 21 - targetSdkVersion 33 + targetSdkVersion 34 versionCode 1 versionName "1.0" From 523a09f4af972d72b637b63fd623df0d02e5cb42 Mon Sep 17 00:00:00 2001 From: Mircea Bucerzan Date: Thu, 14 Aug 2025 17:49:00 +0300 Subject: [PATCH 02/11] Remove deprecated storage permission requests The permissions READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE are legacy permissions that grant broad, unrestricted access to the shared external storage volume. For privacy and security reasons, Google has been systematically deprecating and restricting them. After testing, it seems safe to remove them, they no longer appear to be required. Related to YD-6940 --- webapp/src/main/AndroidManifest.xml | 2 -- .../android/sdk/yotidocscan/websample/MainActivity.kt | 7 +------ 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/webapp/src/main/AndroidManifest.xml b/webapp/src/main/AndroidManifest.xml index e41b2ec..4a7a81d 100644 --- a/webapp/src/main/AndroidManifest.xml +++ b/webapp/src/main/AndroidManifest.xml @@ -3,8 +3,6 @@ - - diff --git a/webapp/src/main/java/com/yoti/mobile/android/sdk/yotidocscan/websample/MainActivity.kt b/webapp/src/main/java/com/yoti/mobile/android/sdk/yotidocscan/websample/MainActivity.kt index 088924a..637fcc5 100644 --- a/webapp/src/main/java/com/yoti/mobile/android/sdk/yotidocscan/websample/MainActivity.kt +++ b/webapp/src/main/java/com/yoti/mobile/android/sdk/yotidocscan/websample/MainActivity.kt @@ -161,12 +161,7 @@ class MainActivity : AppCompatActivity(), SessionConfigurationListener { } private fun requestPermissions() { - val permissions = listOf( - permission.CAMERA, - permission.RECORD_AUDIO, - permission.READ_EXTERNAL_STORAGE, - permission.WRITE_EXTERNAL_STORAGE - ) + val permissions = listOf(permission.CAMERA, permission.RECORD_AUDIO) val permissionsRequest = permissions.mapNotNull { permission -> if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) { From 80437c018f7201a5c73ac68a929f86edcdd1f431 Mon Sep 17 00:00:00 2001 From: Mircea Bucerzan Date: Fri, 15 Aug 2025 12:15:11 +0300 Subject: [PATCH 03/11] Implement Composables for URL input and WebView screens Related to YD-6940 --- webapp/build.gradle | 8 ++ .../sdk/yotidocscan/websample/MainScreen.kt | 82 +++++++++++++++++ .../sdk/yotidocscan/websample/WebScreen.kt | 92 +++++++++++++++++++ .../sdk/yotidocscan/websample/ui/Theme.kt | 13 +++ webapp/src/main/res/values/strings.xml | 2 + 5 files changed, 197 insertions(+) create mode 100644 webapp/src/main/java/com/yoti/mobile/android/sdk/yotidocscan/websample/MainScreen.kt create mode 100644 webapp/src/main/java/com/yoti/mobile/android/sdk/yotidocscan/websample/WebScreen.kt create mode 100644 webapp/src/main/java/com/yoti/mobile/android/sdk/yotidocscan/websample/ui/Theme.kt diff --git a/webapp/build.gradle b/webapp/build.gradle index 44e4e3d..685b0e5 100644 --- a/webapp/build.gradle +++ b/webapp/build.gradle @@ -1,5 +1,6 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' +apply plugin: 'org.jetbrains.kotlin.plugin.compose' android { namespace "com.yoti.mobile.android.sdk.yotidocscan.websample" @@ -24,6 +25,7 @@ android { buildFeatures { viewBinding true buildConfig true + compose true } buildTypes { @@ -46,4 +48,10 @@ dependencies { // Multi-module projects: Add this dependency because of Android Studio Issue // and androix.navigation dependencies management https://issuetracker.google.com/issues/152245564 implementation 'androidx.navigation:navigation-ui-ktx:2.2.2' + + def composeBom = platform("androidx.compose:compose-bom:2025.04.00") + implementation composeBom + implementation 'androidx.compose.material3:material3' + implementation 'androidx.activity:activity-compose' + debugImplementation 'androidx.compose.ui:ui-tooling' } diff --git a/webapp/src/main/java/com/yoti/mobile/android/sdk/yotidocscan/websample/MainScreen.kt b/webapp/src/main/java/com/yoti/mobile/android/sdk/yotidocscan/websample/MainScreen.kt new file mode 100644 index 0000000..1a93f7c --- /dev/null +++ b/webapp/src/main/java/com/yoti/mobile/android/sdk/yotidocscan/websample/MainScreen.kt @@ -0,0 +1,82 @@ +package com.yoti.mobile.android.sdk.yotidocscan.websample + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.Button +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.yoti.mobile.android.sdk.yotidocscan.websample.ui.YotiDocScanWebSampleAppTheme + +@Composable +fun MainScreen( + sessionUrl: String, + onSessionUrlChanged: (String) -> Unit, + onStartSessionClicked: () -> Unit, + modifier: Modifier = Modifier +) { + Column( + modifier = modifier + .fillMaxSize() + .padding(16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + OutlinedTextField( + value = sessionUrl, + onValueChange = onSessionUrlChanged, + label = { + Text(text = stringResource(id = R.string.session_url_hint)) + }, + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Text, + imeAction = ImeAction.Done + ), + modifier = Modifier.fillMaxWidth() + ) + Spacer(Modifier.size(16.dp)) + Button( + onClick = onStartSessionClicked, + enabled = sessionUrl.isNotBlank() + ) { + Text(text = stringResource(id = R.string.start_session_button)) + } + } +} + +@Preview(showBackground = true) +@Composable +fun PreviewEmptyMainScreen() { + YotiDocScanWebSampleAppTheme { + MainScreen( + sessionUrl = "", + onSessionUrlChanged = {}, + onStartSessionClicked = {} + ) + } +} + +@Preview(showBackground = true) +@Composable +fun PreviewMainScreen() { + YotiDocScanWebSampleAppTheme { + MainScreen( + sessionUrl = "https://example.com/session", + onSessionUrlChanged = {}, + onStartSessionClicked = {} + ) + } +} \ No newline at end of file diff --git a/webapp/src/main/java/com/yoti/mobile/android/sdk/yotidocscan/websample/WebScreen.kt b/webapp/src/main/java/com/yoti/mobile/android/sdk/yotidocscan/websample/WebScreen.kt new file mode 100644 index 0000000..72f81c0 --- /dev/null +++ b/webapp/src/main/java/com/yoti/mobile/android/sdk/yotidocscan/websample/WebScreen.kt @@ -0,0 +1,92 @@ +package com.yoti.mobile.android.sdk.yotidocscan.websample + +import android.annotation.SuppressLint +import android.net.Uri +import android.webkit.PermissionRequest +import android.webkit.ValueCallback +import android.webkit.WebChromeClient +import android.webkit.WebChromeClient.FileChooserParams +import android.webkit.WebSettings +import android.webkit.WebView +import android.webkit.WebViewClient +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.viewinterop.AndroidView + +@Composable +fun WebScreen( + sessionUrl: String, + onPageCommitVisible: (String?) -> Unit, + onFilePathCallbackReady: (ValueCallback>?) -> Unit, + onShowCameraAndFilePickerChooser: (FileChooserParams) -> Unit, + modifier: Modifier = Modifier +) { + AndroidView( + modifier = modifier.fillMaxSize(), + factory = { context -> + WebView(context).apply { + configureSettings(settings) + configureWebViewClient(this, onPageCommitVisible) + configureWebChromeClient( + this, + onFilePathCallbackReady, + onShowCameraAndFilePickerChooser + ) + } + }, + update = { webView -> + sessionUrl.takeIf { it.isNotBlank() }?.let { webView.loadUrl(it) } + } + ) +} + +@SuppressLint("SetJavaScriptEnabled") +private fun configureSettings(settings: WebSettings) { + with(settings) { + javaScriptEnabled = true + allowFileAccess = true + allowUniversalAccessFromFileURLs = true + domStorageEnabled = true + javaScriptCanOpenWindowsAutomatically = true + mediaPlaybackRequiresUserGesture = false + } +} + +private fun configureWebViewClient(webView: WebView, onPageCommitVisible: (String?) -> Unit) { + webView.webViewClient = object : WebViewClient() { + override fun onPageCommitVisible(view: WebView?, url: String?) { + super.onPageCommitVisible(view, url) + onPageCommitVisible(url) + } + } +} + +private fun configureWebChromeClient( + webView: WebView, + onFilePathCallbackReady: (ValueCallback>?) -> Unit, + onShowCameraAndFilePickerChooser: (FileChooserParams) -> Unit +) { + webView.webChromeClient = object : WebChromeClient() { + // Launch file picker or camera intent and set the + // filePathCallback to set the capture results to the webview + override fun onShowFileChooser( + webView: WebView?, + filePathCallback: ValueCallback>?, + fileChooserParams: FileChooserParams? + ): Boolean { + onFilePathCallbackReady(filePathCallback) + return if (fileChooserParams?.mode == FileChooserParams.MODE_OPEN) { + onShowCameraAndFilePickerChooser(fileChooserParams) + true + } else { + false + } + } + + // Grant permission requested + override fun onPermissionRequest(request: PermissionRequest?) { + request?.grant(request.resources) + } + } +} \ No newline at end of file diff --git a/webapp/src/main/java/com/yoti/mobile/android/sdk/yotidocscan/websample/ui/Theme.kt b/webapp/src/main/java/com/yoti/mobile/android/sdk/yotidocscan/websample/ui/Theme.kt new file mode 100644 index 0000000..7e695b2 --- /dev/null +++ b/webapp/src/main/java/com/yoti/mobile/android/sdk/yotidocscan/websample/ui/Theme.kt @@ -0,0 +1,13 @@ +package com.yoti.mobile.android.sdk.yotidocscan.websample.ui + +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.runtime.Composable + +@Composable +fun YotiDocScanWebSampleAppTheme(content: @Composable () -> Unit) { + MaterialTheme( + colorScheme = lightColorScheme(), + content = content + ) +} \ No newline at end of file diff --git a/webapp/src/main/res/values/strings.xml b/webapp/src/main/res/values/strings.xml index 5068b9c..dd3f632 100644 --- a/webapp/src/main/res/values/strings.xml +++ b/webapp/src/main/res/values/strings.xml @@ -1,3 +1,5 @@ webapp + Session URL + Start session From 22cf789a5faab606a363adfd078fc15fe282ba2f Mon Sep 17 00:00:00 2001 From: Mircea Bucerzan Date: Mon, 18 Aug 2025 17:09:00 +0300 Subject: [PATCH 04/11] Integrate new Compose UI Instead of the shake-toggled bottom sheet, show the main screen composable directly. After URL input, use navigation-compose to navigate to the web screen composable. Close-session and session-finish alert dialogs have also been migrated to Compose. Related to YD-6940 --- webapp/build.gradle | 2 + .../yotidocscan/websample/AppDestinations.kt | 7 + .../sdk/yotidocscan/websample/MainActivity.kt | 190 ++++++------------ .../sdk/yotidocscan/websample/WebScreen.kt | 93 +++++++-- webapp/src/main/res/values/strings.xml | 7 + 5 files changed, 156 insertions(+), 143 deletions(-) create mode 100644 webapp/src/main/java/com/yoti/mobile/android/sdk/yotidocscan/websample/AppDestinations.kt diff --git a/webapp/build.gradle b/webapp/build.gradle index 685b0e5..6caeb24 100644 --- a/webapp/build.gradle +++ b/webapp/build.gradle @@ -54,4 +54,6 @@ dependencies { implementation 'androidx.compose.material3:material3' implementation 'androidx.activity:activity-compose' debugImplementation 'androidx.compose.ui:ui-tooling' + + implementation 'androidx.navigation:navigation-compose:2.8.9' } diff --git a/webapp/src/main/java/com/yoti/mobile/android/sdk/yotidocscan/websample/AppDestinations.kt b/webapp/src/main/java/com/yoti/mobile/android/sdk/yotidocscan/websample/AppDestinations.kt new file mode 100644 index 0000000..5f02fad --- /dev/null +++ b/webapp/src/main/java/com/yoti/mobile/android/sdk/yotidocscan/websample/AppDestinations.kt @@ -0,0 +1,7 @@ +package com.yoti.mobile.android.sdk.yotidocscan.websample + +object AppDestinations { + + const val MAIN_SCREEN = "main_screen" + const val WEB_SCREEN = "web_screen" +} \ No newline at end of file diff --git a/webapp/src/main/java/com/yoti/mobile/android/sdk/yotidocscan/websample/MainActivity.kt b/webapp/src/main/java/com/yoti/mobile/android/sdk/yotidocscan/websample/MainActivity.kt index 637fcc5..ac18bdd 100644 --- a/webapp/src/main/java/com/yoti/mobile/android/sdk/yotidocscan/websample/MainActivity.kt +++ b/webapp/src/main/java/com/yoti/mobile/android/sdk/yotidocscan/websample/MainActivity.kt @@ -1,7 +1,6 @@ package com.yoti.mobile.android.sdk.yotidocscan.websample import android.Manifest.permission -import android.annotation.SuppressLint import android.app.Activity import android.content.Intent import android.content.pm.PackageManager @@ -9,21 +8,25 @@ import android.net.Uri import android.os.Bundle import android.os.Environment import android.provider.MediaStore -import android.view.View.VISIBLE -import android.webkit.PermissionRequest import android.webkit.ValueCallback -import android.webkit.WebChromeClient import android.webkit.WebChromeClient.FileChooserParams -import android.webkit.WebView -import android.webkit.WebViewClient +import androidx.activity.compose.setContent import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Scaffold +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import androidx.core.content.FileProvider -import com.yoti.mobile.android.sdk.yotidocscan.websample.AccelerometerListener.ShakeListener -import com.yoti.mobile.android.sdk.yotidocscan.websample.SessionBottomSheet.SessionConfigurationListener -import com.yoti.mobile.android.sdk.yotidocscan.websample.databinding.ActivityMainBinding +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import com.yoti.mobile.android.sdk.yotidocscan.websample.ui.YotiDocScanWebSampleAppTheme import java.io.File import java.text.SimpleDateFormat import java.util.Date @@ -69,18 +72,11 @@ private const val PERMISSIONS_REQUEST_CODE = 1114 private const val KEY_IS_VIEW_RECREATED = "MainActivity.KEY_IS_VIEW_RECREATED" private const val FINISH_SESSION_URL = "https://www.yoti.com/" -class MainActivity : AppCompatActivity(), SessionConfigurationListener { - - private var sessionBottomSheet: SessionBottomSheet? = null +class MainActivity : AppCompatActivity() { private lateinit var cameraCaptureFileUri: Uri private var filePathCallback: ValueCallback>? = null private var isViewRecreated: Boolean = false - private val shakeListener = AccelerometerListener(this, object: ShakeListener { - override fun onShake() { - showOptionsDialog() - } - }) private val mimeTypeMap = mapOf( ".pdf" to "application/pdf", @@ -88,19 +84,58 @@ class MainActivity : AppCompatActivity(), SessionConfigurationListener { ".jpg" to "image/jpeg" ) - private lateinit var binding: ActivityMainBinding - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - binding = ActivityMainBinding.inflate(layoutInflater) - setContentView(binding.root) - - isViewRecreated = savedInstanceState?.getBoolean(KEY_IS_VIEW_RECREATED) ?: false - requestPermissions() + isViewRecreated = savedInstanceState?.getBoolean(KEY_IS_VIEW_RECREATED) ?: false - binding.webview.configureForYdsWeb() + setContent { + val navController = rememberNavController() + var sessionUrl by remember { mutableStateOf("") } + var showSessionFinishedDialog by remember { mutableStateOf(false) } + + YotiDocScanWebSampleAppTheme { + Scaffold { innerPadding -> + NavHost( + navController = navController, + startDestination = AppDestinations.MAIN_SCREEN, + modifier = Modifier.padding(innerPadding) + ) { + composable(route = AppDestinations.MAIN_SCREEN) { + MainScreen( + sessionUrl = sessionUrl, + onSessionUrlChanged = { sessionUrl = it }, + onStartSessionClicked = { + navController.navigate(AppDestinations.WEB_SCREEN) + } + ) + } + + composable(route = AppDestinations.WEB_SCREEN) { + WebScreen( + sessionUrl = sessionUrl, + showSessionFinishedDialog = showSessionFinishedDialog, + onPageCommitVisible = { url -> + // Detect the URL that indicates that flow is finished and close the app + if (url == FINISH_SESSION_URL) { + showSessionFinishedDialog = true + } + }, + onFilePathCallbackReady = { callback -> + filePathCallback = callback + }, + onShowCameraAndFilePickerChooser = { fileChooserParams -> + showCameraAndFilePickerChooser(fileChooserParams) + }, + onCloseSession = { navController.popBackStack() }, + onSessionFinished = { finish() } + ) + } + } + } + } + } } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { @@ -114,22 +149,6 @@ class MainActivity : AppCompatActivity(), SessionConfigurationListener { filePathCallback?.onReceiveValue(null) } } - - } - - override fun onResume() { - super.onResume() - shakeListener.start() - } - - override fun onPause() { - shakeListener.stop() - super.onPause() - } - - override fun onDestroy() { - binding.webview.destroy() - super.onDestroy() } override fun onRequestPermissionsResult( @@ -146,15 +165,6 @@ class MainActivity : AppCompatActivity(), SessionConfigurationListener { } } - override fun onBackPressed() { - AlertDialog.Builder(this) - .setTitle("Close YDS Session") - .setMessage("Are you sure you want to finish YDS session?") - .setPositiveButton("Yes") { _, _ -> super.onBackPressed() } - .setNegativeButton("No", null) - .show() - } - override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) outState.putBoolean(KEY_IS_VIEW_RECREATED, true) @@ -178,26 +188,8 @@ class MainActivity : AppCompatActivity(), SessionConfigurationListener { } } - @SuppressLint("SetJavaScriptEnabled") - private fun WebView.configureForYdsWeb() { - WebView.setWebContentsDebuggingEnabled(BuildConfig.DEBUG) - - this.settings.apply { - javaScriptEnabled = true - allowFileAccess = true - allowUniversalAccessFromFileURLs = true - domStorageEnabled = true - javaScriptCanOpenWindowsAutomatically = true - mediaPlaybackRequiresUserGesture = false - } - this.webViewClient = YdsWebClient() - this.webChromeClient = YdsWebChromeClient() - } - private fun showCameraAndFilePickerChooser(fileChooserParams: FileChooserParams) { - cameraCaptureFileUri = createFileUri() - Intent.createChooser(createFilePickerIntent(fileChooserParams), fileChooserParams.title) .run { putExtra( @@ -241,64 +233,4 @@ class MainActivity : AppCompatActivity(), SessionConfigurationListener { File.createTempFile(imageFileName, ".jpg", storageDir) ) } - - private inner class YdsWebChromeClient: WebChromeClient() { - // Launch file picker or camera intent and set the - // filePathCallback to set the capture results to the webview - override fun onShowFileChooser( - webView: WebView?, - filePathCallback: ValueCallback>?, - fileChooserParams: FileChooserParams? - ): Boolean { - this@MainActivity.filePathCallback = filePathCallback - - return if (fileChooserParams?.mode == FileChooserParams.MODE_OPEN) { - showCameraAndFilePickerChooser(fileChooserParams) - true - } else { - false - } - } - - // Grant permission requested - override fun onPermissionRequest(request: PermissionRequest?) { - request?.grant(request.resources) - } - } - - private inner class YdsWebClient : WebViewClient() { - // Detect the URL that indicates that YDS flow is finished - // and close the app - override fun onPageCommitVisible(view: WebView?, url: String?) { - super.onPageCommitVisible(view, url) - if (url == FINISH_SESSION_URL) { - AlertDialog.Builder(this@MainActivity) - .setTitle("YDS Session") - .setMessage("Session finished") - .setPositiveButton("OK") { _, _ -> this@MainActivity.finish() } - .show() - } - } - } - - private fun showOptionsDialog() { - if (sessionBottomSheet != null) return - - sessionBottomSheet = SessionBottomSheet.newInstance() - sessionBottomSheet?.show( - supportFragmentManager, - SessionBottomSheet.FRAGMENT_TAG - ) - } - - override fun onSessionConfigurationSuccess(sessionUrl: String) { - with(binding) { - webview.visibility = VISIBLE - webview.loadUrl(sessionUrl) - } - } - - override fun onSessionConfigurationDismiss() { - sessionBottomSheet = null - } -} +} \ No newline at end of file diff --git a/webapp/src/main/java/com/yoti/mobile/android/sdk/yotidocscan/websample/WebScreen.kt b/webapp/src/main/java/com/yoti/mobile/android/sdk/yotidocscan/websample/WebScreen.kt index 72f81c0..efd5737 100644 --- a/webapp/src/main/java/com/yoti/mobile/android/sdk/yotidocscan/websample/WebScreen.kt +++ b/webapp/src/main/java/com/yoti/mobile/android/sdk/yotidocscan/websample/WebScreen.kt @@ -9,38 +9,103 @@ import android.webkit.WebChromeClient.FileChooserParams import android.webkit.WebSettings import android.webkit.WebView import android.webkit.WebViewClient +import androidx.activity.compose.BackHandler +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource import androidx.compose.ui.viewinterop.AndroidView @Composable fun WebScreen( sessionUrl: String, + showSessionFinishedDialog: Boolean, onPageCommitVisible: (String?) -> Unit, onFilePathCallbackReady: (ValueCallback>?) -> Unit, onShowCameraAndFilePickerChooser: (FileChooserParams) -> Unit, + onCloseSession: () -> Unit, + onSessionFinished: () -> Unit, modifier: Modifier = Modifier ) { - AndroidView( - modifier = modifier.fillMaxSize(), - factory = { context -> - WebView(context).apply { - configureSettings(settings) - configureWebViewClient(this, onPageCommitVisible) - configureWebChromeClient( - this, - onFilePathCallbackReady, - onShowCameraAndFilePickerChooser - ) + Box(modifier = modifier.fillMaxSize()) { + AndroidView( + modifier = Modifier.fillMaxSize(), + factory = { context -> + WebView(context).apply { + WebView.setWebContentsDebuggingEnabled(BuildConfig.DEBUG) + configureSettings(settings) + configureWebViewClient(this, onPageCommitVisible) + configureWebChromeClient( + this, + onFilePathCallbackReady, + onShowCameraAndFilePickerChooser + ) + } + }, + update = { webView -> + sessionUrl.takeIf { it.isNotBlank() }?.let { webView.loadUrl(it) } + } + ) + + var showCloseSessionDialog by remember { mutableStateOf(false) } + BackHandler { showCloseSessionDialog = true } + if (showCloseSessionDialog) { + CloseSessionDialog( + onConfirm = { + showCloseSessionDialog = false + onCloseSession() + }, + onDismiss = { showCloseSessionDialog = false }) + } + + if (showSessionFinishedDialog) { + SessionFinishedDialog(onConfirm = { onSessionFinished() }) + } + } +} + +@Composable +private fun CloseSessionDialog(onConfirm: () -> Unit, onDismiss: () -> Unit) { + AlertDialog( + title = { Text(stringResource(id = R.string.close_session_dialog_title)) }, + text = { Text(stringResource(id = R.string.close_session_dialog_text)) }, + confirmButton = { + TextButton(onClick = onConfirm) { + Text(stringResource(id = R.string.close_session_dialog_confirm_button)) } }, - update = { webView -> - sessionUrl.takeIf { it.isNotBlank() }?.let { webView.loadUrl(it) } - } + dismissButton = { + TextButton(onClick = onDismiss) { + Text(stringResource(id = R.string.close_session_dialog_dismiss_button)) + } + }, + onDismissRequest = onDismiss + ) +} + +@Composable +private fun SessionFinishedDialog(onConfirm: () -> Unit) { + AlertDialog( + title = { Text(stringResource(id = R.string.session_finished_dialog_title)) }, + text = { Text(stringResource(id = R.string.session_finished_dialog_text)) }, + confirmButton = { + TextButton(onClick = onConfirm) { + Text(stringResource(id = R.string.session_finished_dialog_confirm_button)) + } + }, + onDismissRequest = {} ) } +@Suppress("DEPRECATION") @SuppressLint("SetJavaScriptEnabled") private fun configureSettings(settings: WebSettings) { with(settings) { diff --git a/webapp/src/main/res/values/strings.xml b/webapp/src/main/res/values/strings.xml index dd3f632..73140c6 100644 --- a/webapp/src/main/res/values/strings.xml +++ b/webapp/src/main/res/values/strings.xml @@ -2,4 +2,11 @@ webapp Session URL Start session + Close IDV Session + Are you sure you want to close the IDV session? + Yes + No + IDV Session + Session finished + Ok From f4c4f2600400070fc4c2efd9061a9f188656227d Mon Sep 17 00:00:00 2001 From: Mircea Bucerzan Date: Mon, 18 Aug 2025 18:51:16 +0300 Subject: [PATCH 05/11] Migrate permission requests to use ActivityResult API Also replace legacy AlertDialog with Composable equivalent. Related to YD-6940 --- .../sdk/yotidocscan/websample/MainActivity.kt | 59 ++++++------------- .../sdk/yotidocscan/websample/MainScreen.kt | 44 +++++++++++++- webapp/src/main/res/values/strings.xml | 7 ++- 3 files changed, 65 insertions(+), 45 deletions(-) diff --git a/webapp/src/main/java/com/yoti/mobile/android/sdk/yotidocscan/websample/MainActivity.kt b/webapp/src/main/java/com/yoti/mobile/android/sdk/yotidocscan/websample/MainActivity.kt index ac18bdd..a507775 100644 --- a/webapp/src/main/java/com/yoti/mobile/android/sdk/yotidocscan/websample/MainActivity.kt +++ b/webapp/src/main/java/com/yoti/mobile/android/sdk/yotidocscan/websample/MainActivity.kt @@ -3,25 +3,24 @@ package com.yoti.mobile.android.sdk.yotidocscan.websample import android.Manifest.permission import android.app.Activity import android.content.Intent -import android.content.pm.PackageManager import android.net.Uri import android.os.Bundle import android.os.Environment import android.provider.MediaStore import android.webkit.ValueCallback import android.webkit.WebChromeClient.FileChooserParams +import androidx.activity.ComponentActivity +import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.setContent -import androidx.appcompat.app.AlertDialog -import androidx.appcompat.app.AppCompatActivity +import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.layout.padding import androidx.compose.material3.Scaffold +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier -import androidx.core.app.ActivityCompat -import androidx.core.content.ContextCompat import androidx.core.content.FileProvider import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable @@ -67,12 +66,11 @@ import java.util.Locale * - Manage back navigation: users can press back hardware button and exit from the flow */ private const val CAPTURE_REQUEST_CODE = 1112 -private const val PERMISSIONS_REQUEST_CODE = 1114 private const val KEY_IS_VIEW_RECREATED = "MainActivity.KEY_IS_VIEW_RECREATED" private const val FINISH_SESSION_URL = "https://www.yoti.com/" -class MainActivity : AppCompatActivity() { +class MainActivity : ComponentActivity() { private lateinit var cameraCaptureFileUri: Uri private var filePathCallback: ValueCallback>? = null @@ -87,14 +85,23 @@ class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - requestPermissions() isViewRecreated = savedInstanceState?.getBoolean(KEY_IS_VIEW_RECREATED) ?: false setContent { val navController = rememberNavController() var sessionUrl by remember { mutableStateOf("") } + var showMissingPermissionsDialog by remember { mutableStateOf(false) } var showSessionFinishedDialog by remember { mutableStateOf(false) } + val permissionsLauncher = rememberLauncherForActivityResult( + contract = ActivityResultContracts.RequestMultiplePermissions() + ) { permissions -> + showMissingPermissionsDialog = !(permissions.values.all { it }) + } + LaunchedEffect(Unit) { + permissionsLauncher.launch(arrayOf(permission.CAMERA, permission.RECORD_AUDIO)) + } + YotiDocScanWebSampleAppTheme { Scaffold { innerPadding -> NavHost( @@ -105,10 +112,12 @@ class MainActivity : AppCompatActivity() { composable(route = AppDestinations.MAIN_SCREEN) { MainScreen( sessionUrl = sessionUrl, + showMissingPermissionsDialog = showMissingPermissionsDialog, onSessionUrlChanged = { sessionUrl = it }, onStartSessionClicked = { navController.navigate(AppDestinations.WEB_SCREEN) - } + }, + onMissingPermissionsConfirmed = { finish() } ) } @@ -151,43 +160,11 @@ class MainActivity : AppCompatActivity() { } } - override fun onRequestPermissionsResult( - requestCode: Int, permissions: Array, grantResults: IntArray - ) { - if (requestCode == PERMISSIONS_REQUEST_CODE) { - grantResults.firstOrNull { it != PackageManager.PERMISSION_GRANTED }?.let { - AlertDialog.Builder(this) - .setTitle("Permissions needed") - .setMessage("All permissions are needed to continue with the YDS session") - .setPositiveButton("OK") { _, _ -> super.finish() } - .show() - } - } - } - override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) outState.putBoolean(KEY_IS_VIEW_RECREATED, true) } - private fun requestPermissions() { - val permissions = listOf(permission.CAMERA, permission.RECORD_AUDIO) - - val permissionsRequest = permissions.mapNotNull { permission -> - if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) { - permission - } else null - }.toTypedArray() - - if (permissionsRequest.isNotEmpty()) { - ActivityCompat.requestPermissions( - this, - permissionsRequest, - PERMISSIONS_REQUEST_CODE - ) - } - } - private fun showCameraAndFilePickerChooser(fileChooserParams: FileChooserParams) { cameraCaptureFileUri = createFileUri() Intent.createChooser(createFilePickerIntent(fileChooserParams), fileChooserParams.title) diff --git a/webapp/src/main/java/com/yoti/mobile/android/sdk/yotidocscan/websample/MainScreen.kt b/webapp/src/main/java/com/yoti/mobile/android/sdk/yotidocscan/websample/MainScreen.kt index 1a93f7c..b9eb20c 100644 --- a/webapp/src/main/java/com/yoti/mobile/android/sdk/yotidocscan/websample/MainScreen.kt +++ b/webapp/src/main/java/com/yoti/mobile/android/sdk/yotidocscan/websample/MainScreen.kt @@ -8,9 +8,11 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text +import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -24,8 +26,10 @@ import com.yoti.mobile.android.sdk.yotidocscan.websample.ui.YotiDocScanWebSample @Composable fun MainScreen( sessionUrl: String, + showMissingPermissionsDialog: Boolean, onSessionUrlChanged: (String) -> Unit, onStartSessionClicked: () -> Unit, + onMissingPermissionsConfirmed: () -> Unit, modifier: Modifier = Modifier ) { Column( @@ -54,17 +58,37 @@ fun MainScreen( ) { Text(text = stringResource(id = R.string.start_session_button)) } + + if (showMissingPermissionsDialog) { + MissingPermissionsDialog { onMissingPermissionsConfirmed() } + } } } +@Composable +private fun MissingPermissionsDialog(onConfirm: () -> Unit) { + AlertDialog( + title = { Text(stringResource(id = R.string.missing_permissions_dialog_title)) }, + text = { Text(stringResource(id = R.string.missing_permissions_dialog_text)) }, + confirmButton = { + TextButton(onClick = onConfirm) { + Text(stringResource(id = R.string.missing_permissions_dialog_confirm_button)) + } + }, + onDismissRequest = {} + ) +} + @Preview(showBackground = true) @Composable fun PreviewEmptyMainScreen() { YotiDocScanWebSampleAppTheme { MainScreen( sessionUrl = "", + showMissingPermissionsDialog = false, onSessionUrlChanged = {}, - onStartSessionClicked = {} + onStartSessionClicked = {}, + onMissingPermissionsConfirmed = {} ) } } @@ -75,8 +99,24 @@ fun PreviewMainScreen() { YotiDocScanWebSampleAppTheme { MainScreen( sessionUrl = "https://example.com/session", + showMissingPermissionsDialog = false, + onSessionUrlChanged = {}, + onStartSessionClicked = {}, + onMissingPermissionsConfirmed = {} + ) + } +} + +@Preview(showBackground = true) +@Composable +fun PreviewMainScreenWithMissingPermissionsDialog() { + YotiDocScanWebSampleAppTheme { + MainScreen( + sessionUrl = "", + showMissingPermissionsDialog = true, onSessionUrlChanged = {}, - onStartSessionClicked = {} + onStartSessionClicked = {}, + onMissingPermissionsConfirmed = {} ) } } \ No newline at end of file diff --git a/webapp/src/main/res/values/strings.xml b/webapp/src/main/res/values/strings.xml index 73140c6..94a18af 100644 --- a/webapp/src/main/res/values/strings.xml +++ b/webapp/src/main/res/values/strings.xml @@ -8,5 +8,8 @@ No IDV Session Session finished - Ok - + OK + Permissions needed + All permissions are needed to continue with the IDV session + OK + \ No newline at end of file From fd9d33c29da848de4d879a180c5762091ba5cf90 Mon Sep 17 00:00:00 2001 From: Mircea Bucerzan Date: Tue, 19 Aug 2025 11:46:46 +0300 Subject: [PATCH 06/11] Migrate camera and file picker handling to use ActivityResult APIs Related to YD-6940 --- .../sdk/yotidocscan/websample/MainActivity.kt | 58 +++++++++---------- .../sdk/yotidocscan/websample/WebScreen.kt | 15 ++--- 2 files changed, 33 insertions(+), 40 deletions(-) diff --git a/webapp/src/main/java/com/yoti/mobile/android/sdk/yotidocscan/websample/MainActivity.kt b/webapp/src/main/java/com/yoti/mobile/android/sdk/yotidocscan/websample/MainActivity.kt index a507775..bd81169 100644 --- a/webapp/src/main/java/com/yoti/mobile/android/sdk/yotidocscan/websample/MainActivity.kt +++ b/webapp/src/main/java/com/yoti/mobile/android/sdk/yotidocscan/websample/MainActivity.kt @@ -1,7 +1,6 @@ package com.yoti.mobile.android.sdk.yotidocscan.websample import android.Manifest.permission -import android.app.Activity import android.content.Intent import android.net.Uri import android.os.Bundle @@ -12,6 +11,7 @@ import android.webkit.WebChromeClient.FileChooserParams import androidx.activity.ComponentActivity import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.setContent +import androidx.activity.result.ActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.layout.padding import androidx.compose.material3.Scaffold @@ -65,8 +65,6 @@ import java.util.Locale * - Use a NoActionBar theme * - Manage back navigation: users can press back hardware button and exit from the flow */ -private const val CAPTURE_REQUEST_CODE = 1112 - private const val KEY_IS_VIEW_RECREATED = "MainActivity.KEY_IS_VIEW_RECREATED" private const val FINISH_SESSION_URL = "https://www.yoti.com/" @@ -102,6 +100,10 @@ class MainActivity : ComponentActivity() { permissionsLauncher.launch(arrayOf(permission.CAMERA, permission.RECORD_AUDIO)) } + val cameraAndFilePickerChooserLauncher = rememberLauncherForActivityResult( + contract = ActivityResultContracts.StartActivityForResult() + ) { result -> handleCameraAndFilePickerChooserResult(result) } + YotiDocScanWebSampleAppTheme { Scaffold { innerPadding -> NavHost( @@ -131,11 +133,12 @@ class MainActivity : ComponentActivity() { showSessionFinishedDialog = true } }, - onFilePathCallbackReady = { callback -> + onShowCameraAndFilePickerChooser = { callback, fileChooserParams -> filePathCallback = callback - }, - onShowCameraAndFilePickerChooser = { fileChooserParams -> - showCameraAndFilePickerChooser(fileChooserParams) + val intent = createCameraAndFilePickerChooserIntent( + fileChooserParams + ) + cameraAndFilePickerChooserLauncher.launch(intent) }, onCloseSession = { navController.popBackStack() }, onSessionFinished = { finish() } @@ -147,34 +150,31 @@ class MainActivity : ComponentActivity() { } } - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - - if (requestCode == CAPTURE_REQUEST_CODE) { - if (!isViewRecreated && resultCode == Activity.RESULT_OK) { - val resultUri = data?.data ?: cameraCaptureFileUri - filePathCallback?.onReceiveValue(arrayOf(resultUri)) - } else { - filePathCallback?.onReceiveValue(null) - } - } - } - override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) outState.putBoolean(KEY_IS_VIEW_RECREATED, true) } - private fun showCameraAndFilePickerChooser(fileChooserParams: FileChooserParams) { + private fun handleCameraAndFilePickerChooserResult(result: ActivityResult) { + if (!isViewRecreated && result.resultCode == RESULT_OK) { + val resultUri = result.data?.data ?: cameraCaptureFileUri + filePathCallback?.onReceiveValue(arrayOf(resultUri)) + } else { + filePathCallback?.onReceiveValue(null) + } + } + + private fun createCameraAndFilePickerChooserIntent(fileChooserParams: FileChooserParams): Intent { cameraCaptureFileUri = createFileUri() - Intent.createChooser(createFilePickerIntent(fileChooserParams), fileChooserParams.title) - .run { - putExtra( - Intent.EXTRA_INITIAL_INTENTS, - listOf(createCameraIntent(cameraCaptureFileUri)).toTypedArray() - ) - startActivityForResult(this, CAPTURE_REQUEST_CODE) - } + return Intent.createChooser( + createFilePickerIntent(fileChooserParams), + fileChooserParams.title + ).also { + it.putExtra( + Intent.EXTRA_INITIAL_INTENTS, + listOf(createCameraIntent(cameraCaptureFileUri)).toTypedArray() + ) + } } private fun createFilePickerIntent(params: FileChooserParams): Intent? { diff --git a/webapp/src/main/java/com/yoti/mobile/android/sdk/yotidocscan/websample/WebScreen.kt b/webapp/src/main/java/com/yoti/mobile/android/sdk/yotidocscan/websample/WebScreen.kt index efd5737..2f9a84f 100644 --- a/webapp/src/main/java/com/yoti/mobile/android/sdk/yotidocscan/websample/WebScreen.kt +++ b/webapp/src/main/java/com/yoti/mobile/android/sdk/yotidocscan/websample/WebScreen.kt @@ -29,8 +29,7 @@ fun WebScreen( sessionUrl: String, showSessionFinishedDialog: Boolean, onPageCommitVisible: (String?) -> Unit, - onFilePathCallbackReady: (ValueCallback>?) -> Unit, - onShowCameraAndFilePickerChooser: (FileChooserParams) -> Unit, + onShowCameraAndFilePickerChooser: (ValueCallback>?, FileChooserParams) -> Unit, onCloseSession: () -> Unit, onSessionFinished: () -> Unit, modifier: Modifier = Modifier @@ -43,11 +42,7 @@ fun WebScreen( WebView.setWebContentsDebuggingEnabled(BuildConfig.DEBUG) configureSettings(settings) configureWebViewClient(this, onPageCommitVisible) - configureWebChromeClient( - this, - onFilePathCallbackReady, - onShowCameraAndFilePickerChooser - ) + configureWebChromeClient(this, onShowCameraAndFilePickerChooser) } }, update = { webView -> @@ -129,8 +124,7 @@ private fun configureWebViewClient(webView: WebView, onPageCommitVisible: (Strin private fun configureWebChromeClient( webView: WebView, - onFilePathCallbackReady: (ValueCallback>?) -> Unit, - onShowCameraAndFilePickerChooser: (FileChooserParams) -> Unit + onShowCameraAndFilePickerChooser: (ValueCallback>?, FileChooserParams) -> Unit ) { webView.webChromeClient = object : WebChromeClient() { // Launch file picker or camera intent and set the @@ -140,9 +134,8 @@ private fun configureWebChromeClient( filePathCallback: ValueCallback>?, fileChooserParams: FileChooserParams? ): Boolean { - onFilePathCallbackReady(filePathCallback) return if (fileChooserParams?.mode == FileChooserParams.MODE_OPEN) { - onShowCameraAndFilePickerChooser(fileChooserParams) + onShowCameraAndFilePickerChooser(filePathCallback, fileChooserParams) true } else { false From c0216c1f1fb458180da543d14f9058f3aa355414 Mon Sep 17 00:00:00 2001 From: Mircea Bucerzan Date: Tue, 19 Aug 2025 11:53:10 +0300 Subject: [PATCH 07/11] Delete legacy UI code View system components were replaced with Jetpack Compose. Related to YD-6940 --- webapp/build.gradle | 8 -- .../websample/AccelerometerListener.kt | 61 -------------- .../websample/SessionBottomSheet.kt | 79 ------------------- webapp/src/main/res/drawable/ic_shake.xml | 9 --- webapp/src/main/res/layout/activity_main.xml | 45 ----------- webapp/src/main/res/layout/bottom_sheet.xml | 39 --------- webapp/src/main/res/values/colors.xml | 6 -- webapp/src/main/res/values/styles.xml | 9 +-- 8 files changed, 1 insertion(+), 255 deletions(-) delete mode 100644 webapp/src/main/java/com/yoti/mobile/android/sdk/yotidocscan/websample/AccelerometerListener.kt delete mode 100644 webapp/src/main/java/com/yoti/mobile/android/sdk/yotidocscan/websample/SessionBottomSheet.kt delete mode 100644 webapp/src/main/res/drawable/ic_shake.xml delete mode 100644 webapp/src/main/res/layout/activity_main.xml delete mode 100644 webapp/src/main/res/layout/bottom_sheet.xml delete mode 100644 webapp/src/main/res/values/colors.xml diff --git a/webapp/build.gradle b/webapp/build.gradle index 6caeb24..becf707 100644 --- a/webapp/build.gradle +++ b/webapp/build.gradle @@ -40,14 +40,6 @@ android { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation 'androidx.appcompat:appcompat:1.1.0' - implementation 'androidx.core:core-ktx:1.3.0' - implementation 'androidx.constraintlayout:constraintlayout:1.1.3' - implementation 'com.google.android.material:material:1.1.0' - - // Multi-module projects: Add this dependency because of Android Studio Issue - // and androix.navigation dependencies management https://issuetracker.google.com/issues/152245564 - implementation 'androidx.navigation:navigation-ui-ktx:2.2.2' def composeBom = platform("androidx.compose:compose-bom:2025.04.00") implementation composeBom diff --git a/webapp/src/main/java/com/yoti/mobile/android/sdk/yotidocscan/websample/AccelerometerListener.kt b/webapp/src/main/java/com/yoti/mobile/android/sdk/yotidocscan/websample/AccelerometerListener.kt deleted file mode 100644 index c82accc..0000000 --- a/webapp/src/main/java/com/yoti/mobile/android/sdk/yotidocscan/websample/AccelerometerListener.kt +++ /dev/null @@ -1,61 +0,0 @@ -package com.yoti.mobile.android.sdk.yotidocscan.websample - -import android.content.Context -import android.hardware.Sensor -import android.hardware.Sensor.TYPE_ACCELEROMETER -import android.hardware.SensorEvent -import android.hardware.SensorEventListener -import android.hardware.SensorManager -import android.hardware.SensorManager.SENSOR_DELAY_NORMAL -import android.util.Log -import kotlin.math.sqrt - -private const val TAG = "AccelerometerListener" - -class AccelerometerListener( - private val context: Context, - private val shakeListener: ShakeListener? -) : SensorEventListener { - - interface ShakeListener { - fun onShake() - } - - private var sensorManager: SensorManager? = null - private var shake = 0f - private var currentShake = 0f - private var lastShake = 0f - - fun start() { - sensorManager = (context.getSystemService(Context.SENSOR_SERVICE) as? SensorManager) - sensorManager?.getDefaultSensor(TYPE_ACCELEROMETER)?.let { accelerometerSensor -> - Log.d(TAG, "Start: register SensorManager listener") - sensorManager?.registerListener(this, accelerometerSensor, SENSOR_DELAY_NORMAL) - }?:run { Log.d(TAG, "Accelerometer sensor is not available") } - } - - fun stop() { - Log.d(TAG, "Stop: unregister SensorManager listener") - sensorManager?.unregisterListener(this) - } - - override fun onSensorChanged(event: SensorEvent?) { - event?.run { - val x = values[0] - val y = values[1] - val z = values[2] - lastShake = currentShake - currentShake = sqrt((x * x + y * y + z * z)) - val delta = currentShake - lastShake - shake = shake * 0.9f + delta - if (shake > 12) { - Log.d(TAG, "Shake detected") - shakeListener?.onShake() - } - } - } - - override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) { - // Nothing to do here - } -} \ No newline at end of file diff --git a/webapp/src/main/java/com/yoti/mobile/android/sdk/yotidocscan/websample/SessionBottomSheet.kt b/webapp/src/main/java/com/yoti/mobile/android/sdk/yotidocscan/websample/SessionBottomSheet.kt deleted file mode 100644 index 419175c..0000000 --- a/webapp/src/main/java/com/yoti/mobile/android/sdk/yotidocscan/websample/SessionBottomSheet.kt +++ /dev/null @@ -1,79 +0,0 @@ -package com.yoti.mobile.android.sdk.yotidocscan.websample - - -import android.content.Context -import android.content.DialogInterface -import android.content.SharedPreferences -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import com.google.android.material.bottomsheet.BottomSheetDialogFragment -import com.yoti.mobile.android.sdk.yotidocscan.websample.databinding.BottomSheetBinding - -private const val SESSION_PREFERENCES_ID = "SESSION_PREFERENCES_ID" -private const val SESSION_CONFIGURATION_KEY = "SESSION_CONFIGURATION_KEY" - -class SessionBottomSheet: BottomSheetDialogFragment() { - - private var sessionConfigurationListener: SessionConfigurationListener? = null - lateinit var sharedPreferences: SharedPreferences - - private var _binding: BottomSheetBinding? = null - private val binding: BottomSheetBinding get() = _binding!! - - interface SessionConfigurationListener { - fun onSessionConfigurationSuccess(sessionUrl: String) - fun onSessionConfigurationDismiss() - } - - override fun onCreateView( - inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? - ): View { - _binding = BottomSheetBinding.inflate(inflater, container, false) - return binding.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - with (binding) { - sessionUrl.setText(sharedPreferences.getString(SESSION_CONFIGURATION_KEY, "")) - - startSessionButton.setOnClickListener { - sessionConfigurationListener?.onSessionConfigurationSuccess( - sessionUrl.text.toString() - ) - dismiss() - } - } - } - - override fun onDismiss(dialog: DialogInterface) { - sharedPreferences.edit().putString(SESSION_CONFIGURATION_KEY, binding.sessionUrl.text.toString()).apply() - sessionConfigurationListener?.onSessionConfigurationDismiss() - super.onDismiss(dialog) - } - - override fun onAttach(context: Context) { - super.onAttach(context) - sharedPreferences = requireContext().getSharedPreferences(SESSION_PREFERENCES_ID, Context.MODE_PRIVATE) - sessionConfigurationListener = context as? SessionConfigurationListener - } - - override fun onDestroyView() { - super.onDestroyView() - _binding = null - } - - override fun onDetach() { - sessionConfigurationListener = null - super.onDetach() - } - - companion object { - const val FRAGMENT_TAG = "com.yoti.mobile.android.sdk.yotidocscan.websample.SessionBottomSheet" - - fun newInstance() = SessionBottomSheet() - } -} \ No newline at end of file diff --git a/webapp/src/main/res/drawable/ic_shake.xml b/webapp/src/main/res/drawable/ic_shake.xml deleted file mode 100644 index 1d861ec..0000000 --- a/webapp/src/main/res/drawable/ic_shake.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/webapp/src/main/res/layout/activity_main.xml b/webapp/src/main/res/layout/activity_main.xml deleted file mode 100644 index 3fd8ff7..0000000 --- a/webapp/src/main/res/layout/activity_main.xml +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/webapp/src/main/res/layout/bottom_sheet.xml b/webapp/src/main/res/layout/bottom_sheet.xml deleted file mode 100644 index 88347fb..0000000 --- a/webapp/src/main/res/layout/bottom_sheet.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - -