Implemented reading QR coding and if it is a EPC QR code navigating to TransferMoneyDialog

This commit is contained in:
dankito 2020-10-21 18:16:24 +02:00
parent 4ef8e7330f
commit 64cb4ca9e2
20 changed files with 338 additions and 21 deletions

View File

@ -55,6 +55,8 @@ ext {
autocompleteVersion = "1.1.0"
zxingVersion = "3.3.0"
multiDexVersion = "2.0.1"
appCompatVersion = "1.1.0"
@ -149,6 +151,7 @@ task jarAll {
"fints4k:jvm6Jar",
"fints4k-jvm:jar",
"BankFinder:jvmJar",
"EpcQrCodeParser:jvmJar",
"BankingUiCommon:jvmJar",
"fints4kBankingClient:jvmJar",
"BankingUiCommon:jvmJar",
@ -162,6 +165,7 @@ task packAllForXcode {
"common:packForXcode",
"fints4k:packForXcode",
"BankFinder:packForXcode",
"EpcQrCodeParser:packForXcode",
"BankingUiCommon:packForXcode",
"fints4kBankingClient:packForXcode",
"BankingUiNativeIntegration:packForXcode"

View File

@ -106,6 +106,9 @@ dependencies {
implementation "com.otaliastudios:autocomplete:$autocompleteVersion"
implementation("com.journeyapps:zxing-android-embedded:4.1.0") { transitive = false } // transitive to use older Zxing version as ZXing 3.4.0 requires Android > 23
implementation "com.google.zxing:core:$zxingVersion"
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlinCoroutinesVersion"

View File

@ -1,12 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="net.dankito.banking.ui.android">
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="net.dankito.banking.ui.android"
>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<!-- Both are required for ZXing -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-sdk tools:overrideLibrary="com.google.zxing.client.android" />
<application
android:name="net.dankito.banking.ui.android.BankingApp"
@ -15,7 +23,9 @@
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
android:hardwareAccelerated="true"
android:theme="@style/AppTheme"
>
<activity
android:name=".activities.LandingActivity"

View File

@ -1,16 +1,27 @@
package net.dankito.banking.ui.android
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.view.Menu
import android.view.MotionEvent
import androidx.appcompat.app.ActionBarDrawerToggle
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.Toolbar
import androidx.core.os.postDelayed
import androidx.drawerlayout.widget.DrawerLayout
import com.github.clans.fab.FloatingActionMenu
import com.google.zxing.integration.android.IntentIntegrator
import com.google.zxing.integration.android.IntentResult
import kotlinx.android.synthetic.main.activity_main.*
import net.codinux.banking.tools.epcqrcode.ParseEpcQrCodeResult
import net.dankito.banking.ui.android.activities.BaseActivity
import net.dankito.banking.ui.android.di.BankingComponent
import net.dankito.banking.ui.android.di.BankingModule
import net.dankito.banking.ui.android.activities.BaseActivity
import net.dankito.banking.ui.android.views.DrawerView
import net.dankito.banking.ui.android.views.MainActivityFloatingActionMenuButton
import net.dankito.banking.ui.presenter.BankingPresenter
@ -87,6 +98,48 @@ class MainActivity : BaseActivity() {
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
permissionsService.onRequestPermissionsResult(requestCode, permissions, grantResults)
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
val scanQrCodeResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, data)
if (scanQrCodeResult != null) {
// at this point camera activity is still displayed and not returned yet to MainActivity -> app would crash if we don't wait
Handler(Looper.getMainLooper()).postDelayed(250) {
handleQrCodeScanResult(scanQrCodeResult)
}
}
else {
super.onActivityResult(requestCode, resultCode, data)
}
}
private fun handleQrCodeScanResult(scanQrCodeResult: IntentResult) {
scanQrCodeResult.contents?.let { decodedQrCode ->
val result = presenter.showTransferMoneyDialogWithDataFromQrCode(decodedQrCode)
if (result.successful == false) {
showParseQrCodeError(result)
}
}
}
protected fun showParseQrCodeError(result: ParseEpcQrCodeResult) {
// TODO: show localized error message that matches ParseEpcQrCodeResultCode
val errorMessage = getString(R.string.money_transfer_from_scanning_qr_code_error, result.error, result.decodedQrCode)
AlertDialog.Builder(this)
.setMessage(errorMessage)
.setPositiveButton(android.R.string.ok) { dialog, _ -> dialog.dismiss() }
.show()
}
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
if(floatingActionMenuButton.handlesTouch(event)) { // close menu when menu is opened and touch is outside floatingActionMenuButton
return true

View File

@ -1,11 +1,13 @@
package net.dankito.banking.ui.android.views
import android.Manifest
import android.content.Context
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.FragmentActivity
import com.github.clans.fab.FloatingActionButton
import com.github.clans.fab.FloatingActionMenu
import com.google.zxing.integration.android.IntentIntegrator
import kotlinx.android.synthetic.main.view_floating_action_button_main.view.*
import net.dankito.utils.multiplatform.toFile
import net.dankito.banking.ui.android.R
@ -27,6 +29,8 @@ open class MainActivityFloatingActionMenuButton(
protected lateinit var fabTransferMoney: FloatingActionButton
protected lateinit var fabMoneyTransferFromScanningQrCode: FloatingActionButton
protected lateinit var fabTransferMoneyFromPdf: FloatingActionButton
protected var lastSelectedFolder: File? = null
@ -51,14 +55,19 @@ open class MainActivityFloatingActionMenuButton(
}
fabTransferMoney = floatingActionMenu.fabTransferMoney
fabMoneyTransferFromScanningQrCode = floatingActionMenu.fabMoneyTransferFromScanningQrCode
fabTransferMoneyFromPdf = floatingActionMenu.fabTransferMoneyFromPdf
fabTransferMoney.setOnClickListener {
executeAndCloseMenu { presenter.showTransferMoneyDialog() }
}
fabMoneyTransferFromScanningQrCode.setOnClickListener {
executeAndCloseMenu { scanQrCode() }
}
fabTransferMoneyFromPdf.setOnClickListener {
executeAndCloseMenu { transferMoneyWithDataFromPdf() }
executeAndCloseMenu { showTransferMoneyDialogWithDataFromPdf() }
}
}
}
@ -67,11 +76,27 @@ open class MainActivityFloatingActionMenuButton(
protected open fun checkIfThereAreAccountsThatCanTransferMoney() {
fabTransferMoney.isEnabled = presenter.hasAccountsSupportTransferringMoney
fabMoneyTransferFromScanningQrCode.isEnabled = presenter.hasAccountsSupportTransferringMoney
fabTransferMoneyFromPdf.isEnabled = presenter.hasAccountsSupportTransferringMoney
}
protected open fun transferMoneyWithDataFromPdf() {
protected open fun scanQrCode() {
permissionsService.checkPermission(Manifest.permission.CAMERA, R.string.rationale_camera_permission_to_scan_qr_code) { _, isGranted ->
if (isGranted) {
floatingActionMenu.context.asActivity()?.let { activity ->
val intentIntegrator = IntentIntegrator(activity)
intentIntegrator.setOrientationLocked(false)
intentIntegrator.initiateScan(listOf(IntentIntegrator.QR_CODE))
// parsing decoded QR-code and showing TransferMoneyDialog is done in MainActivity.handleQrCodeScanResult()
}
}
}
}
protected open fun showTransferMoneyDialogWithDataFromPdf() {
(floatingActionMenu.context.asActivity() as? FragmentActivity)?.let { activity ->
val config = FileChooserDialogConfig(listOf("*.pdf"), lastSelectedFolder)
@ -79,17 +104,17 @@ open class MainActivityFloatingActionMenuButton(
selectedFile?.let {
lastSelectedFolder = selectedFile.parentFile
val result = presenter.transferMoneyWithDataFromPdf(selectedFile.toFile())
val result = presenter.showTransferMoneyDialogWithDataFromPdf(selectedFile.toFile())
if (result.type != ExtractTransferMoneyDataFromPdfResultType.Success) {
showTransferMoneyWithDataFromPdfError(activity, selectedFile, result)
showTransferMoneyDialogWithDataFromPdfError(activity, selectedFile, result)
}
}
}
}
}
protected open fun showTransferMoneyWithDataFromPdfError(context: Context, pdfFile: File, result: ExtractTransferMoneyDataFromPdfResult) {
protected open fun showTransferMoneyDialogWithDataFromPdfError(context: Context, pdfFile: File, result: ExtractTransferMoneyDataFromPdfResult) {
val errorMessage = when (result.type) {
ExtractTransferMoneyDataFromPdfResultType.NotASearchablePdf ->
context.getString(R.string.transfer_money_from_pdf_error_message_not_a_searchable_pdf, pdfFile.absolutePath)

View File

@ -25,6 +25,14 @@
fab:fab_label="@string/floating_action_menu_transfer_money_from_pdf"
/>
<com.github.clans.fab.FloatingActionButton
android:id="@+id/fabMoneyTransferFromScanningQrCode"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/MenuButtonsStyle"
fab:fab_label="@string/floating_action_menu_money_transfer_from_scanning_qr_code"
/>
<com.github.clans.fab.FloatingActionButton
android:id="@+id/fabTransferMoney"
android:layout_width="wrap_content"

View File

@ -63,8 +63,11 @@
<string name="floating_action_menu_add_account">Konto</string>
<string name="floating_action_menu_transfer_money">Überweisung</string>
<string name="floating_action_menu_money_transfer_from_scanning_qr_code">Überweisung aus QR-Code</string>
<string name="floating_action_menu_transfer_money_from_pdf">Überweisung aus PDF</string>
<string name="rationale_camera_permission_to_scan_qr_code">Um QR-Codes scannen zu können wird der Zugriff auf die Kamera benötigt.</string>
<string name="menu_home">Umsätze</string>
<string name="menu_main_update_transactions">Umsätze aktualisieren</string>
@ -107,6 +110,8 @@
<string name="dialog_transfer_money_message_transfer_successful">%1$s %2$s wurden erfolgreich an %3$s überwiesen.</string>
<string name="dialog_transfer_money_message_transfer_failed">Konnte nicht %1$s %2$s an %3$s überweisen.\n\nFehlermeldung Ihrer Bank:\n\n%4$s</string>
<string name="money_transfer_from_scanning_qr_code_error">Überweisungsdaten konnten nicht aus QR-Code gelesen werden: %1$s\n\Gelesener QR-Code ist:\n%2$s</string>
<string name="transfer_money_from_pdf_error_message_not_a_searchable_pdf">Konnte Text nicht aus Datei "%1$s" extrahieren. Enthält die Datei auch Text oder nur Bilder?</string>
<string name="transfer_money_from_pdf_error_message_could_not_extract_text">Text konnte nicht aus Datei "%1$s" extrahiert werden:\n\n%2$s</string>
<string name="transfer_money_from_pdf_error_message_could_not_extract_invoice_data">Überweisungsdaten konnten aus der Datei "%1$s" nicht ausgelesen werden:\n\n%2$s</string>

View File

@ -63,8 +63,11 @@
<string name="floating_action_menu_add_account">Account</string>
<string name="floating_action_menu_transfer_money">Transfer money</string>
<string name="floating_action_menu_money_transfer_from_scanning_qr_code">Money transfer from scanning QR-Code</string>
<string name="floating_action_menu_transfer_money_from_pdf">Transfer money from PDF</string>
<string name="rationale_camera_permission_to_scan_qr_code">To scan QR-Codes permission to access camera is required.</string>
<string name="menu_home">Home</string>
<string name="menu_main_update_transactions">Update transactions</string>
@ -107,6 +110,8 @@
<string name="dialog_transfer_money_message_transfer_successful">Successfully transferred %1$s %2$s to %3$s.</string>
<string name="dialog_transfer_money_message_transfer_failed">Could not transfer %1$s %2$s to %3$s.\n\nError message from your bank:\n\n%4$s</string>
<string name="money_transfer_from_scanning_qr_code_error">Could not extract transfer data from QR-Code: %1$s\n\nExtracted QR-Code was:\n%2$s</string>
<string name="transfer_money_from_pdf_error_message_not_a_searchable_pdf">File "%1$s" is not a searchable PDF. Could therefore not extract text from it.</string>
<string name="transfer_money_from_pdf_error_message_could_not_extract_text">Could not extract text from file "%1$s":\n\n%2$s</string>
<string name="transfer_money_from_pdf_error_message_could_not_extract_invoice_data">Could not extract cash transfer data from file "%1$s":\n\n%2$s</string>

View File

@ -54,7 +54,7 @@ open class MainMenuBar(protected val presenter: BankingPresenter) : View() {
item(messages["main.window.menu.file.new.cash.transfer.from.pdf"], KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN, KeyCodeCombination.SHIFT_DOWN)) {
enableWhen(areAccountsThatCanTransferMoneyAdded)
action { transferMoneyWithDataFromPdf() }
action { showTransferMoneyDialogWithDataFromPdf() }
}
}
@ -71,7 +71,7 @@ open class MainMenuBar(protected val presenter: BankingPresenter) : View() {
areAccountsThatCanTransferMoneyAdded.value = presenter.hasAccountsSupportTransferringMoney
}
protected open fun transferMoneyWithDataFromPdf() {
protected open fun showTransferMoneyDialogWithDataFromPdf() {
val fileChooser = FileChooser()
fileChooser.initialDirectory = lastSelectedFolder
@ -80,15 +80,15 @@ open class MainMenuBar(protected val presenter: BankingPresenter) : View() {
fileChooser.showOpenDialog(currentStage)?.let { pdfFile ->
lastSelectedFolder = pdfFile.parentFile
val result = presenter.transferMoneyWithDataFromPdf(pdfFile.toFile())
val result = presenter.showTransferMoneyDialogWithDataFromPdf(pdfFile.toFile())
if (result.type != ExtractTransferMoneyDataFromPdfResultType.Success) {
showTransferMoneyWithDataFromPdfError(pdfFile, result)
showTransferMoneyDialogWithDataFromPdfError(pdfFile, result)
}
}
}
protected open fun showTransferMoneyWithDataFromPdfError(pdfFile: File, result: ExtractTransferMoneyDataFromPdfResult) {
protected open fun showTransferMoneyDialogWithDataFromPdfError(pdfFile: File, result: ExtractTransferMoneyDataFromPdfResult) {
val errorMessageKey = when (result.type) {
ExtractTransferMoneyDataFromPdfResultType.NotASearchablePdf -> "transfer.money.from.pdf.error.message.not.a.searchable.pdf"
ExtractTransferMoneyDataFromPdfResultType.CouldNotExtractText -> "transfer.money.from.pdf.error.message.could.not.extract.text"

View File

@ -40,6 +40,8 @@ kotlin {
api project(":fints4k")
api project(":BankFinder")
api project(":EpcQrCodeParser")
}
}

View File

@ -32,6 +32,7 @@ import net.dankito.banking.util.extraction.IInvoiceDataExtractor
import net.dankito.banking.util.extraction.ITextExtractorRegistry
import net.dankito.banking.util.extraction.NoOpInvoiceDataExtractor
import net.dankito.banking.util.extraction.NoOpTextExtractorRegistry
import net.codinux.banking.tools.epcqrcode.*
import net.dankito.utils.multiplatform.*
import net.dankito.utils.multiplatform.log.LoggerFactory
import kotlin.collections.ArrayList
@ -49,7 +50,8 @@ open class BankingPresenter(
protected val textExtractorRegistry: ITextExtractorRegistry = NoOpTextExtractorRegistry(),
protected val invoiceDataExtractor: IInvoiceDataExtractor = NoOpInvoiceDataExtractor(),
protected val currencyInfoProvider: ICurrencyInfoProvider = CurrencyInfoProvider(),
protected val asyncRunner: IAsyncRunner = CoroutinesAsyncRunner()
protected val asyncRunner: IAsyncRunner = CoroutinesAsyncRunner(),
protected val qrCodeParser: EpcQrCodeParser = EpcQrCodeParser() // TODO: create interface
) {
companion object {
@ -586,7 +588,28 @@ open class BankingPresenter(
}
}
open fun transferMoneyWithDataFromPdf(pdf: File): ExtractTransferMoneyDataFromPdfResult {
open fun showTransferMoneyDialogWithDataFromQrCode(decodedQrCode: String): ParseEpcQrCodeResult {
val result = qrCodeParser.parseEpcQrCode(decodedQrCode)
if (result.successful) {
result.epcQrCode?.let { epcQrCode ->
// TODO: show originatorInformation to user
val transferMoneyData = TransferMoneyData(
allAccounts.first(),
epcQrCode.receiverName,
epcQrCode.iban,
epcQrCode.bic ?: "",
epcQrCode.amount?.let { BigDecimal(it) } ?: BigDecimal.Zero,
epcQrCode.remittance)
showTransferMoneyDialog(transferMoneyData)
}
}
return result
}
open fun showTransferMoneyDialogWithDataFromPdf(pdf: File): ExtractTransferMoneyDataFromPdfResult {
val extractionResult = textExtractorRegistry.extractTextWithBestExtractorForFile(pdf)
if (extractionResult.couldExtractText == false || extractionResult.text == null) {

View File

@ -6,6 +6,8 @@ plugins {
ext.artifactName = "banking-ui-native-integration"
def frameworkName = "BankingUiSwift"
kotlin {
@ -15,12 +17,13 @@ kotlin {
fromPreset(iOSTarget, 'ios') {
binaries {
framework {
baseName = "BankingUiSwift"
baseName = frameworkName
// transitiveExport = true
export(project(":BankingUiCommon"))
export(project(":fints4kBankingClient"))
export(project(":BankFinder"))
export(project(":EpcQrCodeParser"))
// do not add fints4k to exports, would lead to a lot of naming conflicts. In this way fints4k classes get prefixed with 'Fints4k' which is Ok
// export(project(":fints4k"))
// exporting common would lead to naming conflicts with Foundation classes like Date, UUID, Thread, ...

View File

@ -35,6 +35,8 @@
3642F01A2502931F005186FE /* RealTimeTransferInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3642F0192502931F005186FE /* RealTimeTransferInfoView.swift */; };
3642F04B25031157005186FE /* SectionHeaderWithRightAlignedEditButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3642F04A25031157005186FE /* SectionHeaderWithRightAlignedEditButton.swift */; };
36671255253A761500BD2301 /* BankCredentialsPasswordView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36671254253A761500BD2301 /* BankCredentialsPasswordView.swift */; };
36671290253F8D5200BD2301 /* ScanQrCodeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3667128F253F8D5200BD2301 /* ScanQrCodeViewController.swift */; };
36671294254045E100BD2301 /* EpcQrCodeParser.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 36671293254045E100BD2301 /* EpcQrCodeParser.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
36693A4E25280BCB00BB7AE5 /* InfoButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36693A4D25280BCB00BB7AE5 /* InfoButton.swift */; };
366FA4DA24C472A90094F009 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 366FA4D924C472A90094F009 /* Extensions.swift */; };
366FA4DC24C479120094F009 /* BankInfoListItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 366FA4DB24C479120094F009 /* BankInfoListItem.swift */; };
@ -157,6 +159,7 @@
dstPath = "";
dstSubfolderSpec = 10;
files = (
36671294254045E100BD2301 /* EpcQrCodeParser.framework in Embed Frameworks */,
3684EB8F250B7F3C0001139E /* BankingUiCommon.framework in Embed Frameworks */,
36BCF86A24BA550D005BEC29 /* BankFinder.framework in Embed Frameworks */,
36BCF85F24BA4DA8005BEC29 /* MultiplatformUtils.framework in Embed Frameworks */,
@ -199,6 +202,8 @@
3642F0192502931F005186FE /* RealTimeTransferInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealTimeTransferInfoView.swift; sourceTree = "<group>"; };
3642F04A25031157005186FE /* SectionHeaderWithRightAlignedEditButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SectionHeaderWithRightAlignedEditButton.swift; sourceTree = "<group>"; };
36671254253A761500BD2301 /* BankCredentialsPasswordView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BankCredentialsPasswordView.swift; sourceTree = "<group>"; };
3667128F253F8D5200BD2301 /* ScanQrCodeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanQrCodeViewController.swift; sourceTree = "<group>"; };
36671293254045E100BD2301 /* EpcQrCodeParser.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = EpcQrCodeParser.framework; path = "../../tools/EpcQrCodeParser/build/xcode-frameworks/EpcQrCodeParser.framework"; sourceTree = "<group>"; };
36693A4D25280BCB00BB7AE5 /* InfoButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoButton.swift; sourceTree = "<group>"; };
366FA4D924C472A90094F009 /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = "<group>"; };
366FA4DB24C479120094F009 /* BankInfoListItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BankInfoListItem.swift; sourceTree = "<group>"; };
@ -371,6 +376,7 @@
36E21ED424DC549800649DC8 /* BankSettingsDialog.swift */,
36E21ED624DC617200649DC8 /* BankAccountSettingsDialog.swift */,
36B8A4472503D12100C15359 /* ProtectAppSettingsDialog.swift */,
3667128F253F8D5200BD2301 /* ScanQrCodeViewController.swift */,
);
path = dialogs;
sourceTree = "<group>";
@ -481,6 +487,7 @@
36FC928F24B39A05002B12E9 = {
isa = PBXGroup;
children = (
36671293254045E100BD2301 /* EpcQrCodeParser.framework */,
3684EB8E250B7F3C0001139E /* BankingUiCommon.framework */,
3684EB8C250B7F2B0001139E /* BankingUiCommon.framework.dSYM */,
36FC929A24B39A05002B12E9 /* BankingiOSApp */,
@ -992,6 +999,7 @@
36BE068F24CEE1BD00CBBB68 /* AllBanksListItem.swift in Sources */,
360782C324E49FF70098FEFE /* ValidationLabel.swift in Sources */,
3684EB92250FD4AF0001139E /* LabelledValue.swift in Sources */,
36671290253F8D5200BD2301 /* ScanQrCodeViewController.swift in Sources */,
36BE069124CEF52800CBBB68 /* UpdateButton.swift in Sources */,
36E21ED124DC540400649DC8 /* SettingsDialog.swift in Sources */,
3684EB8B2508F6F00001139E /* SearchBarWithLabel.swift in Sources */,

View File

@ -130,7 +130,8 @@
/* New action sheet */
"Show transfer money dialog" = "Transfer money";
"Show transfer money dialog" = "Money transfer";
"Money transfer from scanning QR-Code" = "Money transfer from QR-Code";
/* TransferMoneyDialog */

View File

@ -2,6 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSCameraUsageDescription</key>
<string>Used to scan QR codes to initiate money transfer</string>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>

View File

@ -62,7 +62,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
DependencyInjector.register(dependency: presenter)
window.rootViewController = UINavigationController(rootViewController: TabBarController())
window.rootViewController = UINavigationController(rootViewController: TabBarController(presenter))
}
}

View File

@ -1,10 +1,25 @@
import SwiftUI
import AVFoundation
import BankingUiSwift
class TabBarController : UITabBarController, UITabBarControllerDelegate {
@ObservedObject var data: AppData = AppData()
private let presenter: BankingPresenterSwift
init(_ presenter: BankingPresenterSwift) {
self.presenter = presenter
super.init(nibName: nil, bundle: Bundle.main)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
@ -88,15 +103,33 @@ class TabBarController : UITabBarController, UITabBarControllerDelegate {
private func showNewOptionsActionSheet() {
let transferMoneyAction = UIAlertAction.default("Show transfer money dialog".localize()) { SceneDelegate.navigateToView(TransferMoneyDialog()) }
let moneyTransferFromQrCodeAction = UIAlertAction.default("Money transfer from scanning QR-Code".localize()) { self.scanQrCodeIfCameraAccessGranted() }
moneyTransferFromQrCodeAction.isEnabled = data.hasAccountsThatSupportTransferringMoney
let transferMoneyAction = UIAlertAction.default("Show transfer money dialog".localize()) { self.presenter.showTransferMoneyDialog(preselectedValues: nil) }
transferMoneyAction.isEnabled = data.hasAccountsThatSupportTransferringMoney
ActionSheet(
nil,
moneyTransferFromQrCodeAction,
transferMoneyAction,
UIAlertAction.default("Add account") { SceneDelegate.navigateToView(AddAccountDialog()) },
UIAlertAction.cancel()
).show(self.tabBar, self.tabBar.bounds.midX, 0)
}
private func scanQrCodeIfCameraAccessGranted() {
AVCaptureDevice.requestAccess(for: .video) { granted in
if granted {
DispatchQueue.main.async { // completionHandler is called on an arbitrary dispatch queue
SceneDelegate.navigateToViewController(ScanQrCodeViewController() { decodedQrCode in
if let decodedQrCode = decodedQrCode {
self.presenter.showTransferMoneyDialogWithDataFromQrCode(decodedQrCode: decodedQrCode)
}
})
}
}
}
}
}

View File

@ -132,6 +132,7 @@
/* New action sheet */
"Show transfer money dialog" = "Überweisung";
"Money transfer from scanning QR-Code" = "Überweisung aus QR-Code";
/* TransferMoneyDialog */

View File

@ -0,0 +1,131 @@
import AVFoundation
import UIKit
/**
Copied from Hacking with Swift: https://www.hackingwithswift.com/example-code/media/how-to-scan-a-qr-code
*/
class ScanQrCodeViewController: UIViewController, AVCaptureMetadataOutputObjectsDelegate {
private let scanResult: (String?) -> Void
private var captureSession: AVCaptureSession!
private var previewLayer: AVCaptureVideoPreviewLayer!
init(_ scanResult: @escaping (String?) -> Void) {
self.scanResult = scanResult
super.init(nibName: nil, bundle: Bundle.main)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.black
captureSession = AVCaptureSession()
guard let videoCaptureDevice = AVCaptureDevice.default(for: .video) else { return }
let videoInput: AVCaptureDeviceInput
do {
videoInput = try AVCaptureDeviceInput(device: videoCaptureDevice)
} catch {
return
}
if (captureSession.canAddInput(videoInput)) {
captureSession.addInput(videoInput)
} else {
failed()
return
}
let metadataOutput = AVCaptureMetadataOutput()
if (captureSession.canAddOutput(metadataOutput)) {
captureSession.addOutput(metadataOutput)
metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
metadataOutput.metadataObjectTypes = [.qr]
} else {
failed()
return
}
previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
previewLayer.frame = view.layer.bounds
previewLayer.videoGravity = .resizeAspectFill
view.layer.addSublayer(previewLayer)
captureSession.startRunning()
}
func failed() {
self.closeDialog()
let ac = UIAlertController(title: "Scanning not supported", message: "Your device does not support scanning a code from an item. Please use a device with a camera.", preferredStyle: .alert)
ac.addAction(UIAlertAction(title: "OK", style: .default))
present(ac, animated: true)
captureSession = nil
scanResult(nil)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if (captureSession?.isRunning == false) {
captureSession.startRunning()
}
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if (captureSession?.isRunning == true) {
captureSession.stopRunning()
}
}
func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
captureSession.stopRunning()
var didCloseDialog = false
if let metadataObject = metadataObjects.first {
if let readableObject = metadataObject as? AVMetadataMachineReadableCodeObject {
if let decodedQrCode = readableObject.stringValue {
AudioServicesPlaySystemSound(SystemSoundID(kSystemSoundID_Vibrate))
didCloseDialog = true
SceneDelegate.dismissCurrentView()
scanResult(decodedQrCode)
}
}
}
if didCloseDialog == false { // for all other cases where QR code could not successfully be read
closeDialog()
}
}
private func closeDialog() {
SceneDelegate.dismissCurrentView()
}
override var prefersStatusBarHidden: Bool {
return true
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return .portrait
}
}

View File

@ -94,7 +94,7 @@ struct AccountTransactionListItem: View {
}
private func navigateToTransferMoneyDialog(_ preselectedValues: TransferMoneyData) {
SceneDelegate.navigateToView(TransferMoneyDialog(preselectedValues: preselectedValues))
SceneDelegate.navigateToView(TransferMoneyDialog(preselectedValues))
}
}