diff --git a/.gitignore b/.gitignore index 2018b927..38418e16 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ # Built application files *.apk *.aab -google-services.json +app/google-services.json #*.aar #*.ap_ diff --git a/README.md b/README.md index e64d4225..b70950d8 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,6 @@ ID scanning Android app and library. Supports MRZ, NFC, Barcodes, and [ID PASS Lite](https://github.com/idpass/idpass-lite) cards. -[![Get it on Google Play](https://buttercup.pw/static/img/googleplay.svg)](https://play.google.com/store/apps/details?id=org.newlogic.smartscanner) - **Note: The library's API might keep evolving before we reach v1.0, so be careful when upgrading between these pre-v1.0 versions. Starting at v1.0 we will be careful in introducing breaking API changes.** ## Features diff --git a/app/build.gradle b/app/build.gradle index c8868d7b..51e1c2a7 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,6 +1,4 @@ -plugins { - id 'com.gladed.androidgitversion' version '0.4.14' -} + apply plugin: 'com.android.application' apply plugin: 'kotlin-android' @@ -8,9 +6,6 @@ apply plugin: 'kotlin-parcelize' apply plugin: 'com.google.gms.google-services' apply plugin: 'com.google.firebase.crashlytics' -androidGitVersion { - baseCode 1 -} android { compileSdkVersion 32 @@ -20,8 +15,8 @@ android { applicationId "org.newlogic.smartscanner" minSdkVersion 21 targetSdkVersion 32 - versionName androidGitVersion.name() - versionCode androidGitVersion.code() + versionName = "1.0.2" + versionCode = 1 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles "consumer-rules.pro" @@ -33,20 +28,20 @@ android { viewBinding = true } - signingConfigs{ - release { - def props = new Properties() - - def fileInputStream = new FileInputStream(file('../signing.properties')) - props.load(fileInputStream) - fileInputStream.close() - - storeFile = file('upload-keystore') - storePassword = props['STORE_PASSWORD'] - keyAlias = props['KEY_ALIAS'] - keyPassword = props['KEY_PASSWORD'] - } - } +// signingConfigs{ +// release { +// def props = new Properties() +// +// def fileInputStream = new FileInputStream(file('../signing.properties')) +// props.load(fileInputStream) +// fileInputStream.close() +// +// storeFile = file('upload-keystore') +// storePassword = props['STORE_PASSWORD'] +// keyAlias = props['KEY_ALIAS'] +// keyPassword = props['KEY_PASSWORD'] +// } +// } buildTypes { debug { @@ -55,7 +50,7 @@ android { } release { minifyEnabled false - signingConfig signingConfigs.release +// signingConfig signingConfigs.release proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } android.applicationVariants.all { variant -> @@ -89,9 +84,9 @@ dependencies { implementation 'androidx.core:core-ktx:1.3.2' implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'com.google.firebase:firebase-analytics-ktx:18.0.1' + implementation 'com.google.firebase:firebase-crashlytics-ktx:17.3.0' + implementation 'com.google.firebase:firebase-crashlytics:17.3.0' implementation 'com.google.firebase:firebase-analytics:18.0.1' - implementation 'com.google.firebase:firebase-crashlytics-ktx:18.2.12' - implementation 'com.google.firebase:firebase-crashlytics:18.2.12' implementation 'androidx.multidex:multidex:2.0.1' testImplementation 'junit:junit:4.13.1' androidTestImplementation 'androidx.test.ext:junit:1.1.2' @@ -99,7 +94,6 @@ dependencies { implementation 'com.google.code.gson:gson:2.8.6' implementation 'com.google.android.material:material:1.2.1' implementation 'androidx.constraintlayout:constraintlayout:2.0.4' - implementation 'androidx.recyclerview:recyclerview:1.2.1' implementation 'com.github.bumptech.glide:glide:4.11.0' implementation 'com.jakewharton.timber:timber:4.7.1' annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0' @@ -111,4 +105,21 @@ dependencies { implementation 'org.idpass:idpass-lite-java-android:0.1@aar' // SmartScanner API Intent Call Out implementation project(path: ':smartscanner-android-api') + + //Authentication + implementation "com.auth0:java-jwt:4.4.0" + implementation "com.auth0:auth0:1.30.0" + implementation 'com.auth0:jwks-rsa:0.22.0' + + // UI + implementation 'de.hdodenhof:circleimageview:3.1.0' + + // Asynch function + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.0" + + implementation "com.fasterxml.jackson.module:jackson-module-kotlin:2.12.0" + + + } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 637340ad..7319a421 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,16 +1,13 @@ + package="org.newlogic.smartscanner" + android:versionName="1.0.2" + android:versionCode="1"> - - - @@ -26,7 +23,9 @@ android:hardwareAccelerated="true" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" - android:theme="@style/AppTheme"> + android:theme="@style/AppTheme" + android:debuggable="true" + tools:ignore="HardcodedDebugMode"> - + Log.d(SmartScannerActivity.TAG, "Scanner result bundle: $bundleResult") + if (bundleResult.getString(ScannerConstants.MODE) == Modes.IDPASS_LITE.value) { // Go to ID PASS Lite Results Screen via bundle - mIntent = Intent(this, IDPassResultActivity::class.java) - mIntent.putExtra(IDPassResultActivity.BUNDLE_RESULT, bundle) + val myIntent = Intent(this, IDPassResultActivity::class.java) + myIntent.putExtra(IDPassResultActivity.BUNDLE_RESULT, bundleResult) + startActivity(myIntent) } else { - // Go to Results Screen via bundle - mIntent = Intent(this, ResultActivity::class.java) - mIntent.putExtra(ResultActivity.BUNDLE_RESULT, bundle) + // Go to Barcode/MRZ Results Screen via bundle + val resultIntent = Intent(this, ResultActivity::class.java) + resultIntent.putExtra(ResultActivity.BUNDLE_RESULT, bundleResult) + startActivity(resultIntent) } - } else { - // Get Result from Intent extras - if (intent?.getStringExtra(ScannerConstants.MODE) == Modes.IDPASS_LITE.value) { - // Go to ID PASS Lite Results Screen - val resultBytes = intent.getByteArrayExtra(SCANNER_RESULT_BYTES) - mIntent = Intent(this, IDPassResultActivity::class.java) - mIntent.putExtra(IDPassResultActivity.RESULT, resultBytes) + } ?: run { + // Get Result from JSON String + val result = intent?.getStringExtra(SCANNER_RESULT) + Log.d(SmartScannerActivity.TAG, "Scanner result string: $result") + if (!result.isNullOrEmpty()) { + // Go to Barcode/MRZ Results Screen + val resultIntent = Intent(this, ResultActivity::class.java) + resultIntent.putExtra(ResultActivity.RESULT, result) + startActivity(resultIntent) } else { - - // Check if it should go to the settings instead - val isConfigUpdated = intent?.getBooleanExtra(SCANNER_JWT_CONFIG_UPDATE, false) - - // should go to settings - if (isConfigUpdated == true) { - val sIntent = Intent(this, SettingsActivity::class.java) - sIntent.putExtra(SettingsActivity.CONFIG_UPDATED, true) - startActivity(sIntent) - return - } - - // Go to Results Screen - val result = intent?.getStringExtra(SCANNER_RESULT) - val verified = intent?.getBooleanExtra(SCANNER_SIGNATURE_VERIFICATION, false) - val rawResult = intent?.getStringExtra(SCANNER_RAW_RESULT) - val failResult = intent?.getStringExtra(SCANNER_FAIL_RESULT) - val headerResult = intent?.getStringExtra(SCANNER_HEADER_RESULT) - - mIntent = Intent(this, ResultActivity::class.java) - mIntent.putExtra(ResultActivity.SIGNATURE_VERIFIED, verified) - mIntent.putExtra(ResultActivity.IMAGE_TYPE, intent?.getStringExtra(SCANNER_IMAGE_TYPE)) - mIntent.putExtra(ResultActivity.RESULT, result) - mIntent.putExtra(ResultActivity.FAIL_RESULT, failResult) - mIntent.putExtra(ResultActivity.RAW_RESULT, rawResult) - mIntent.putExtra(ResultActivity.HEADER_RESULT, headerResult) - + // Go to ID PASS Lite Results Screen + val resultBytes = intent?.getByteArrayExtra(SCANNER_RESULT_BYTES) + val myIntent = Intent(this, IDPassResultActivity::class.java) + myIntent.putExtra(IDPassResultActivity.RESULT, resultBytes) + startActivity(myIntent) } } - - - mIntent.putExtra(ScannerConstants.MODE, intent?.getStringExtra(ScannerConstants.MODE)) - startActivity(mIntent) } } } diff --git a/app/src/main/java/org/newlogic/smartscanner/SmartScannerApplication.kt b/app/src/main/java/org/newlogic/smartscanner/SmartScannerApplication.kt index abaa58b7..fc742ec5 100644 --- a/app/src/main/java/org/newlogic/smartscanner/SmartScannerApplication.kt +++ b/app/src/main/java/org/newlogic/smartscanner/SmartScannerApplication.kt @@ -50,4 +50,8 @@ class SmartScannerApplication : MultiDexApplication() { } } } + + companion object { + const val SHARED = "SHARED" + } } \ No newline at end of file diff --git a/app/src/main/java/org/newlogic/smartscanner/adapters/RecyclerResultAdapter.kt b/app/src/main/java/org/newlogic/smartscanner/adapters/RecyclerResultAdapter.kt deleted file mode 100644 index 85efacb0..00000000 --- a/app/src/main/java/org/newlogic/smartscanner/adapters/RecyclerResultAdapter.kt +++ /dev/null @@ -1,35 +0,0 @@ -package org.newlogic.smartscanner.adapters - -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.TextView -import androidx.recyclerview.widget.RecyclerView -import org.newlogic.smartscanner.R - -class RecyclerResultAdapter (private var results: HashMap): RecyclerView.Adapter() { - - inner class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) { - val itemLabel: TextView = itemView.findViewById(R.id.tv_label) - val itemValue: TextView = itemView.findViewById(R.id.tv_value) - - // no click listener here - } - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { - val v = LayoutInflater.from(parent.context).inflate(R.layout.recycler_result_item, parent, false) - return ViewHolder(v) - } - - override fun onBindViewHolder(holder: ViewHolder, position: Int) { - - val keyByIndex = results.keys.elementAt(position) - val valueByKey = results.getValue(keyByIndex) - holder.itemLabel.text = keyByIndex - holder.itemValue.text = valueByKey - } - - override fun getItemCount(): Int { - return results.size - } -} \ No newline at end of file diff --git a/app/src/main/java/org/newlogic/smartscanner/result/IDPassResultActivity.kt b/app/src/main/java/org/newlogic/smartscanner/result/IDPassResultActivity.kt index 44085fec..fdc59a3f 100755 --- a/app/src/main/java/org/newlogic/smartscanner/result/IDPassResultActivity.kt +++ b/app/src/main/java/org/newlogic/smartscanner/result/IDPassResultActivity.kt @@ -36,10 +36,10 @@ import org.idpass.lite.exceptions.InvalidKeyException import org.idpass.smartscanner.api.ScannerConstants import org.idpass.smartscanner.lib.SmartScannerActivity import org.idpass.smartscanner.lib.idpasslite.IDPassManager -import org.idpass.smartscanner.lib.utils.DateUtils.formatDate -import org.idpass.smartscanner.lib.utils.DateUtils.isValidDate -import org.idpass.smartscanner.lib.utils.extension.empty -import org.idpass.smartscanner.lib.utils.extension.hideKeyboard +import org.idpass.smartscanner.lib.platform.extension.empty +import org.idpass.smartscanner.lib.platform.extension.hideKeyboard +import org.idpass.smartscanner.lib.platform.utils.DateUtils.formatDate +import org.idpass.smartscanner.lib.platform.utils.DateUtils.isValidDate import org.newlogic.smartscanner.R class IDPassResultActivity : AppCompatActivity(), View.OnClickListener { diff --git a/app/src/main/java/org/newlogic/smartscanner/result/RawResultActivity.kt b/app/src/main/java/org/newlogic/smartscanner/result/RawResultActivity.kt deleted file mode 100644 index c96a476e..00000000 --- a/app/src/main/java/org/newlogic/smartscanner/result/RawResultActivity.kt +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (C) 2020 Newlogic Pte. Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - * - * - */ - -package org.newlogic.smartscanner.result - -import android.os.Bundle -import android.view.MenuItem -import androidx.appcompat.app.AppCompatActivity -import android.view.View.GONE -import android.view.View.VISIBLE -import org.newlogic.smartscanner.R -import org.newlogic.smartscanner.databinding.ActivityRawResultBinding - -class RawResultActivity : AppCompatActivity() { - - companion object { - const val HEADER = "SCAN_HEADER_RESULT" - const val RESULT = "SCAN_RESULT" - const val PAYLOAD = "SCAN_PAYLOAD" - } - - private lateinit var binding: ActivityRawResultBinding - - private var resultHeader: String? = null - private var resultRaw: String? = null - private var payload: String? = null - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - binding = ActivityRawResultBinding.inflate(layoutInflater) - setContentView(binding.root) - overridePendingTransition(R.anim.slide_in_up, android.R.anim.fade_out) - setSupportActionBar(binding.toolbar) - supportActionBar?.setDisplayShowTitleEnabled(false) - supportActionBar?.setDisplayHomeAsUpEnabled(true) - supportActionBar?.setHomeButtonEnabled(true) - supportActionBar?.setHomeAsUpIndicator(R.drawable.ic_close) - - resultHeader = intent.getStringExtra(HEADER) - resultRaw = intent.getStringExtra(RESULT) - payload = intent.getStringExtra(PAYLOAD) - - binding.layoutHeader.visibility = GONE - binding.layoutPayload.visibility = GONE - binding.layoutRaw.visibility = GONE - } - - override fun onStart() { - super.onStart() - - if (resultRaw != null) { - binding.tvRawValue.text = resultRaw - binding.layoutRaw.visibility = VISIBLE - } - - if (resultHeader != null) { - binding.tvHeaderValue.text = formatString(resultHeader) - binding.layoutHeader.visibility = VISIBLE - } - - if (payload != null) { - binding.tvPayloadValue.text = formatString(payload) - binding.layoutPayload.visibility = VISIBLE - } - - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { - android.R.id.home -> finish() - } - - return super.onOptionsItemSelected(item) - } - - - private fun formatString(text: String?): String { - - if (text == null) { - return "" - } - - - val json = StringBuilder() - var indentString = "" - for (element in text) { - when (element) { - '{', '[' -> { - json.append( - """ - - $indentString$element - - """.trimIndent() - ) - indentString += "\t" - json.append(indentString) - } - '}', ']' -> { - indentString = indentString.replaceFirst("\t".toRegex(), "") - json.append( - """ - - $indentString$element - """.trimIndent() - ) - } - ',' -> json.append( - """ - $element - $indentString - """.trimIndent() - ) - else -> json.append(element) - } - } - return json.toString() - } - -} \ No newline at end of file diff --git a/app/src/main/java/org/newlogic/smartscanner/result/ResultActivity.kt b/app/src/main/java/org/newlogic/smartscanner/result/ResultActivity.kt index 2ebcbacc..45dcfcb8 100644 --- a/app/src/main/java/org/newlogic/smartscanner/result/ResultActivity.kt +++ b/app/src/main/java/org/newlogic/smartscanner/result/ResultActivity.kt @@ -17,56 +17,80 @@ */ package org.newlogic.smartscanner.result + +//Authenticate + +//Asynch function +import android.content.Context import android.content.Intent +import android.content.SharedPreferences import android.graphics.BitmapFactory import android.graphics.Paint +import android.net.Uri import android.os.Bundle +import android.text.SpannableString +import android.text.Spanned +import android.text.method.LinkMovementMethod +import android.text.style.ClickableSpan +import android.text.style.URLSpan import android.view.Menu import android.view.MenuItem +import android.view.View import android.view.View.GONE import android.view.View.VISIBLE +import android.widget.TextView import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat -import androidx.recyclerview.widget.DividerItemDecoration -import androidx.recyclerview.widget.LinearLayoutManager +import com.auth0.jwk.Jwk +import com.auth0.jwk.NetworkException +import com.auth0.jwk.UrlJwkProvider +import com.auth0.jwt.JWT +import com.auth0.jwt.algorithms.Algorithm import com.bumptech.glide.Glide +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.databind.ObjectReader +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.google.android.material.snackbar.Snackbar +import com.google.gson.Gson import com.google.gson.JsonParser +import com.google.gson.reflect.TypeToken +import kotlinx.coroutines.* import org.idpass.smartscanner.api.ScannerConstants +import org.idpass.smartscanner.lib.platform.extension.decodeBase64 import org.idpass.smartscanner.lib.scanner.config.ImageResultType import org.idpass.smartscanner.lib.scanner.config.Modes -import org.idpass.smartscanner.lib.utils.extension.decodeBase64 -import org.idpass.smartscanner.lib.utils.extension.isJSONValid -import org.json.JSONException -import org.json.JSONObject +import org.newlogic.smartscanner.MainActivity.Companion.imageType import org.newlogic.smartscanner.R -import org.newlogic.smartscanner.adapters.RecyclerResultAdapter import org.newlogic.smartscanner.databinding.ActivityResultBinding -import org.newlogic.smartscanner.result.RawResultActivity.Companion.PAYLOAD +import java.io.IOException +import java.net.Proxy +import java.net.URL +import java.net.URLConnection +import java.security.interfaces.RSAPublicKey +import java.text.SimpleDateFormat +import java.util.* +import com.fasterxml.jackson.module.kotlin.readValue +import java.io.BufferedReader class ResultActivity : AppCompatActivity() { companion object { - const val RAW_RESULT = "SCAN_RAW_RESULT" - const val HEADER_RESULT = "SCAN_HEADER_RESULT" const val RESULT = "SCAN_RESULT" - const val FAIL_RESULT = "SCAN_FAIL_RESULT" const val BUNDLE_RESULT = "SCAN_BUNDLE_RESULT" - const val IMAGE_TYPE = "SCAN_IMAGE_TYPE" - const val SIGNATURE_VERIFIED = "SCAN_SIGNATURE_VERIFIED" } + private lateinit var binding : ActivityResultBinding - private var result : String? = null - private var rawResult : String? = null - private var headerResult : String? = null - private var failResult : String? = null - private var imageType : String? = null - private var resultList = mutableMapOf(); - private var isVerifiedSignature: Boolean = false + private var resultString : String? = null + override fun onCreate(savedInstanceState: Bundle?) { + // caching + val sharedPrefFile = "ScannerJwksFile" + val sharedPreferences: SharedPreferences = this.getSharedPreferences(sharedPrefFile, Context.MODE_PRIVATE) + + super.onCreate(savedInstanceState) binding = ActivityResultBinding.inflate(layoutInflater) val view = binding.root @@ -77,141 +101,319 @@ class ResultActivity : AppCompatActivity() { supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setHomeButtonEnabled(true) supportActionBar?.setHomeAsUpIndicator(R.drawable.ic_close) + intent.getStringExtra(RESULT)?.let { + val scanResult = intent.getStringExtra(RESULT) + setupResult(result = scanResult, imageType = imageType) + resultString = getShareResult(result = scanResult) + } ?: run { + intent.getBundleExtra(BUNDLE_RESULT)?.let { + val result = when (it.getString(ScannerConstants.MODE)) { + Modes.BARCODE.value -> it.getString(ScannerConstants.BARCODE_VALUE) + Modes.QRCODE.value -> it.getString(ScannerConstants.QRCODE_TEXT) + Modes.MRZ.value -> it.getString(ScannerConstants.MRZ_RAW) + else -> null + } - rawResult = intent.getStringExtra(RAW_RESULT) - headerResult = intent.getStringExtra(HEADER_RESULT) - failResult = intent.getStringExtra(FAIL_RESULT) - result = intent.getStringExtra(RESULT) - imageType = intent.getStringExtra(IMAGE_TYPE) - isVerifiedSignature = intent.getBooleanExtra(SIGNATURE_VERIFIED, false) - - binding.rvResultList.layoutManager = LinearLayoutManager(this) - binding.rvResultList.adapter = RecyclerResultAdapter(resultList as HashMap) - - val dividerItemDecoration = DividerItemDecoration( - this, - LinearLayoutManager(this).orientation - ) - binding.rvResultList.addItemDecoration(dividerItemDecoration) - binding.btnViewRawResult.setOnClickListener { showRawResult() } - + resultString = result + val parts = result?.split(".") + if (parts != null) { + if (parts.size != 3){ + displayFailed() + } + else{ + val decodedPayload = parseJwt(result) + val iss = decodedPayload?.get("iss") as String? + println(iss) + val jwksUrl = iss + "/.well-known/jwks.json" + val myCoroutineScope = CoroutineScope(Dispatchers.Main) + myCoroutineScope.launch { + val (key, output) = verification(result, jwksUrl) + println("verification 1 output") + println(key) + println(output) + if (key != null){ + if (output){ + val editor = sharedPreferences.edit() + editor.putString(iss, key).apply() + decodePayLoad(decodedPayload) + } + else{ + displayFailed() + } + } + else{ + val cache_key = sharedPreferences.getString(iss, null) + if (cache_key != null){ + val out = verification_offline(result, cache_key) + if (out){ + decodePayLoad(decodedPayload) + } + else{ + println("offline verification failed") + displayFailed() + } + } + else{ + println("key not found") + displayFailed() + } + } + } + } + } + } ?: run { + binding.textResult.text = getString(R.string.label_result_none) + } + } } - override fun onStart() { - super.onStart() + fun convertSecondsToDate(seconds: Long): String { + val formatter = SimpleDateFormat("dd-MM-yyyy") + formatter.timeZone = TimeZone.getDefault() + val date = Date(seconds * 1000) + return formatter.format(date) + } - if (failResult?.isNotEmpty() == true) { - showFailResult() - return + private fun decodePayLoad(decodedPayload: Map?){ + val name = decodedPayload?.get("bName") as String? + val iss = decodedPayload?.get("iss") as String? + val address = decodedPayload?.get("bAddress") as String? + val amount = decodedPayload?.get("amount") as String? + val code = decodedPayload?.get("code") as String? + val dateIssue = decodedPayload?.get("iat") as Double? + val dateExpire = decodedPayload?.get("exp") as Double? + val serviceProvider = decodedPayload?.get("spName") as String? + val docs = decodedPayload?.get("docs") as? List<*> + if (docs != null) { + for (doc in docs) { + println(doc) + } } + if (dateIssue != null) { + if (dateExpire != null) { + displayVerified(name, address, amount, code, + convertSecondsToDate(dateIssue.toLong()), + convertSecondsToDate(dateExpire.toLong()), + serviceProvider, docs, iss) + } + } + } + private fun parseJwt(token: String?): Map? { + val parts = token?.split(".") + val base64Url = parts?.get(1) + val base64 = base64Url?.replace('-', '+')?.replace('_', '/') + val jsonBytes = android.util.Base64.decode(base64, android.util.Base64.DEFAULT) + val jsonString = String(jsonBytes, Charsets.UTF_8) + val gson = Gson() + return gson.fromJson(jsonString, object : TypeToken>() {}.type) + } - if (result != null) { - when (intent.getStringExtra(ScannerConstants.MODE)) { - - // Display for MRZ here - Modes.MRZ.value -> { - displayResult(result = result, imageType = imageType) - // Check composite validity - val resultObj = JsonParser.parseString(result).asJsonObject - val validComposite = if (resultObj["validComposite"]!= null) resultObj["validComposite"].asBoolean else true - if (!validComposite) { - val snackBar = Snackbar.make(binding.root, getString(R.string.label_warning_invalid_composite_digit), Snackbar.LENGTH_INDEFINITE) - snackBar.setAction("Dismiss") { _ -> snackBar.dismiss() } - snackBar.setActionTextColor(ContextCompat.getColor(this, R.color.idpass_orange)) - snackBar.show() - } - } + // async function + suspend fun verification(jwt: String?, jwksUrl: String): Pair = withContext(Dispatchers.IO) { + try { +// val jwkProvider = UrlJwkProvider(URL(jwksUrl),10, 10 ) + val jwkstring = getJwks(URL(jwksUrl), 30000, 30000, null) + val jwks = jacksonObjectMapper().readValue>>>(jwkstring)["keys"] + ?: throw RuntimeException("jwks is null") +// val jwksFinal : MutableList = mutableListOf() +// for (jwk in jwks){ +// jwksFinal.add(Jwk.fromValues(jwk)) +// } + println(jwkstring) + val jwtDecoded = JWT.decode(jwt) + println("jwt decoded") + val keyId = jwtDecoded.keyId + val jwk = Jwk.fromValues(jwks.filter {Jwk.fromValues(it).id == keyId}[0]) + println(jwk) + val publicKey: RSAPublicKey = jwk.publicKey as RSAPublicKey + println(publicKey) + val algorithm = when (jwk.algorithm) { + "RS256" -> Algorithm.RSA256(publicKey, null) + else -> throw Exception("Unsupported Algorithm") + } + val verifier = JWT.require(algorithm).build() + val verified = verifier.verify(jwt) != null - // Display for QR Code here - Modes.QRCODE.value -> showListResult(result) + Pair(jwkstring, verified) + } catch (ex: Exception) { + println(ex) + Pair(null, false) + } + } - // Otherwise - else -> displayResult(result = result, imageType = imageType) + private fun getJwks(url : URL, connectTimeout :Int?, readTimeout : Int?, proxy : Proxy?): String { + try { + val c: URLConnection = + if (proxy == null) url.openConnection() else url.openConnection(proxy) + if (connectTimeout != null) { + c.connectTimeout = connectTimeout } - } else { - // Result from intent extras is null, check bundle result instead - val bundleResult = intent.getBundleExtra(BUNDLE_RESULT) - if (bundleResult != null) { - result = when (bundleResult.getString(ScannerConstants.MODE)) { - Modes.BARCODE.value -> bundleResult.getString(ScannerConstants.BARCODE_VALUE) - Modes.QRCODE.value -> bundleResult.getString(ScannerConstants.QRCODE_TEXT) - Modes.MRZ.value -> bundleResult.getString(ScannerConstants.MRZ_RAW) - else -> null - } - - // this should not show raw result, for this one it only needs the result directly - // we have to end this - finish() - showRawResult() - } else { - binding.textResult.text = getString(R.string.label_result_none) + if (readTimeout != null) { + c.readTimeout = readTimeout } +// for ((key, value): Map.Entry in headers.entries) { +// c.setRequestProperty(key, value) +// } + return c.getInputStream().bufferedReader().use(BufferedReader::readText) + +// c.getInputStream().use { inputStream -> +// return jacksonObjectMapper().readValue( +// inputStream +// ) +// } + } catch (e: IOException) { + println(e) + throw NetworkException("Cannot obtain jwks from url " + url.toString(), e) } + } + suspend fun verification_offline(jwt: String?, jwkstring : String): Boolean = withContext(Dispatchers.IO) { + try { + val jwks = jacksonObjectMapper().readValue>>>(jwkstring)["keys"] + ?: throw RuntimeException("jwks is null") + + val jwtDecoded = JWT.decode(jwt) + println("jwt decoded from offline verification") + val keyId = jwtDecoded.keyId + val jwk = Jwk.fromValues(jwks.filter {Jwk.fromValues(it).id == keyId}[0]) + println(jwk) + val publicKey: RSAPublicKey = jwk.publicKey as RSAPublicKey + println(publicKey) + val algorithm = when (jwk.algorithm) { + "RS256" -> Algorithm.RSA256(publicKey, null) + else -> throw Exception("Unsupported Algorithm") + } + val verifier = JWT.require(algorithm).build() + verifier.verify(jwt) != null + } catch (ex: Exception) { + false + } } - private fun displayResult(result: String? = null, imageType: String?) { - if (result?.isJSONValid() == true) { - val predefinedResult: StringBuilder = getResult(result) - // Text Data Result - binding.textResult.text = if (predefinedResult.isNotEmpty()) predefinedResult.toString() else result + private fun setupResult(result: String? = null, imageType: String) { + val dump: StringBuilder = getResult(result) + // Text Data Result + if (dump.isNotEmpty()) { binding.textResult.visibility = VISIBLE - - // image object from result - val imageJson = JsonParser.parseString(result).asJsonObject["image"] - if (imageJson != null) { - val image = imageJson.asString - if (image.isNotEmpty()) { - val imageBitmap = if (imageType == ImageResultType.PATH.value) BitmapFactory.decodeFile(image) else image.decodeBase64() - Glide.with(this) - .load(imageBitmap) - .optionalCenterCrop() - .into(binding.imageResult) - binding.imageLabel.paintFlags = binding.imageLabel.paintFlags or Paint.UNDERLINE_TEXT_FLAG - binding.imageLabel.visibility = VISIBLE - binding.imageResult.visibility = VISIBLE - } else { - binding.imageLabel.visibility = GONE - binding.imageResult.visibility = GONE - } + binding.textResult.text = dump.toString() + } + // Image & Raw Data Result + result?.let { + // image object from MRZ or Barcode + val image = JsonParser.parseString(it).asJsonObject["image"] + if (image != null) { + displayImage(image.asString, imageType) } + } ?: run { + // TODO implement proper image passing + // if (bundle != null) { + // showResultImage(bundle.getString(ScannerConstants.MRZ_IMAGE) ?: "", imageType) + // } + } + // Check composite validity + val resultObj = JsonParser.parseString(result).asJsonObject + val validComposite = if (resultObj["validComposite"]!= null) resultObj["validComposite"].asBoolean else true + if (!validComposite) { + val snackBar = Snackbar.make(binding.root, getString(R.string.label_warning_invalid_composite_digit), Snackbar.LENGTH_INDEFINITE) + snackBar.setAction("Dismiss") { _ -> snackBar.dismiss() } + snackBar.setActionTextColor(ContextCompat.getColor(this, R.color.idpass_orange)) + snackBar.show() } } - - private fun showRawResult(isRawOnly: Boolean = false) { - val intent = Intent(this, RawResultActivity::class.java) - - if (!isRawOnly) { - intent.putExtra(HEADER_RESULT, headerResult) - intent.putExtra(PAYLOAD, result) + private fun displayVerified(name: String?, address: String?, amount: String?, voucherCode: String?, + issueDate: String?, expiryDate: String?, serviceProvider: String?, docs: List<*>?, iss: String?) { + + val link = iss?.split("/") + val iss_url = link?.subList(0, 3)?.joinToString("/") + binding.editTextName.setText(name) + binding.editTextAddress.setText(address) + binding.editTextAmount.setText("₱" +amount) + binding.editTextCode.setText(voucherCode) + binding.editTextDateIssue.setText(issueDate) + binding.editTextDateExpiry.setText(expiryDate) + binding.editTextServiceProvider.setText(serviceProvider) + + val docSize = if(docs != null) docs.size else 0 + println(docSize) + + if (docSize != 0){ + if (docSize > 0){ + val docOne: TextView = findViewById(R.id.docOne) + val linkText = docs?.get(0)?.toString() + val spannableString = SpannableString(linkText) + createDocHyperlink(docOne, iss_url, linkText, spannableString) + + binding.docOneBox.visibility = VISIBLE + binding.iconOne.visibility = VISIBLE} + if (docSize > 1){ + val docTwo: TextView = findViewById(R.id.docTwo) + val linkText = (docs?.get(1)?.toString()) + val spannableString = SpannableString(linkText) + createDocHyperlink(docTwo, iss_url, linkText, spannableString) + binding.docTwoBox.visibility = VISIBLE + binding.iconTwo.visibility = VISIBLE} + if (docSize > 2){ + val docThree: TextView = findViewById(R.id.docThree) + val linkText = (docs?.get(2)?.toString()) + val spannableString = SpannableString(linkText) + createDocHyperlink(docThree, iss_url, linkText, spannableString) + binding.docThreeBox.visibility = VISIBLE + binding.iconThree.visibility = VISIBLE} + + binding.Box2.visibility= VISIBLE } - intent.putExtra(RESULT, rawResult) + binding.greenTick.visibility = VISIBLE + binding.Box1.visibility = VISIBLE + binding.logos.visibility = VISIBLE + - startActivity(intent) } - private fun showFailResult() { + private fun createDocHyperlink(doc : TextView, iss_url: String?, linkText: String?, spannableString: SpannableString){ + val urlSpan = object : ClickableSpan() { + override fun onClick(widget: View) { + val intent = Intent(Intent.ACTION_VIEW, Uri.parse(iss_url + "/storage.file/" + linkText)) + startActivity(intent) + } + } - // Lets try to map fail result here - var failMessage: String? = "" - failMessage = if (failResult?.contains("JWT signature does not match") == true) { - "Error: Signature verification failed. Please ensure a configuration profile was loaded" - } else { - failResult + if (linkText != null) { + spannableString.setSpan(urlSpan, 0, linkText.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) } + doc.text = spannableString + doc.movementMethod = LinkMovementMethod.getInstance() + doc.isFocusable = false + doc.isClickable = false + doc.isLongClickable = false + } + private fun displayFailed(){ + binding.failed.visibility = VISIBLE + } - binding.tvInformation.text = failMessage - binding.tvInformation.visibility = VISIBLE - binding.btnViewRawResult.visibility = GONE + private fun displayImage(image: String, imageType: String) { + if (image.isNotEmpty()) { + val imageBitmap = if (imageType == ImageResultType.PATH.value) BitmapFactory.decodeFile(image) else image.decodeBase64() + Glide.with(this) + .load(imageBitmap) + .optionalCenterCrop() + .into(binding.imageResult) + binding.imageLabel.paintFlags = binding.imageLabel.paintFlags or Paint.UNDERLINE_TEXT_FLAG + binding.imageLabel.visibility = VISIBLE + binding.imageResult.visibility = VISIBLE + } else { + binding.imageLabel.visibility = GONE + binding.imageResult.visibility = GONE + } } + private fun getShareResult(result: String? = null) : String { - val dump: StringBuilder = if (result?.isJSONValid() == true) getResult(result = result) else StringBuilder() + val dump: StringBuilder = getResult(result = result) if (dump.isEmpty()) { dump.append(result) } @@ -251,46 +453,6 @@ class ResultActivity : AppCompatActivity() { return dump } - - private fun showListResult(result: String?) { - - if (result == null) { - // show error here - failResult = "Err: Unable to get any result." - showFailResult() - return - } else if (!result.isJSONValid()) { - // show the raw result - showRawResult(true) - finish() - return - } - - - val jsonResult = JSONObject(result.toString()) - val iResult: Iterator = jsonResult.keys() - - while (iResult.hasNext()) { - val mKey: String = iResult.next() - try { - val value: String = jsonResult.get(mKey).toString() - - resultList[mKey] = value - } catch (e: JSONException) { - // TODO Something went wrong! - } - } - - binding.rvResultList.visibility = VISIBLE - binding.rvResultList.adapter?.notifyItemInserted(jsonResult.length()) - - binding.tvInformation.visibility = GONE - if (isVerifiedSignature) { - binding.tvSignatureVerified.visibility = VISIBLE - } - } - - override fun onCreateOptionsMenu(menu: Menu?): Boolean { menuInflater.inflate(R.menu.share_menu, menu) return true @@ -300,14 +462,15 @@ class ResultActivity : AppCompatActivity() { when (item.itemId) { android.R.id.home -> finish() R.id.share -> { - val sendIntent: Intent = Intent().apply { - action = Intent.ACTION_SEND - putExtra(Intent.EXTRA_TEXT, getShareResult(result = result)) - type = "text/plain" + resultString?.let { + val sendIntent: Intent = Intent().apply { + action = Intent.ACTION_SEND + putExtra(Intent.EXTRA_TEXT, it) + type = "text/plain" + } + val shareIntent = Intent.createChooser(sendIntent, null) + startActivity(shareIntent) } - val shareIntent = Intent.createChooser(sendIntent, null) - startActivity(shareIntent) - } } return super.onOptionsItemSelected(item) diff --git a/app/src/main/java/org/newlogic/smartscanner/settings/SettingsActivity.kt b/app/src/main/java/org/newlogic/smartscanner/settings/SettingsActivity.kt index 69403e74..8e45f3b2 100644 --- a/app/src/main/java/org/newlogic/smartscanner/settings/SettingsActivity.kt +++ b/app/src/main/java/org/newlogic/smartscanner/settings/SettingsActivity.kt @@ -17,55 +17,44 @@ */ package org.newlogic.smartscanner.settings -import android.annotation.SuppressLint import android.content.Context import android.content.Intent import android.content.SharedPreferences import android.os.Bundle -import android.util.Log import android.view.View -import android.widget.Toast import androidx.appcompat.app.AppCompatActivity -import com.google.gson.JsonParser -import org.idpass.smartscanner.lib.SmartScannerActivity -import org.idpass.smartscanner.lib.scanner.config.* -import org.idpass.smartscanner.lib.scanner.config.Config.Companion.CONFIG_PROFILE_NAME -import org.idpass.smartscanner.lib.scanner.config.Config.Companion.CONFIG_PUB_KEY -import org.idpass.smartscanner.lib.scanner.config.Config.Companion.OP_SCANNER -import org.idpass.smartscanner.lib.scanner.config.Config.Companion.ORIENTATION +import org.idpass.smartscanner.lib.platform.utils.LanguageUtils +import org.idpass.smartscanner.lib.scanner.config.Language import org.idpass.smartscanner.lib.scanner.config.Orientation.LANDSCAPE import org.idpass.smartscanner.lib.scanner.config.Orientation.PORTRAIT -import org.idpass.smartscanner.lib.utils.LanguageUtils import org.newlogic.smartscanner.BuildConfig import org.newlogic.smartscanner.MainActivity import org.newlogic.smartscanner.R +import org.newlogic.smartscanner.SmartScannerApplication import org.newlogic.smartscanner.databinding.ActivitySettingsBinding -import org.newlogic.smartscanner.result.ResultActivity +import android.content.pm.PackageManager -class SettingsActivity : AppCompatActivity() { - private lateinit var binding: ActivitySettingsBinding - private var preference: SharedPreferences? = null - private var isConfigUpdated: Boolean = false +class SettingsActivity : AppCompatActivity() { companion object { - const val CONFIG_UPDATED = "CONFIG_UPDATED" + val ORIENTATION = "Orientation" } + private lateinit var binding : ActivitySettingsBinding + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivitySettingsBinding.inflate(layoutInflater) - setContentView(binding.root) - - isConfigUpdated = intent.getBooleanExtra(CONFIG_UPDATED, false) - - preference = getSharedPreferences(Config.SHARED, Context.MODE_PRIVATE) + val view = binding.root + setContentView(view) + setupViews() } - override fun onStart() { - super.onStart() - // Language (English/Arabic) + private fun setupViews() { + val preference = getSharedPreferences(SmartScannerApplication.SHARED, Context.MODE_PRIVATE) + val editor = preference.edit() val currentLanguage = resources.configuration.locale.displayLanguage if (currentLanguage == "English") { binding.arabicpic.visibility = View.INVISIBLE @@ -74,126 +63,71 @@ class SettingsActivity : AppCompatActivity() { binding.arabicpic.visibility = View.VISIBLE binding.englishpic.visibility = View.INVISIBLE } - // Orientation (Portrait/Landscape) - val orientation = preference?.getString(ORIENTATION, PORTRAIT.value) - if (orientation == PORTRAIT.value) { - binding.portraitCheck.visibility = View.VISIBLE - binding.landscapeCheck.visibility = View.INVISIBLE - } else { - binding.portraitCheck.visibility = View.INVISIBLE - binding.landscapeCheck.visibility = View.VISIBLE - } - // Configuration Profile - if ( - preference?.getString(CONFIG_PROFILE_NAME, null) == null || - preference?.getString(CONFIG_PUB_KEY, null) == null - ) { - binding.layoutConfigEmpty.visibility = View.VISIBLE - binding.layoutConfigLoaded.visibility = View.GONE - } else { - binding.layoutConfigEmpty.visibility = View.GONE - binding.layoutConfigLoaded.visibility = View.VISIBLE - binding.tvConfigName.text = preference?.getString(CONFIG_PROFILE_NAME, "") - binding.tvConfigPubKey.text = preference?.getString(CONFIG_PUB_KEY, "") - } - // Display version - val version = BuildConfig.VERSION_NAME - val versionLabel = if (BuildConfig.DEBUG) version else version.split("-").first() - binding.versionText.text = getString(R.string.label_version, versionLabel) - - // Display Toast message once a flag for config update's true - if (isConfigUpdated) { - Toast.makeText( - this@SettingsActivity, - getString(R.string.config_loaded), - Toast.LENGTH_LONG) - .show() - } - // Setup click listeners - setupViewListeners(preference) - } - - private fun setupViewListeners(preference: SharedPreferences?) { - val editor = preference?.edit() - // Language (English/Arabic) + // Arabic language binding.arabiclayout.setOnClickListener { binding.arabicpic.visibility = View.VISIBLE binding.englishpic.visibility = View.INVISIBLE saveLanguage(editor = editor, language = Language.AR) - startActivity( - Intent( - this, - MainActivity::class.java - ).addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) - ) + startActivity(Intent(this, MainActivity::class.java).addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)) } + // English language binding.englishLayout.setOnClickListener { binding.arabicpic.visibility = View.INVISIBLE binding.englishpic.visibility = View.VISIBLE saveLanguage(editor = editor, language = Language.EN) - startActivity( - Intent( - this, - MainActivity::class.java - ).addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) - ) + startActivity(Intent(this, MainActivity::class.java).addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)) } // Orientation (Portrait/Landscape) + val orientation = preference.getString(ORIENTATION, PORTRAIT.value) + if (orientation == PORTRAIT.value) { + binding.portraitCheck.visibility = View.VISIBLE + binding.landscapeCheck.visibility = View.INVISIBLE + } else { + binding.portraitCheck.visibility = View.INVISIBLE + binding.landscapeCheck.visibility = View.VISIBLE + } + + // Landscape binding.landscapeLayout.setOnClickListener { binding.portraitCheck.visibility = View.INVISIBLE binding.landscapeCheck.visibility = View.VISIBLE - saveToPreference(key = ORIENTATION, value = LANDSCAPE.value) - startActivity( - Intent( - this, - MainActivity::class.java - ).addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) - ) + saveToPreference(editor = editor, key = ORIENTATION, value = LANDSCAPE.value) + startActivity(Intent(this, MainActivity::class.java).addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)) } + // Portrait binding.portraitLayout.setOnClickListener { binding.portraitCheck.visibility = View.VISIBLE binding.landscapeCheck.visibility = View.INVISIBLE - saveToPreference(key = ORIENTATION, value = PORTRAIT.value) - startActivity( - Intent( - this, - MainActivity::class.java - ).addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) - ) + saveToPreference(editor = editor, key = ORIENTATION, value = PORTRAIT.value) + startActivity(Intent(this, MainActivity::class.java).addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)) } + // Display version + + val version = "1.0.2" + val versionLabel = if (BuildConfig.DEBUG) version else version.split("-").first() + binding.versionText.text = getString(R.string.label_version, versionLabel) + // Go Back binding.backspace.setOnClickListener { onBackPressed() this.finish() } - - binding.btnConfigReset.setOnClickListener { - // Reset and remove config profile name and public key - preference?.edit()?.remove(CONFIG_PUB_KEY)?.apply() - preference?.edit()?.remove(CONFIG_PROFILE_NAME)?.apply() - binding.tvConfigName.text = "" - binding.tvConfigPubKey.text = "" - - binding.layoutConfigEmpty.visibility = View.VISIBLE - binding.layoutConfigLoaded.visibility = View.GONE - } } - private fun saveLanguage(editor: SharedPreferences.Editor?, language: String) { + private fun saveLanguage(editor: SharedPreferences.Editor, language : String) { // Set new language - saveToPreference(Language.NAME, language) LanguageUtils.changeLanguage(this, language) + // Save new language to sharedPrefs + saveToPreference(editor, Language.NAME, language) } - private fun saveToPreference(key: String, value: String?) { - // Remove previous set - val editor = preference?.edit() - editor?.remove(key)?.apply() - editor?.putString(key, value) - editor?.apply() + private fun saveToPreference( editor: SharedPreferences.Editor, key: String, value : String) { + // Remove previous set language + editor.remove(key).apply() + editor.putString(key, value) + editor.apply() } - } \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_checkbox_marked_circle.xml b/app/src/main/res/drawable/ic_checkbox_marked_circle.xml deleted file mode 100644 index 5a23fb68..00000000 --- a/app/src/main/res/drawable/ic_checkbox_marked_circle.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_circle_check_fill.xml b/app/src/main/res/drawable/ic_circle_check_fill.xml deleted file mode 100644 index 978aa798..00000000 --- a/app/src/main/res/drawable/ic_circle_check_fill.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_launcher_foreground_beta.xml b/app/src/main/res/drawable/ic_launcher_foreground_beta.xml deleted file mode 100644 index a3c1ebcf..00000000 --- a/app/src/main/res/drawable/ic_launcher_foreground_beta.xml +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - - diff --git a/app/src/main/res/drawable/ic_scan_pdf417.xml b/app/src/main/res/drawable/ic_scan_pdf417.xml deleted file mode 100644 index 4da9d112..00000000 --- a/app/src/main/res/drawable/ic_scan_pdf417.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index b2701d37..f793561e 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -9,146 +9,131 @@ android:orientation="vertical" tools:context="org.newlogic.smartscanner.MainActivity"> - + app:layout_constraintHorizontal_bias="0.976" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintVertical_bias="0.4" /> - - - - - - - + app:layout_constraintTop_toTopOf="parent" /> - + - + - + + + + + + + + + + - + + + + + + + + + + - + + + + + + + + + + - + + + + + + + + + + - + + + + + + + + + + - - + - + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_raw_result.xml b/app/src/main/res/layout/activity_raw_result.xml deleted file mode 100644 index 922a8af2..00000000 --- a/app/src/main/res/layout/activity_raw_result.xml +++ /dev/null @@ -1,167 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/activity_result.xml b/app/src/main/res/layout/activity_result.xml index d0351a67..845581c0 100755 --- a/app/src/main/res/layout/activity_result.xml +++ b/app/src/main/res/layout/activity_result.xml @@ -5,13 +5,20 @@ android:layout_width="match_parent" android:layout_height="match_parent" tools:context="org.newlogic.smartscanner.result.ResultActivity"> + + + android:layout_height="1000dp" + android:orientation="vertical" + android:background="#FAFAFA" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent"> - - + android:layout_height="wrap_content" + android:layout_marginStart="16dp" + android:layout_marginTop="8dp" + android:layout_marginEnd="16dp" + android:fontFamily="@font/sourcesanspro_regular" + android:text="@string/label_hex" + android:textColor="@color/newlogic_black" + android:textSize="16sp" + tools:ignore="MissingConstraints" + android:visibility="gone" + tools:text="54 65 73 74 20 51 52 20 Test QR\n43 6F 64 65 Code" /> - + + + + + + + + - - + + + + + + + + + + android:layout_height="wrap_content" + android:layout_marginTop = "137dp" + android:fontFamily="@font/inter_bold" + android:gravity="center" + android:textColor="#E2781D" + android:editable="false" + android:textSize="34sp" + android:background="@null" + tools:ignore="Autofill,LabelFor"/> - + + android:layout_marginTop="203dp" + android:gravity="center" + android:fontFamily="@font/inter_regular" + android:textColor="#666666" + android:textSize="13sp" + android:text="@string/service_provider"/> + + + + + + + + + + + + android:layout_marginTop="40dp" + android:layout_marginStart="20dp" + android:textColor="#000000" + android:fontFamily="@font/inter_semi_bold" + android:editable="false" + android:textSize="12sp" + android:background="@null" + tools:ignore="Autofill,LabelFor" + app:layout_constraintTop_toBottomOf="@+id/valid_from"/> + + - - - + android:layout_marginStart="225dp" + android:layout_marginTop="20dp" + android:fontFamily="@font/inter_regular" + android:textColor="#666666" + android:textSize="11sp" + android:text="@string/valid_to"/> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + android:layout_marginStart="14dp" + android:background="@drawable/docs_icon_box"> -