-
Notifications
You must be signed in to change notification settings - Fork 2
[TASK][YD-6940] Migrate webapp module UI to Jetpack Compose #59
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
base: YD-6939
Are you sure you want to change the base?
Changes from 1 commit
2e88364
523a09f
80437c0
22cf789
f4c4f26
fd9d33c
c0216c1
0f121ce
00b87cc
74bddca
a78069a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,29 +1,32 @@ | ||
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 | ||
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,38 +72,70 @@ 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<Array<Uri>>? = 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", | ||
".png" to "image/png", | ||
".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<Array<Uri>>?, | ||
fileChooserParams: FileChooserParams? | ||
): Boolean { | ||
[email protected] = 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") { _, _ -> [email protected]() } | ||
.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 | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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<Array<Uri>>?) -> 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) } | ||
} | ||
) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This could be a dedicated There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done here: a78069a 🚀 |
||
|
||
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)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No color or font size/style ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Answered above |
||
} | ||
}, | ||
onDismissRequest = {} | ||
) | ||
} | ||
|
||
mircea-yoti marked this conversation as resolved.
Show resolved
Hide resolved
|
||
@Suppress("DEPRECATION") | ||
@SuppressLint("SetJavaScriptEnabled") | ||
private fun configureSettings(settings: WebSettings) { | ||
with(settings) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is it ok to have the version hardcoded?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done here: 74bddca 🚀