Implemented exporting account transactions as .csv at least for JVM and Android
This commit is contained in:
parent
704ac55239
commit
bc3439baa5
|
@ -23,6 +23,8 @@ ext {
|
||||||
|
|
||||||
textInfoExtractorVersion = "1.0.1"
|
textInfoExtractorVersion = "1.0.1"
|
||||||
|
|
||||||
|
commonsCsvVersion = "1.8"
|
||||||
|
|
||||||
|
|
||||||
hbci4jVersion = '3.1.37'
|
hbci4jVersion = '3.1.37'
|
||||||
|
|
||||||
|
@ -45,7 +47,7 @@ ext {
|
||||||
androidTargetSdkVersion = 28
|
androidTargetSdkVersion = 28
|
||||||
|
|
||||||
|
|
||||||
fileChooserDialogVersion = "1.2.0-androidx"
|
fileChooserDialogVersion = "1.3.0-androidx"
|
||||||
|
|
||||||
androidUtilsVersion = '1.1.1-SNAPSHOT'
|
androidUtilsVersion = '1.1.1-SNAPSHOT'
|
||||||
|
|
||||||
|
|
|
@ -190,7 +190,7 @@ open class RoomBankingPersistence(protected open val applicationContext: Context
|
||||||
|
|
||||||
override fun saveOrUpdateAppSettings(appSettings: AppSettings) {
|
override fun saveOrUpdateAppSettings(appSettings: AppSettings) {
|
||||||
val mapped = net.dankito.banking.persistence.model.AppSettings(appSettings.automaticallyUpdateAccountsAfterMinutes,
|
val mapped = net.dankito.banking.persistence.model.AppSettings(appSettings.automaticallyUpdateAccountsAfterMinutes,
|
||||||
appSettings.lockAppAfterMinutes, appSettings.screenshotsAllowed)
|
appSettings.lockAppAfterMinutes, appSettings.screenshotsAllowed, appSettings.lastSelectedExportFolder)
|
||||||
database.appSettingsDao().saveOrUpdate(mapped)
|
database.appSettingsDao().saveOrUpdate(mapped)
|
||||||
|
|
||||||
saveOrUpdateTanMethodSettings(appSettings.flickerCodeSettings, FlickerCodeTanMethodSettingsId)
|
saveOrUpdateTanMethodSettings(appSettings.flickerCodeSettings, FlickerCodeTanMethodSettingsId)
|
||||||
|
@ -215,6 +215,7 @@ open class RoomBankingPersistence(protected open val applicationContext: Context
|
||||||
settings.automaticallyUpdateAccountsAfterMinutes = persistedSettings.automaticallyUpdateAccountsAfterMinutes
|
settings.automaticallyUpdateAccountsAfterMinutes = persistedSettings.automaticallyUpdateAccountsAfterMinutes
|
||||||
settings.lockAppAfterMinutes = persistedSettings.lockAppAfterMinutes
|
settings.lockAppAfterMinutes = persistedSettings.lockAppAfterMinutes
|
||||||
settings.screenshotsAllowed = persistedSettings.screenshotsAllowed
|
settings.screenshotsAllowed = persistedSettings.screenshotsAllowed
|
||||||
|
settings.lastSelectedExportFolder = persistedSettings.lastSelectedExportFolder
|
||||||
}
|
}
|
||||||
|
|
||||||
settings.flickerCodeSettings = findTanMethodSettings(FlickerCodeTanMethodSettingsId, tanMethodSettings)
|
settings.flickerCodeSettings = findTanMethodSettings(FlickerCodeTanMethodSettingsId, tanMethodSettings)
|
||||||
|
|
|
@ -10,7 +10,8 @@ import net.dankito.banking.ui.model.settings.AppSettings
|
||||||
open class AppSettings(
|
open class AppSettings(
|
||||||
open var automaticallyUpdateAccountsAfterMinutes: Int? = AppSettings.DefaultAutomaticallyUpdateAccountsAfterMinutes,
|
open var automaticallyUpdateAccountsAfterMinutes: Int? = AppSettings.DefaultAutomaticallyUpdateAccountsAfterMinutes,
|
||||||
open var lockAppAfterMinutes: Int? = null,
|
open var lockAppAfterMinutes: Int? = null,
|
||||||
open var screenshotsAllowed: Boolean = false
|
open var screenshotsAllowed: Boolean = false,
|
||||||
|
open var lastSelectedExportFolder: String? = null
|
||||||
) {
|
) {
|
||||||
|
|
||||||
internal constructor() : this(AppSettings.DefaultAutomaticallyUpdateAccountsAfterMinutes, null, false)
|
internal constructor() : this(AppSettings.DefaultAutomaticallyUpdateAccountsAfterMinutes, null, false)
|
||||||
|
|
|
@ -71,10 +71,12 @@ project(':fints4kRest').projectDir = "$rootDir/rest/fints4kRest/" as File
|
||||||
include ':BankFinder'
|
include ':BankFinder'
|
||||||
include ':LuceneBankFinder'
|
include ':LuceneBankFinder'
|
||||||
include ':BankListCreator'
|
include ':BankListCreator'
|
||||||
|
include ':CsvAccountTransactionsImporterAndExporter'
|
||||||
include ':EpcQrCodeParser'
|
include ':EpcQrCodeParser'
|
||||||
|
|
||||||
|
|
||||||
project(':BankFinder').projectDir = "$rootDir/tools/BankFinder/" as File
|
project(':BankFinder').projectDir = "$rootDir/tools/BankFinder/" as File
|
||||||
project(':LuceneBankFinder').projectDir = "$rootDir/tools/LuceneBankFinder/" as File
|
project(':LuceneBankFinder').projectDir = "$rootDir/tools/LuceneBankFinder/" as File
|
||||||
project(':BankListCreator').projectDir = "$rootDir/tools/BankListCreator/" as File
|
project(':BankListCreator').projectDir = "$rootDir/tools/BankListCreator/" as File
|
||||||
|
project(':CsvAccountTransactionsImporterAndExporter').projectDir = "$rootDir/tools/CsvAccountTransactionsImporterAndExporter/" as File
|
||||||
project(':EpcQrCodeParser').projectDir = "$rootDir/tools/EpcQrCodeParser/" as File
|
project(':EpcQrCodeParser').projectDir = "$rootDir/tools/EpcQrCodeParser/" as File
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
plugins {
|
||||||
|
id 'org.jetbrains.kotlin.jvm'
|
||||||
|
id "maven-publish"
|
||||||
|
}
|
||||||
|
|
||||||
|
group 'net.codinux.banking.tools'
|
||||||
|
|
||||||
|
ext.artifactName = "csv-account-transactions-importer-exporter"
|
||||||
|
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation "org.jetbrains.kotlin:kotlin-stdlib"
|
||||||
|
|
||||||
|
implementation "org.apache.commons:commons-csv:$commonsCsvVersion"
|
||||||
|
|
||||||
|
implementation "org.slf4j:slf4j-api:$slf4jVersion"
|
||||||
|
|
||||||
|
|
||||||
|
testImplementation "org.junit.jupiter:junit-jupiter:$junit5Version"
|
||||||
|
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junit5Version"
|
||||||
|
|
||||||
|
testImplementation "org.assertj:assertj-core:$assertJVersion"
|
||||||
|
testImplementation "org.mockito:mockito-core:$mockitoVersion"
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
package net.codinux.banking.tools.importerexporter
|
||||||
|
|
||||||
|
import net.codinux.banking.tools.importerexporter.model.AccountTransaction
|
||||||
|
import org.apache.commons.csv.CSVFormat
|
||||||
|
import org.apache.commons.csv.CSVPrinter
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import java.io.Writer
|
||||||
|
import java.math.BigDecimal
|
||||||
|
import java.text.DateFormat
|
||||||
|
import java.text.NumberFormat
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
|
open class CsvAccountTransactionsExporter : IAccountTransactionsExporter {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
// TODO: translate
|
||||||
|
val Headers = listOf(
|
||||||
|
"Auftragskonto", "Buchungstag", "Wertstellungstag", "Umsatzart", "Empfänger / Auftraggeber",
|
||||||
|
"Verwendungszweck", "IBAN/Kontonummer", "BIC", "Umsatz", "Währung"
|
||||||
|
)
|
||||||
|
|
||||||
|
const val DateSeparators = "[./ -]"
|
||||||
|
val DateOnlyContainsNumbersRegex = Regex("\\d{1,4}$DateSeparators\\d{1,2}$DateSeparators\\d{2,4}")
|
||||||
|
|
||||||
|
private val log = LoggerFactory.getLogger(CsvAccountTransactionsExporter::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// set as fields not as companion object members so they use the Locale set at CsvAccountTransactionsExporter instantiation time not when the first CsvAccountTransactionsExporter has been created
|
||||||
|
|
||||||
|
protected open val DateFormatter: DateFormat = findLongestDateFormatWithoutWrittenOutMonth() // ensure converted dates only contain numbers, not dates like 27 Mar 2020
|
||||||
|
|
||||||
|
protected open val DecimalFormat = NumberFormat.getNumberInstance()
|
||||||
|
|
||||||
|
|
||||||
|
override fun export(writer: Writer, transactions: Collection<AccountTransaction>) {
|
||||||
|
try {
|
||||||
|
writer.use {
|
||||||
|
val csvPrinter = CSVPrinter(writer, CSVFormat.DEFAULT.withHeader(*Headers.toTypedArray()))
|
||||||
|
|
||||||
|
transactions.forEach { transaction ->
|
||||||
|
csvPrinter.printRecord(transaction.account, format(transaction.bookingDate), format(transaction.valueDate),
|
||||||
|
format(transaction.bookingText), format(transaction.otherPartyName), transaction.reference,
|
||||||
|
format(transaction.otherPartyAccountId), format(transaction.otherPartyBankCode), format(transaction.amount), transaction.currency
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
csvPrinter.flush()
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
log.error("Could not export ${transactions.size} transactions to CSV", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected open fun format(date: Date): String {
|
||||||
|
return DateFormatter.format(date)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected open fun format(bigDecimal: BigDecimal): String {
|
||||||
|
return DecimalFormat.format(bigDecimal)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected open fun format(string: String?): String {
|
||||||
|
return string ?: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected open fun findLongestDateFormatWithoutWrittenOutMonth(): DateFormat {
|
||||||
|
val mediumDateFormat = DateFormat.getDateInstance(DateFormat.MEDIUM)
|
||||||
|
val dateFormatTest = mediumDateFormat.format(Date())
|
||||||
|
|
||||||
|
return if (DateOnlyContainsNumbersRegex.matches(dateFormatTest)) {
|
||||||
|
mediumDateFormat
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
DateFormat.getDateInstance(DateFormat.SHORT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
package net.codinux.banking.tools.importerexporter
|
||||||
|
|
||||||
|
import net.codinux.banking.tools.importerexporter.model.AccountTransaction
|
||||||
|
import java.io.File
|
||||||
|
import java.io.Writer
|
||||||
|
|
||||||
|
|
||||||
|
interface IAccountTransactionsExporter {
|
||||||
|
|
||||||
|
fun export(file: File, transactions: Collection<AccountTransaction>) {
|
||||||
|
return export(file.outputStream().bufferedWriter(), transactions)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun export(writer: Writer, transactions: Collection<AccountTransaction>)
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package net.codinux.banking.tools.importerexporter
|
||||||
|
|
||||||
|
import net.codinux.banking.tools.importerexporter.model.AccountTransaction
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
|
||||||
|
interface IAccountTransactionsImporter {
|
||||||
|
|
||||||
|
fun import(file: File): List<AccountTransaction>
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
package net.codinux.banking.tools.importerexporter
|
||||||
|
|
||||||
|
|
||||||
|
interface IAccountTransactionsImporterExporter : IAccountTransactionsImporter, IAccountTransactionsExporter
|
|
@ -0,0 +1,20 @@
|
||||||
|
package net.codinux.banking.tools.importerexporter.model
|
||||||
|
|
||||||
|
import java.math.BigDecimal
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
|
open class AccountTransaction(
|
||||||
|
open val account: String,
|
||||||
|
open val amount: BigDecimal,
|
||||||
|
open val currency: String,
|
||||||
|
open val reference: String,
|
||||||
|
open val bookingDate: Date,
|
||||||
|
open val valueDate: Date,
|
||||||
|
open val otherPartyName: String?,
|
||||||
|
open val otherPartyBankCode: String?,
|
||||||
|
open val otherPartyAccountId: String?,
|
||||||
|
open val bookingText: String?
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,172 @@
|
||||||
|
package net.codinux.banking.tools.importerexporter
|
||||||
|
|
||||||
|
import net.codinux.banking.tools.importerexporter.model.AccountTransaction
|
||||||
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
|
import org.junit.jupiter.api.AfterEach
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Assertions.*
|
||||||
|
import java.awt.print.Book
|
||||||
|
import java.io.StringWriter
|
||||||
|
import java.math.BigDecimal
|
||||||
|
import java.util.*
|
||||||
|
import java.util.concurrent.ThreadLocalRandom
|
||||||
|
|
||||||
|
internal class CsvAccountTransactionsExporterTest {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val AccountId = "DE00876543210123456789"
|
||||||
|
|
||||||
|
const val Currency = "EUR"
|
||||||
|
|
||||||
|
val BookingDateDay = 26 // one day before value date
|
||||||
|
val ValueDateDay = 27
|
||||||
|
val DateMonth = 3
|
||||||
|
val DateYear = 1988
|
||||||
|
|
||||||
|
val BookingDate = Date(DateYear - 1900, DateMonth - 1, BookingDateDay)
|
||||||
|
|
||||||
|
val ValueDate = Date(DateYear - 1900, DateMonth - 1, ValueDateDay)
|
||||||
|
|
||||||
|
const val ReferenceWithUmlaute = "Was für ein schöner Verwendungszweck!"
|
||||||
|
|
||||||
|
const val AsciiReference = "A total normal reference"
|
||||||
|
|
||||||
|
const val Amount1String = "84.23"
|
||||||
|
val Amount1 = BigDecimal(Amount1String)
|
||||||
|
|
||||||
|
const val OtherParty1Name = "Nelson Mandela"
|
||||||
|
const val OtherParty1BankCode = "ABCDEFGH123"
|
||||||
|
const val OtherParty1AccountId = "SA99012345679876543210"
|
||||||
|
|
||||||
|
const val BookingText1 = "Überweisung"
|
||||||
|
|
||||||
|
const val Amount2String = "-123.45"
|
||||||
|
val Amount2 = BigDecimal(Amount2String)
|
||||||
|
|
||||||
|
val OtherParty2Name: String? = null
|
||||||
|
val OtherParty2BankCode: String? = null
|
||||||
|
val OtherParty2AccountId : String? = null
|
||||||
|
|
||||||
|
const val BookingText2 = "Bargeldabhebung"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private val defaultLocale = Locale.getDefault(Locale.Category.FORMAT)
|
||||||
|
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
fun tearDown() {
|
||||||
|
setLocale(defaultLocale) // restore locale
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun exportWithEnglishLocale() {
|
||||||
|
setLocale(Locale.US)
|
||||||
|
|
||||||
|
val underTest = CsvAccountTransactionsExporter() // has to be created after locale is set as otherwise DateFormat works with the wrong locale
|
||||||
|
|
||||||
|
val transactions = createTransactions(2)
|
||||||
|
|
||||||
|
val writer = StringWriter()
|
||||||
|
|
||||||
|
underTest.export(writer, transactions)
|
||||||
|
|
||||||
|
|
||||||
|
val result = writer.toString()
|
||||||
|
|
||||||
|
assertThat(result).contains(Amount1String)
|
||||||
|
assertThat(result).contains(Amount2String)
|
||||||
|
|
||||||
|
assertThat(result).contains("$DateMonth/$BookingDateDay/${DateYear - 1900}")
|
||||||
|
assertThat(result).contains("$DateMonth/$ValueDateDay/${DateYear - 1900}")
|
||||||
|
|
||||||
|
assertThat(result).contains(ReferenceWithUmlaute, AsciiReference)
|
||||||
|
|
||||||
|
// assertThat(countOccurrences(result, ',')).isEqualTo(calculateCountSeparators(2)) // actually first thought using the German standard delimiter ';', but now sticking with ','
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun exportWithGermanLocale() {
|
||||||
|
setLocale(Locale.GERMANY)
|
||||||
|
|
||||||
|
val underTest = CsvAccountTransactionsExporter() // has to be created after locale is set as otherwise DateFormat works with the wrong locale
|
||||||
|
|
||||||
|
val transactions = createTransactions(2)
|
||||||
|
|
||||||
|
val writer = StringWriter()
|
||||||
|
|
||||||
|
underTest.export(writer, transactions)
|
||||||
|
|
||||||
|
|
||||||
|
val result = writer.toString()
|
||||||
|
|
||||||
|
assertThat(result).contains(Amount1String.replace('.', ','))
|
||||||
|
assertThat(result).contains(Amount2String.replace('.', ','))
|
||||||
|
|
||||||
|
assertThat(result).contains("$BookingDateDay.0$DateMonth.$DateYear")
|
||||||
|
assertThat(result).contains("$ValueDateDay.0$DateMonth.$DateYear")
|
||||||
|
|
||||||
|
assertThat(result).contains(ReferenceWithUmlaute, AsciiReference)
|
||||||
|
|
||||||
|
// assertThat(countOccurrences(result, ',')).isEqualTo(calculateCountSeparators(2)) // actually first thought using the German standard delimiter ';', but now sticking with ','
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun createTransactions(count: Int): List<AccountTransaction> {
|
||||||
|
val transactions = mutableListOf<AccountTransaction>()
|
||||||
|
|
||||||
|
IntRange(0, count - 1).forEach { index ->
|
||||||
|
transactions.add(createTransaction(index))
|
||||||
|
}
|
||||||
|
|
||||||
|
return transactions
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createTransaction(index: Int): AccountTransaction {
|
||||||
|
return when (index) {
|
||||||
|
0 -> createTransaction(Amount1, ReferenceWithUmlaute, OtherParty1Name, OtherParty1BankCode, OtherParty1AccountId, BookingText1)
|
||||||
|
1 -> createTransaction(Amount2, AsciiReference, OtherParty2Name, OtherParty2BankCode, OtherParty2AccountId, BookingText2)
|
||||||
|
else -> createRandomTransaction()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createRandomTransaction(): AccountTransaction {
|
||||||
|
val random = ThreadLocalRandom.current()
|
||||||
|
|
||||||
|
val amount = random.nextDouble(-1_000_000.0, 1_000_000.0)
|
||||||
|
|
||||||
|
return createTransaction(BigDecimal.valueOf(amount), "")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createTransaction(amount: BigDecimal, reference: String, otherPartyName: String? = null, otherPartyBankCode: String? = null,
|
||||||
|
otherPartyAccountId: String? = null, bookingText: String? = null): AccountTransaction {
|
||||||
|
return AccountTransaction(
|
||||||
|
AccountId, amount, Currency, reference, BookingDate, ValueDate, otherPartyName, otherPartyBankCode, otherPartyAccountId, bookingText
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun countOccurrences(string: String, characterToFind: Char): Int {
|
||||||
|
var countOccurrences = 0
|
||||||
|
|
||||||
|
for (char in string) {
|
||||||
|
if (char == characterToFind) {
|
||||||
|
countOccurrences++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return countOccurrences
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun calculateCountSeparators(countTransactions: Int): Int {
|
||||||
|
return (countTransactions + 1) * // + 1 cause of header row
|
||||||
|
(10 - 1) // - 1 cause for the last column no separator gets printed
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun setLocale(locale: Locale) {
|
||||||
|
Locale.setDefault(Locale.Category.FORMAT, locale)
|
||||||
|
}
|
||||||
|
}
|
|
@ -97,6 +97,8 @@ dependencies {
|
||||||
implementation project(':LuceneBankingPersistence')
|
implementation project(':LuceneBankingPersistence')
|
||||||
implementation project(':RoomBankingPersistence')
|
implementation project(':RoomBankingPersistence')
|
||||||
|
|
||||||
|
implementation project(':CsvAccountTransactionsImporterAndExporter')
|
||||||
|
|
||||||
|
|
||||||
implementation "net.dankito.text.extraction:itext2-text-extractor:$textExtractorVersion"
|
implementation "net.dankito.text.extraction:itext2-text-extractor:$textExtractorVersion"
|
||||||
implementation "net.dankito.text.extraction:pdfbox-android-text-extractor:$textExtractorVersion"
|
implementation "net.dankito.text.extraction:pdfbox-android-text-extractor:$textExtractorVersion"
|
||||||
|
|
|
@ -40,12 +40,13 @@ 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
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
protected lateinit var permissionsService: IPermissionsService
|
||||||
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
|
@ -10,6 +10,7 @@ import net.dankito.banking.ui.android.dialogs.EnterTanDialog
|
||||||
import net.dankito.banking.ui.android.dialogs.SendMessageLogDialog
|
import net.dankito.banking.ui.android.dialogs.SendMessageLogDialog
|
||||||
import net.dankito.banking.ui.android.dialogs.TransferMoneyDialog
|
import net.dankito.banking.ui.android.dialogs.TransferMoneyDialog
|
||||||
import net.dankito.banking.ui.android.dialogs.settings.ProtectAppSettingsDialog
|
import net.dankito.banking.ui.android.dialogs.settings.ProtectAppSettingsDialog
|
||||||
|
import net.dankito.banking.ui.android.dialogs.settings.SettingsDialog
|
||||||
import net.dankito.banking.ui.android.dialogs.settings.SettingsDialogBase
|
import net.dankito.banking.ui.android.dialogs.settings.SettingsDialogBase
|
||||||
import net.dankito.banking.ui.android.home.HomeFragment
|
import net.dankito.banking.ui.android.home.HomeFragment
|
||||||
import net.dankito.banking.ui.android.views.BiometricAuthenticationButton
|
import net.dankito.banking.ui.android.views.BiometricAuthenticationButton
|
||||||
|
@ -43,6 +44,8 @@ interface BankingComponent {
|
||||||
|
|
||||||
fun inject(settingsDialogBase: SettingsDialogBase)
|
fun inject(settingsDialogBase: SettingsDialogBase)
|
||||||
|
|
||||||
|
fun inject(settingsDialog: SettingsDialog)
|
||||||
|
|
||||||
fun inject(protectAppSettingsDialog: ProtectAppSettingsDialog)
|
fun inject(protectAppSettingsDialog: ProtectAppSettingsDialog)
|
||||||
|
|
||||||
fun inject(biometricAuthenticationButton: BiometricAuthenticationButton)
|
fun inject(biometricAuthenticationButton: BiometricAuthenticationButton)
|
||||||
|
|
|
@ -29,6 +29,8 @@ import net.dankito.text.extraction.TextExtractorRegistry
|
||||||
import net.dankito.text.extraction.pdf.PdfBoxAndroidPdfTextExtractor
|
import net.dankito.text.extraction.pdf.PdfBoxAndroidPdfTextExtractor
|
||||||
import net.dankito.text.extraction.pdf.iText2PdfTextExtractor
|
import net.dankito.text.extraction.pdf.iText2PdfTextExtractor
|
||||||
import net.dankito.utils.ThreadPool
|
import net.dankito.utils.ThreadPool
|
||||||
|
import net.dankito.utils.android.permissions.IPermissionsService
|
||||||
|
import net.dankito.utils.android.permissions.PermissionsService
|
||||||
import net.dankito.utils.web.client.IWebClient
|
import net.dankito.utils.web.client.IWebClient
|
||||||
import net.dankito.utils.web.client.OkHttpWebClient
|
import net.dankito.utils.web.client.OkHttpWebClient
|
||||||
import javax.inject.Named
|
import javax.inject.Named
|
||||||
|
@ -87,6 +89,13 @@ class BankingModule(private val applicationContext: Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun providePermissionsService() : IPermissionsService {
|
||||||
|
return PermissionsService(mainActivity)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideAuthenticationService(biometricAuthenticationService: IBiometricAuthenticationService, persistence: IBankingPersistence,
|
fun provideAuthenticationService(biometricAuthenticationService: IBiometricAuthenticationService, persistence: IBankingPersistence,
|
||||||
|
|
|
@ -1,24 +1,41 @@
|
||||||
package net.dankito.banking.ui.android.dialogs.settings
|
package net.dankito.banking.ui.android.dialogs.settings
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.*
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
import androidx.appcompat.content.res.AppCompatResources
|
import androidx.appcompat.content.res.AppCompatResources
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
import kotlinx.android.synthetic.main.dialog_settings.*
|
import kotlinx.android.synthetic.main.dialog_settings.*
|
||||||
import kotlinx.android.synthetic.main.dialog_settings.view.*
|
import kotlinx.android.synthetic.main.dialog_settings.view.*
|
||||||
|
import net.codinux.banking.tools.importerexporter.CsvAccountTransactionsExporter
|
||||||
|
import net.codinux.banking.tools.importerexporter.model.AccountTransaction
|
||||||
import net.dankito.banking.ui.android.R
|
import net.dankito.banking.ui.android.R
|
||||||
import net.dankito.banking.ui.android.adapter.BankDataAdapterItem
|
import net.dankito.banking.ui.android.adapter.BankDataAdapterItem
|
||||||
import net.dankito.banking.ui.android.adapter.FastAdapterRecyclerView
|
import net.dankito.banking.ui.android.adapter.FastAdapterRecyclerView
|
||||||
|
import net.dankito.banking.ui.android.di.BankingComponent
|
||||||
|
import net.dankito.banking.ui.model.IAccountTransaction
|
||||||
import net.dankito.banking.ui.model.TypedBankData
|
import net.dankito.banking.ui.model.TypedBankData
|
||||||
|
import net.dankito.filechooserdialog.FileChooserDialog
|
||||||
|
import net.dankito.filechooserdialog.model.FileChooserDialogConfig
|
||||||
|
import net.dankito.utils.android.permissions.IPermissionsService
|
||||||
|
import java.io.File
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
||||||
open class SettingsDialog : SettingsDialogBase() {
|
open class SettingsDialog : SettingsDialogBase() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
val ExportTransactionsDateFormat = SimpleDateFormat("yyyyMMdd")
|
||||||
|
|
||||||
const val DialogTag = "SettingsDialog"
|
const val DialogTag = "SettingsDialog"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
protected lateinit var permissionsService: IPermissionsService
|
||||||
|
|
||||||
protected lateinit var banksAdapter: FastAdapterRecyclerView<BankDataAdapterItem>
|
protected lateinit var banksAdapter: FastAdapterRecyclerView<BankDataAdapterItem>
|
||||||
|
|
||||||
protected var banksChangedListener = { _: List<TypedBankData> ->
|
protected var banksChangedListener = { _: List<TypedBankData> ->
|
||||||
|
@ -26,6 +43,11 @@ open class SettingsDialog : SettingsDialogBase() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
init {
|
||||||
|
BankingComponent.component.inject(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
fun show(activity: FragmentActivity) {
|
fun show(activity: FragmentActivity) {
|
||||||
show(activity, DialogTag)
|
show(activity, DialogTag)
|
||||||
}
|
}
|
||||||
|
@ -59,6 +81,8 @@ open class SettingsDialog : SettingsDialogBase() {
|
||||||
btnSetAppProtection.setOnClickListener { navigateToProtectAppSettingsDialog() }
|
btnSetAppProtection.setOnClickListener { navigateToProtectAppSettingsDialog() }
|
||||||
selectLockAppAfter.periodInMinutes = presenter.appSettings.lockAppAfterMinutes
|
selectLockAppAfter.periodInMinutes = presenter.appSettings.lockAppAfterMinutes
|
||||||
|
|
||||||
|
btnExportAccountTransactions.setOnClickListener { exportAccountTransactions() }
|
||||||
|
|
||||||
// on Pre Lollipop devices setting vector drawables in xml is not supported -> set left drawable here
|
// on Pre Lollipop devices setting vector drawables in xml is not supported -> set left drawable here
|
||||||
val sendIcon = AppCompatResources.getDrawable(context, R.drawable.ic_baseline_send_24)
|
val sendIcon = AppCompatResources.getDrawable(context, R.drawable.ic_baseline_send_24)
|
||||||
btnShowSendMessageLogDialog.setCompoundDrawablesWithIntrinsicBounds(sendIcon, null, null, null)
|
btnShowSendMessageLogDialog.setCompoundDrawablesWithIntrinsicBounds(sendIcon, null, null, null)
|
||||||
|
@ -97,6 +121,62 @@ open class SettingsDialog : SettingsDialogBase() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected open fun exportAccountTransactions() {
|
||||||
|
val initialDirectory = presenter.appSettings.lastSelectedExportFolder?.let { File(it) }
|
||||||
|
val suggestedFilename = getExportCsvSuggestedFilename()
|
||||||
|
|
||||||
|
// val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
|
||||||
|
// intent.addCategory(Intent.CATEGORY_OPENABLE)
|
||||||
|
// intent.type = "text/csv"
|
||||||
|
//
|
||||||
|
// intent.putExtra(Intent.EXTRA_TITLE, suggestedFilename)
|
||||||
|
//
|
||||||
|
// startActivityForResult(intent, 1)
|
||||||
|
|
||||||
|
activity?.let { activity ->
|
||||||
|
val config = FileChooserDialogConfig(initialDirectory = initialDirectory, suggestedFilenameForSaveFileDialog = suggestedFilename)
|
||||||
|
FileChooserDialog().showSaveFileInFullscreenDialog(activity, permissionsService, config) { _, selectedFile ->
|
||||||
|
selectedFile?.let {
|
||||||
|
val transactions = presenter.allTransactions.map { mapTransaction(it) }
|
||||||
|
|
||||||
|
CsvAccountTransactionsExporter().export(selectedFile, transactions)
|
||||||
|
|
||||||
|
presenter.appSettings.lastSelectedExportFolder = selectedFile.parentFile.absolutePath
|
||||||
|
presenter.appSettingsChanged()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: this is almost the same code as in JavaFX MainMenuBar.getExportCsvSuggestedFilename() -> merge
|
||||||
|
protected open fun getExportCsvSuggestedFilename(): String? {
|
||||||
|
val transactions = presenter.allTransactions
|
||||||
|
val transactionsDates = transactions.map { it.valueDate }
|
||||||
|
val transactionsStartDate = transactionsDates.min()
|
||||||
|
val transactionsEndDate = transactionsDates.max()
|
||||||
|
|
||||||
|
return context?.getString(R.string.dialog_settings_export_account_transactions_suggested_file_name,
|
||||||
|
transactionsStartDate?.let { ExportTransactionsDateFormat.format(it) } ?: "", transactionsEndDate?.let { ExportTransactionsDateFormat.format(it) } ?: "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: this is exactly the same code as in JavaFX MainMenuBar.mapTransaction() -> merge
|
||||||
|
protected open fun mapTransaction(transaction: IAccountTransaction): AccountTransaction {
|
||||||
|
return AccountTransaction(
|
||||||
|
transaction.account.iban ?: transaction.account.identifier,
|
||||||
|
transaction.amount,
|
||||||
|
transaction.currency,
|
||||||
|
transaction.reference,
|
||||||
|
transaction.bookingDate,
|
||||||
|
transaction.valueDate,
|
||||||
|
transaction.otherPartyName,
|
||||||
|
transaction.otherPartyBankCode,
|
||||||
|
transaction.otherPartyAccountId,
|
||||||
|
transaction.bookingText
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
protected open fun reorderedBanks(oldPosition: Int, oldItem: TypedBankData, newPosition: Int, newItem: TypedBankData) {
|
protected open fun reorderedBanks(oldPosition: Int, oldItem: TypedBankData, newPosition: Int, newItem: TypedBankData) {
|
||||||
oldItem.displayIndex = oldPosition
|
oldItem.displayIndex = oldPosition
|
||||||
newItem.displayIndex = newPosition
|
newItem.displayIndex = newPosition
|
||||||
|
|
|
@ -115,6 +115,21 @@
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnExportAccountTransactions"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="@dimen/dialog_settings_navigate_to_sub_dialog_button_height"
|
||||||
|
android:layout_marginTop="@dimen/dialog_settings_navigate_to_sub_dialog_button_margin_top"
|
||||||
|
style="?android:attr/buttonBarButtonStyle"
|
||||||
|
android:gravity="start|center_vertical"
|
||||||
|
android:textAlignment="gravity"
|
||||||
|
android:textAllCaps="false"
|
||||||
|
android:textColor="@color/formButtonsTextColor"
|
||||||
|
android:textSize="@dimen/dialog_settings_navigate_to_sub_dialog_button_text_size"
|
||||||
|
android:text="@string/dialog_settings_export_account_transactions_title"
|
||||||
|
/>
|
||||||
|
|
||||||
|
|
||||||
<!-- left drawable is set in code as pre Lollipop devices don't support setting vector drawables in xml -->
|
<!-- left drawable is set in code as pre Lollipop devices don't support setting vector drawables in xml -->
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/btnShowSendMessageLogDialog"
|
android:id="@+id/btnShowSendMessageLogDialog"
|
||||||
|
|
|
@ -144,6 +144,8 @@
|
||||||
<string name="dialog_settings_update_accounts_automatically_after">Konten aktualisieren nach (kommt noch)</string>
|
<string name="dialog_settings_update_accounts_automatically_after">Konten aktualisieren nach (kommt noch)</string>
|
||||||
<string name="dialog_settings_secure_app_data">Appdaten schützen</string>
|
<string name="dialog_settings_secure_app_data">Appdaten schützen</string>
|
||||||
<string name="dialog_settings_lock_app_after">App sperren nach (kommt noch)</string>
|
<string name="dialog_settings_lock_app_after">App sperren nach (kommt noch)</string>
|
||||||
|
<string name="dialog_settings_export_account_transactions_title">Umsätze exportieren</string>
|
||||||
|
<string name="dialog_settings_export_account_transactions_suggested_file_name">umsaetze_%1$s-%2$s.csv</string>
|
||||||
<string name="dialog_settings_send_message_log_title">Message Log senden</string>
|
<string name="dialog_settings_send_message_log_title">Message Log senden</string>
|
||||||
|
|
||||||
<string name="dialog_protect_app_settings_title">Appzugangsschutz</string>
|
<string name="dialog_protect_app_settings_title">Appzugangsschutz</string>
|
||||||
|
|
|
@ -144,6 +144,8 @@
|
||||||
<string name="dialog_settings_update_accounts_automatically_after">Update accounts after (to be implemented)</string>
|
<string name="dialog_settings_update_accounts_automatically_after">Update accounts after (to be implemented)</string>
|
||||||
<string name="dialog_settings_secure_app_data">Secure app data</string>
|
<string name="dialog_settings_secure_app_data">Secure app data</string>
|
||||||
<string name="dialog_settings_lock_app_after">Lock app after (to be implemented)</string>
|
<string name="dialog_settings_lock_app_after">Lock app after (to be implemented)</string>
|
||||||
|
<string name="dialog_settings_export_account_transactions_title">Export account transactions</string>
|
||||||
|
<string name="dialog_settings_export_account_transactions_suggested_file_name">transactions_%1$s-%2$s.csv</string>
|
||||||
<string name="dialog_settings_send_message_log_title">Send message log</string>
|
<string name="dialog_settings_send_message_log_title">Send message log</string>
|
||||||
|
|
||||||
<string name="dialog_protect_app_settings_title">App protection</string>
|
<string name="dialog_protect_app_settings_title">App protection</string>
|
||||||
|
|
|
@ -39,6 +39,8 @@ dependencies {
|
||||||
|
|
||||||
implementation project(':LuceneBankingPersistence')
|
implementation project(':LuceneBankingPersistence')
|
||||||
|
|
||||||
|
implementation project(':CsvAccountTransactionsImporterAndExporter')
|
||||||
|
|
||||||
|
|
||||||
implementation "net.dankito.text.extraction:poppler-text-extractor:$textExtractorVersion"
|
implementation "net.dankito.text.extraction:poppler-text-extractor:$textExtractorVersion"
|
||||||
implementation "net.dankito.text.extraction:pdfbox-text-extractor:$textExtractorVersion"
|
implementation "net.dankito.text.extraction:pdfbox-text-extractor:$textExtractorVersion"
|
||||||
|
|
|
@ -5,6 +5,9 @@ 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 javafx.stage.FileChooser
|
||||||
|
import net.codinux.banking.tools.importerexporter.CsvAccountTransactionsExporter
|
||||||
|
import net.codinux.banking.tools.importerexporter.model.AccountTransaction
|
||||||
|
import net.dankito.banking.ui.model.IAccountTransaction
|
||||||
import net.dankito.utils.multiplatform.toFile
|
import net.dankito.utils.multiplatform.toFile
|
||||||
import net.dankito.banking.ui.model.moneytransfer.ExtractTransferMoneyDataFromPdfResult
|
import net.dankito.banking.ui.model.moneytransfer.ExtractTransferMoneyDataFromPdfResult
|
||||||
import net.dankito.banking.ui.model.moneytransfer.ExtractTransferMoneyDataFromPdfResultType
|
import net.dankito.banking.ui.model.moneytransfer.ExtractTransferMoneyDataFromPdfResultType
|
||||||
|
@ -13,10 +16,16 @@ 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
|
import java.io.File
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
|
||||||
|
|
||||||
open class MainMenuBar(protected val presenter: BankingPresenter) : View() {
|
open class MainMenuBar(protected val presenter: BankingPresenter) : View() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val ExportTransactionsDateFormat = SimpleDateFormat("yyyyMMdd")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
protected val areAccountsThatCanTransferMoneyAdded = SimpleBooleanProperty()
|
protected val areAccountsThatCanTransferMoneyAdded = SimpleBooleanProperty()
|
||||||
|
|
||||||
protected var lastSelectedFolder: File? = null
|
protected var lastSelectedFolder: File? = null
|
||||||
|
@ -58,6 +67,12 @@ open class MainMenuBar(protected val presenter: BankingPresenter) : View() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
menu(messages["main.window.menu.file.export"]) {
|
||||||
|
item(messages["main.window.menu.file.export.csv"]) {
|
||||||
|
action { exportAccountTransactions() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
separator()
|
separator()
|
||||||
|
|
||||||
item(messages["main.window.menu.file.quit"], KeyCodeCombination(KeyCode.Q, KeyCombination.SHORTCUT_DOWN)) {
|
item(messages["main.window.menu.file.quit"], KeyCodeCombination(KeyCode.Q, KeyCombination.SHORTCUT_DOWN)) {
|
||||||
|
@ -101,4 +116,58 @@ open class MainMenuBar(protected val presenter: BankingPresenter) : View() {
|
||||||
JavaFXDialogService().showErrorMessage(errorMessage, exception = result.error)
|
JavaFXDialogService().showErrorMessage(errorMessage, exception = result.error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected open fun exportAccountTransactions() {
|
||||||
|
val fileChooser = FileChooser()
|
||||||
|
|
||||||
|
fileChooser.extensionFilters.addAll(
|
||||||
|
FileChooser.ExtensionFilter("CSV files", "*.csv"),
|
||||||
|
FileChooser.ExtensionFilter("All files", "*.*")
|
||||||
|
)
|
||||||
|
|
||||||
|
fileChooser.initialDirectory = presenter.appSettings.lastSelectedExportFolder?.let { net.dankito.utils.multiplatform.File(it) }
|
||||||
|
fileChooser.initialFileName = getExportCsvSuggestedFilename()
|
||||||
|
|
||||||
|
fileChooser.showSaveDialog(currentWindow)?.let { selectedFile ->
|
||||||
|
presenter.appSettings.lastSelectedExportFolder = selectedFile.parent
|
||||||
|
presenter.appSettingsChanged()
|
||||||
|
|
||||||
|
var destinationFile = selectedFile
|
||||||
|
if (destinationFile.extension.isNullOrBlank()) {
|
||||||
|
destinationFile = File(destinationFile.absolutePath + ".csv")
|
||||||
|
}
|
||||||
|
|
||||||
|
val transactions = presenter.allTransactions.map { mapTransaction(it) }
|
||||||
|
|
||||||
|
CsvAccountTransactionsExporter().export(destinationFile, transactions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: this is almost the same code as in JAndroid SettingsDialog.getExportCsvSuggestedFilename() -> merge
|
||||||
|
protected open fun getExportCsvSuggestedFilename(): String? {
|
||||||
|
val transactions = presenter.allTransactions
|
||||||
|
val transactionsDates = transactions.map { it.valueDate }
|
||||||
|
val transactionsStartDate = transactionsDates.min()
|
||||||
|
val transactionsEndDate = transactionsDates.max()
|
||||||
|
|
||||||
|
return String.format(messages["main.window.menu.file.export.csv.suggested.filename"], transactionsStartDate?.let { ExportTransactionsDateFormat.format(it) } ?: "",
|
||||||
|
transactionsEndDate?.let { ExportTransactionsDateFormat.format(it) } ?: "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: this is exactly the same code as in Android SettingsDialog.mapTransaction() -> merge
|
||||||
|
protected open fun mapTransaction(transaction: IAccountTransaction): AccountTransaction {
|
||||||
|
return AccountTransaction(
|
||||||
|
transaction.account.iban ?: transaction.account.identifier,
|
||||||
|
transaction.amount,
|
||||||
|
transaction.currency,
|
||||||
|
transaction.reference,
|
||||||
|
transaction.bookingDate,
|
||||||
|
transaction.valueDate,
|
||||||
|
transaction.otherPartyName,
|
||||||
|
transaction.otherPartyBankCode,
|
||||||
|
transaction.otherPartyAccountId,
|
||||||
|
transaction.bookingText
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -14,6 +14,9 @@ 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.new.cash.transfer.from.pdf=Cash transfer from PDF
|
||||||
|
main.window.menu.file.export=Export
|
||||||
|
main.window.menu.file.export.csv=CSV
|
||||||
|
main.window.menu.file.export.csv.suggested.filename=transactions_%1$s-%2$s.csv
|
||||||
main.window.menu.file.quit=Quit
|
main.window.menu.file.quit=Quit
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,9 @@ 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.new.cash.transfer.from.pdf=Überweisung aus PDF
|
||||||
|
main.window.menu.file.export=Export
|
||||||
|
main.window.menu.file.export.csv=CSV
|
||||||
|
main.window.menu.file.export.csv.suggested.filename=umsaetze_%1$s-%2$s.csv
|
||||||
main.window.menu.file.quit=Beenden
|
main.window.menu.file.quit=Beenden
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,8 @@ open class AppSettings(
|
||||||
open var screenshotsAllowed: Boolean = false, // TODO: implement
|
open var screenshotsAllowed: Boolean = false, // TODO: implement
|
||||||
open var flickerCodeSettings: TanMethodSettings? = null,
|
open var flickerCodeSettings: TanMethodSettings? = null,
|
||||||
open var qrCodeSettings: TanMethodSettings? = null,
|
open var qrCodeSettings: TanMethodSettings? = null,
|
||||||
open var photoTanSettings: TanMethodSettings? = null
|
open var photoTanSettings: TanMethodSettings? = null,
|
||||||
|
open var lastSelectedExportFolder: String? = null // File is not that easily persistable so modeled it as string
|
||||||
) {
|
) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
Loading…
Reference in New Issue