Author SHA1 Message Date
Renovate Bot 86954f1653 Update plugin com.android.application to v9.4.0 2026-09-02 02:04:00 +00:00
choelzlandGitHub 1034e961ab New Crowdin updates (#6)
* New translations strings.xml (Romanian)

[ci skip]

* New translations strings.xml (French)

[ci skip]

* New translations strings.xml (Spanish)

[ci skip]

* New translations strings.xml (Afrikaans)

[ci skip]

* New translations strings.xml (Arabic)

[ci skip]

* New translations strings.xml (Catalan)

[ci skip]

* New translations strings.xml (Czech)

[ci skip]

* New translations strings.xml (Danish)

[ci skip]

* New translations strings.xml (German)

[ci skip]

* New translations strings.xml (Greek)

[ci skip]

* New translations strings.xml (Finnish)

[ci skip]

* New translations strings.xml (Hebrew)

[ci skip]

* New translations strings.xml (Hungarian)

[ci skip]

* New translations strings.xml (Italian)

[ci skip]

* New translations strings.xml (Japanese)

[ci skip]

* New translations strings.xml (Korean)

[ci skip]

* New translations strings.xml (Dutch)

[ci skip]

* New translations strings.xml (Norwegian)

[ci skip]

* New translations strings.xml (Polish)

[ci skip]

* New translations strings.xml (Portuguese)

[ci skip]

* New translations strings.xml (Russian)

[ci skip]

* New translations strings.xml (Serbian (Cyrillic))

[ci skip]

* New translations strings.xml (Swedish)

[ci skip]

* New translations strings.xml (Turkish)

[ci skip]

* New translations strings.xml (Ukrainian)

[ci skip]

* New translations strings.xml (Chinese Simplified)

[ci skip]

* New translations strings.xml (Chinese Traditional)

[ci skip]

* New translations strings.xml (English)

[ci skip]

* New translations strings.xml (Vietnamese)

[ci skip]

* New translations strings.xml (Portuguese, Brazilian)

[ci skip]

* New translations strings.xml (Romanian)

[ci skip]

* New translations strings.xml (French)

[ci skip]

* New translations strings.xml (Spanish)

[ci skip]

* New translations strings.xml (Arabic)

[ci skip]

* New translations strings.xml (Czech)

[ci skip]

* New translations strings.xml (Danish)

[ci skip]

* New translations strings.xml (German)

[ci skip]

* New translations strings.xml (Greek)

[ci skip]

* New translations strings.xml (Finnish)

[ci skip]

* New translations strings.xml (Italian)

[ci skip]

* New translations strings.xml (Japanese)

[ci skip]

* New translations strings.xml (Dutch)

[ci skip]

* New translations strings.xml (Norwegian)

[ci skip]

* New translations strings.xml (Polish)

[ci skip]

* New translations strings.xml (Portuguese)

[ci skip]

* New translations strings.xml (Russian)

[ci skip]

* New translations strings.xml (Swedish)

[ci skip]

* New translations strings.xml (Ukrainian)

[ci skip]

* New translations strings.xml (Chinese Simplified)

[ci skip]

* New translations strings.xml (Portuguese, Brazilian)

[ci skip]
2026-08-03 20:20:30 +02:00
choelzl ff93fdd4d7 Update Crowdin configuration file
[ci skip]
2026-08-03 20:02:44 +02:00
soraefir 726c4a73dc Fix project creation nextcloud 2026-08-03 19:51:26 +02:00
soraefir 191f221faa Allow comma in title 2026-08-03 19:51:26 +02:00
sora 553e33d441 Merge pull request 'Configure Renovate' (#1) from renovate/configure into main
Reviewed-on: #1
2026-07-12 14:47:56 +02:00
sora eec95cdcb0 Merge branch 'main' into renovate/configure 2026-07-12 14:47:47 +02:00
soraefir 106c4e27fc Fix sync and add beta features 2026-07-09 19:22:04 +02:00
soraefir 47d0471b48 Label sync and workflows 2026-07-05 15:02:36 +02:00
sora 56968dbe3b Update metadata/en-US/full_description.txt 2026-07-02 03:05:16 +02:00
soraefir 56036d5840 Fix toast & minor category/paymentmode fix 2026-07-02 02:43:33 +02:00
soraefir 4fa29da79d Better category/paymentmode storage and handling 2026-07-02 02:37:40 +02:00
sora c2c1017633 Update app/src/main/java/net/helcel/cowspent/model/DBPaymentMode.kt 2026-07-02 02:06:55 +02:00
sora 3aeabf4dd3 Update app/src/main/java/net/helcel/cowspent/model/DBCategory.kt 2026-07-02 02:06:41 +02:00
sora 937c3db1f5 Update app/src/main/java/net/helcel/cowspent/persistence/CowspentSQLiteOpenHelper.kt 2026-07-02 02:06:08 +02:00
sora 5b3c47045c Disable custom Label until fully implemented 2026-07-02 02:01:36 +02:00
soraefir 7581985abd Stripping fix 2026-07-01 22:12:42 +02:00
soraefir 810ca20201 Custom Labels, User archive, Versioning, String and UI cleanup 2026-07-01 22:10:08 +02:00
soraefir 678a90ead9 Archiving and Label options 2026-06-30 20:40:32 +02:00
soraefir 88469fa96c Fix monochrome logo 2026-06-30 19:31:39 +02:00
soraefir 119ae757b9 Migrate to SecureStorage 2026-06-30 19:24:25 +02:00
soraefir c3d380ddb4 Migrate to encryptedPref 2026-06-30 18:59:50 +02:00
soraefir a8ba00269b Hide inactive members in drawer 2026-06-30 07:41:34 +02:00
soraefir 756a37de23 String cleanup 2026-06-30 07:37:52 +02:00
soraefir 5f7266013d More cleanup 2026-06-30 07:33:49 +02:00
soraefir e98dc4a86b Cleanup imports and co 2026-06-30 07:30:57 +02:00
soraefir afb14ff7b9 Fix Monochrome 2026-06-30 07:26:33 +02:00
soraefir 1bee1dabbe Disable dependency tree, logo to png 2026-06-30 07:17:57 +02:00
soraefir 84432fe68d Better formating and placeholder 2026-06-29 21:38:01 +02:00
soraefir 7e00b14d27 Fix keyboard padding and better currency management 2026-06-29 21:31:59 +02:00
soraefir ec4d60897a Currency parsing and formating
# Conflicts:
#	app/src/main/java/net/helcel/cowspent/android/main/BillsListComponents.kt
2026-06-29 21:30:35 +02:00
soraefir 5c74336c10 Fixes for currency, logo, render and more 2026-06-27 12:18:33 +02:00
soraefir d617c5c7be project creation and currency db 2026-06-14 21:34:58 +02:00
soraefir a9a303478c better currency handling 2026-06-14 21:33:08 +02:00
sora 110ab397b8 Update README.md 2026-06-09 19:19:24 +02:00
sora 3675e87651 Upload files to ".github/images" 2026-06-09 19:18:02 +02:00
sora 65799496f9 Update app/build.gradle 2026-06-09 08:51:43 +02:00
soraefir 2967dbd001 fix ui 2026-06-09 01:51:42 +02:00
Renovate Bot c2f59fa1df Add renovate.json 2026-05-30 02:02:13 +00:00
sora 5c43bedc77 Update README.md 2026-05-30 00:00:04 +02:00
soraefir 5ee2041769 init
CI-Android APK / build (push) Failing after 4m27s
2026-05-29 23:19:55 +02:00
22 changed files with 467 additions and 607 deletions
-9
View File
@@ -49,21 +49,12 @@ jobs:
- name: Setup Gradle - name: Setup Gradle
uses: gradle/actions/setup-gradle@v6 uses: gradle/actions/setup-gradle@v6
- name: Run unit tests
run: ./gradlew testDebugUnitTest
- name: Build APK - name: Build APK
run: | run: |
VERSION_CODE=$(git rev-list --count HEAD) VERSION_CODE=$(git rev-list --count HEAD)
VERSION_NAME=$(git describe --tags --always) VERSION_NAME=$(git describe --tags --always)
./gradlew assembleSignedRelease -PVERSION_CODE=$VERSION_CODE -PVERSION_NAME=$VERSION_NAME ./gradlew assembleSignedRelease -PVERSION_CODE=$VERSION_CODE -PVERSION_NAME=$VERSION_NAME
- name: Upload APK
uses: actions/upload-artifact@v4
with:
name: Cowspent-APK
path: app/build/outputs/apk/signedRelease/app-signedRelease.apk
- name: Release - name: Release
uses: softprops/action-gh-release@v3 uses: softprops/action-gh-release@v3
if: startsWith(github.ref, 'refs/tags/') if: startsWith(github.ref, 'refs/tags/')
+9 -42
View File
@@ -1,8 +1,7 @@
plugins { plugins {
id 'com.android.application' version '9.4.0' id 'com.android.application' version '9.4.0'
id 'org.jetbrains.kotlin.plugin.serialization' version '2.4.10' id 'org.jetbrains.kotlin.plugin.serialization' version '2.4.0'
id 'org.jetbrains.kotlin.plugin.compose' version '2.4.10' id 'org.jetbrains.kotlin.plugin.compose' version '2.4.0'
id 'jacoco'
} }
android { android {
@@ -41,7 +40,6 @@ android {
debuggable true debuggable true
initWith(buildTypes.release) initWith(buildTypes.release)
signingConfig = signingConfigs.debug signingConfig = signingConfigs.debug
enableUnitTestCoverage true
} }
release { release {
minifyEnabled true minifyEnabled true
@@ -71,12 +69,6 @@ android {
compose = true compose = true
} }
testOptions {
unitTests {
includeAndroidResources = true
}
}
kotlin { kotlin {
jvmToolchain(21) jvmToolchain(21)
} }
@@ -104,21 +96,21 @@ android {
} }
dependencies { dependencies {
implementation 'androidx.compose.foundation:foundation:1.12.0' implementation 'androidx.compose.foundation:foundation:1.11.4'
implementation 'androidx.compose.runtime:runtime:1.12.0' implementation 'androidx.compose.runtime:runtime:1.11.4'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.5' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.5'
implementation 'androidx.preference:preference-ktx:1.2.1' implementation 'androidx.preference:preference-ktx:1.2.1'
implementation 'androidx.work:work-runtime-ktx:2.11.2' implementation 'androidx.work:work-runtime-ktx:2.11.2'
implementation 'com.google.zxing:core:3.5.4' implementation 'com.google.zxing:core:3.5.4'
implementation 'androidx.camera:camera-camera2:1.6.2' implementation 'androidx.camera:camera-camera2:1.6.1'
implementation 'androidx.camera:camera-lifecycle:1.6.2' implementation 'androidx.camera:camera-lifecycle:1.6.1'
implementation 'androidx.camera:camera-view:1.6.2' implementation 'androidx.camera:camera-view:1.6.1'
implementation 'com.github.nextcloud:Android-SingleSignOn:1.1.0' implementation 'com.github.nextcloud:Android-SingleSignOn:1.1.0'
implementation 'com.opencsv:opencsv:5.12.0' implementation 'com.opencsv:opencsv:5.12.0'
implementation platform('androidx.compose:compose-bom:2026.08.00') implementation platform('androidx.compose:compose-bom:2026.06.01')
implementation 'androidx.compose.ui:ui' implementation 'androidx.compose.ui:ui'
implementation 'androidx.compose.material:material' implementation 'androidx.compose.material:material'
implementation 'androidx.compose.material:material-icons-extended' implementation 'androidx.compose.material:material-icons-extended'
@@ -129,30 +121,5 @@ dependencies {
implementation 'androidx.security:security-crypto:1.1.0' implementation 'androidx.security:security-crypto:1.1.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.11.0' implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.11.0'
implementation "androidx.datastore:datastore-preferences:1.2.1" implementation "androidx.datastore:datastore-preferences:1.2.1"
implementation "com.google.crypto.tink:tink-android:1.23.0" implementation "com.google.crypto.tink:tink-android:1.22.0"
testImplementation 'junit:junit:4.13.2'
testImplementation 'io.mockk:mockk:1.14.11'
testImplementation 'org.robolectric:robolectric:4.16.1'
testImplementation 'androidx.test:core:1.7.0'
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.11.0'
}
tasks.register('jacocoTestReport', JacocoReport) {
dependsOn 'testDebugUnitTest'
reports {
xml.required = true
html.required = true
}
def fileFilter = ['**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*', '**/*Test*.*']
def debugTree = fileTree(dir: "${project.layout.buildDirectory.asFile.get()}/intermediates/built_in_kotlinc/debug/compileDebugKotlin/classes", excludes: fileFilter)
def mainSrc = "${project.projectDir}/src/main/java"
sourceDirectories.from = files([mainSrc])
classDirectories.from = files([debugTree])
executionData.from = fileTree(dir: project.layout.buildDirectory.asFile.get(), includes: [
'jacoco/testDebugUnitTest.exec', 'outputs/unit_test_code_coverage/debugUnitTest/testDebugUnitTest.exec'
])
} }
@@ -151,9 +151,8 @@ class EditBillActivity : AppCompatActivity() {
val billIdToDuplicate = intent.getLongExtra(PARAM_BILL_ID_TO_DUPLICATE, 0) val billIdToDuplicate = intent.getLongExtra(PARAM_BILL_ID_TO_DUPLICATE, 0)
val timeNowSeconds = System.currentTimeMillis() / 1000 val timeNowSeconds = System.currentTimeMillis() / 1000
if (billIdToDuplicate == 0L) { if (billIdToDuplicate == 0L) {
val project = db.getProject(projectId)
bill = DBBill( bill = DBBill(
0, 0, projectId, project?.lastPayerId ?: 0, 0.0, timeNowSeconds, 0, 0, projectId, 0, 0.0, timeNowSeconds,
"", DBBill.STATE_ADDED, DBBill.NON_REPEATED, "", DBBill.STATE_ADDED, DBBill.NON_REPEATED,
DBBill.PAYMODE_NONE, DBBill.CATEGORY_NONE, "", DBBill.PAYMODE_ID_NONE DBBill.PAYMODE_NONE, DBBill.CATEGORY_NONE, "", DBBill.PAYMODE_ID_NONE
) )
@@ -325,25 +324,15 @@ class EditBillActivity : AppCompatActivity() {
private suspend fun saveBill(): Long = withContext(Dispatchers.IO) { private suspend fun saveBill(): Long = withContext(Dispatchers.IO) {
val groupedBillIds = intent.getLongArrayExtra(PARAM_GROUPED_BILL_IDS) val groupedBillIds = intent.getLongArrayExtra(PARAM_GROUPED_BILL_IDS)
val splitMode = viewModel.splitMode val isCustomSplit = viewModel.isCustomSplit
if (splitMode != SplitMode.EVEN) { if (isCustomSplit) {
val splits: Map<Long, Double> = if (splitMode == SplitMode.CUSTOM) { val splits: Map<Long, Double> = viewModel.owersCustomSplit.filter { (id, amountStr) ->
viewModel.owersCustomSplit.filter { (id, amountStr) -> viewModel.owersSelection[id] == true && (amountStr.replace(',', '.').toDoubleOrNull()
viewModel.owersSelection[id] == true && viewModel.parseAmountFromUi(amountStr) > 0 ?: 0.0) > 0
}.mapValues { }.mapValues {
val uiAmount = viewModel.parseAmountFromUi(it.value) val uiAmount = it.value.replace(',', '.').toDoubleOrNull() ?: 0.0
SupportUtil.round2(uiAmount / viewModel.selectedCurrencyRate) SupportUtil.round2(uiAmount / viewModel.selectedCurrencyRate)
}
} else { // PERCENT
val totalAmount = viewModel.amountAsDouble
viewModel.owersPercentSplit.filter { (id, percentStr) ->
viewModel.owersSelection[id] == true && viewModel.parseAmountFromUi(percentStr) > 0
}.mapValues {
val percent = viewModel.parseAmountFromUi(it.value)
val uiAmount = SupportUtil.round2(totalAmount * percent / 100.0)
SupportUtil.round2(uiAmount / viewModel.selectedCurrencyRate)
}
} }
if (splits.isEmpty()) return@withContext 0L if (splits.isEmpty()) return@withContext 0L
@@ -416,8 +405,16 @@ class EditBillActivity : AppCompatActivity() {
val proj = db.getProject(bill.projectId) val proj = db.getProject(bill.projectId)
if (proj != null) db.syncIfRemote(proj) if (proj != null) db.syncIfRemote(proj)
db.updateProject( db.updateProject(
projId = bill.projectId, bill.projectId,
newLastPayerId = viewModel.payerId null,
null,
null,
viewModel.payerId,
null,
null,
null,
null,
null
) )
return@withContext firstSavedId return@withContext firstSavedId
@@ -461,8 +458,16 @@ class EditBillActivity : AppCompatActivity() {
newOwersIds.forEach { newBill.billOwers += DBBillOwer(0, 0, it) } newOwersIds.forEach { newBill.billOwers += DBBillOwer(0, 0, it) }
val newBillId = db.addBill(newBill) val newBillId = db.addBill(newBill)
db.updateProject( db.updateProject(
projId = bill.projectId, bill.projectId,
newLastPayerId = viewModel.payerId null,
null,
null,
viewModel.payerId,
null,
null,
null,
null,
null
) )
val proj = db.getProject(bill.projectId) val proj = db.getProject(bill.projectId)
if (proj != null) db.syncIfRemote(proj) if (proj != null) db.syncIfRemote(proj)
@@ -1,13 +1,11 @@
package net.helcel.cowspent.android.bill_edit package net.helcel.cowspent.android.bill_edit
import android.annotation.SuppressLint import android.annotation.SuppressLint
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.* import androidx.compose.material.*
@@ -18,18 +16,12 @@ import androidx.compose.material.icons.filled.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.scale import androidx.compose.ui.draw.scale
import androidx.compose.ui.focus.FocusDirection
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
@@ -61,13 +53,6 @@ fun EditBillScreen(
) { ) {
val canEdit = accessLevel == DBProject.ACCESS_LEVEL_UNKNOWN || accessLevel >= DBProject.ACCESS_LEVEL_PARTICIPANT val canEdit = accessLevel == DBProject.ACCESS_LEVEL_UNKNOWN || accessLevel >= DBProject.ACCESS_LEVEL_PARTICIPANT
val context = LocalContext.current val context = LocalContext.current
val focusRequester = remember { FocusRequester() }
LaunchedEffect(Unit) {
if (viewModel.isNewBill) {
focusRequester.requestFocus()
}
}
StatefulAlertDialog( StatefulAlertDialog(
state = viewModel.dialogState, state = viewModel.dialogState,
@@ -143,8 +128,7 @@ fun EditBillScreen(
viewModel = viewModel, viewModel = viewModel,
canEdit = canEdit, canEdit = canEdit,
onDateClick = onDateClick, onDateClick = onDateClick,
onTimeClick = onTimeClick, onTimeClick = onTimeClick
amountFocusRequester = focusRequester
) )
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
@@ -180,8 +164,7 @@ fun BillBasicInfoSection(
viewModel: EditBillViewModel, viewModel: EditBillViewModel,
canEdit: Boolean, canEdit: Boolean,
onDateClick: () -> Unit, onDateClick: () -> Unit,
onTimeClick: () -> Unit, onTimeClick: () -> Unit
amountFocusRequester: FocusRequester
) { ) {
Text( Text(
text = "GENERAL", text = "GENERAL",
@@ -192,20 +175,29 @@ fun BillBasicInfoSection(
) )
val context = LocalContext.current val context = LocalContext.current
val focusManager = LocalFocusManager.current
val currencyDialogTitle = val currencyDialogTitle =
stringResource(R.string.currency_dialog_title, viewModel.mainCurrencyName) stringResource(R.string.currency_dialog_title, viewModel.mainCurrencyName)
OutlinedTextField(
value = viewModel.what,
onValueChange = { viewModel.what = it },
enabled = canEdit,
placeholder = { Text(stringResource(R.string.label_what)) },
modifier = Modifier.fillMaxWidth(),
leadingIcon = { Icon(Icons.Default.Title, contentDescription = null) }
)
Spacer(modifier = Modifier.height(8.dp))
OutlinedTextField( OutlinedTextField(
value = viewModel.amount, value = viewModel.amount,
onValueChange = { nv-> onValueChange = {
val filteredValue = nv.filter { it in "0123456789.+-*/" } viewModel.amount = it
viewModel.amount = filteredValue
viewModel.updateSplits() viewModel.updateSplits()
}, },
enabled = canEdit, enabled = canEdit,
placeholder = { Text("0") }, placeholder = { Text("0") },
modifier = Modifier.fillMaxWidth().focusRequester(amountFocusRequester), modifier = Modifier.fillMaxWidth(),
leadingIcon = { leadingIcon = {
val currencyToShow = viewModel.selectedCurrencyName.ifEmpty { val currencyToShow = viewModel.selectedCurrencyName.ifEmpty {
viewModel.mainCurrencyName.ifEmpty { "$" } viewModel.mainCurrencyName.ifEmpty { "$" }
@@ -239,21 +231,7 @@ fun BillBasicInfoSection(
Icon(Icons.Default.SwapHoriz, contentDescription = "Change Currency") Icon(Icons.Default.SwapHoriz, contentDescription = "Change Currency")
} }
}, },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Phone, imeAction = ImeAction.Next), keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
keyboardActions = KeyboardActions(onNext = { focusManager.moveFocus(FocusDirection.Next) })
)
Spacer(modifier = Modifier.height(8.dp))
OutlinedTextField(
value = viewModel.what,
onValueChange = { viewModel.what = it },
enabled = canEdit,
placeholder = { Text(stringResource(R.string.label_what)) },
modifier = Modifier.fillMaxWidth(),
leadingIcon = { Icon(Icons.Default.Title, contentDescription = null) },
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
keyboardActions = KeyboardActions(onNext = { focusManager.moveFocus(FocusDirection.Next) })
) )
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
@@ -379,54 +357,34 @@ fun OwerSelectionSection(
) )
Spacer(Modifier.weight(1f)) Spacer(Modifier.weight(1f))
if (viewModel.splitMode != SplitMode.EVEN) { if (viewModel.isCustomSplit) {
val diff = viewModel.getDiffSplit() val diff = viewModel.getDiffSplit()
if (abs(diff) > 0.01) { if (abs(diff) > 0.01) {
val unit = if (viewModel.splitMode == SplitMode.PERCENT) "%" else ""
val diffText = val diffText =
if (diff > 0) "Missing: ${SupportUtil.normalNumberFormat.format(diff)}$unit" else "Excess: ${ if (diff > 0) "Missing: ${SupportUtil.normalNumberFormat.format(diff)}" else "Excess: ${
SupportUtil.normalNumberFormat.format(-diff) SupportUtil.normalNumberFormat.format(-diff)
}$unit" }"
Text( Text(
diffText, diffText,
color = MaterialTheme.colors.error, color = MaterialTheme.colors.error,
fontSize = 12.sp, fontSize = 12.sp,
modifier = Modifier.padding(horizontal = 4.dp) modifier = Modifier.padding(horizontal = 16.dp)
) )
} }
} else {
Text("Even Split", fontSize = 12.sp)
} }
Switch(
val splitOptions = listOf(SplitMode.EVEN, SplitMode.CUSTOM, SplitMode.PERCENT) checked = !viewModel.isCustomSplit,
Row( enabled = canEdit,
modifier = Modifier onCheckedChange = {
.padding(start = 8.dp) viewModel.isCustomSplit = !it
.background(MaterialTheme.colors.onSurface.copy(alpha = 0.05f), MaterialTheme.shapes.small) viewModel.updateSplits()
) { },
splitOptions.forEach { mode -> colors = SwitchDefaults.colors(
val isSelected = viewModel.splitMode == mode uncheckedThumbColor = MaterialTheme.colors.onSurface,
Box( )
modifier = Modifier )
.clip(MaterialTheme.shapes.small)
.background(if (isSelected) MaterialTheme.colors.primary else Color.Transparent)
.clickable(enabled = canEdit) {
viewModel.splitMode = mode
viewModel.updateSplits()
}
.padding(horizontal = 8.dp, vertical = 4.dp)
) {
Text(
text = when (mode) {
SplitMode.EVEN -> "="
SplitMode.CUSTOM -> "#"
SplitMode.PERCENT -> "%"
},
color = if (isSelected) MaterialTheme.colors.onPrimary else MaterialTheme.colors.onSurface,
fontSize = 14.sp,
fontWeight = FontWeight.Bold
)
}
}
}
} }
viewModel.members.forEach { member -> viewModel.members.forEach { member ->
@@ -452,34 +410,21 @@ fun OwerSelectionSection(
Spacer(modifier = Modifier.width(8.dp)) Spacer(modifier = Modifier.width(8.dp))
Text(member.name, modifier = Modifier.weight(1f)) Text(member.name, modifier = Modifier.weight(1f))
if (isSelected || viewModel.splitMode != SplitMode.EVEN) { if (isSelected || viewModel.isCustomSplit) {
val focusManager = LocalFocusManager.current
val interactionSource = remember { MutableInteractionSource() } val interactionSource = remember { MutableInteractionSource() }
val value = if (viewModel.splitMode == SplitMode.PERCENT) {
viewModel.owersPercentSplit[member.id] ?: ""
} else {
viewModel.owersCustomSplit[member.id] ?: ""
}
BasicTextField( BasicTextField(
value = value, value = viewModel.owersCustomSplit[member.id] ?: "",
onValueChange = { nv -> onValueChange = {
val filteredValue = nv.filter { it in "0123456789.+-*/" } viewModel.owersCustomSplit[member.id] = it
if (viewModel.splitMode == SplitMode.PERCENT) { viewModel.owersSelection[member.id] = (it != "")
viewModel.owersPercentSplit[member.id] = filteredValue
} else {
viewModel.owersCustomSplit[member.id] = filteredValue
}
viewModel.owersSelection[member.id] = (filteredValue != "")
}, },
modifier = Modifier modifier = Modifier
.width(80.dp) .width(80.dp)
.height(46.dp), .height(46.dp),
interactionSource = interactionSource, interactionSource = interactionSource,
singleLine = true, singleLine = true,
enabled = viewModel.splitMode != SplitMode.EVEN && canEdit, enabled = viewModel.isCustomSplit && canEdit,
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Next), keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
keyboardActions = KeyboardActions(onNext = { focusManager.moveFocus(FocusDirection.Next) }),
cursorBrush = SolidColor(MaterialTheme.colors.onSurface), cursorBrush = SolidColor(MaterialTheme.colors.onSurface),
textStyle = LocalTextStyle.current.copy( textStyle = LocalTextStyle.current.copy(
textAlign = TextAlign.Right, textAlign = TextAlign.Right,
@@ -487,13 +432,13 @@ fun OwerSelectionSection(
), ),
) { innerTextField -> ) { innerTextField ->
TextFieldDefaults.OutlinedTextFieldDecorationBox( TextFieldDefaults.OutlinedTextFieldDecorationBox(
value = value, value = viewModel.owersCustomSplit[member.id] ?: "",
visualTransformation = VisualTransformation.None, visualTransformation = VisualTransformation.None,
innerTextField = innerTextField, innerTextField = innerTextField,
singleLine = true, singleLine = true,
enabled = viewModel.splitMode != SplitMode.EVEN && canEdit, enabled = viewModel.isCustomSplit && canEdit,
interactionSource = interactionSource, interactionSource = interactionSource,
contentPadding = PaddingValues(horizontal = 8.dp, vertical = 0.dp), contentPadding = PaddingValues(horizontal = 12.dp, vertical = 0.dp),
colors = TextFieldDefaults.textFieldColors( colors = TextFieldDefaults.textFieldColors(
cursorColor = MaterialTheme.colors.onSurface, cursorColor = MaterialTheme.colors.onSurface,
backgroundColor = Color.Transparent, backgroundColor = Color.Transparent,
@@ -16,10 +16,6 @@ import net.helcel.cowspent.model.DBMember
import net.helcel.cowspent.util.SupportUtil import net.helcel.cowspent.util.SupportUtil
import net.helcel.cowspent.util.evalMath import net.helcel.cowspent.util.evalMath
enum class SplitMode {
EVEN, CUSTOM, PERCENT
}
class EditBillViewModel : ViewModel() { class EditBillViewModel : ViewModel() {
var what by mutableStateOf("") var what by mutableStateOf("")
var amount by mutableStateOf("") var amount by mutableStateOf("")
@@ -38,36 +34,27 @@ class EditBillViewModel : ViewModel() {
var members by mutableStateOf<List<DBMember>>(emptyList()) var members by mutableStateOf<List<DBMember>>(emptyList())
var owersSelection = mutableStateMapOf<Long, Boolean>() var owersSelection = mutableStateMapOf<Long, Boolean>()
var splitMode by mutableStateOf(SplitMode.EVEN) var isCustomSplit by mutableStateOf(false)
var owersCustomSplit = mutableStateMapOf<Long, String>() var owersCustomSplit = mutableStateMapOf<Long, String>()
var owersPercentSplit = mutableStateMapOf<Long, String>()
var dialogState by mutableStateOf<DialogState?>(null) var dialogState by mutableStateOf<DialogState?>(null)
fun parseAmountFromUi(input: String): Double {
if (input.isBlank()) return 0.0
val sanitized = input.replace(',', '.')
// If it contains any math operator, try evalMath first
if (sanitized.any { it in "+-*/" }) {
try {
val result = evalMath(sanitized)
if (result != 0.0) return SupportUtil.round2(result)
} catch (_: Exception) {}
}
val parsed = parseAmount(input) ?: 0.0
return SupportUtil.round2(parsed)
}
val amountAsDouble: Double val amountAsDouble: Double
get() = parseAmountFromUi(amount) get() {
return parseAmount(amount) ?: try {
evalMath(amount.replace(',', '.'))
} catch (_: Exception) {
0.0
}
}
fun getEvenSplit(): Double { fun getEvenSplit(): Double {
val selectedOwersCount = owersSelection.count { it.value } val selectedOwersCount = owersSelection.count { it.value }
return if (selectedOwersCount > 0) SupportUtil.round2(amountAsDouble / selectedOwersCount) else 0.0 return if (selectedOwersCount > 0) amountAsDouble / selectedOwersCount else 0.0
} }
fun updateSplits() { fun updateSplits() {
if (splitMode == SplitMode.EVEN) { if (!isCustomSplit) {
val even = getEvenSplit() val even = getEvenSplit()
val evenStr = if (even == 0.0) "" else SupportUtil.round2(even).toString() val evenStr = if (even == 0.0) "" else SupportUtil.round2(even).toString()
members.forEach { m -> members.forEach { m ->
@@ -77,36 +64,18 @@ class EditBillViewModel : ViewModel() {
owersCustomSplit.remove(m.id) owersCustomSplit.remove(m.id)
} }
} }
} else if (splitMode == SplitMode.PERCENT) {
val selectedCount = owersSelection.count { it.value }
if (selectedCount > 0) {
val evenPercent = 100.0 / selectedCount
val evenPercentStr = SupportUtil.round2(evenPercent).toString()
members.forEach { m ->
if (owersSelection[m.id] == true) {
if (owersPercentSplit[m.id].isNullOrEmpty()) {
owersPercentSplit[m.id] = evenPercentStr
}
} else {
owersPercentSplit.remove(m.id)
}
}
}
} }
} }
fun toggleMember(id: Long, selected: Boolean) { fun toggleMember(id: Long, selected: Boolean) {
owersSelection[id] = selected owersSelection[id] = selected
if (splitMode != SplitMode.EVEN) { if (isCustomSplit) {
if (selected) { if (selected) {
if (splitMode == SplitMode.CUSTOM && owersCustomSplit[id].isNullOrEmpty()) { if (owersCustomSplit[id].isNullOrEmpty()) {
owersCustomSplit[id] = "0" owersCustomSplit[id] = "0"
} else if (splitMode == SplitMode.PERCENT && owersPercentSplit[id].isNullOrEmpty()) {
owersPercentSplit[id] = "0"
} }
} else { } else {
owersCustomSplit.remove(id) owersCustomSplit.remove(id)
owersPercentSplit.remove(id)
} }
} else { } else {
updateSplits() updateSplits()
@@ -114,17 +83,10 @@ class EditBillViewModel : ViewModel() {
} }
fun getDiffSplit(): Double { fun getDiffSplit(): Double {
return if (splitMode == SplitMode.PERCENT) { val customTotal = owersCustomSplit.entries
val customTotal = owersPercentSplit.entries .filter { owersSelection[it.key] == true }
.filter { owersSelection[it.key] == true } .sumOf { it.value.replace(',', '.').toDoubleOrNull() ?: 0.0 }
.sumOf { parseAmountFromUi(it.value) } return amountAsDouble - customTotal
SupportUtil.round2(100.0 - customTotal)
} else {
val customTotal = owersCustomSplit.entries
.filter { owersSelection[it.key] == true }
.sumOf { parseAmountFromUi(it.value) }
SupportUtil.round2(amountAsDouble - customTotal)
}
} }
fun getOwersIds(): List<Long> { fun getOwersIds(): List<Long> {
@@ -242,10 +204,9 @@ class EditBillViewModel : ViewModel() {
owersSelection.clear() owersSelection.clear()
owersCustomSplit.clear() owersCustomSplit.clear()
owersPercentSplit.clear()
if (customSplits != null) { if (customSplits != null) {
splitMode = SplitMode.CUSTOM isCustomSplit = true
for (member in members) { for (member in members) {
val selected = customSplits.containsKey(member.id) val selected = customSplits.containsKey(member.id)
owersSelection[member.id] = selected owersSelection[member.id] = selected
@@ -261,7 +222,6 @@ class EditBillViewModel : ViewModel() {
} }
} else { } else {
// Even split logic // Even split logic
splitMode = SplitMode.EVEN
val billOwerIds = bill.billOwersIds val billOwerIds = bill.billOwersIds
val selectedCount = billOwerIds.size val selectedCount = billOwerIds.size
@@ -84,8 +84,9 @@ class ManageCurrenciesActivity : AppCompatActivity() {
lifecycleScope.launch { lifecycleScope.launch {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
db!!.updateProject( db!!.updateProject(
projId = selectedProjectID, selectedProjectID, null, null, null,
newCurrencyName = newMainCurrencyName null, null, newMainCurrencyName,
null, null, null
) )
val project = db!!.getProject(selectedProjectID) val project = db!!.getProject(selectedProjectID)
if (project != null) { if (project != null) {
@@ -93,12 +94,7 @@ class ManageCurrenciesActivity : AppCompatActivity() {
if (project.type == ProjectType.COSPEND) { if (project.type == ProjectType.COSPEND) {
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
if (!db!!.cowspentServerSyncHelper if (!db!!.cowspentServerSyncHelper
.editRemoteProject( .editRemoteProject(selectedProjectID, project.name, null, null, newMainCurrencyName, editMainCurrencyCallBack)
projId = selectedProjectID,
newName = project.name,
newMainCurrencyName = newMainCurrencyName,
callback = editMainCurrencyCallBack
)
) { ) {
showToast(this@ManageCurrenciesActivity, getString(R.string.remote_project_operation_no_network), Toast.LENGTH_LONG) showToast(this@ManageCurrenciesActivity, getString(R.string.remote_project_operation_no_network), Toast.LENGTH_LONG)
} }
@@ -4,10 +4,8 @@ import android.annotation.SuppressLint
import android.content.Intent import android.content.Intent
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
@@ -419,9 +417,6 @@ fun BillsListScreen(
is SectionItem -> SectionHeader(item.title) is SectionItem -> SectionHeader(item.title)
} }
} }
item {
Spacer(modifier = Modifier.height(64.dp).fillMaxWidth())
}
} }
} }
} }
@@ -88,7 +88,6 @@ object BillsListUtils {
shareIntent, shareIntent,
context.getString(R.string.title_settle) context.getString(R.string.title_settle)
) )
chooserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(chooserIntent) context.startActivity(chooserIntent)
} }
@@ -427,26 +427,19 @@ class BillsListViewActivity :
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
db.updateProject( db.updateProject(
projId = projectId, projId = projectId,
newName = null,
newEmail = null,
newPassword = null,
newLastPayerId = null,
newLastSyncedTimestamp = null,
newCurrencyName = null,
newDeletionDisabled = null,
newMyAccessLevel = null,
newBearerToken = null,
newArchivedTs = newArchivedTs newArchivedTs = newArchivedTs
) )
} }
if (!proj.isLocal) {
db.cowspentServerSyncHelper.editRemoteProject(
projId = projectId,
newArchivedTs = newArchivedTs,
callback = object : ICallback {
override fun onFinish() {}
override fun onFinish(result: String, message: String) {
if (message.isNotEmpty()) {
showToast(this@BillsListViewActivity, getString(R.string.error_sync, message))
}
}
override fun onScheduled() {}
}
)
}
setupDrawerProjects() setupDrawerProjects()
refreshLists() refreshLists()
@@ -114,7 +114,7 @@ object ProjectImportHelper {
} }
else -> 0 else -> 0
} }
val payerName = if (columns.containsKey("payer_name")) line[columns["payer_name"]!!].trim() else "" val payerName = if (columns.containsKey("payer_name")) line[columns["payer_name"]!!] else ""
val payerWeight = if (columns.containsKey("payer_weight")) line[columns["payer_weight"]!!].toDouble() else 1.0 val payerWeight = if (columns.containsKey("payer_weight")) line[columns["payer_weight"]!!].toDouble() else 1.0
val owersStr = if (columns.containsKey("owers")) line[columns["owers"]!!] else "" val owersStr = if (columns.containsKey("owers")) line[columns["owers"]!!] else ""
val payerActive = columns.containsKey("payer_active") && line[columns["payer_active"]!!] == "1" val payerActive = columns.containsKey("payer_active") && line[columns["payer_active"]!!] == "1"
@@ -122,10 +122,8 @@ object ProjectImportHelper {
val pmId = if (columns.containsKey("paymentmodeid") && line[columns["paymentmodeid"]!!].isNotEmpty()) line[columns["paymentmodeid"]!!].toLong() else 0L val pmId = if (columns.containsKey("paymentmodeid") && line[columns["paymentmodeid"]!!].isNotEmpty()) line[columns["paymentmodeid"]!!].toLong() else 0L
val pm = if (columns.containsKey("paymentmode")) line[columns["paymentmode"]!!] else null val pm = if (columns.containsKey("paymentmode")) line[columns["paymentmode"]!!] else null
if (payerName.isNotEmpty()) { membersActive[payerName] = payerActive
membersActive[payerName] = payerActive membersWeight[payerName] = payerWeight
membersWeight[payerName] = payerWeight
}
if (owersStr.trim().isEmpty()) { if (owersStr.trim().isEmpty()) {
onError(context.getString(R.string.import_error_owers, row)) onError(context.getString(R.string.import_error_owers, row))
@@ -134,10 +132,10 @@ object ProjectImportHelper {
if (what != "deleteMeIfYouWant") { if (what != "deleteMeIfYouWant") {
billRemoteIdToOwerStr[row.toLong()] = owersStr billRemoteIdToOwerStr[row.toLong()] = owersStr
val owersArray = owersStr.split(",").map { it.trim() }.filter { it.isNotEmpty() } val owersArray = owersStr.split(", ").filter { it.isNotEmpty() }
for (ower in owersArray) { for (ower in owersArray) {
if (!membersWeight.containsKey(ower)) { if (!membersWeight.containsKey(ower.trim())) {
membersWeight[ower] = 1.0 membersWeight[ower.trim()] = 1.0
} }
} }
bills.add(DBBill(0, row.toLong(), 0, 0, amount, timestamp, what, DBBill.STATE_OK, "n", pm, catId, comment, pmId)) bills.add(DBBill(0, row.toLong(), 0, 0, amount, timestamp, what, DBBill.STATE_OK, "n", pm, catId, comment, pmId))
@@ -173,8 +171,8 @@ object ProjectImportHelper {
val localCatId = catRemoteToLocal[b.categoryId] ?: 0L val localCatId = catRemoteToLocal[b.categoryId] ?: 0L
val localPmId = pmRemoteToLocal[b.paymentModeId] ?: 0L val localPmId = pmRemoteToLocal[b.paymentModeId] ?: 0L
val billId = db.addBill(DBBill(0, 0, pid, payerId, b.amount, b.timestamp, b.what, DBBill.STATE_OK, b.repeat, b.paymentMode, localCatId, b.comment, localPmId)) val billId = db.addBill(DBBill(0, 0, pid, payerId, b.amount, b.timestamp, b.what, DBBill.STATE_OK, b.repeat, b.paymentMode, localCatId, b.comment, localPmId))
billRemoteIdToOwerStr[b.remoteId]?.split(",")?.map { it.trim() }?.filter { it.isNotEmpty() }?.forEach { ower -> billRemoteIdToOwerStr[b.remoteId]?.split(", ")?.filter { it.isNotEmpty() }?.forEach { ower ->
memberNameToId[ower]?.let { owerId -> db.addBillower(billId, owerId) } memberNameToId[ower.trim()]?.let { owerId -> db.addBillower(billId, owerId) }
} }
} }
onSuccess(pid) onSuccess(pid)
@@ -85,11 +85,10 @@ class EditProjectActivity : AppCompatActivity() {
lifecycleScope.launch { lifecycleScope.launch {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
db.updateProject( db.updateProject(
projId = project.id, project.id, newName, newEmail, targetPwd,
newName = newName, null, project.type, null,
newEmail = newEmail, null, null,
newPassword = targetPwd, null, null
projectType = project.type
) )
} }
closeOnEdit(project.id) closeOnEdit(project.id)
@@ -104,21 +103,18 @@ class EditProjectActivity : AppCompatActivity() {
project.password = currentPwd project.password = currentPwd
lifecycleScope.launch { lifecycleScope.launch {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
db.updateProject( db.updateProject(project.id, null, null, currentPwd, null, project.type, null, null, null, null, null)
projId = project.id,
newPassword = currentPwd,
projectType = project.type
)
} }
} }
} }
if (!db.cowspentServerSyncHelper.editRemoteProject( if (!db.cowspentServerSyncHelper.editRemoteProject(
projId = project.id, project.id,
newName = newName, newName,
newEmail = newEmail, newEmail,
newPassword = if (pwdChanged) newPwd else null, if (pwdChanged) newPwd else null,
callback = editCallBack null,
editCallBack
) )
) { ) {
showToast(this, getString(R.string.remote_project_operation_no_network), Toast.LENGTH_LONG) showToast(this, getString(R.string.remote_project_operation_no_network), Toast.LENGTH_LONG)
@@ -128,9 +124,10 @@ class EditProjectActivity : AppCompatActivity() {
lifecycleScope.launch { lifecycleScope.launch {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
db.updateProject( db.updateProject(
projId = project.id, project.id, null, null, currentPwd,
newPassword = currentPwd, null, project.type, null,
projectType = project.type null, null,
null, null
) )
} }
closeOnEdit(project.id) closeOnEdit(project.id)
@@ -271,18 +271,12 @@ class CowspentSQLiteOpenHelper private constructor(val context: Context) :
} }
fun updateProject( fun updateProject(
projId: Long, projId: Long, newName: String?, newEmail: String?,
newName: String? = null, newPassword: String?, newLastPayerId: Long?,
newEmail: String? = null, newLastSyncedTimestamp: Long?,
newPassword: String? = null, newCurrencyName: String?, newDeletionDisabled: Boolean?,
newLastPayerId: Long? = null, newMyAccessLevel: Int?, newBearerToken: String?,
newLastSyncedTimestamp: Long? = null, newArchivedTs: Long? = null
newCurrencyName: String? = null,
newDeletionDisabled: Boolean? = null,
newMyAccessLevel: Int? = null,
newBearerToken: String? = null,
newArchivedTs: Long? = null,
projectType: ProjectType? = null
) { ) {
val db = writableDatabase val db = writableDatabase
val values = ContentValues() val values = ContentValues()
@@ -296,7 +290,47 @@ class CowspentSQLiteOpenHelper private constructor(val context: Context) :
if (newDeletionDisabled != null) values.put(key_deletionDisabled, if (newDeletionDisabled) 1 else 0) if (newDeletionDisabled != null) values.put(key_deletionDisabled, if (newDeletionDisabled) 1 else 0)
if (newMyAccessLevel != null) values.put(key_myAccessLevel, newMyAccessLevel) if (newMyAccessLevel != null) values.put(key_myAccessLevel, newMyAccessLevel)
if (newArchivedTs != null) values.put(key_archived, newArchivedTs) if (newArchivedTs != null) values.put(key_archived, newArchivedTs)
if (projectType != null) values.put(key_type, projectType.id) if (values.size() > 0) {
db.update(table_projects, values, "$key_id = ?", arrayOf(projId.toString()))
}
}
fun updateProject(
projId: Long, newName: String?, newEmail: String?,
newPassword: String?, newLastPayerId: Long?,
projectType: ProjectType, newLastSyncedTimestamp: Long?,
newCurrencyName: String?, newDeletionDisabled: Boolean?,
newMyAccessLevel: Int?, newBearerToken: String?,
newArchivedTs: Long? = null
) {
val db = writableDatabase
updateProject(
projId, newName, newEmail, newPassword, newLastPayerId, projectType,
newLastSyncedTimestamp, newCurrencyName, newDeletionDisabled, newMyAccessLevel,
newBearerToken, newArchivedTs, db
)
}
private fun updateProject(
projId: Long, newName: String?, newEmail: String?,
newPassword: String?, newLastPayerId: Long?,
projectType: ProjectType, newLastSyncedTimestamp: Long?,
newCurrencyName: String?, newDeletionDisabled: Boolean?,
newMyAccessLevel: Int?, newBearerToken: String?,
newArchivedTs: Long?, db: SQLiteDatabase
) {
val values = ContentValues()
if (newName != null) values.put(key_name, newName)
if (newEmail != null) values.put(key_email, newEmail)
if (newPassword != null) SecureStorage.savePasswordSync(context, "ProjectPassword_$projId", newPassword)
if (newBearerToken != null) values.put(key_bearer_token, newBearerToken)
if (newLastPayerId != null) values.put(key_lastPayerId, newLastPayerId)
if (newLastSyncedTimestamp != null) values.put(key_lastSyncTimestamp, newLastSyncedTimestamp)
if (newCurrencyName != null) values.put(key_currencyName, newCurrencyName)
if (newDeletionDisabled != null) values.put(key_deletionDisabled, if (newDeletionDisabled) 1 else 0)
if (newMyAccessLevel != null) values.put(key_myAccessLevel, newMyAccessLevel)
if (newArchivedTs != null) values.put(key_archived, newArchivedTs)
values.put(key_type, projectType.id)
if (values.size() > 0) { if (values.size() > 0) {
db.update(table_projects, values, "$key_id = ?", arrayOf(projId.toString())) db.update(table_projects, values, "$key_id = ?", arrayOf(projId.toString()))
} }
@@ -85,8 +85,7 @@ class CowspentServerSyncHelper private constructor(private val dbHelper: Cowspen
val isSyncPossible: Boolean val isSyncPossible: Boolean
get() { get() {
updateNetworkStatus() updateNetworkStatus()
val offlineMode = preferences.getBoolean(appContext.getString(R.string.pref_key_offline_mode), false) return networkConnected
return networkConnected && !offlineMode
} }
fun addCallbackPull(callback: ICallback) { fun addCallbackPull(callback: ICallback) {
@@ -818,8 +817,10 @@ class CowspentServerSyncHelper private constructor(private val dbHelper: Cowspen
} }
dbHelper.updateProject( dbHelper.updateProject(
projId = project.id, project.id, null, null,
newLastSyncedTimestamp = serverSyncTimestamp null, null, serverSyncTimestamp,
null, null, null,
null
) )
LoginStatus.OK LoginStatus.OK
} catch (_: ServerResponse.NotModifiedException) { } catch (_: ServerResponse.NotModifiedException) {
@@ -934,18 +935,11 @@ class CowspentServerSyncHelper private constructor(private val dbHelper: Cowspen
return isCospend && projUrl == accountUrl return isCospend && projUrl == accountUrl
} }
fun editRemoteProject( fun editRemoteProject(projId: Long, newName: String?, newEmail: String?,
projId: Long, newPassword: String?, newMainCurrencyName: String?, callback: ICallback): Boolean {
newName: String? = null,
newEmail: String? = null,
newPassword: String? = null,
newMainCurrencyName: String? = null,
newArchivedTs: Long? = null,
callback: ICallback
): Boolean {
updateNetworkStatus() updateNetworkStatus()
if (isSyncPossible) { if (isSyncPossible) {
EditRemoteProjectTask(projId, newName, newEmail, newPassword, newMainCurrencyName, newArchivedTs, callback).execute() EditRemoteProjectTask(projId, newName, newEmail, newPassword, newMainCurrencyName, callback).execute()
return true return true
} }
return false return false
@@ -957,7 +951,6 @@ class CowspentServerSyncHelper private constructor(private val dbHelper: Cowspen
private val newEmail: String?, private val newEmail: String?,
private val newPassword: String?, private val newPassword: String?,
private val newMainCurrencyName: String?, private val newMainCurrencyName: String?,
private val newArchivedTs: Long?,
private val callback: ICallback private val callback: ICallback
) { ) {
private val project: DBProject? = dbHelper.getProject(projId) private val project: DBProject? = dbHelper.getProject(projId)
@@ -992,7 +985,7 @@ class CowspentServerSyncHelper private constructor(private val dbHelper: Cowspen
var status = LoginStatus.OK var status = LoginStatus.OK
try { try {
val response = client!!.editRemoteProject( val response = client!!.editRemoteProject(
project!!, newName, newEmail, newPassword, newMainCurrencyName, newArchivedTs project!!, newName, newEmail, newPassword, newMainCurrencyName
) )
if (BillsListViewActivity.DEBUG) { if (BillsListViewActivity.DEBUG) {
Log.i(TAG, "RESPONSE edit remote project : ${response.stringContent}") Log.i(TAG, "RESPONSE edit remote project : ${response.stringContent}")
@@ -1032,11 +1025,8 @@ class CowspentServerSyncHelper private constructor(private val dbHelper: Cowspen
} }
} else { } else {
dbHelper.updateProject( dbHelper.updateProject(
projId = project!!.id, project!!.id, newName, newEmail, newPassword,
newName = newName, null, null, null, null, null, null
newEmail = newEmail,
newPassword = newPassword,
newArchivedTs = newArchivedTs
) )
} }
callback.onFinish(newName ?: "", errorString) callback.onFinish(newName ?: "", errorString)
@@ -1215,7 +1205,7 @@ class CowspentServerSyncHelper private constructor(private val dbHelper: Cowspen
} }
} }
internal fun hasChanged(localBill: DBBill, remoteBill: DBBill): Boolean { private fun hasChanged(localBill: DBBill, remoteBill: DBBill): Boolean {
if (localBill.payerId == remoteBill.payerId && if (localBill.payerId == remoteBill.payerId &&
localBill.amount == remoteBill.amount && localBill.amount == remoteBill.amount &&
localBill.timestamp == remoteBill.timestamp && localBill.timestamp == remoteBill.timestamp &&
@@ -114,7 +114,7 @@ class VersatileProjectSyncClient(
@Throws(IOException::class, TokenMismatchException::class, NextcloudHttpRequestFailedException::class) @Throws(IOException::class, TokenMismatchException::class, NextcloudHttpRequestFailedException::class)
fun editRemoteProject( fun editRemoteProject(
project: DBProject, newName: String?, newEmail: String?, newPassword: String?, project: DBProject, newName: String?, newEmail: String?, newPassword: String?,
newMainCurrencyName: String?, newArchivedTs: Long? = null newMainCurrencyName: String?
): ServerResponse.EditRemoteProjectResponse { ): ServerResponse.EditRemoteProjectResponse {
val paramKeys: MutableList<String> = ArrayList() val paramKeys: MutableList<String> = ArrayList()
val paramValues: MutableList<String> = ArrayList() val paramValues: MutableList<String> = ArrayList()
@@ -130,10 +130,6 @@ class VersatileProjectSyncClient(
paramKeys.add("password") paramKeys.add("password")
paramValues.add(newPassword) paramValues.add(newPassword)
} }
if (newArchivedTs != null) {
paramKeys.add("archived_ts")
paramValues.add(newArchivedTs.toString())
}
var target: String var target: String
var username: String? = null var username: String? = null
@@ -5,11 +5,9 @@ fun evalMath(expression: String): Double {
var result = 0.0 var result = 0.0
var db: SQLiteDatabase? = null var db: SQLiteDatabase? = null
try { try {
// Force floating point division by replacing / with * 1.0 /
val forcedRealExpr = expression.replace("/", "* 1.0 /")
// Opens a temporary, in-memory system database block // Opens a temporary, in-memory system database block
db = SQLiteDatabase.create(null) db = SQLiteDatabase.create(null)
val cursor = db.rawQuery("SELECT ($forcedRealExpr);", null) val cursor = db.rawQuery("SELECT ($expression);", null)
if (cursor.moveToFirst()) { if (cursor.moveToFirst()) {
result = cursor.getDouble(0) result = cursor.getDouble(0)
} }
+205 -205
View File
@@ -2,240 +2,240 @@
<resources xmlns:tools="http://schemas.android.com/tools"> <resources xmlns:tools="http://schemas.android.com/tools">
<string name="app_name">Cowspent</string> <string name="app_name">Cowspent</string>
<!-- Actions --> <!-- Actions -->
<string name="action_new_bill">Nuevo gasto</string> <string name="action_new_bill">New bill</string>
<string name="action_add_project">Agregar proyecto</string> <string name="action_add_project">Add project</string>
<string name="action_save">Guardar</string> <string name="action_save">Save</string>
<string name="action_edit">Editar</string> <string name="action_edit">Edit</string>
<string name="action_share">Compartir</string> <string name="action_share">Share</string>
<string name="action_search">Buscar</string> <string name="action_search">Search</string>
<string name="action_delete">Borrar</string> <string name="action_delete">Delete</string>
<string name="action_archive">Archivar</string> <string name="action_archive">Archive</string>
<string name="action_unarchive">Desarchivar</string> <string name="action_unarchive">Unarchive</string>
<string name="action_export">Exportar</string> <string name="action_export">Export</string>
<string name="action_stats">Stadísticas</string> <string name="action_stats">Stats</string>
<string name="action_settle">Saldar</string> <string name="action_settle">Settle</string>
<string name="action_scan_qrcode">Escanear QR</string> <string name="action_scan_qrcode">Scan QR Code</string>
<string name="action_settings">Configuraciones</string> <string name="action_settings">Settings</string>
<string name="action_label_bills">Categorías sin etiquetas</string> <string name="action_label_bills">Label missing categories</string>
<string name="action_logout">Logout</string> <string name="action_logout">Logout</string>
<string name="action_connect">Conectar</string> <string name="action_connect">Connect</string>
<string name="action_discard">Discartar</string> <string name="action_discard">Discard</string>
<string name="action_members">Miembros</string> <string name="action_members">Members</string>
<string name="action_labels">Etiquetas</string> <string name="action_labels">Labels</string>
<string name="action_currencies">Moneda</string> <string name="action_currencies">Currencies</string>
<!-- Titles --> <!-- Titles -->
<string name="title_stats">Estadísticas</string> <string name="title_stats">Statistics</string>
<string name="title_edit_project">Editar Proyecto</string> <string name="title_edit_project">Edit project</string>
<string name="title_label_bills">Etiquetar gastos</string> <string name="title_label_bills">Label Bills</string>
<string name="title_labels">Administrar etiquetas</string> <string name="title_labels">Manage Labels</string>
<string name="title_about">Acerca de</string> <string name="title_about">About</string>
<string name="title_settle">Saldar Proyecto</string> <string name="title_settle">Settle Project</string>
<string name="title_share">Compartir Proyecto</string> <string name="title_share">Share Project</string>
<string name="title_add_project">Agregar Proyecto</string> <string name="title_add_project">Add Project</string>
<string name="title_add_category">Agregar Categoría</string> <string name="title_add_category">Add Category</string>
<string name="title_add_payment_mode">Agregar Modo de Pago</string> <string name="title_add_payment_mode">Add Payment Mode</string>
<string name="title_account">Cuenta Nextcloud</string> <string name="title_account">Nextcloud Account</string>
<string name="title_share_web">Enlace Web</string> <string name="title_share_web">Web link</string>
<string name="title_share_qr">Enlace Cowspent</string> <string name="title_share_qr">Cowspent link</string>
<string name="title_confirm">Estás seguro?</string> <string name="title_confirm">Are you sure?</string>
<!-- Labels and Fields --> <!-- Labels and Fields -->
<string name="label_all_bills">Todos los gastos</string> <string name="label_all_bills">All bills</string>
<string name="label_categories">Categorias</string> <string name="label_categories">Categories</string>
<string name="label_payment_modes">Modos de Pago</string> <string name="label_payment_modes">Payment Modes</string>
<string name="label_name">Nombre</string> <string name="label_name">Name</string>
<string name="label_icon">Icono / Emoji</string> <string name="label_icon">Icon / Emoji</string>
<string name="label_color">Color</string> <string name="label_color">Color</string>
<string name="label_weight">Ponderación</string> <string name="label_weight">Weight</string>
<string name="label_activated">Activado</string> <string name="label_activated">Activated</string>
<string name="label_password">Contraseña</string> <string name="label_password">Password</string>
<string name="label_email">E-mail</string> <string name="label_email">E-mail</string>
<string name="label_url">Dirección del Servidor</string> <string name="label_url">Server address</string>
<string name="label_username">Nombre de Usuario</string> <string name="label_username">Username</string>
<string name="label_comment">Comentario</string> <string name="label_comment">Comment</string>
<string name="label_what">Qué?</string> <string name="label_what">What?</string>
<string name="label_payer">Quién paga?</string> <string name="label_payer">Who paid?</string>
<string name="label_owers">Para Quién?</string> <string name="label_owers">For whom?</string>
<string name="label_repeat">Repetir cada</string> <string name="label_repeat">Repeat every</string>
<string name="label_mode">Modo</string> <string name="label_mode">Mode</string>
<string name="label_category">Categoría</string> <string name="label_category">Category</string>
<string name="label_project_id">ID Proyecto/Nombre</string> <string name="label_project_id">Project ID/name</string>
<string name="label_project_title">Título Proyecto</string> <string name="label_project_title">Project title</string>
<string name="label_use_sso">Usar cuenta de Nextcloud App</string> <string name="label_use_sso">Use Nextcloud App Account</string>
<!-- Dialogs and Messages --> <!-- Dialogs and Messages -->
<string name="dialog_unsaved_changes_title">Cambios sin guardar</string> <string name="dialog_unsaved_changes_title">Unsaved changes</string>
<string name="dialog_unsaved_changes_msg">Guardar cambios antes de salir?</string> <string name="dialog_unsaved_changes_msg">Save changes before leaving?</string>
<string name="dialog_confirm_remove_project_msg">El proyecto remoto no será eliminado.</string> <string name="dialog_confirm_remove_project_msg">The remote project will not be deleted.</string>
<string name="dialog_sync_error_title">Error de sincronización</string> <string name="dialog_sync_error_title">Sync error</string>
<string name="dialog_sync_error_msg">Falla de sincronización para %1$s.\n\n%2$s</string> <string name="dialog_sync_error_msg">Sync failed for %1$s.\n\n%2$s</string>
<string name="dialog_balanced_msg">Gastos ya balanceados.</string> <string name="dialog_balanced_msg">Expenses are already balanced.</string>
<string name="msg_project_added">Agregado proyecto %1$s</string> <string name="msg_project_added">Project %1$s added</string>
<string name="msg_bill_labeled_done">Todos los gastos etiquetados</string> <string name="msg_bill_labeled_done">All bills labeled</string>
<string name="msg_no_suggestions">Sin sugerencias</string> <string name="msg_no_suggestions">No suggestions</string>
<string name="msg_auth_warning">Se requiere Cospend v0.3.4+.</string> <string name="msg_auth_warning">Requires Cospend v0.3.4+.</string>
<string name="msg_link_copied">Vínculo copiado al portapapeles</string> <string name="msg_link_copied">Link copied to clipboard</string>
<string name="msg_share_qr">Escanea el QR o comparte el vínculo para unirte.</string> <string name="msg_share_qr">Scan QR code or share the link to join.</string>
<string name="msg_share_web">Vínculo para acceso mediante navegador web.</string> <string name="msg_share_web">Link for web browser access.</string>
<string name="msg_share_qr_warn">Comparte este vínculo con otro usuario de Cowspent.</string> <string name="msg_share_qr_warn">Share this link with a Cowspent user.</string>
<string name="msg_settle_intro">Liquidación para %1$s:</string> <string name="msg_settle_intro">Settlement for %1$s:</string>
<string name="msg_settle_sentence">%1$s debe %3$.2f a %2$s</string> <string name="msg_settle_sentence">%1$s owes %3$.2f to %2$s</string>
<string name="msg_stats_intro">Estadísticas para %1$s:</string> <string name="msg_stats_intro">Stats for %1$s:</string>
<string name="msg_stats_header">Miembro (Pagado | Gastado | Balance)</string> <string name="msg_stats_header">Member (Paid | Spent | Balance)</string>
<string name="msg_logged_in_as">Conectado como %1$s</string> <string name="msg_logged_in_as">Logged in as %1$s</string>
<!-- Errors --> <!-- Errors -->
<string name="error_generic">Error</string> <string name="error_generic">Error</string>
<string name="error_loading">Cargando</string> <string name="error_loading">Loading</string>
<string name="error_no_projects">No se encontraron proyectos</string> <string name="error_no_projects">No projects found</string>
<string name="error_no_members">No se encontraron miembros</string> <string name="error_no_members">No members found</string>
<string name="error_no_bills">No se encontraron gastos</string> <string name="error_no_bills">No bills found</string>
<string name="error_no_member">Se requiere al menos un miembro</string> <string name="error_no_member">At least one member required</string>
<string name="error_maintenance_mode">Servidor en modo mantenimiento</string> <string name="error_maintenance_mode">Server is in maintenance mode</string>
<string name="error_400">400 Bad request</string> <string name="error_400">400 Bad request</string>
<string name="error_401">401 Unauthorized</string> <string name="error_401">401 Unauthorized</string>
<string name="error_403">403 Forbidden</string> <string name="error_403">403 Forbidden</string>
<string name="error_404">404 Not Found</string> <string name="error_404">404 Not Found</string>
<string name="error_sync">Fallo en sicronización: %1$s</string> <string name="error_sync">Sync failed: %1$s</string>
<string name="error_invalid_login">Inicio sesión inválida: %1$s</string> <string name="error_invalid_login">Invalid login: %1$s</string>
<string name="error_auth">Usuario o contraseña incorrectos</string> <string name="error_auth">Wrong username or password</string>
<string name="error_json">Respuesta inválida del servidor</string> <string name="error_json">Invalid server response</string>
<string name="error_req_failed">Falla en solicitud</string> <string name="error_req_failed">Request failed</string>
<string name="error_invalid_email">E-mail inválido</string> <string name="error_invalid_email">Invalid e-mail</string>
<string name="error_invalid_project_id">ID de proyecto inválido</string> <string name="error_invalid_project_id">Invalid project ID</string>
<string name="error_invalid_project_name">Título de proyecto inválido</string> <string name="error_invalid_project_name">Invalid project title</string>
<string name="error_invalid_bill_name">Nombre de gasto inválido</string> <string name="error_invalid_bill_name">Invalid bill name</string>
<string name="error_invalid_bill_date">Fecha de gasto inválido</string> <string name="error_invalid_bill_date">Invalid bill date</string>
<string name="error_invalid_bill_payer">Se requiere pagador</string> <string name="error_invalid_bill_payer">Payer required</string>
<string name="error_invalid_bill_owers">Se requiere deudor</string> <string name="error_invalid_bill_owers">Owers required</string>
<string name="error_no_network">Sin conexión de red</string> <string name="error_no_network">No network connection</string>
<string name="error_server">Error de servidor</string> <string name="error_server">Server error</string>
<string name="error_io">Conexión de servidor interrumpida</string> <string name="error_io">Server connection broken</string>
<string name="error_share_impossible">No se puede compartir este proyecto</string> <string name="error_share_impossible">Cannot share this project</string>
<!-- Drawer / Common UI --> <!-- Drawer / Common UI -->
<string name="drawer_no_account">Conectar a cuenta Nextcloud</string> <string name="drawer_no_account">Connect to Nextcloud account</string>
<string name="drawer_last_sync">Ultima sincronización: %1$02d:%2$02d</string> <string name="drawer_last_sync">Last sync: %1$02d:%2$02d</string>
<string name="simple_cancel">Cancelar</string> <string name="simple_cancel">Cancel</string>
<string name="simple_ok" tools:ignore="ButtonCase,Typos">Ok</string> <string name="simple_ok" tools:ignore="ButtonCase,Typos">Ok</string>
<string name="simple_yes">Si</string> <string name="simple_yes">Yes</string>
<string name="simple_no">No</string> <string name="simple_no">No</string>
<string name="simple_close">Cerrar</string> <string name="simple_close">Close</string>
<!-- Settings --> <!-- Settings -->
<string name="settings_appearance">Apariencia</string> <string name="settings_appearance">Appearance</string>
<string name="settings_network">Red</string> <string name="settings_network">Network</string>
<string name="settings_other">Otros</string> <string name="settings_other">Other</string>
<string name="settings_night_mode">Tema</string> <string name="settings_night_mode">Theme</string>
<string name="settings_offline_mode">Modo fuera de línea</string> <string name="settings_offline_mode">Offline mode</string>
<string name="settings_offline_mode_summary">Solo sincronización manual.</string> <string name="settings_offline_mode_summary">Only sync manually.</string>
<string name="settings_color_custom">Color personalizado</string> <string name="settings_color_custom">Custom color</string>
<string name="settings_color_mode">Selección de color</string> <string name="settings_color_mode">Color Selection</string>
<string name="settings_show_archived">Mostrar proyectos archivados</string> <string name="settings_show_archived">Show archived projects</string>
<string name="settings_beta_features">Funciones beta</string> <string name="settings_beta_features">Beta Features</string>
<string name="settings_beta_features_summary">Habilitar funciones beta. Usar bajo tu propia responsabilidad.</string> <string name="settings_beta_features_summary">Enable experimental features. Use at your own risk.</string>
<string name="settings_url_warn_http">ADVERTENCIA: \"http\" es inseguro. Usa \"https\".</string> <string name="settings_url_warn_http">WARNING: \"http\" is unsafe. Use \"https\".</string>
<string name="settings_colorpicker_title">Elegir Color</string> <string name="settings_colorpicker_title">Choose Color</string>
<string name="pref_value_color_system">Sistema</string> <string name="pref_value_color_system">System</string>
<string name="pref_value_color_server">Nextcloud</string> <string name="pref_value_color_server">Nextcloud</string>
<string name="pref_value_color_manual">Manual</string> <string name="pref_value_color_manual">Manual</string>
<string name="pref_value_theme_light">Claro</string> <string name="pref_value_theme_light">Light</string>
<string name="pref_value_theme_dark">Oscuro</string> <string name="pref_value_theme_dark">Dark</string>
<string name="pref_value_theme_system">Seguir sistema</string> <string name="pref_value_theme_system">Follow system</string>
<!-- Constants (Do not translate) --> <!-- Constants (Do not translate) -->
<!-- Enums and Lists --> <!-- Enums and Lists -->
<string name="repeat_no">No repetir</string> <string name="repeat_no">No repeat</string>
<string name="repeat_day">Diario</string> <string name="repeat_day">Daily</string>
<string name="repeat_week">Semanal</string> <string name="repeat_week">Weekly</string>
<string name="repeat_fortnight">Quincenal</string> <string name="repeat_fortnight">Fortnightly</string>
<string name="repeat_month">Mensual</string> <string name="repeat_month">Monthly</string>
<string name="repeat_year">Anual</string> <string name="repeat_year">Yearly</string>
<string name="payment_mode_none">Ninguno</string> <string name="payment_mode_none">None</string>
<string name="payment_mode_all">Todos</string> <string name="payment_mode_all">All</string>
<string name="payment_mode_credit_card">Tarjeta de crédito</string> <string name="payment_mode_credit_card">Credit card</string>
<string name="payment_mode_cash">Efectivo</string> <string name="payment_mode_cash">Cash</string>
<string name="payment_mode_check">Cheque</string> <string name="payment_mode_check">Check</string>
<string name="payment_mode_online">En línea</string> <string name="payment_mode_online">Online</string>
<string name="payment_mode_transfer">Transferencia</string> <string name="payment_mode_transfer">Transfer</string>
<string name="category_none">Ninguna</string> <string name="category_none">None</string>
<string name="category_all">Todas</string> <string name="category_all">All</string>
<string name="category_all_except_reimbursement">Todas excepto reembolsos</string> <string name="category_all_except_reimbursement">All except reimbursement</string>
<string name="category_groceries">Supermercado</string> <string name="category_groceries">Grocery</string>
<string name="category_leisure">Bar/Fiesta</string> <string name="category_leisure">Bar/Party</string>
<string name="category_rent">Renta</string> <string name="category_rent">Rent</string>
<string name="category_bills">Facturas/Servicios</string> <string name="category_bills">Bill</string>
<string name="category_excursion">Excursión/Cultura</string> <string name="category_excursion">Excursion/Culture</string>
<string name="category_health">Salud</string> <string name="category_health">Health</string>
<string name="category_shopping">Compras</string> <string name="category_shopping">Shopping</string>
<string name="category_reimbursement">Reintegro</string> <string name="category_reimbursement">Reimbursement</string>
<string name="category_restaurant">Restaurante</string> <string name="category_restaurant">Restaurant</string>
<string name="category_accomodation">Alojamiento</string> <string name="category_accomodation">Accommodation</string>
<string name="category_transport">Transporte</string> <string name="category_transport">Transport</string>
<string name="category_sport">Deportes</string> <string name="category_sport">Sport</string>
<!-- Project specific --> <!-- Project specific -->
<string name="new_project_action">Qué</string> <string name="new_project_action">What</string>
<string name="new_project_where">Cuando</string> <string name="new_project_where">Where</string>
<string name="where_local">Solo local</string> <string name="where_local">Local only</string>
<string name="where_cospend">Cospend</string> <string name="where_cospend">Cospend</string>
<string name="where_ihatemoney">IHateMoney</string> <string name="where_ihatemoney">IHateMoney</string>
<string name="todo_join">Unirse a proyecto existente</string> <string name="todo_join">Join existing project</string>
<string name="todo_create">Crear nuevo proyecto</string> <string name="todo_create">Create new project</string>
<string name="import_tooltip">Importar desde archivo</string> <string name="import_tooltip">Import from file</string>
<string name="choose_account_project_dialog_title">Elegir projecto</string> <string name="choose_account_project_dialog_title">Choose project</string>
<string name="choose_account_project_dialog_impossible">No se encotraron proyectos en esta cuenta.</string> <string name="choose_account_project_dialog_impossible">No projects found on this account.</string>
<string name="choose_project_management_action">Proyecto</string> <string name="choose_project_management_action">Project</string>
<string name="project_added_success">Proyecto añadido correctamente.</string> <string name="project_added_success">Project added successfully.</string>
<string name="no_projects_text">Aún no tienes proyectos.</string> <string name="no_projects_text">You have no projects yet.</string>
<string name="configure_account_choice">Configurar cuenta Nextcloud</string> <string name="configure_account_choice">Configure Nextcloud account</string>
<string name="add_project_choice">Añadir proyecto manualmente</string> <string name="add_project_choice">Add project manually</string>
<string name="no_members_text">No hay miembros en este proyecto.</string> <string name="no_members_text">No members in this project.</string>
<string name="no_bills_text">No se encontraron gastos.</string> <string name="no_bills_text">No bills found.</string>
<string name="member_already_exists">Miembro ya existe.</string> <string name="member_already_exists">Member already exists.</string>
<string name="activity_dialog_title">Proyecto: %1$s</string> <string name="activity_dialog_title">Project: %1$s</string>
<string name="remove_project_confirmation">Proyecto %1$s eliminado.</string> <string name="remove_project_confirmation">Project %1$s removed.</string>
<string name="file_saved_success">Archivo guardado: %1$s</string> <string name="file_saved_success">File saved: %1$s</string>
<string name="import_error_header">Importación fallida en línea %d</string> <string name="import_error_header">Import failed at row %d</string>
<string name="import_error_date">Formato de fecha inválido en línea %d</string> <string name="import_error_date">Invalid date format at row %d</string>
<string name="import_error_owers">Deudores inválidos en línea %d</string> <string name="import_error_owers">Invalid owers at row %d</string>
<string name="add_member_dialog_title">Agregar Miembro</string> <string name="add_member_dialog_title">Add Member</string>
<string name="edit_member_dialog_title">Editar Miembro</string> <string name="edit_member_dialog_title">Edit Member</string>
<string name="member_edit_delete">Eliminar</string> <string name="member_edit_delete">Delete</string>
<string name="project_edition_no_change">Sin cambios para guardar.</string> <string name="project_edition_no_change">No changes to save.</string>
<!-- Settlement --> <!-- Settlement -->
<string name="center_none">Ninguno (Optimo)</string> <string name="center_none">None (Optimal)</string>
<string name="settle_who">Quién paga</string> <string name="settle_who">Who pays</string>
<string name="settle_to_whom">A quién</string> <string name="settle_to_whom">To whom</string>
<string name="settle_how_much">Monto</string> <string name="settle_how_much">Amount</string>
<string name="simple_settle_share">Compartir</string> <string name="simple_settle_share">Share</string>
<string name="simple_create_bills">Crear gastos</string> <string name="simple_create_bills">Create bills</string>
<string name="settle_bill_what">Liquidación</string> <string name="settle_bill_what">Settlement</string>
<!-- Currencies --> <!-- Currencies -->
<string name="currency_dialog_title">Elegir moneda (%s)</string> <string name="currency_dialog_title">Choose Currency (%s)</string>
<string name="setting_none">Ninguna</string> <string name="setting_none">None</string>
<string name="setting_all">Todas</string> <string name="setting_all">All</string>
<string name="currency_saved_success">Configuración de moneda guardada.</string> <string name="currency_saved_success">Currency settings saved.</string>
<string name="main_currency">Moneda principal</string> <string name="main_currency">Main Currency</string>
<!-- Statistics --> <!-- Statistics -->
<string name="label_bills_suggested">Categorías sugeridas</string> <string name="label_bills_suggested">Suggested Categories</string>
<string name="label_bills_skip">Saltear</string> <string name="label_bills_skip">Skip</string>
<string name="stats_date_min">Desde</string> <string name="stats_date_min">From</string>
<string name="stats_date_max">Hasta</string> <string name="stats_date_max">To</string>
<string name="stats_who">Miembro</string> <string name="stats_who">Member</string>
<string name="stats_paid">Pagado</string> <string name="stats_paid">Paid</string>
<string name="stats_spent">Gastado</string> <string name="stats_spent">Spent</string>
<string name="stats_balance">Balance</string> <string name="stats_balance">Balance</string>
<string name="total">Total: %1$s</string> <string name="total">Total: %1$s</string>
<!-- Errors Extra --> <!-- Errors Extra -->
<string name="error_project_connect_check">Conexión fallida: %1$s</string> <string name="error_project_connect_check">Connection failed: %1$s</string>
<string name="error_create_remote_project_helper">Creación fallida: %1$s</string> <string name="error_create_remote_project_helper">Creation failed: %1$s</string>
<string name="error_edit_remote_project_helper">Error actualizado proyecto remoto: %1$s</string> <string name="error_edit_remote_project_helper">Error updating remote project: %1$s</string>
<string name="remote_project_operation_no_network">Red no disponible para operación remota.</string> <string name="remote_project_operation_no_network">Network unavailable for remote operation.</string>
<string name="error_scanning_bill_qr_code">No se pudo interpretar el código QR.</string> <string name="error_scanning_bill_qr_code">Failed to parse QR code.</string>
<string name="error_token_mismatch">El token de autenticación no coincide. Inicia sesión de nuevo.</string> <string name="error_token_mismatch">Authentication token mismatch. Please log in again.</string>
<string name="insufficient_access_level">No tienes permiso para realizar esta acción.</string> <string name="insufficient_access_level">You don\'t have permission to perform this action.</string>
<string name="delete_label_confirmation_title">Eliminar Etiqueta</string> <string name="delete_label_confirmation_title">Delete Label</string>
<string name="delete_label_confirmation_message">Seguro quieres eliminar la etiqueta?</string> <string name="delete_label_confirmation_message">Are you sure you want to delete this label?</string>
<!-- About --> <!-- About -->
<string name="about_version">Versión %1$s</string> <string name="about_version">Version %1$s</string>
<string name="about_maintainer_title">Responsable</string> <string name="about_maintainer_title">Maintainer</string>
<string name="about_license_title">Licencia</string> <string name="about_license_title">License</string>
<string name="about_source_title">Código fuente</string> <string name="about_source_title">Source code</string>
<!-- New constants for backward compatibility or shared use --> <!-- New constants for backward compatibility or shared use -->
<string name="share_intent_title">Proyecto %1$s</string> <string name="share_intent_title">Project %1$s</string>
<string name="share_chooser_title">Compartir %1$s</string> <string name="share_chooser_title">Share %1$s</string>
</resources> </resources>
+33 -49
View File
@@ -1,8 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"> <resources xmlns:tools="http://schemas.android.com/tools">
<string name="app_name">Kuh ausgegeben</string>
<string name="app_name">Cowspent</string>
<!-- Actions --> <!-- Actions -->
<string name="action_new_bill">Neue Rechnung</string> <string name="action_new_bill">Neue Rechnung</string>
<string name="action_add_project">Projekt hinzufügen</string> <string name="action_add_project">Projekt hinzufügen</string>
@@ -12,40 +10,38 @@
<string name="action_search">Suchen</string> <string name="action_search">Suchen</string>
<string name="action_delete">Löschen</string> <string name="action_delete">Löschen</string>
<string name="action_archive">Archivieren</string> <string name="action_archive">Archivieren</string>
<string name="action_unarchive">Reaktivieren</string> <string name="action_unarchive">Archivieren</string>
<string name="action_export">Exportieren</string> <string name="action_export">Exportieren</string>
<string name="action_stats">Statistik</string> <string name="action_stats">Statistik</string>
<string name="action_settle">Abrechnen</string> <string name="action_settle">Abrechnen</string>
<string name="action_scan_qrcode">QR Code einscannen</string> <string name="action_scan_qrcode">Scan QR Code</string>
<string name="action_settings">Einstellungen</string> <string name="action_settings">Einstellungen</string>
<string name="action_label_bills">Fehlende Kategorien</string> <string name="action_label_bills">Fehlende Kategorien</string>
<string name="action_logout">Abmelden</string> <string name="action_logout">Abmelden</string>
<string name="action_connect">Verbinden</string> <string name="action_connect">Verbinden</string>
<string name="action_discard">Verwerfen</string> <string name="action_discard">Verwerfen</string>
<string name="action_members">Mitglieder</string> <string name="action_members">Mitglieder</string>
<string name="action_labels">Labels</string> <string name="action_labels">Etiketten</string>
<string name="action_currencies">Währungen</string> <string name="action_currencies">Währungen</string>
<!-- Titles --> <!-- Titles -->
<string name="title_stats">Statistiken</string> <string name="title_stats">Statistiken</string>
<string name="title_edit_project">Projekt bearbeiten</string> <string name="title_edit_project">Projekt bearbeiten</string>
<string name="title_label_bills">Rechnungen kennzeichnen</string> <string name="title_label_bills">Etikett Rechnungen</string>
<string name="title_labels">Labels verwalten</string> <string name="title_labels">Labels verwalten</string>
<string name="title_about">Über</string> <string name="title_about">Über</string>
<string name="title_settle">Projekt abrechnen</string> <string name="title_settle">Projekt regeln</string>
<string name="title_share">Projekt teilen</string> <string name="title_share">Projekt teilen</string>
<string name="title_add_project">Projekt hinzufügen</string> <string name="title_add_project">Projekt hinzufügen</string>
<string name="title_add_category">Neue Kategorie</string> <string name="title_add_category">Neue Kategorie</string>
<string name="title_add_payment_mode">Zahlungsmethode hinzufügen</string> <string name="title_add_payment_mode">Zahlungsmodus hinzufügen</string>
<string name="title_account">Nextloud-Konto</string> <string name="title_account">Nächstes Cloud-Konto</string>
<string name="title_share_web">Weblink</string> <string name="title_share_web">Weblink</string>
<string name="title_share_qr">Cowspent link</string> <string name="title_share_qr">Cowspent link</string>
<string name="title_confirm">Bist du sicher?</string> <string name="title_confirm">Bist du sicher?</string>
<!-- Labels and Fields --> <!-- Labels and Fields -->
<string name="label_all_bills">Alle Rechnungen</string> <string name="label_all_bills">Alle Rechnungen</string>
<string name="label_categories">Kategorien</string> <string name="label_categories">Kategorien</string>
<string name="label_payment_modes">Zahlungsmethoden</string> <string name="label_payment_modes">Zahlungsarten</string>
<string name="label_name">Name</string> <string name="label_name">Name</string>
<string name="label_icon">Icon / Emoji</string> <string name="label_icon">Icon / Emoji</string>
<string name="label_color">Farbe</string> <string name="label_color">Farbe</string>
@@ -65,28 +61,26 @@
<string name="label_project_id">Projekt-ID/Name</string> <string name="label_project_id">Projekt-ID/Name</string>
<string name="label_project_title">Projekttitel</string> <string name="label_project_title">Projekttitel</string>
<string name="label_use_sso">Nextcloud App-Konto verwenden</string> <string name="label_use_sso">Nextcloud App-Konto verwenden</string>
<!-- Dialogs and Messages --> <!-- Dialogs and Messages -->
<string name="dialog_unsaved_changes_title">Ungespeicherte Änderungen</string> <string name="dialog_unsaved_changes_title">Ungespeicherte Änderungen</string>
<string name="dialog_unsaved_changes_msg">Änderungen vor dem Verlassen speichern?</string> <string name="dialog_unsaved_changes_msg">Änderungen vor dem Verlassen speichern?</string>
<string name="dialog_confirm_remove_project_msg">Das Remote-Projekt wird nicht gelöscht.</string> <string name="dialog_confirm_remove_project_msg">Das entfernte Projekt wird nicht gelöscht.</string>
<string name="dialog_sync_error_title">Sync-Fehler</string> <string name="dialog_sync_error_title">Sync-Fehler</string>
<string name="dialog_sync_error_msg">Synchronisation fehlgeschlagen für %1$s.\n\n%2$s</string> <string name="dialog_sync_error_msg">Synchronisation fehlgeschlagen für %1$s.\n\n%2$s</string>
<string name="dialog_balanced_msg">Die Ausgaben sind bereits ausgeglichen.</string> <string name="dialog_balanced_msg">Die Ausgaben sind bereits ausgeglichen.</string>
<string name="msg_project_added">Projekt %1$s hinzugefügt</string> <string name="msg_project_added">Projekt %1$s hinzugefügt</string>
<string name="msg_bill_labeled_done">Alle Rechnungen kategorisiert</string> <string name="msg_bill_labeled_done">Alle Rechnungen markiert</string>
<string name="msg_no_suggestions">Keine Vorschläge</string> <string name="msg_no_suggestions">Keine Vorschläge</string>
<string name="msg_auth_warning">Benötigt Cospend v0.3.4+.</string> <string name="msg_auth_warning">Benötigt Cospend v0.3.4+.</string>
<string name="msg_link_copied">Link in Zwischenablage kopiert</string> <string name="msg_link_copied">Link in Zwischenablage kopiert</string>
<string name="msg_share_qr">Scanne den QR-Code oder teile den Link, um beizutreten.</string> <string name="msg_share_qr">Scannen Sie QR-Code oder teilen Sie den Link, um beizutreten.</string>
<string name="msg_share_web">Link für den Zugriff per Web-Browser.</string> <string name="msg_share_web">Link für den Zugriff auf Web-Browser.</string>
<string name="msg_share_qr_warn">Teile diesen Link mit einem Cowspent Benutzer.</string> <string name="msg_share_qr_warn">Teilen Sie diesen Link mit einem Cowspent Benutzer.</string>
<string name="msg_settle_intro">Abrechnung für %1$s:</string> <string name="msg_settle_intro">Abrechnung für %1$s:</string>
<string name="msg_settle_sentence">%1$s schuldet %3$.2f an %2$s</string> <string name="msg_settle_sentence">%1$s schuldet %3$.2f %2$s</string>
<string name="msg_stats_intro">Statistiken für %1$s:</string> <string name="msg_stats_intro">Statistiken für %1$s:</string>
<string name="msg_stats_header">Mitglied (Gezahlt | Ausgegeben | Saldo)</string> <string name="msg_stats_header">Mitglied (Paid | Ausgaben | Guthaben)</string>
<string name="msg_logged_in_as">Angemeldet als %1$s</string> <string name="msg_logged_in_as">Angemeldet als %1$s</string>
<!-- Errors --> <!-- Errors -->
<string name="error_generic">Fehler</string> <string name="error_generic">Fehler</string>
<string name="error_loading">Lädt</string> <string name="error_loading">Lädt</string>
@@ -110,12 +104,11 @@
<string name="error_invalid_bill_name">Ungültiger Rechnungsname</string> <string name="error_invalid_bill_name">Ungültiger Rechnungsname</string>
<string name="error_invalid_bill_date">Ungültiges Rechnungsdatum</string> <string name="error_invalid_bill_date">Ungültiges Rechnungsdatum</string>
<string name="error_invalid_bill_payer">Zahler erforderlich</string> <string name="error_invalid_bill_payer">Zahler erforderlich</string>
<string name="error_invalid_bill_owers">Schuldner erforderlich</string> <string name="error_invalid_bill_owers">Eigene erforderlich</string>
<string name="error_no_network">Keine Netzwerkverbindung</string> <string name="error_no_network">Keine Netzwerkverbindung</string>
<string name="error_server">Serverfehler</string> <string name="error_server">Serverfehler</string>
<string name="error_io">Serververbindung getrennt</string> <string name="error_io">Serververbindung defekt</string>
<string name="error_share_impossible">Dieses Projekt kann nicht geteilt werden</string> <string name="error_share_impossible">Dieses Projekt kann nicht geteilt werden</string>
<!-- Drawer / Common UI --> <!-- Drawer / Common UI -->
<string name="drawer_no_account">Mit Nextcloud Konto verbinden</string> <string name="drawer_no_account">Mit Nextcloud Konto verbinden</string>
<string name="drawer_last_sync">Letzter Sync: %1$02d:%2$02d</string> <string name="drawer_last_sync">Letzter Sync: %1$02d:%2$02d</string>
@@ -124,7 +117,6 @@
<string name="simple_yes">Ja</string> <string name="simple_yes">Ja</string>
<string name="simple_no">Nein</string> <string name="simple_no">Nein</string>
<string name="simple_close">Schließen</string> <string name="simple_close">Schließen</string>
<!-- Settings --> <!-- Settings -->
<string name="settings_appearance">Erscheinung</string> <string name="settings_appearance">Erscheinung</string>
<string name="settings_network">Netzwerk</string> <string name="settings_network">Netzwerk</string>
@@ -137,21 +129,20 @@
<string name="settings_show_archived">Archivierte Projekte anzeigen</string> <string name="settings_show_archived">Archivierte Projekte anzeigen</string>
<string name="settings_beta_features">Beta-Funktionen</string> <string name="settings_beta_features">Beta-Funktionen</string>
<string name="settings_beta_features_summary">Experimentelle Funktionen aktivieren. Benutzung auf eigene Gefahr.</string> <string name="settings_beta_features_summary">Experimentelle Funktionen aktivieren. Benutzung auf eigene Gefahr.</string>
<string name="settings_url_warn_http">WARNUNG: \"http\" ist unsicher. Verwende \"https\".</string> <string name="settings_url_warn_http">WARNUNG: \"http\" ist unsicher. Verwenden Sie \"https\".</string>
<string name="settings_colorpicker_title">Farbe wählen</string> <string name="settings_colorpicker_title">Farbe wählen</string>
<string name="pref_value_color_system">System</string> <string name="pref_value_color_system">System</string>
<string name="pref_value_color_server">Nextcloud</string> <string name="pref_value_color_server">Nächste Cloud</string>
<string name="pref_value_color_manual">Manuell</string> <string name="pref_value_color_manual">Manuell</string>
<string name="pref_value_theme_light">Hell</string> <string name="pref_value_theme_light">Hell</string>
<string name="pref_value_theme_dark">Dunkel</string> <string name="pref_value_theme_dark">Dunkel</string>
<string name="pref_value_theme_system">System folgen</string> <string name="pref_value_theme_system">System folgen</string>
<!-- Constants (Do not translate) --> <!-- Constants (Do not translate) -->
<!-- Enums and Lists --> <!-- Enums and Lists -->
<string name="repeat_no">Keine Wiederholung</string> <string name="repeat_no">Keine Wiederholung</string>
<string name="repeat_day">Täglich</string> <string name="repeat_day">Täglich</string>
<string name="repeat_week">Wöchentlich</string> <string name="repeat_week">Wöchentlich</string>
<string name="repeat_fortnight">Vierzehntägig</string> <string name="repeat_fortnight">Vierzehn</string>
<string name="repeat_month">Monatlich</string> <string name="repeat_month">Monatlich</string>
<string name="repeat_year">Jährlich</string> <string name="repeat_year">Jährlich</string>
<string name="payment_mode_none">Keine</string> <string name="payment_mode_none">Keine</string>
@@ -163,34 +154,33 @@
<string name="payment_mode_transfer">Überweisung</string> <string name="payment_mode_transfer">Überweisung</string>
<string name="category_none">Keine</string> <string name="category_none">Keine</string>
<string name="category_all">Alle</string> <string name="category_all">Alle</string>
<string name="category_all_except_reimbursement">Alle außer Erstattung</string> <string name="category_all_except_reimbursement">Alle außer Rückerstattung</string>
<string name="category_groceries">Lebensmittel</string> <string name="category_groceries">Fleischgerichte</string>
<string name="category_leisure">Bar/Party</string> <string name="category_leisure">Bar/Party</string>
<string name="category_rent">Miete</string> <string name="category_rent">Miete</string>
<string name="category_bills">Abrechnung</string> <string name="category_bills">Abrechnung</string>
<string name="category_excursion">Ausflug/Kultur</string> <string name="category_excursion">Ausflug/Kultur</string>
<string name="category_health">Gesundheit</string> <string name="category_health">Gesundheit</string>
<string name="category_shopping">Einkaufen</string> <string name="category_shopping">Einkaufen</string>
<string name="category_reimbursement">Erstattung</string> <string name="category_reimbursement">Rückzahlung</string>
<string name="category_restaurant">Restaurant</string> <string name="category_restaurant">Restaurant</string>
<string name="category_accomodation">Unterkunft</string> <string name="category_accomodation">Unterkunft</string>
<string name="category_transport">Transport</string> <string name="category_transport">Transport</string>
<string name="category_sport">Sport</string> <string name="category_sport">Sport</string>
<!-- Project specific --> <!-- Project specific -->
<string name="new_project_action">Was</string> <string name="new_project_action">Was</string>
<string name="new_project_where">Wo</string> <string name="new_project_where">Wo</string>
<string name="where_local">Nur lokal</string> <string name="where_local">Nur lokal</string>
<string name="where_cospend">Cospend</string> <string name="where_cospend">Ko-Ausgaben</string>
<string name="where_ihatemoney">IHateMoney</string> <string name="where_ihatemoney">IHateMoney</string>
<string name="todo_join">Bestehendem Projekt beitreten</string> <string name="todo_join">Bestehendes Projekt beitreten</string>
<string name="todo_create">Neues Projekt erstellen</string> <string name="todo_create">Neues Projekt erstellen</string>
<string name="import_tooltip">Aus Datei importieren</string> <string name="import_tooltip">Aus Datei importieren</string>
<string name="choose_account_project_dialog_title">Projekt auswählen</string> <string name="choose_account_project_dialog_title">Projekt auswählen</string>
<string name="choose_account_project_dialog_impossible">Keine Projekte auf diesem Konto gefunden.</string> <string name="choose_account_project_dialog_impossible">Keine Projekte auf diesem Konto gefunden.</string>
<string name="choose_project_management_action">Projekt</string> <string name="choose_project_management_action">Projekt</string>
<string name="project_added_success">Projekt erfolgreich hinzugefügt.</string> <string name="project_added_success">Projekt erfolgreich hinzugefügt.</string>
<string name="no_projects_text">Du hast noch keine Projekte.</string> <string name="no_projects_text">Sie haben noch keine Projekte.</string>
<string name="configure_account_choice">Nextcloud-Konto konfigurieren</string> <string name="configure_account_choice">Nextcloud-Konto konfigurieren</string>
<string name="add_project_choice">Projekt manuell hinzufügen</string> <string name="add_project_choice">Projekt manuell hinzufügen</string>
<string name="no_members_text">Keine Mitglieder in diesem Projekt.</string> <string name="no_members_text">Keine Mitglieder in diesem Projekt.</string>
@@ -201,12 +191,11 @@
<string name="file_saved_success">Datei gespeichert: %1$s</string> <string name="file_saved_success">Datei gespeichert: %1$s</string>
<string name="import_error_header">Import fehlgeschlagen in Zeile %d</string> <string name="import_error_header">Import fehlgeschlagen in Zeile %d</string>
<string name="import_error_date">Ungültiges Datumsformat in Zeile %d</string> <string name="import_error_date">Ungültiges Datumsformat in Zeile %d</string>
<string name="import_error_owers">Ungültige Schuldner in Zeile %d</string> <string name="import_error_owers">Ungültige Eigener in Zeile %d</string>
<string name="add_member_dialog_title">Mitglied hinzufügen</string> <string name="add_member_dialog_title">Mitglied hinzufügen</string>
<string name="edit_member_dialog_title">Mitglied bearbeiten</string> <string name="edit_member_dialog_title">Mitglied bearbeiten</string>
<string name="member_edit_delete">Löschen</string> <string name="member_edit_delete">Löschen</string>
<string name="project_edition_no_change">Keine zu speichernden Änderungen.</string> <string name="project_edition_no_change">Keine zu speichernden Änderungen.</string>
<!-- Settlement --> <!-- Settlement -->
<string name="center_none">Keine (optimal)</string> <string name="center_none">Keine (optimal)</string>
<string name="settle_who">Wer zahlt</string> <string name="settle_who">Wer zahlt</string>
@@ -215,14 +204,12 @@
<string name="simple_settle_share">Teilen</string> <string name="simple_settle_share">Teilen</string>
<string name="simple_create_bills">Rechnungen erstellen</string> <string name="simple_create_bills">Rechnungen erstellen</string>
<string name="settle_bill_what">Abrechnung</string> <string name="settle_bill_what">Abrechnung</string>
<!-- Currencies --> <!-- Currencies -->
<string name="currency_dialog_title">Währung wählen (%s)</string> <string name="currency_dialog_title">Währung wählen (%s)</string>
<string name="setting_none">Keine</string> <string name="setting_none">Keine</string>
<string name="setting_all">Alle</string> <string name="setting_all">Alle</string>
<string name="currency_saved_success">Währungseinstellungen gespeichert.</string> <string name="currency_saved_success">Währungseinstellungen gespeichert.</string>
<string name="main_currency">Hauptwährung</string> <string name="main_currency">Hauptwährung</string>
<!-- Statistics --> <!-- Statistics -->
<string name="label_bills_suggested">Empfohlene Kategorien</string> <string name="label_bills_suggested">Empfohlene Kategorien</string>
<string name="label_bills_skip">Überspringen</string> <string name="label_bills_skip">Überspringen</string>
@@ -233,24 +220,21 @@
<string name="stats_spent">Ausgegeben</string> <string name="stats_spent">Ausgegeben</string>
<string name="stats_balance">Saldo</string> <string name="stats_balance">Saldo</string>
<string name="total">Gesamt: %1$s</string> <string name="total">Gesamt: %1$s</string>
<!-- Errors Extra --> <!-- Errors Extra -->
<string name="error_project_connect_check">Verbindung fehlgeschlagen: %1$s</string> <string name="error_project_connect_check">Verbindung fehlgeschlagen: %1$s</string>
<string name="error_create_remote_project_helper">Erstellung fehlgeschlagen: %1$s</string> <string name="error_create_remote_project_helper">Erstellung fehlgeschlagen: %1$s</string>
<string name="error_edit_remote_project_helper">Fehler beim Aktualisieren des Remote-Projekts: %1$s</string> <string name="error_edit_remote_project_helper">Fehler beim Aktualisieren des fernen Projekts: %1$s</string>
<string name="remote_project_operation_no_network">Netzwerk nicht verfügbar für Remote Operation.</string> <string name="remote_project_operation_no_network">Netzwerk nicht verfügbar für Remote Operation.</string>
<string name="error_scanning_bill_qr_code">Fehler beim Analysieren des QR-Codes.</string> <string name="error_scanning_bill_qr_code">Fehler beim Analysieren des QR-Codes.</string>
<string name="error_token_mismatch">Authentifizierungstoken stimmen nicht überein. Bitte melde dich erneut an.</string> <string name="error_token_mismatch">Authentifizierungstoken stimmen nicht überein. Bitte melden Sie sich erneut an.</string>
<string name="insufficient_access_level">Du hast keine Berechtigung, um diese Aktion durchzuführen.</string> <string name="insufficient_access_level">Sie haben keine Berechtigung, um diese Aktion durchzuführen.</string>
<string name="delete_label_confirmation_title">Kategorie löschen</string> <string name="delete_label_confirmation_title">Label löschen</string>
<string name="delete_label_confirmation_message">Bist du sicher, dass du diese Kategorie löschen möchtest?</string> <string name="delete_label_confirmation_message">Sind Sie sicher, dass Sie diese Bezeichnung löschen möchten?</string>
<!-- About --> <!-- About -->
<string name="about_version">Version %1$s</string> <string name="about_version">Version %1$s</string>
<string name="about_maintainer_title">Betreuer</string> <string name="about_maintainer_title">Betreuer</string>
<string name="about_license_title">Lizenz</string> <string name="about_license_title">Lizenz</string>
<string name="about_source_title">Quellcode</string> <string name="about_source_title">Quellcode</string>
<!-- New constants for backward compatibility or shared use --> <!-- New constants for backward compatibility or shared use -->
<string name="share_intent_title">Projekt %1$s</string> <string name="share_intent_title">Projekt %1$s</string>
<string name="share_chooser_title">Teilen %1$s</string> <string name="share_chooser_title">Teilen %1$s</string>
Binary file not shown.
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip
networkTimeout=10000 networkTimeout=10000
retries=0 retries=0
retryBackOffMs=500 retryBackOffMs=500
Vendored
+8 -5
View File
@@ -1,7 +1,7 @@
#!/bin/sh #!/bin/sh
# #
# Copyright © 2015 the original authors. # Copyright © 2015-2021 the original authors.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@@ -20,7 +20,7 @@
############################################################################## ##############################################################################
# #
# gradlew start up script for POSIX generated by Gradle. # Gradle start up script for POSIX generated by Gradle.
# #
# Important for running: # Important for running:
# #
@@ -29,7 +29,7 @@
# bash, then to run this script, type that shell name before the whole # bash, then to run this script, type that shell name before the whole
# command line, like: # command line, like:
# #
# ksh gradlew # ksh Gradle
# #
# Busybox and similar reduced shells will NOT work, because this script # Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features: # requires all of these POSIX shell features:
@@ -57,7 +57,7 @@
# Darwin, MinGW, and NonStop. # Darwin, MinGW, and NonStop.
# #
# (3) This script is generated from the Groovy template # (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project. # within the Gradle project.
# #
# You can find Gradle at https://github.com/gradle/gradle/. # You can find Gradle at https://github.com/gradle/gradle/.
@@ -114,6 +114,7 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;; NONSTOP* ) nonstop=true ;;
esac esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM. # Determine the Java command to use to start the JVM.
@@ -171,6 +172,7 @@ fi
# For Cygwin or MSYS, switch paths to Windows format before running java # For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" ) JAVACMD=$( cygpath --unix "$JAVACMD" )
@@ -210,7 +212,8 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
set -- \ set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \ "-Dorg.gradle.appname=$APP_BASE_NAME" \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ -classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@" "$@"
# Stop when "xargs" is not available. # Stop when "xargs" is not available.
Vendored
+24 -12
View File
@@ -19,12 +19,12 @@
@if "%DEBUG%"=="" @echo off @if "%DEBUG%"=="" @echo off
@rem ########################################################################## @rem ##########################################################################
@rem @rem
@rem gradlew startup script for Windows @rem Gradle startup script for Windows
@rem @rem
@rem ########################################################################## @rem ##########################################################################
@rem Set local scope for the variables, and ensure extensions are enabled @rem Set local scope for the variables with windows NT shell
setlocal EnableExtensions if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0 set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=. if "%DIRNAME%"=="" set DIRNAME=.
@@ -51,7 +51,7 @@ echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2 echo location of your Java installation. 1>&2
"%COMSPEC%" /c exit 1 goto fail
:findJavaFromJavaHome :findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=% set JAVA_HOME=%JAVA_HOME:"=%
@@ -65,18 +65,30 @@ echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2 echo location of your Java installation. 1>&2
"%COMSPEC%" /c exit 1 goto fail
:execute :execute
@rem Setup the command line @rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute gradlew @rem Execute Gradle
@rem endlocal doesn't take effect until after the line is parsed and variables are expanded "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
@rem which allows us to clear the local environment before executing the java command
endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel
:exitWithErrorLevel :end
@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts @rem End local scope for the variables with windows NT shell
"%COMSPEC%" /c exit %ERRORLEVEL% if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
-3
View File
@@ -6,9 +6,6 @@ pluginManagement {
maven { url = 'https://jitpack.io' } maven { url = 'https://jitpack.io' }
} }
} }
plugins {
id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0'
}
dependencyResolutionManagement { dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories { repositories {