Implemented extracting cash transfer data from PDF files and setting them as preselected values in TransferMoneyDialog
This commit is contained in:
parent
80680a16b6
commit
996204f54f
|
@ -27,6 +27,11 @@ ext {
|
||||||
luceneUtilsVersion = "0.5.1-SNAPSHOT"
|
luceneUtilsVersion = "0.5.1-SNAPSHOT"
|
||||||
|
|
||||||
|
|
||||||
|
textExtractorVersion = "0.5.1-SNAPSHOT"
|
||||||
|
|
||||||
|
textInfoExtractorVersion = "1.0.1-SNAPSHOT"
|
||||||
|
|
||||||
|
|
||||||
hbci4jVersion = '3.1.37'
|
hbci4jVersion = '3.1.37'
|
||||||
|
|
||||||
|
|
||||||
|
@ -41,6 +46,8 @@ ext {
|
||||||
androidTargetSdkVersion = 28
|
androidTargetSdkVersion = 28
|
||||||
|
|
||||||
|
|
||||||
|
fileChooserDialogVersion = "1.2.0-androidx"
|
||||||
|
|
||||||
androidUtilsVersion = '1.1.1-SNAPSHOT'
|
androidUtilsVersion = '1.1.1-SNAPSHOT'
|
||||||
|
|
||||||
materialDrawerVersion = "8.0.1"
|
materialDrawerVersion = "8.0.1"
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
android.enableJetifier=true
|
android.enableJetifier=true
|
||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
kotlin.code.style=official
|
kotlin.code.style=official
|
||||||
|
|
||||||
|
# Specifies the JVM arguments used for the daemon process.
|
||||||
|
# The setting is particularly useful for tweaking memory settings.
|
||||||
|
org.gradle.jvmargs=-Xmx3072m
|
|
@ -83,6 +83,10 @@ dependencies {
|
||||||
implementation project(':BankingPersistenceJson')
|
implementation project(':BankingPersistenceJson')
|
||||||
implementation project(':LuceneBankingPersistence')
|
implementation project(':LuceneBankingPersistence')
|
||||||
|
|
||||||
|
|
||||||
|
implementation "net.dankito.text.extraction:itext2-text-extractor:$textExtractorVersion"
|
||||||
|
implementation "net.dankito.text.extraction:pdfbox-android-text-extractor:$textExtractorVersion"
|
||||||
|
|
||||||
implementation "com.github.clans:fab:$clansFloatingActionButtonVersion"
|
implementation "com.github.clans:fab:$clansFloatingActionButtonVersion"
|
||||||
|
|
||||||
implementation "com.otaliastudios:autocomplete:$autocompleteVersion"
|
implementation "com.otaliastudios:autocomplete:$autocompleteVersion"
|
||||||
|
@ -90,6 +94,14 @@ dependencies {
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlinCoroutinesVersion"
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlinCoroutinesVersion"
|
||||||
|
|
||||||
|
implementation "net.dankito.filechooserdialog:filechooserdialog-android:$fileChooserDialogVersion", {
|
||||||
|
exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib'
|
||||||
|
exclude group: 'com.android.support', module: 'appcompat-v7'
|
||||||
|
exclude group: 'com.android.support', module: 'design'
|
||||||
|
exclude group: 'com.android.support.constraint', module: 'constraint-layout'
|
||||||
|
exclude module: 'recyclerview-v7'
|
||||||
|
}
|
||||||
|
|
||||||
implementation "net.dankito.utils:android-utils:$androidUtilsVersion", {
|
implementation "net.dankito.utils:android-utils:$androidUtilsVersion", {
|
||||||
exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-jdk7'
|
exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-jdk7'
|
||||||
exclude group: 'com.android.support', module: 'appcompat-v7'
|
exclude group: 'com.android.support', module: 'appcompat-v7'
|
||||||
|
|
|
@ -14,6 +14,8 @@ import net.dankito.banking.ui.android.activities.BaseActivity
|
||||||
import net.dankito.banking.ui.android.views.DrawerView
|
import net.dankito.banking.ui.android.views.DrawerView
|
||||||
import net.dankito.banking.ui.android.views.MainActivityFloatingActionMenuButton
|
import net.dankito.banking.ui.android.views.MainActivityFloatingActionMenuButton
|
||||||
import net.dankito.banking.ui.presenter.BankingPresenter
|
import net.dankito.banking.ui.presenter.BankingPresenter
|
||||||
|
import net.dankito.utils.android.permissions.IPermissionsService
|
||||||
|
import net.dankito.utils.android.permissions.PermissionsService
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
||||||
|
@ -27,6 +29,8 @@ class MainActivity : BaseActivity() {
|
||||||
|
|
||||||
private lateinit var floatingActionMenuButton: MainActivityFloatingActionMenuButton
|
private lateinit var floatingActionMenuButton: MainActivityFloatingActionMenuButton
|
||||||
|
|
||||||
|
private val permissionsService: IPermissionsService = PermissionsService(this)
|
||||||
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
protected lateinit var presenter: BankingPresenter
|
protected lateinit var presenter: BankingPresenter
|
||||||
|
@ -73,7 +77,7 @@ class MainActivity : BaseActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
val floatingActionMenu = findViewById<FloatingActionMenu>(R.id.floatingActionMenu)
|
val floatingActionMenu = findViewById<FloatingActionMenu>(R.id.floatingActionMenu)
|
||||||
floatingActionMenuButton = MainActivityFloatingActionMenuButton(floatingActionMenu, presenter)
|
floatingActionMenuButton = MainActivityFloatingActionMenuButton(floatingActionMenu, permissionsService, presenter)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
|
|
|
@ -19,6 +19,12 @@ import net.dankito.banking.util.BankIconFinder
|
||||||
import net.dankito.banking.util.IBankIconFinder
|
import net.dankito.banking.util.IBankIconFinder
|
||||||
import net.dankito.banking.bankfinder.IBankFinder
|
import net.dankito.banking.bankfinder.IBankFinder
|
||||||
import net.dankito.banking.bankfinder.LuceneBankFinder
|
import net.dankito.banking.bankfinder.LuceneBankFinder
|
||||||
|
import net.dankito.text.extraction.ITextExtractorRegistry
|
||||||
|
import net.dankito.text.extraction.TextExtractorRegistry
|
||||||
|
import net.dankito.text.extraction.info.invoice.IInvoiceDataExtractor
|
||||||
|
import net.dankito.text.extraction.info.invoice.InvoiceDataExtractor
|
||||||
|
import net.dankito.text.extraction.pdf.PdfBoxAndroidPdfTextExtractor
|
||||||
|
import net.dankito.text.extraction.pdf.iText2PdfTextExtractor
|
||||||
import net.dankito.utils.IThreadPool
|
import net.dankito.utils.IThreadPool
|
||||||
import net.dankito.utils.ThreadPool
|
import net.dankito.utils.ThreadPool
|
||||||
import net.dankito.utils.serialization.ISerializer
|
import net.dankito.utils.serialization.ISerializer
|
||||||
|
@ -78,10 +84,11 @@ class BankingModule(private val applicationContext: Context) {
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideBankingPresenter(bankingClientCreator: IBankingClientCreator, bankFinder: IBankFinder,
|
fun provideBankingPresenter(bankingClientCreator: IBankingClientCreator, bankFinder: IBankFinder,
|
||||||
@Named(DatabaseFolderKey) databaseFolder: File, @Named(DataFolderKey) dataFolder: File,
|
@Named(DatabaseFolderKey) databaseFolder: File, @Named(DataFolderKey) dataFolder: File,
|
||||||
persister: IBankingPersistence, bankIconFinder: IBankIconFinder, remitteeSearcher: IRemitteeSearcher,
|
persister: IBankingPersistence, remitteeSearcher: IRemitteeSearcher, bankIconFinder: IBankIconFinder,
|
||||||
router: IRouter, serializer: ISerializer, threadPool: IThreadPool) : BankingPresenter {
|
textExtractorRegistry: ITextExtractorRegistry, router: IRouter, invoiceDataExtractor: IInvoiceDataExtractor,
|
||||||
|
serializer: ISerializer, threadPool: IThreadPool) : BankingPresenter {
|
||||||
return BankingPresenter(bankingClientCreator, bankFinder, databaseFolder, dataFolder, persister,
|
return BankingPresenter(bankingClientCreator, bankFinder, databaseFolder, dataFolder, persister,
|
||||||
remitteeSearcher, bankIconFinder, router, serializer, threadPool)
|
remitteeSearcher, bankIconFinder, textExtractorRegistry, router, invoiceDataExtractor, serializer, threadPool)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
@ -127,6 +134,22 @@ class BankingModule(private val applicationContext: Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideTextExtractorRegistry(applicationContext: Context) : ITextExtractorRegistry {
|
||||||
|
// TODO: add PdfTypeDetector
|
||||||
|
return TextExtractorRegistry(listOf(
|
||||||
|
iText2PdfTextExtractor(), PdfBoxAndroidPdfTextExtractor(applicationContext)
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideInvoiceDataExtractor() : IInvoiceDataExtractor {
|
||||||
|
return InvoiceDataExtractor()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideWebClient() : IWebClient {
|
fun provideWebClient() : IWebClient {
|
||||||
|
|
|
@ -1,18 +1,36 @@
|
||||||
package net.dankito.banking.ui.android.views
|
package net.dankito.banking.ui.android.views
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.fragment.app.FragmentActivity
|
||||||
import com.github.clans.fab.FloatingActionButton
|
import com.github.clans.fab.FloatingActionButton
|
||||||
import com.github.clans.fab.FloatingActionMenu
|
import com.github.clans.fab.FloatingActionMenu
|
||||||
import kotlinx.android.synthetic.main.view_floating_action_button_main.view.*
|
import kotlinx.android.synthetic.main.view_floating_action_button_main.view.*
|
||||||
|
import net.dankito.banking.ui.android.R
|
||||||
|
import net.dankito.banking.ui.model.moneytransfer.ExtractTransferMoneyDataFromPdfResult
|
||||||
|
import net.dankito.banking.ui.model.moneytransfer.ExtractTransferMoneyDataFromPdfResultType
|
||||||
import net.dankito.banking.ui.presenter.BankingPresenter
|
import net.dankito.banking.ui.presenter.BankingPresenter
|
||||||
|
import net.dankito.filechooserdialog.FileChooserDialog
|
||||||
|
import net.dankito.filechooserdialog.model.FileChooserDialogConfig
|
||||||
import net.dankito.utils.android.extensions.asActivity
|
import net.dankito.utils.android.extensions.asActivity
|
||||||
|
import net.dankito.utils.android.permissions.IPermissionsService
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
|
||||||
open class MainActivityFloatingActionMenuButton(floatingActionMenu: FloatingActionMenu, protected val presenter: BankingPresenter)
|
open class MainActivityFloatingActionMenuButton(
|
||||||
: FloatingActionMenuButton(floatingActionMenu) {
|
floatingActionMenu: FloatingActionMenu,
|
||||||
|
protected val permissionsService: IPermissionsService,
|
||||||
|
protected val presenter: BankingPresenter
|
||||||
|
) : FloatingActionMenuButton(floatingActionMenu) {
|
||||||
|
|
||||||
protected lateinit var fabTransferMoney: FloatingActionButton
|
protected lateinit var fabTransferMoney: FloatingActionButton
|
||||||
|
|
||||||
|
protected lateinit var fabTransferMoneyFromPdf: FloatingActionButton
|
||||||
|
|
||||||
|
protected var lastSelectedFolder: File? = null
|
||||||
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
setupButtons(floatingActionMenu)
|
setupButtons(floatingActionMenu)
|
||||||
|
|
||||||
|
@ -32,16 +50,59 @@ open class MainActivityFloatingActionMenuButton(floatingActionMenu: FloatingActi
|
||||||
}
|
}
|
||||||
|
|
||||||
fabTransferMoney = floatingActionMenu.fabTransferMoney
|
fabTransferMoney = floatingActionMenu.fabTransferMoney
|
||||||
|
fabTransferMoneyFromPdf = floatingActionMenu.fabTransferMoneyFromPdf
|
||||||
|
|
||||||
fabTransferMoney.setOnClickListener {
|
fabTransferMoney.setOnClickListener {
|
||||||
executeAndCloseMenu { presenter.showTransferMoneyDialog() }
|
executeAndCloseMenu { presenter.showTransferMoneyDialog() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fabTransferMoneyFromPdf.setOnClickListener {
|
||||||
|
executeAndCloseMenu { transferMoneyWithDataFromPdf() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected open fun checkIfThereAreAccountsThatCanTransferMoney() {
|
protected open fun checkIfThereAreAccountsThatCanTransferMoney() {
|
||||||
fabTransferMoney.isEnabled = presenter.hasBankAccountsSupportTransferringMoney
|
fabTransferMoney.isEnabled = presenter.hasBankAccountsSupportTransferringMoney
|
||||||
|
|
||||||
|
fabTransferMoneyFromPdf.isEnabled = presenter.hasBankAccountsSupportTransferringMoney
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected open fun transferMoneyWithDataFromPdf() {
|
||||||
|
(floatingActionMenu.context.asActivity() as? FragmentActivity)?.let { activity ->
|
||||||
|
val config = FileChooserDialogConfig(listOf("*.pdf"), lastSelectedFolder)
|
||||||
|
|
||||||
|
FileChooserDialog().showOpenSingleFileDialog(activity, permissionsService, config) { _, selectedFile ->
|
||||||
|
selectedFile?.let {
|
||||||
|
lastSelectedFolder = selectedFile.parentFile
|
||||||
|
|
||||||
|
val result = presenter.transferMoneyWithDataFromPdf(selectedFile)
|
||||||
|
|
||||||
|
if (result.type != ExtractTransferMoneyDataFromPdfResultType.Success) {
|
||||||
|
showTransferMoneyWithDataFromPdfError(activity, selectedFile, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected open fun showTransferMoneyWithDataFromPdfError(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)
|
||||||
|
ExtractTransferMoneyDataFromPdfResultType.CouldNotExtractText ->
|
||||||
|
context.getString(R.string.transfer_money_from_pdf_error_message_could_not_extract_text, pdfFile.absolutePath, result.error)
|
||||||
|
ExtractTransferMoneyDataFromPdfResultType.CouldNotExtractInvoiceDataFromExtractedText ->
|
||||||
|
context.getString(R.string.transfer_money_from_pdf_error_message_could_not_extract_invoice_data, pdfFile.absolutePath, result.error)
|
||||||
|
ExtractTransferMoneyDataFromPdfResultType.Success -> "" // will never come to this
|
||||||
|
}
|
||||||
|
|
||||||
|
AlertDialog.Builder(context)
|
||||||
|
.setMessage(errorMessage)
|
||||||
|
.setPositiveButton(android.R.string.ok) { dialog, _ -> dialog.dismiss() }
|
||||||
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -17,6 +17,14 @@
|
||||||
fab:menu_animationDelayPerItem="50">
|
fab:menu_animationDelayPerItem="50">
|
||||||
|
|
||||||
|
|
||||||
|
<com.github.clans.fab.FloatingActionButton
|
||||||
|
android:id="@+id/fabTransferMoneyFromPdf"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
style="@style/MenuButtonsStyle"
|
||||||
|
fab:fab_label="@string/floating_action_menu_transfer_money_from_pdf"
|
||||||
|
/>
|
||||||
|
|
||||||
<com.github.clans.fab.FloatingActionButton
|
<com.github.clans.fab.FloatingActionButton
|
||||||
android:id="@+id/fabTransferMoney"
|
android:id="@+id/fabTransferMoney"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
|
|
||||||
<string name="floating_action_menu_add_account">Konto</string>
|
<string name="floating_action_menu_add_account">Konto</string>
|
||||||
<string name="floating_action_menu_transfer_money">Überweisung</string>
|
<string name="floating_action_menu_transfer_money">Überweisung</string>
|
||||||
|
<string name="floating_action_menu_transfer_money_from_pdf">Überweisung aus PDF</string>
|
||||||
|
|
||||||
<string name="menu_home">Umsätze</string>
|
<string name="menu_home">Umsätze</string>
|
||||||
|
|
||||||
|
@ -60,6 +61,10 @@
|
||||||
<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_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="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="transfer_money_from_pdf_error_message_not_a_searchable_pdf">Konnte Text nicht aus Datei "%1$s" extrahieren. Enthält sie 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>
|
||||||
|
|
||||||
<string name="view_tan_image_size_controls_size">Größe:</string>
|
<string name="view_tan_image_size_controls_size">Größe:</string>
|
||||||
<string name="view_tan_image_size_controls_decrease_size">-</string>
|
<string name="view_tan_image_size_controls_decrease_size">-</string>
|
||||||
<string name="view_tan_image_size_controls_increase_size">+</string>
|
<string name="view_tan_image_size_controls_increase_size">+</string>
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
|
|
||||||
<string name="floating_action_menu_add_account">Account</string>
|
<string name="floating_action_menu_add_account">Account</string>
|
||||||
<string name="floating_action_menu_transfer_money">Transfer money</string>
|
<string name="floating_action_menu_transfer_money">Transfer money</string>
|
||||||
|
<string name="floating_action_menu_transfer_money_from_pdf">Transfer money from PDF</string>
|
||||||
|
|
||||||
<string name="menu_home">Home</string>
|
<string name="menu_home">Home</string>
|
||||||
|
|
||||||
|
@ -60,6 +61,10 @@
|
||||||
<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_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="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="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>
|
||||||
|
|
||||||
<string name="view_tan_image_size_controls_size">Size:</string>
|
<string name="view_tan_image_size_controls_size">Size:</string>
|
||||||
<string name="view_tan_image_size_controls_decrease_size">-</string>
|
<string name="view_tan_image_size_controls_decrease_size">-</string>
|
||||||
<string name="view_tan_image_size_controls_increase_size">+</string>
|
<string name="view_tan_image_size_controls_increase_size">+</string>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<resources>
|
<resources>
|
||||||
|
|
||||||
<!-- Base application theme. -->
|
<!-- Base application theme. -->
|
||||||
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
|
<style name="AppTheme" parent="FileChooserDialogAppThemeLight">
|
||||||
<!-- Customize your theme here. -->
|
<!-- Customize your theme here. -->
|
||||||
<item name="colorPrimary">@color/colorPrimary</item>
|
<item name="colorPrimary">@color/colorPrimary</item>
|
||||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||||
|
|
|
@ -24,8 +24,18 @@ dependencies {
|
||||||
implementation project(':fints4kBankingClient')
|
implementation project(':fints4kBankingClient')
|
||||||
implementation project(':hbci4jBankingClient')
|
implementation project(':hbci4jBankingClient')
|
||||||
|
|
||||||
|
implementation project(':fints4k')
|
||||||
|
implementation project(':fints4k-jvm')
|
||||||
|
|
||||||
implementation project(':LuceneBankingPersistence')
|
implementation project(':LuceneBankingPersistence')
|
||||||
|
|
||||||
|
|
||||||
|
implementation "net.dankito.text.extraction:poppler-text-extractor:$textExtractorVersion"
|
||||||
|
implementation "net.dankito.text.extraction:pdfbox-text-extractor:$textExtractorVersion"
|
||||||
|
implementation "net.dankito.text.extraction:itext2-text-extractor:$textExtractorVersion"
|
||||||
|
implementation "net.dankito.text.extraction:tesseract4-commandline-text-extractor:$textExtractorVersion"
|
||||||
|
implementation "net.dankito.text.extraction:tika-text-extractor:$textExtractorVersion"
|
||||||
|
|
||||||
implementation "ch.qos.logback:logback-classic:$logbackVersion"
|
implementation "ch.qos.logback:logback-classic:$logbackVersion"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,12 @@ import net.dankito.banking.util.BankIconFinder
|
||||||
import net.dankito.banking.bankfinder.LuceneBankFinder
|
import net.dankito.banking.bankfinder.LuceneBankFinder
|
||||||
import net.dankito.banking.persistence.LuceneBankingPersistence
|
import net.dankito.banking.persistence.LuceneBankingPersistence
|
||||||
import net.dankito.banking.search.LuceneRemitteeSearcher
|
import net.dankito.banking.search.LuceneRemitteeSearcher
|
||||||
|
import net.dankito.text.extraction.TextExtractorRegistry
|
||||||
|
import net.dankito.text.extraction.TikaTextExtractor
|
||||||
|
import net.dankito.text.extraction.image.Tesseract4CommandlineImageTextExtractor
|
||||||
|
import net.dankito.text.extraction.image.model.OcrLanguage
|
||||||
|
import net.dankito.text.extraction.image.model.TesseractConfig
|
||||||
|
import net.dankito.text.extraction.pdf.*
|
||||||
import net.dankito.utils.web.client.OkHttpWebClient
|
import net.dankito.utils.web.client.OkHttpWebClient
|
||||||
import tornadofx.*
|
import tornadofx.*
|
||||||
import tornadofx.FX.Companion.messages
|
import tornadofx.FX.Companion.messages
|
||||||
|
@ -26,11 +32,20 @@ class MainWindow : View(messages["application.title"]) {
|
||||||
|
|
||||||
private val indexFolder = File(dataFolder, "index")
|
private val indexFolder = File(dataFolder, "index")
|
||||||
|
|
||||||
|
private val tesseractTextExtractor = Tesseract4CommandlineImageTextExtractor(TesseractConfig(listOf(OcrLanguage.English, OcrLanguage.German)))
|
||||||
|
|
||||||
|
private val textExtractorRegistry = TextExtractorRegistry(pdffontsPdfTypeDetector(), listOf(
|
||||||
|
pdfToTextPdfTextExtractor(), PdfBoxPdfTextExtractor(), iText2PdfTextExtractor(),
|
||||||
|
ImageOnlyPdfTextExtractor(tesseractTextExtractor, pdfimagesImagesFromPdfExtractor()),
|
||||||
|
tesseractTextExtractor, TikaTextExtractor()
|
||||||
|
))
|
||||||
|
|
||||||
private val presenter = BankingPresenter(fints4kBankingClientCreator(OkHttpWebClient(), Base64ServiceJava8()),
|
private val presenter = BankingPresenter(fints4kBankingClientCreator(OkHttpWebClient(), Base64ServiceJava8()),
|
||||||
LuceneBankFinder(indexFolder), databaseFolder, dataFolder, LuceneBankingPersistence(indexFolder, databaseFolder),
|
LuceneBankFinder(indexFolder), databaseFolder, dataFolder, LuceneBankingPersistence(indexFolder, databaseFolder),
|
||||||
LuceneRemitteeSearcher(indexFolder), BankIconFinder(), RouterJavaFx())
|
LuceneRemitteeSearcher(indexFolder), BankIconFinder(), textExtractorRegistry, RouterJavaFx())
|
||||||
// private val presenter = BankingPresenter(hbci4jBankingClientCreator(), LuceneBankFinder(indexFolder), databaseFolder,
|
// private val presenter = BankingPresenter(hbci4jBankingClientCreator(), LuceneBankFinder(indexFolder), databaseFolder,
|
||||||
// dataFolder, LuceneBankingPersistence(indexFolder, databaseFolder), LuceneRemitteeSearcher(indexFolder), BankIconFinder(), RouterJavaFx())
|
// dataFolder, LuceneBankingPersistence(indexFolder, databaseFolder), LuceneRemitteeSearcher(indexFolder),
|
||||||
|
// BankIconFinder(), textExtractorRegistry, RouterJavaFx())
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -4,15 +4,22 @@ import javafx.beans.property.SimpleBooleanProperty
|
||||||
import javafx.scene.input.KeyCode
|
import javafx.scene.input.KeyCode
|
||||||
import javafx.scene.input.KeyCodeCombination
|
import javafx.scene.input.KeyCodeCombination
|
||||||
import javafx.scene.input.KeyCombination
|
import javafx.scene.input.KeyCombination
|
||||||
|
import javafx.stage.FileChooser
|
||||||
|
import net.dankito.banking.ui.model.moneytransfer.ExtractTransferMoneyDataFromPdfResult
|
||||||
|
import net.dankito.banking.ui.model.moneytransfer.ExtractTransferMoneyDataFromPdfResultType
|
||||||
import net.dankito.banking.ui.presenter.BankingPresenter
|
import net.dankito.banking.ui.presenter.BankingPresenter
|
||||||
|
import net.dankito.utils.javafx.ui.dialogs.JavaFXDialogService
|
||||||
import net.dankito.utils.javafx.ui.extensions.fixedHeight
|
import net.dankito.utils.javafx.ui.extensions.fixedHeight
|
||||||
import tornadofx.*
|
import tornadofx.*
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
|
||||||
open class MainMenuBar(protected val presenter: BankingPresenter) : View() {
|
open class MainMenuBar(protected val presenter: BankingPresenter) : View() {
|
||||||
|
|
||||||
protected val areAccountsThatCanTransferMoneyAdded = SimpleBooleanProperty()
|
protected val areAccountsThatCanTransferMoneyAdded = SimpleBooleanProperty()
|
||||||
|
|
||||||
|
protected var lastSelectedFolder: File? = null
|
||||||
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
presenter.addAccountsChangedListener {
|
presenter.addAccountsChangedListener {
|
||||||
|
@ -35,11 +42,19 @@ open class MainMenuBar(protected val presenter: BankingPresenter) : View() {
|
||||||
action { presenter.showAddAccountDialog() }
|
action { presenter.showAddAccountDialog() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
separator()
|
||||||
|
|
||||||
item(messages["main.window.menu.file.new.cash.transfer"], KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN)) {
|
item(messages["main.window.menu.file.new.cash.transfer"], KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN)) {
|
||||||
enableWhen(areAccountsThatCanTransferMoneyAdded)
|
enableWhen(areAccountsThatCanTransferMoneyAdded)
|
||||||
|
|
||||||
action { presenter.showTransferMoneyDialog() }
|
action { presenter.showTransferMoneyDialog() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
item(messages["main.window.menu.file.new.cash.transfer.from.pdf"], KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN, KeyCodeCombination.SHIFT_DOWN)) {
|
||||||
|
enableWhen(areAccountsThatCanTransferMoneyAdded)
|
||||||
|
|
||||||
|
action { transferMoneyWithDataFromPdf() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
separator()
|
separator()
|
||||||
|
@ -55,4 +70,34 @@ open class MainMenuBar(protected val presenter: BankingPresenter) : View() {
|
||||||
areAccountsThatCanTransferMoneyAdded.value = presenter.hasBankAccountsSupportTransferringMoney
|
areAccountsThatCanTransferMoneyAdded.value = presenter.hasBankAccountsSupportTransferringMoney
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected open fun transferMoneyWithDataFromPdf() {
|
||||||
|
val fileChooser = FileChooser()
|
||||||
|
|
||||||
|
fileChooser.initialDirectory = lastSelectedFolder
|
||||||
|
fileChooser.extensionFilters.add(FileChooser.ExtensionFilter("PDFs (*.pdf)", "*.pdf"))
|
||||||
|
|
||||||
|
fileChooser.showOpenDialog(currentStage)?.let { pdfFile ->
|
||||||
|
lastSelectedFolder = pdfFile.parentFile
|
||||||
|
|
||||||
|
val result = presenter.transferMoneyWithDataFromPdf(pdfFile)
|
||||||
|
|
||||||
|
if (result.type != ExtractTransferMoneyDataFromPdfResultType.Success) {
|
||||||
|
showTransferMoneyWithDataFromPdfError(pdfFile, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected open fun showTransferMoneyWithDataFromPdfError(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"
|
||||||
|
ExtractTransferMoneyDataFromPdfResultType.CouldNotExtractInvoiceDataFromExtractedText -> "transfer.money.from.pdf.error.message.could.not.extract.invoice.data"
|
||||||
|
ExtractTransferMoneyDataFromPdfResultType.Success -> "" // will never come to this
|
||||||
|
}
|
||||||
|
|
||||||
|
val errorMessage = String.format(messages[errorMessageKey], pdfFile.absolutePath)
|
||||||
|
|
||||||
|
JavaFXDialogService().showErrorMessage(errorMessage, exception = result.error)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -13,6 +13,7 @@ main.window.menu.file=File
|
||||||
main.window.menu.file.new=New...
|
main.window.menu.file.new=New...
|
||||||
main.window.menu.file.new.account=Account
|
main.window.menu.file.new.account=Account
|
||||||
main.window.menu.file.new.cash.transfer=Cash transfer
|
main.window.menu.file.new.cash.transfer=Cash transfer
|
||||||
|
main.window.menu.file.new.cash.transfer.from.pdf=Cash transfer from PDF
|
||||||
main.window.menu.file.quit=Quit
|
main.window.menu.file.quit=Quit
|
||||||
|
|
||||||
|
|
||||||
|
@ -71,4 +72,8 @@ transfer.money.dialog.transfer.money.label=Transfer
|
||||||
transfer.money.dialog.bank.name.will.be.entered.automatically=Will be entered automatically
|
transfer.money.dialog.bank.name.will.be.entered.automatically=Will be entered automatically
|
||||||
transfer.money.dialog.bank.not.found.for.iban=No bank found for this IBAN
|
transfer.money.dialog.bank.not.found.for.iban=No bank found for this IBAN
|
||||||
transfer.money.dialog.message.transfer.cash.success=Successfully transferred %.02f %s to %s
|
transfer.money.dialog.message.transfer.cash.success=Successfully transferred %.02f %s to %s
|
||||||
transfer.money.dialog.message.transfer.cash.error=Could not transfer %.02f %s to %s.\r\n\r\nError message from your bank:\r\n\r\n%s
|
transfer.money.dialog.message.transfer.cash.error=Could not transfer %.02f %s to %s.\r\n\r\nError message from your bank:\r\n\r\n%s
|
||||||
|
|
||||||
|
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.
|
||||||
|
transfer.money.from.pdf.error.message.could.not.extract.text=Could not extract text from file "%1$s".
|
||||||
|
transfer.money.from.pdf.error.message.could.not.extract.invoice.data=Could not extract cash transfer data from file "%1$s".
|
|
@ -13,6 +13,7 @@ main.window.menu.file=Datei
|
||||||
main.window.menu.file.new=Neu...
|
main.window.menu.file.new=Neu...
|
||||||
main.window.menu.file.new.account=Konto
|
main.window.menu.file.new.account=Konto
|
||||||
main.window.menu.file.new.cash.transfer=Überweisung
|
main.window.menu.file.new.cash.transfer=Überweisung
|
||||||
|
main.window.menu.file.new.cash.transfer.from.pdf=Überweisung aus PDF
|
||||||
main.window.menu.file.quit=Beenden
|
main.window.menu.file.quit=Beenden
|
||||||
|
|
||||||
|
|
||||||
|
@ -71,4 +72,8 @@ transfer.money.dialog.transfer.money.label=Überweisen
|
||||||
transfer.money.dialog.bank.name.will.be.entered.automatically=Wird automatisch eingetragen
|
transfer.money.dialog.bank.name.will.be.entered.automatically=Wird automatisch eingetragen
|
||||||
transfer.money.dialog.bank.not.found.for.iban=Für diese IBAN wurde keine Bank gefunden
|
transfer.money.dialog.bank.not.found.for.iban=Für diese IBAN wurde keine Bank gefunden
|
||||||
transfer.money.dialog.message.transfer.cash.success=%.02f %s erfolgreich an %s überwiesen.
|
transfer.money.dialog.message.transfer.cash.success=%.02f %s erfolgreich an %s überwiesen.
|
||||||
transfer.money.dialog.message.transfer.cash.error=%.02f %s konnten nicht an %s überwiesen werden.\r\n\r\nFehlermeldung Ihrer Bank:\r\n\r\n%s
|
transfer.money.dialog.message.transfer.cash.error=%.02f %s konnten nicht an %s überwiesen werden.\r\n\r\nFehlermeldung Ihrer Bank:\r\n\r\n%s
|
||||||
|
|
||||||
|
transfer.money.from.pdf.error.message.not.a.searchable.pdf=Konnte Text nicht aus Datei "%1$s" extrahieren. Enthält sie auch Text oder nur Bilder?
|
||||||
|
transfer.money.from.pdf.error.message.could.not.extract.text=Text konnte nicht aus Datei "%1$s" extrahiert werden.
|
||||||
|
transfer.money.from.pdf.error.message.could.not.extract.invoice.data=Überweisungsdaten konnten aus der Datei "%1$s" nicht ausgelesen werden.
|
|
@ -13,11 +13,22 @@ compileTestKotlin {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
// for security issues fixed version of iText 2 from JasperReports
|
||||||
|
maven {
|
||||||
|
url "http://jaspersoft.jfrog.io/jaspersoft/third-party-ce-artifacts"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
|
api "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
|
||||||
|
|
||||||
api "net.dankito.utils:java-utils:$javaUtilsVersion"
|
api "net.dankito.utils:java-utils:$javaUtilsVersion"
|
||||||
|
|
||||||
|
api "net.dankito.text.extraction:text-extractor-common:$textExtractorVersion"
|
||||||
|
api "net.dankito.text.extraction:text-info-extractor:$textInfoExtractorVersion"
|
||||||
|
|
||||||
implementation "net.dankito.utils:favicon-finder:1.0.0-SNAPSHOT"
|
implementation "net.dankito.utils:favicon-finder:1.0.0-SNAPSHOT"
|
||||||
|
|
||||||
implementation "org.jsoup:jsoup:1.13.1"
|
implementation "org.jsoup:jsoup:1.13.1"
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
package net.dankito.banking.ui.model.moneytransfer
|
||||||
|
|
||||||
|
|
||||||
|
open class ExtractTransferMoneyDataFromPdfResult(
|
||||||
|
val type: ExtractTransferMoneyDataFromPdfResultType,
|
||||||
|
val error: Exception? = null
|
||||||
|
) {
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return type.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package net.dankito.banking.ui.model.moneytransfer
|
||||||
|
|
||||||
|
|
||||||
|
enum class ExtractTransferMoneyDataFromPdfResultType {
|
||||||
|
|
||||||
|
Success,
|
||||||
|
|
||||||
|
NotASearchablePdf,
|
||||||
|
|
||||||
|
CouldNotExtractText,
|
||||||
|
|
||||||
|
CouldNotExtractInvoiceDataFromExtractedText
|
||||||
|
|
||||||
|
}
|
|
@ -19,8 +19,14 @@ import net.dankito.banking.bankfinder.IBankFinder
|
||||||
import net.dankito.banking.bankfinder.BankInfo
|
import net.dankito.banking.bankfinder.BankInfo
|
||||||
import net.dankito.banking.search.IRemitteeSearcher
|
import net.dankito.banking.search.IRemitteeSearcher
|
||||||
import net.dankito.banking.search.Remittee
|
import net.dankito.banking.search.Remittee
|
||||||
|
import net.dankito.banking.ui.model.moneytransfer.ExtractTransferMoneyDataFromPdfResult
|
||||||
|
import net.dankito.banking.ui.model.moneytransfer.ExtractTransferMoneyDataFromPdfResultType
|
||||||
import net.dankito.banking.ui.model.parameters.GetTransactionsParameter
|
import net.dankito.banking.ui.model.parameters.GetTransactionsParameter
|
||||||
import net.dankito.banking.ui.model.settings.AppSettings
|
import net.dankito.banking.ui.model.settings.AppSettings
|
||||||
|
import net.dankito.text.extraction.ITextExtractorRegistry
|
||||||
|
import net.dankito.text.extraction.info.invoice.IInvoiceDataExtractor
|
||||||
|
import net.dankito.text.extraction.info.invoice.InvoiceDataExtractor
|
||||||
|
import net.dankito.text.extraction.model.ErrorType
|
||||||
import net.dankito.utils.IThreadPool
|
import net.dankito.utils.IThreadPool
|
||||||
import net.dankito.utils.ThreadPool
|
import net.dankito.utils.ThreadPool
|
||||||
import net.dankito.utils.extensions.containsExactly
|
import net.dankito.utils.extensions.containsExactly
|
||||||
|
@ -45,7 +51,9 @@ open class BankingPresenter(
|
||||||
protected val persister: IBankingPersistence,
|
protected val persister: IBankingPersistence,
|
||||||
protected val remitteeSearcher: IRemitteeSearcher,
|
protected val remitteeSearcher: IRemitteeSearcher,
|
||||||
protected val bankIconFinder: IBankIconFinder,
|
protected val bankIconFinder: IBankIconFinder,
|
||||||
|
protected val textExtractorRegistry: ITextExtractorRegistry,
|
||||||
protected val router: IRouter,
|
protected val router: IRouter,
|
||||||
|
protected val invoiceDataExtractor: IInvoiceDataExtractor = InvoiceDataExtractor(),
|
||||||
protected val serializer: ISerializer = JacksonJsonSerializer(),
|
protected val serializer: ISerializer = JacksonJsonSerializer(),
|
||||||
protected val threadPool: IThreadPool = ThreadPool()
|
protected val threadPool: IThreadPool = ThreadPool()
|
||||||
) {
|
) {
|
||||||
|
@ -380,6 +388,35 @@ open class BankingPresenter(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
open fun transferMoneyWithDataFromPdf(pdf: File): ExtractTransferMoneyDataFromPdfResult {
|
||||||
|
val extractionResult = textExtractorRegistry.extractTextWithBestExtractorForFile(pdf)
|
||||||
|
|
||||||
|
if (extractionResult.couldExtractText == false || extractionResult.text == null) {
|
||||||
|
val resultType = if (extractionResult.error?.type == ErrorType.NoExtractorFoundForFileType) ExtractTransferMoneyDataFromPdfResultType.NotASearchablePdf
|
||||||
|
else ExtractTransferMoneyDataFromPdfResultType.CouldNotExtractText
|
||||||
|
return ExtractTransferMoneyDataFromPdfResult(resultType, extractionResult.error?.exception)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
extractionResult.text?.let { extractedText ->
|
||||||
|
val invoiceData = invoiceDataExtractor.extractInvoiceData(extractedText)
|
||||||
|
|
||||||
|
if (invoiceData.potentialTotalAmount != null || invoiceData.potentialIban != null) { // if at least an amount or IBAN could get extracted
|
||||||
|
val transferMoneyData = TransferMoneyData("",
|
||||||
|
invoiceData.potentialIban ?: "",
|
||||||
|
invoiceData.potentialBic ?: "",
|
||||||
|
invoiceData.potentialTotalAmount?.amount ?: BigDecimal.ZERO, "")
|
||||||
|
showTransferMoneyDialog(null, transferMoneyData)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return ExtractTransferMoneyDataFromPdfResult(
|
||||||
|
ExtractTransferMoneyDataFromPdfResultType.CouldNotExtractInvoiceDataFromExtractedText, invoiceData.error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ExtractTransferMoneyDataFromPdfResult(ExtractTransferMoneyDataFromPdfResultType.Success)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
open fun findUniqueBankForIbanAsync(iban: String, callback: (BankInfo?) -> Unit) {
|
open fun findUniqueBankForIbanAsync(iban: String, callback: (BankInfo?) -> Unit) {
|
||||||
threadPool.runAsync {
|
threadPool.runAsync {
|
||||||
|
|
|
@ -15,5 +15,5 @@ compileTestKotlin {
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api project(':BankingUiCommon')
|
api project(':BankingUiCommon')
|
||||||
implementation project(':fints4k')
|
api project(':fints4k')
|
||||||
}
|
}
|
Loading…
Reference in New Issue