diff --git a/build.gradle b/build.gradle index e2b405f6..69b303b6 100644 --- a/build.gradle +++ b/build.gradle @@ -27,6 +27,11 @@ ext { luceneUtilsVersion = "0.5.1-SNAPSHOT" + textExtractorVersion = "0.5.1-SNAPSHOT" + + textInfoExtractorVersion = "1.0.1-SNAPSHOT" + + hbci4jVersion = '3.1.37' @@ -41,6 +46,8 @@ ext { androidTargetSdkVersion = 28 + fileChooserDialogVersion = "1.2.0-androidx" + androidUtilsVersion = '1.1.1-SNAPSHOT' materialDrawerVersion = "8.0.1" diff --git a/gradle.properties b/gradle.properties index eeed7cd8..db748cc8 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,7 @@ android.enableJetifier=true android.useAndroidX=true -kotlin.code.style=official \ No newline at end of file +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 \ No newline at end of file diff --git a/ui/BankingAndroidApp/build.gradle b/ui/BankingAndroidApp/build.gradle index 7a8b93d3..1ac514c3 100644 --- a/ui/BankingAndroidApp/build.gradle +++ b/ui/BankingAndroidApp/build.gradle @@ -83,6 +83,10 @@ dependencies { implementation project(':BankingPersistenceJson') 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.otaliastudios:autocomplete:$autocompleteVersion" @@ -90,6 +94,14 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion" 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", { exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-jdk7' exclude group: 'com.android.support', module: 'appcompat-v7' diff --git a/ui/BankingAndroidApp/src/main/java/net/dankito/banking/ui/android/MainActivity.kt b/ui/BankingAndroidApp/src/main/java/net/dankito/banking/ui/android/MainActivity.kt index cd4db731..60923021 100644 --- a/ui/BankingAndroidApp/src/main/java/net/dankito/banking/ui/android/MainActivity.kt +++ b/ui/BankingAndroidApp/src/main/java/net/dankito/banking/ui/android/MainActivity.kt @@ -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.MainActivityFloatingActionMenuButton 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 @@ -27,6 +29,8 @@ class MainActivity : BaseActivity() { private lateinit var floatingActionMenuButton: MainActivityFloatingActionMenuButton + private val permissionsService: IPermissionsService = PermissionsService(this) + @Inject protected lateinit var presenter: BankingPresenter @@ -73,7 +77,7 @@ class MainActivity : BaseActivity() { } val floatingActionMenu = findViewById(R.id.floatingActionMenu) - floatingActionMenuButton = MainActivityFloatingActionMenuButton(floatingActionMenu, presenter) + floatingActionMenuButton = MainActivityFloatingActionMenuButton(floatingActionMenu, permissionsService, presenter) } override fun onCreateOptionsMenu(menu: Menu): Boolean { diff --git a/ui/BankingAndroidApp/src/main/java/net/dankito/banking/ui/android/di/BankingModule.kt b/ui/BankingAndroidApp/src/main/java/net/dankito/banking/ui/android/di/BankingModule.kt index 55466a9e..31814a40 100644 --- a/ui/BankingAndroidApp/src/main/java/net/dankito/banking/ui/android/di/BankingModule.kt +++ b/ui/BankingAndroidApp/src/main/java/net/dankito/banking/ui/android/di/BankingModule.kt @@ -19,6 +19,12 @@ import net.dankito.banking.util.BankIconFinder import net.dankito.banking.util.IBankIconFinder import net.dankito.banking.bankfinder.IBankFinder 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.ThreadPool import net.dankito.utils.serialization.ISerializer @@ -78,10 +84,11 @@ class BankingModule(private val applicationContext: Context) { @Singleton fun provideBankingPresenter(bankingClientCreator: IBankingClientCreator, bankFinder: IBankFinder, @Named(DatabaseFolderKey) databaseFolder: File, @Named(DataFolderKey) dataFolder: File, - persister: IBankingPersistence, bankIconFinder: IBankIconFinder, remitteeSearcher: IRemitteeSearcher, - router: IRouter, serializer: ISerializer, threadPool: IThreadPool) : BankingPresenter { + persister: IBankingPersistence, remitteeSearcher: IRemitteeSearcher, bankIconFinder: IBankIconFinder, + textExtractorRegistry: ITextExtractorRegistry, router: IRouter, invoiceDataExtractor: IInvoiceDataExtractor, + serializer: ISerializer, threadPool: IThreadPool) : BankingPresenter { return BankingPresenter(bankingClientCreator, bankFinder, databaseFolder, dataFolder, persister, - remitteeSearcher, bankIconFinder, router, serializer, threadPool) + remitteeSearcher, bankIconFinder, textExtractorRegistry, router, invoiceDataExtractor, serializer, threadPool) } @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 @Singleton fun provideWebClient() : IWebClient { diff --git a/ui/BankingAndroidApp/src/main/java/net/dankito/banking/ui/android/views/MainActivityFloatingActionMenuButton.kt b/ui/BankingAndroidApp/src/main/java/net/dankito/banking/ui/android/views/MainActivityFloatingActionMenuButton.kt index d318e667..f01d087d 100644 --- a/ui/BankingAndroidApp/src/main/java/net/dankito/banking/ui/android/views/MainActivityFloatingActionMenuButton.kt +++ b/ui/BankingAndroidApp/src/main/java/net/dankito/banking/ui/android/views/MainActivityFloatingActionMenuButton.kt @@ -1,18 +1,36 @@ package net.dankito.banking.ui.android.views +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 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.filechooserdialog.FileChooserDialog +import net.dankito.filechooserdialog.model.FileChooserDialogConfig 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) - : FloatingActionMenuButton(floatingActionMenu) { +open class MainActivityFloatingActionMenuButton( + floatingActionMenu: FloatingActionMenu, + protected val permissionsService: IPermissionsService, + protected val presenter: BankingPresenter +) : FloatingActionMenuButton(floatingActionMenu) { protected lateinit var fabTransferMoney: FloatingActionButton + protected lateinit var fabTransferMoneyFromPdf: FloatingActionButton + + protected var lastSelectedFolder: File? = null + + init { setupButtons(floatingActionMenu) @@ -32,16 +50,59 @@ open class MainActivityFloatingActionMenuButton(floatingActionMenu: FloatingActi } fabTransferMoney = floatingActionMenu.fabTransferMoney + fabTransferMoneyFromPdf = floatingActionMenu.fabTransferMoneyFromPdf fabTransferMoney.setOnClickListener { executeAndCloseMenu { presenter.showTransferMoneyDialog() } } + + fabTransferMoneyFromPdf.setOnClickListener { + executeAndCloseMenu { transferMoneyWithDataFromPdf() } + } } } protected open fun checkIfThereAreAccountsThatCanTransferMoney() { 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() } } \ No newline at end of file diff --git a/ui/BankingAndroidApp/src/main/res/layout/view_floating_action_button_main.xml b/ui/BankingAndroidApp/src/main/res/layout/view_floating_action_button_main.xml index aaed848b..a24b5f16 100644 --- a/ui/BankingAndroidApp/src/main/res/layout/view_floating_action_button_main.xml +++ b/ui/BankingAndroidApp/src/main/res/layout/view_floating_action_button_main.xml @@ -17,6 +17,14 @@ fab:menu_animationDelayPerItem="50"> + + Konto Überweisung + Überweisung aus PDF Umsätze @@ -60,6 +61,10 @@ %1$s %2$s wurden erfolgreich an %3$s überwiesen. Konnte nicht %1$s %2$s an %3$s überweisen.\n\nFehlermeldung Ihrer Bank:\n\n%4$s + Konnte Text nicht aus Datei "%1$s" extrahieren. Enthält sie auch Text oder nur Bilder? + Text konnte nicht aus Datei "%1$s" extrahiert werden:\n\n%2$s + Überweisungsdaten konnten aus der Datei "%1$s" nicht ausgelesen werden:\n\n%2$s + Größe: - + diff --git a/ui/BankingAndroidApp/src/main/res/values/strings.xml b/ui/BankingAndroidApp/src/main/res/values/strings.xml index f696a614..56a5dd5c 100644 --- a/ui/BankingAndroidApp/src/main/res/values/strings.xml +++ b/ui/BankingAndroidApp/src/main/res/values/strings.xml @@ -23,6 +23,7 @@ Account Transfer money + Transfer money from PDF Home @@ -60,6 +61,10 @@ Successfully transferred %1$s %2$s to %3$s. Could not transfer %1$s %2$s to %3$s.\n\nError message from your bank:\n\n%4$s + File "%1$s" is not a searchable PDF. Could therefore not extract text from it. + Could not extract text from file "%1$s":\n\n%2$s + Could not extract cash transfer data from file "%1$s":\n\n%2$s + Size: - + diff --git a/ui/BankingAndroidApp/src/main/res/values/styles.xml b/ui/BankingAndroidApp/src/main/res/values/styles.xml index 84cdea14..67778839 100644 --- a/ui/BankingAndroidApp/src/main/res/values/styles.xml +++ b/ui/BankingAndroidApp/src/main/res/values/styles.xml @@ -1,7 +1,7 @@ -