Compare commits
23
Commits
fix-sync
...
8ae8df6ef8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8ae8df6ef8 | ||
|
|
791f3572b6 | ||
|
|
ce8053ff2c | ||
|
|
f5f4c083f0 | ||
|
|
6800fc6c41
|
||
|
|
642b3b9c68
|
||
|
|
291694eaf4
|
||
|
|
b518c2a8c2 | ||
|
|
5054525cfd | ||
|
|
47b74de720 | ||
|
|
86384dd826 | ||
|
|
625747389b | ||
|
|
bdc39d3c7e | ||
|
|
12b424679d | ||
|
|
3ba5ee4d78 | ||
|
|
c01399f45d
|
||
|
|
a9cd70ea07 | ||
|
|
73dc64a688 | ||
|
|
3b22037496 | ||
|
|
c21a2b05ef
|
||
|
|
7ffda1c2f9 | ||
|
|
206c756ad7 | ||
|
|
a66d046b3f |
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 4.8 KiB |
@@ -34,6 +34,11 @@ jobs:
|
|||||||
gpg -d --passphrase "${{ secrets.RELEASE_KEYSTORE_PASSWORD }}" --batch keystore.asc > app/keystore.properties
|
gpg -d --passphrase "${{ secrets.RELEASE_KEYSTORE_PASSWORD }}" --batch keystore.asc > app/keystore.properties
|
||||||
gpg -d --passphrase "${{ secrets.RELEASE_KEYSTORE_PASSWORD }}" --batch key.asc > app/key.jks
|
gpg -d --passphrase "${{ secrets.RELEASE_KEYSTORE_PASSWORD }}" --batch key.asc > app/key.jks
|
||||||
|
|
||||||
|
- name: Generate Dynamic Release Notes
|
||||||
|
run: |
|
||||||
|
mkdir -p tmp/whatsnew
|
||||||
|
echo "Various improvements for you to experience..." > tmp/whatsnew/whatsnew-en-US
|
||||||
|
|
||||||
- name: create and checkout branch
|
- name: create and checkout branch
|
||||||
if: github.event_name == 'pull_request'
|
if: github.event_name == 'pull_request'
|
||||||
env:
|
env:
|
||||||
@@ -62,7 +67,9 @@ jobs:
|
|||||||
else
|
else
|
||||||
VERSION_NAME="${VERSION_BASE}"
|
VERSION_NAME="${VERSION_BASE}"
|
||||||
fi
|
fi
|
||||||
./gradlew assembleSignedRelease -PVERSION_CODE=$VERSION_CODE -PVERSION_NAME=$VERSION_NAME
|
./gradlew bundleSignedRelease assembleSignedRelease -PVERSION_CODE=$VERSION_CODE -PVERSION_NAME=$VERSION_NAME
|
||||||
|
# ./gradlew assembleSignedRelease -PVERSION_CODE=$VERSION_CODE -PVERSION_NAME=$VERSION_NAME
|
||||||
|
# ./gradlew bundleSignedRelease -PVERSION_CODE=$VERSION_CODE -PVERSION_NAME=$VERSION_NAME
|
||||||
|
|
||||||
- name: Upload APK
|
- name: Upload APK
|
||||||
uses: actions/upload-artifact@v7
|
uses: actions/upload-artifact@v7
|
||||||
@@ -83,3 +90,14 @@ jobs:
|
|||||||
else
|
else
|
||||||
echo "true"
|
echo "true"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
- name: Upload to Google Play
|
||||||
|
uses: r0adkll/upload-google-play@v1
|
||||||
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
|
with:
|
||||||
|
serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }}
|
||||||
|
packageName: net.helcel.cowspent
|
||||||
|
releaseFiles: app/build/outputs/bundle/signedRelease/app-signedRelease.aab
|
||||||
|
tracks: ${{ startsWith(github.ref, 'refs/tags/') && 'production' || 'alpha' }}
|
||||||
|
status: completed
|
||||||
|
whatsNewDirectory: tmp/whatsnew
|
||||||
+70
-8
@@ -1,12 +1,74 @@
|
|||||||
# Cowspent Privacy Policy
|
# Privacy Policy for Cowspent
|
||||||
|
|
||||||
Cowspent does not collect any personal data.
|
**App:** Cowspent (`net.helcel.cowspent`)
|
||||||
This is a Free/Libre software so you can verify no data is collected
|
**Developer:** Helcel
|
||||||
by reading the source code or asking someone to do it.
|
**Effective date:** 19 September 2026
|
||||||
|
|
||||||
Members and bills are synchronized with
|
## What we collect
|
||||||
the Cospend server of your choice.
|
|
||||||
|
|
||||||
CAMERA permission is used to read QR-codes.
|
Nothing. We operate no servers and receive no data from the app. Cowspent
|
||||||
|
contains no analytics, advertising, or tracking libraries.
|
||||||
|
|
||||||
GET\_ACCOUNTS permission is used by Single-Sign-On library to access accounts from Nextcloud Files app.
|
## Data you enter and where it goes
|
||||||
|
|
||||||
|
Cowspent stores your projects, members, bills, categories, and app settings on
|
||||||
|
your device.
|
||||||
|
|
||||||
|
If you connect the app to a Cospend or IHateMoney server, this data is
|
||||||
|
synchronised with **the server you choose**: either your own or one operated by
|
||||||
|
a third party. That server's operator controls that copy of your data and their
|
||||||
|
privacy policy applies to it. We are not involved in that transfer and cannot
|
||||||
|
access it.
|
||||||
|
|
||||||
|
Your server address, username, and password or access token are stored on your
|
||||||
|
device and sent only to the server you configured, over the connection it
|
||||||
|
provides. If you sign in through the Nextcloud Files app, authentication is
|
||||||
|
handled by that app's Single Sign-On and we never see your password.
|
||||||
|
|
||||||
|
## Sharing projects
|
||||||
|
|
||||||
|
Project share links and QR codes are generated by your server. Anyone who
|
||||||
|
obtains a link or code may be able to access the project, so share them only
|
||||||
|
with people you trust.
|
||||||
|
|
||||||
|
## Permissions
|
||||||
|
|
||||||
|
- `INTERNET`, `ACCESS_NETWORK_STATE` — synchronising with your chosen server.
|
||||||
|
- `CAMERA` — scanning QR codes to import a project. Images are processed on the
|
||||||
|
device, never stored or transmitted.
|
||||||
|
- `GET_ACCOUNTS` — used by the Nextcloud Single Sign-On library to list accounts
|
||||||
|
from the Nextcloud Files app.
|
||||||
|
|
||||||
|
## Retention and deletion
|
||||||
|
|
||||||
|
Data on your device remains until you delete it. Clearing the app's data in
|
||||||
|
Android Settings, or uninstalling Cowspent, removes the local copy and the
|
||||||
|
stored credentials. Data already synchronised to a server must be deleted on
|
||||||
|
that server, by you or its administrator.
|
||||||
|
|
||||||
|
## Children
|
||||||
|
|
||||||
|
Cowspent is suitable for all ages and collects no data from any user, including
|
||||||
|
children.
|
||||||
|
|
||||||
|
## Security
|
||||||
|
|
||||||
|
Credentials and project data are held in the app's private storage, protected by
|
||||||
|
the Android sandbox. Use an HTTPS server address so that synchronisation is
|
||||||
|
encrypted in transit.
|
||||||
|
|
||||||
|
## Open source
|
||||||
|
|
||||||
|
Cowspent is released under the GNU General Public License v3.0. The full source
|
||||||
|
is available at https://github.com/helcel-net/cowspent, so these statements can
|
||||||
|
be independently verified.
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
|
||||||
|
Any change affecting privacy will be published in this document before or with
|
||||||
|
the release that introduces it.
|
||||||
|
|
||||||
|
## Contact
|
||||||
|
|
||||||
|
Email: net-helcel+privacy@gmail.com
|
||||||
|
Issues: https://github.com/helcel-net/cowspent/issues
|
||||||
@@ -12,8 +12,6 @@ Which is originally a fork of [PhoneTrack-Android](https://gitlab.com/eneiluj/ph
|
|||||||
Which is itself a fork of [Nextcloud Notes for Android](https://github.com/stefan-niedermann/nextcloud-notes).
|
Which is itself a fork of [Nextcloud Notes for Android](https://github.com/stefan-niedermann/nextcloud-notes).
|
||||||
Many thanks to their developers :heart: !
|
Many thanks to their developers :heart: !
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<a href="https://ko-fi.com/I2I615VP5M"><img src="https://ko-fi.com/img/githubbutton_sm.svg" alt="ko-fi"></a>
|
<a href="https://ko-fi.com/I2I615VP5M"><img src="https://ko-fi.com/img/githubbutton_sm.svg" alt="ko-fi"></a>
|
||||||
@@ -24,6 +22,7 @@ Many thanks to their developers :heart: !
|
|||||||
<a href="https://github.com/helcel-net/cowspent/actions/workflows/build.yml">
|
<a href="https://github.com/helcel-net/cowspent/actions/workflows/build.yml">
|
||||||
<img src="https://github.com/helcel-net/cowspent/actions/workflows/build.yml/badge.svg?branch=main" alt="Build Status">
|
<img src="https://github.com/helcel-net/cowspent/actions/workflows/build.yml/badge.svg?branch=main" alt="Build Status">
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
## 🌄 Screenshots
|
## 🌄 Screenshots
|
||||||
@@ -57,18 +56,27 @@ Extra Features:
|
|||||||
- Support for Cospend archived-status (hide archived projects by default)
|
- Support for Cospend archived-status (hide archived projects by default)
|
||||||
- Modern UI and code with Kotlin and Jetpack-Compose
|
- Modern UI and code with Kotlin and Jetpack-Compose
|
||||||
|
|
||||||
|
|
||||||
## 📳 Installation
|
## 📳 Installation
|
||||||
|
|
||||||
<div style="display: flex; justify-content: center; align-items: center; flex-direction: row;">
|
<div style="display: flex; justify-content: center; align-items: center; flex-direction: row;">
|
||||||
<a href="https://apt.izzysoft.de/fdroid/index/apk/net.helcel.cowspent">
|
<a href="https://apt.izzysoft.de/fdroid/index/apk/net.helcel.cowspent">
|
||||||
<img width="200" height="80" alt="Izzy Download" src=".github/images/izzy.png">
|
<img width="200" height="84" alt="Izzy Download" src=".github/images/izzy.png">
|
||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/helcel-net/cowspent/releases/latest">
|
<a href="https://github.com/helcel-net/cowspent/releases/latest">
|
||||||
<img width="200" height="84" alt="APK Download" src=".github/images/apk.png">
|
<img width="200" height="84" alt="APK Download" src=".github/images/apk.png">
|
||||||
</a>
|
</a>
|
||||||
|
<a future-href="https://f-droid.org/en/packages/net.helcel.cowspent">
|
||||||
|
<img width="200" height="84" alt="Fdroid Download" src=".github/images/fdroid.png">
|
||||||
|
</a>
|
||||||
|
<a href="https://play.google.com/store/apps/details?id=net.helcel.cowspent">
|
||||||
|
<img width="200" height="84" alt="GooglePlay Download" src=".github/images/playstore.png">
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
Note:
|
||||||
|
- Fdroid: requested, see https://gitlab.com/fdroid/rfp/-/work_items/4412
|
||||||
|
- PlayStore: alpha test restriction by Google. To access, join https://groups.google.com/g/helcel-android-test
|
||||||
|
|
||||||
## ⚙️ Permissions
|
## ⚙️ Permissions
|
||||||
|
|
||||||
- `CAMERA`: recommended for scanning barcodes from camera
|
- `CAMERA`: recommended for scanning barcodes from camera
|
||||||
@@ -88,6 +96,13 @@ Cowspent is a user-driven project. We welcome any contribution, big or small.
|
|||||||
|
|
||||||
Thanks to all contributors, the developers of our dependencies, and our users.
|
Thanks to all contributors, the developers of our dependencies, and our users.
|
||||||
|
|
||||||
|
## Signinig key
|
||||||
|
|
||||||
|
```
|
||||||
|
net.helcel.cowspent
|
||||||
|
D3:EE:68:A8:5A:7F:35:A1:14:D2:74:B4:50:AA:CE:8C:ED:68:EA:C8:1A:A2:22:E2:7F:F5:0E:98:4C:C6:29
|
||||||
|
```
|
||||||
|
|
||||||
## 📝 License
|
## 📝 License
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
+5
-5
@@ -1,7 +1,7 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application' version '9.3.2'
|
id 'com.android.application' version '9.3.2'
|
||||||
id 'org.jetbrains.kotlin.plugin.serialization' version '2.4.10'
|
id 'org.jetbrains.kotlin.plugin.serialization' version '2.4.10'
|
||||||
id 'org.jetbrains.kotlin.plugin.compose' version '2.4.10'
|
id 'org.jetbrains.kotlin.plugin.compose' version '2.4.20'
|
||||||
id 'jacoco'
|
id 'jacoco'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,7 +118,7 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'androidx.compose.foundation:foundation:1.12.0'
|
implementation 'androidx.compose.foundation:foundation:1.12.1'
|
||||||
implementation 'androidx.compose.runtime:runtime:1.12.0'
|
implementation 'androidx.compose.runtime:runtime:1.12.0'
|
||||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.5'
|
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.5'
|
||||||
|
|
||||||
@@ -132,7 +132,7 @@ dependencies {
|
|||||||
implementation 'com.github.nextcloud:Android-SingleSignOn:1.3.4'
|
implementation 'com.github.nextcloud:Android-SingleSignOn:1.3.4'
|
||||||
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.09.00')
|
||||||
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'
|
||||||
@@ -147,12 +147,12 @@ dependencies {
|
|||||||
|
|
||||||
testImplementation 'junit:junit:4.13.2'
|
testImplementation 'junit:junit:4.13.2'
|
||||||
testImplementation 'io.mockk:mockk:1.14.11'
|
testImplementation 'io.mockk:mockk:1.14.11'
|
||||||
testImplementation 'org.robolectric:robolectric:4.16.1'
|
testImplementation 'org.robolectric:robolectric:4.17'
|
||||||
testImplementation 'androidx.test:core:1.7.0'
|
testImplementation 'androidx.test:core:1.7.0'
|
||||||
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.11.0'
|
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.11.0'
|
||||||
testImplementation 'androidx.compose.ui:ui-test-junit4'
|
testImplementation 'androidx.compose.ui:ui-test-junit4'
|
||||||
|
|
||||||
androidTestImplementation platform('androidx.compose:compose-bom:2026.08.00')
|
androidTestImplementation platform('androidx.compose:compose-bom:2026.09.00')
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.3.0'
|
androidTestImplementation 'androidx.test.ext:junit:1.3.0'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.7.0'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.7.0'
|
||||||
androidTestImplementation 'androidx.compose.ui:ui-test-junit4'
|
androidTestImplementation 'androidx.compose.ui:ui-test-junit4'
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
CUE6FCSBR6JIAAAAAAAAAAAAAA
|
||||||
@@ -1,11 +1,14 @@
|
|||||||
package net.helcel.cowspent.android.bill_edit
|
package net.helcel.cowspent.android.bill_edit
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.os.Build
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
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.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.text.BasicTextField
|
import androidx.compose.foundation.text.BasicTextField
|
||||||
import androidx.compose.foundation.text.KeyboardActions
|
import androidx.compose.foundation.text.KeyboardActions
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
@@ -19,18 +22,24 @@ 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.clip
|
||||||
|
import androidx.compose.ui.draw.drawBehind
|
||||||
|
import androidx.compose.ui.geometry.Offset
|
||||||
import androidx.compose.ui.draw.scale
|
import androidx.compose.ui.draw.scale
|
||||||
import androidx.compose.ui.focus.FocusDirection
|
import androidx.compose.ui.focus.FocusDirection
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
import androidx.compose.ui.focus.focusRequester
|
import androidx.compose.ui.focus.focusRequester
|
||||||
|
import androidx.compose.ui.focus.onFocusChanged
|
||||||
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.platform.LocalFocusManager
|
||||||
|
import androidx.compose.ui.res.colorResource
|
||||||
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.ImeAction
|
||||||
|
import androidx.compose.ui.text.TextRange
|
||||||
import androidx.compose.ui.text.input.KeyboardType
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
|
import androidx.compose.ui.text.input.TextFieldValue
|
||||||
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
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
@@ -62,6 +71,8 @@ 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() }
|
val focusRequester = remember { FocusRequester() }
|
||||||
|
var amountFocused by remember { mutableStateOf(false) }
|
||||||
|
var amountSelection by remember { mutableStateOf(TextRange(viewModel.amount.length)) }
|
||||||
|
|
||||||
// The activity reads the project off the main thread and only then reports whether this is a
|
// The activity reads the project off the main thread and only then reports whether this is a
|
||||||
// new bill, so the flag arrives after the first composition. Keying on it rather than on Unit
|
// new bill, so the flag arrives after the first composition. Keying on it rather than on Unit
|
||||||
@@ -118,16 +129,21 @@ fun EditBillScreen(
|
|||||||
val errorOwers = stringResource(R.string.error_invalid_bill_owers)
|
val errorOwers = stringResource(R.string.error_invalid_bill_owers)
|
||||||
val errorInvalidForm = stringResource(R.string.error_generic)
|
val errorInvalidForm = stringResource(R.string.error_generic)
|
||||||
|
|
||||||
FloatingActionButton(onClick = {
|
FloatingActionButton(
|
||||||
val validationError = viewModel.getValidationError(
|
// Keep the button clear of the operator row pinned above the keyboard.
|
||||||
errorWhat, errorDate, errorPayer, errorOwers, errorInvalidForm
|
modifier = Modifier.padding(
|
||||||
)
|
bottom = if (amountFocused) MathOperatorRowHeight else 0.dp
|
||||||
if (validationError == null) {
|
),
|
||||||
onSave()
|
onClick = {
|
||||||
} else {
|
val validationError = viewModel.getValidationError(
|
||||||
showToast(context, validationError)
|
errorWhat, errorDate, errorPayer, errorOwers, errorInvalidForm
|
||||||
}
|
)
|
||||||
}) {
|
if (validationError == null) {
|
||||||
|
onSave()
|
||||||
|
} else {
|
||||||
|
showToast(context, validationError)
|
||||||
|
}
|
||||||
|
}) {
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Default.Done,
|
Icons.Default.Done,
|
||||||
contentDescription = stringResource(R.string.action_save)
|
contentDescription = stringResource(R.string.action_save)
|
||||||
@@ -141,42 +157,138 @@ fun EditBillScreen(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(padding)
|
.padding(padding)
|
||||||
.imePadding()
|
.imePadding()
|
||||||
.padding(16.dp)
|
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.verticalScroll(scrollState)
|
|
||||||
) {
|
) {
|
||||||
BillBasicInfoSection(
|
Column(
|
||||||
viewModel = viewModel,
|
modifier = Modifier
|
||||||
canEdit = canEdit,
|
.weight(1f)
|
||||||
onDateClick = onDateClick,
|
.padding(16.dp)
|
||||||
onTimeClick = onTimeClick,
|
.verticalScroll(scrollState)
|
||||||
amountFocusRequester = focusRequester
|
) {
|
||||||
)
|
BillBasicInfoSection(
|
||||||
|
viewModel = viewModel,
|
||||||
|
canEdit = canEdit,
|
||||||
|
onDateClick = onDateClick,
|
||||||
|
onTimeClick = onTimeClick,
|
||||||
|
amountFocusRequester = focusRequester,
|
||||||
|
amountSelection = amountSelection,
|
||||||
|
onAmountSelectionChange = { amountSelection = it },
|
||||||
|
onAmountFocusChange = { amountFocused = it }
|
||||||
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
PayerSection(
|
PayerSection(
|
||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
canEdit = canEdit
|
canEdit = canEdit
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
OwerSelectionSection(
|
OwerSelectionSection(
|
||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
canEdit = canEdit
|
canEdit = canEdit
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
BillAdditionalDetailsSection(
|
BillAdditionalDetailsSection(
|
||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
categories = categories,
|
categories = categories,
|
||||||
paymentModes = paymentModes,
|
paymentModes = paymentModes,
|
||||||
canEdit = canEdit
|
canEdit = canEdit
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (amountFocused && canEdit) {
|
||||||
|
MathOperatorRow(onInsert = { op ->
|
||||||
|
val text = viewModel.amount
|
||||||
|
val start = amountSelection.min.coerceIn(0, text.length)
|
||||||
|
val end = amountSelection.max.coerceIn(0, text.length)
|
||||||
|
viewModel.amount = text.replaceRange(start, end, op)
|
||||||
|
amountSelection = TextRange(start + op.length)
|
||||||
|
viewModel.updateSplits()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val MathOperatorRowHeight = 54.dp
|
||||||
|
|
||||||
|
private data class KeyboardColors(val tray: Color, val edge: Color, val key: Color, val label: Color)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun keyboardColors(): KeyboardColors {
|
||||||
|
val dark = isSystemInDarkTheme()
|
||||||
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
val (tray, edge) = when {
|
||||||
|
Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE -> if (dark) {
|
||||||
|
colorResource(android.R.color.system_surface_container_dark) to
|
||||||
|
colorResource(android.R.color.system_surface_container_high_dark)
|
||||||
|
} else {
|
||||||
|
colorResource(android.R.color.system_surface_container_light) to
|
||||||
|
colorResource(android.R.color.system_surface_container_high_light)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> if (dark) {
|
||||||
|
colorResource(android.R.color.system_neutral1_900) to
|
||||||
|
colorResource(android.R.color.system_neutral1_800)
|
||||||
|
} else {
|
||||||
|
colorResource(android.R.color.system_neutral1_50) to
|
||||||
|
colorResource(android.R.color.system_neutral1_100)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (dark) KeyboardColors(
|
||||||
|
tray = tray,
|
||||||
|
edge = edge,
|
||||||
|
key = colorResource(android.R.color.system_accent2_700),
|
||||||
|
label = colorResource(android.R.color.system_neutral1_50)
|
||||||
|
) else KeyboardColors(
|
||||||
|
tray = tray,
|
||||||
|
edge = edge,
|
||||||
|
key = colorResource(android.R.color.system_accent2_100),
|
||||||
|
label = colorResource(android.R.color.system_neutral1_900)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
if (dark) KeyboardColors(Color(0xFF1B1B1F), Color(0xFF2B2B2F), Color(0xFF43474E), Color(0xFFE3E2E6))
|
||||||
|
else KeyboardColors(Color(0xFFEEEDF6), Color(0xFFE7E7F0), Color(0xFFDDE2F9), Color(0xFF1B1B1F))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Operator keys shown above the numeric keypad, which has no room for them itself. */
|
||||||
|
@Composable
|
||||||
|
fun MathOperatorRow(onInsert: (String) -> Unit) {
|
||||||
|
val colors = keyboardColors()
|
||||||
|
Surface(color = colors.tray) {
|
||||||
|
// Flat pill-shaped keys, styled like the keyboard's own function keys (e.g. its minus).
|
||||||
|
val keyShape = RoundedCornerShape(percent = 50)
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(MathOperatorRowHeight)
|
||||||
|
.drawBehind {
|
||||||
|
// A thin edge in the same tone the keyboard uses for its own top border.
|
||||||
|
drawLine(colors.edge, Offset(0f, 0f), Offset(size.width, 0f), 1.dp.toPx())
|
||||||
|
}
|
||||||
|
.padding(horizontal = 3.dp, vertical = 5.dp)
|
||||||
|
) {
|
||||||
|
listOf("(" to "(", ")" to ")", "+" to "+", "−" to "-", "×" to "*", "÷" to "/").forEach { (label, op) ->
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.fillMaxHeight()
|
||||||
|
.padding(horizontal = 3.dp)
|
||||||
|
.clip(keyShape)
|
||||||
|
.background(colors.key)
|
||||||
|
.clickable { onInsert(op) },
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Text(label, fontSize = 24.sp, color = colors.label)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -187,7 +299,10 @@ fun BillBasicInfoSection(
|
|||||||
canEdit: Boolean,
|
canEdit: Boolean,
|
||||||
onDateClick: () -> Unit,
|
onDateClick: () -> Unit,
|
||||||
onTimeClick: () -> Unit,
|
onTimeClick: () -> Unit,
|
||||||
amountFocusRequester: FocusRequester
|
amountFocusRequester: FocusRequester,
|
||||||
|
amountSelection: TextRange = TextRange(viewModel.amount.length),
|
||||||
|
onAmountSelectionChange: (TextRange) -> Unit = {},
|
||||||
|
onAmountFocusChange: (Boolean) -> Unit = {}
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "GENERAL",
|
text = "GENERAL",
|
||||||
@@ -203,15 +318,29 @@ fun BillBasicInfoSection(
|
|||||||
stringResource(R.string.currency_dialog_title, viewModel.mainCurrencyName)
|
stringResource(R.string.currency_dialog_title, viewModel.mainCurrencyName)
|
||||||
|
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = viewModel.amount,
|
value = TextFieldValue(
|
||||||
|
text = viewModel.amount,
|
||||||
|
selection = TextRange(
|
||||||
|
amountSelection.start.coerceIn(0, viewModel.amount.length),
|
||||||
|
amountSelection.end.coerceIn(0, viewModel.amount.length)
|
||||||
|
)
|
||||||
|
),
|
||||||
onValueChange = { nv ->
|
onValueChange = { nv ->
|
||||||
val filteredValue = nv.filter { it in "0123456789.+-*/" }
|
val filteredValue = nv.text.filter { it in "0123456789.,+-*/()" }
|
||||||
viewModel.amount = filteredValue
|
onAmountSelectionChange(
|
||||||
viewModel.updateSplits()
|
if (filteredValue == nv.text) nv.selection else TextRange(filteredValue.length)
|
||||||
|
)
|
||||||
|
if (filteredValue != viewModel.amount) {
|
||||||
|
viewModel.amount = filteredValue
|
||||||
|
viewModel.updateSplits()
|
||||||
|
}
|
||||||
},
|
},
|
||||||
enabled = canEdit,
|
enabled = canEdit,
|
||||||
placeholder = { Text("0") },
|
placeholder = { Text("0") },
|
||||||
modifier = Modifier.fillMaxWidth().focusRequester(amountFocusRequester),
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.focusRequester(amountFocusRequester)
|
||||||
|
.onFocusChanged { onAmountFocusChange(it.isFocused) },
|
||||||
leadingIcon = {
|
leadingIcon = {
|
||||||
val currencyToShow = viewModel.selectedCurrencyName.ifEmpty {
|
val currencyToShow = viewModel.selectedCurrencyName.ifEmpty {
|
||||||
viewModel.mainCurrencyName.ifEmpty { "$" }
|
viewModel.mainCurrencyName.ifEmpty { "$" }
|
||||||
@@ -245,7 +374,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.Decimal, imeAction = ImeAction.Next),
|
||||||
keyboardActions = KeyboardActions(onNext = { focusManager.moveFocus(FocusDirection.Next) })
|
keyboardActions = KeyboardActions(onNext = { focusManager.moveFocus(FocusDirection.Next) })
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -473,7 +602,7 @@ fun OwerSelectionSection(
|
|||||||
BasicTextField(
|
BasicTextField(
|
||||||
value = value,
|
value = value,
|
||||||
onValueChange = { nv ->
|
onValueChange = { nv ->
|
||||||
val filteredValue = nv.filter { it in "0123456789.+-*/" }
|
val filteredValue = nv.filter { it in "0123456789,.+-*/" }
|
||||||
if (viewModel.splitMode == SplitMode.PERCENT) {
|
if (viewModel.splitMode == SplitMode.PERCENT) {
|
||||||
viewModel.owersPercentSplit[member.id] = filteredValue
|
viewModel.owersPercentSplit[member.id] = filteredValue
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ class EditBillViewModel : ViewModel() {
|
|||||||
if (input.isBlank()) return 0.0
|
if (input.isBlank()) return 0.0
|
||||||
val sanitized = input.replace(',', '.')
|
val sanitized = input.replace(',', '.')
|
||||||
// If it contains any math operator, try evalMath first
|
// If it contains any math operator, try evalMath first
|
||||||
if (sanitized.any { it in "+-*/" }) {
|
if (sanitized.any { it in "+-*/()" }) {
|
||||||
try {
|
try {
|
||||||
val result = evalMath(sanitized)
|
val result = evalMath(sanitized)
|
||||||
if (result != 0.0) return SupportUtil.round2(result)
|
if (result != 0.0) return SupportUtil.round2(result)
|
||||||
|
|||||||
+10
-2
@@ -48,8 +48,10 @@ import net.helcel.cowspent.model.Transaction
|
|||||||
import net.helcel.cowspent.model.UserItem
|
import net.helcel.cowspent.model.UserItem
|
||||||
import net.helcel.cowspent.persistence.CowspentSQLiteOpenHelper
|
import net.helcel.cowspent.persistence.CowspentSQLiteOpenHelper
|
||||||
import net.helcel.cowspent.util.SupportUtil
|
import net.helcel.cowspent.util.SupportUtil
|
||||||
|
import net.helcel.cowspent.util.SupportUtil.SETTLE_DIRECT
|
||||||
import net.helcel.cowspent.util.SupportUtil.SETTLE_OPTIMAL
|
import net.helcel.cowspent.util.SupportUtil.SETTLE_OPTIMAL
|
||||||
import net.helcel.cowspent.util.SupportUtil.settleBills
|
import net.helcel.cowspent.util.SupportUtil.settleBills
|
||||||
|
import net.helcel.cowspent.util.SupportUtil.settleBillsDirect
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ProjectSettlementDialogContent(
|
fun ProjectSettlementDialogContent(
|
||||||
@@ -60,10 +62,12 @@ fun ProjectSettlementDialogContent(
|
|||||||
onDismiss: () -> Unit
|
onDismiss: () -> Unit
|
||||||
) {
|
) {
|
||||||
val centerNoneStr = stringResource(R.string.center_none)
|
val centerNoneStr = stringResource(R.string.center_none)
|
||||||
|
val centerDirectStr = stringResource(R.string.center_none_direct)
|
||||||
val memberList = remember(proj.id) { db.getMembersOfProject(proj.id, null) }
|
val memberList = remember(proj.id) { db.getMembersOfProject(proj.id, null) }
|
||||||
val userList = remember(memberList, centerNoneStr) {
|
val userList = remember(memberList, centerNoneStr, centerDirectStr) {
|
||||||
buildList {
|
buildList {
|
||||||
add(UserItem(SETTLE_OPTIMAL, centerNoneStr))
|
add(UserItem(SETTLE_OPTIMAL, centerNoneStr))
|
||||||
|
add(UserItem(SETTLE_DIRECT, centerDirectStr))
|
||||||
addAll(memberList.map { UserItem(it.id, it.name) })
|
addAll(memberList.map { UserItem(it.id, it.name) })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -91,7 +95,11 @@ fun ProjectSettlementDialogContent(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val transactions = remember(selectedMemberId, membersBalance, membersSortedByName) {
|
val transactions = remember(selectedMemberId, membersBalance, membersSortedByName) {
|
||||||
settleBills(membersSortedByName, membersBalance, selectedMemberId)
|
if (selectedMemberId == SETTLE_DIRECT) {
|
||||||
|
settleBillsDirect(membersSortedByName, db.getBillsOfProject(proj.id))
|
||||||
|
} else {
|
||||||
|
settleBills(membersSortedByName, membersBalance, selectedMemberId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ProjectSettlementUI(
|
ProjectSettlementUI(
|
||||||
|
|||||||
@@ -167,6 +167,39 @@ object SupportUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const val SETTLE_OPTIMAL: Long = 0
|
const val SETTLE_OPTIMAL: Long = 0
|
||||||
|
const val SETTLE_DIRECT: Long = -1
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun settleBillsDirect(members: List<DBMember>, bills: List<DBBill>): List<Transaction> {
|
||||||
|
val membersWeight = members.associate { it.id to it.weight }
|
||||||
|
// owed[debtor][creditor] = total the debtor owes the creditor over all bills
|
||||||
|
val owed = HashMap<Long, HashMap<Long, Double>>()
|
||||||
|
|
||||||
|
for (b in bills) {
|
||||||
|
if (b.state == DBBill.STATE_DELETED) continue
|
||||||
|
val nbOwerShares = b.billOwers.sumOf { membersWeight[it.memberId] ?: 0.0 }
|
||||||
|
if (nbOwerShares <= 0.0) continue
|
||||||
|
for (bo in b.billOwers) {
|
||||||
|
if (bo.memberId == b.payerId) continue
|
||||||
|
val share = b.amount / nbOwerShares * (membersWeight[bo.memberId] ?: 0.0)
|
||||||
|
val row = owed.getOrPut(bo.memberId) { HashMap() }
|
||||||
|
row[b.payerId] = (row[b.payerId] ?: 0.0) + share
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val results: MutableList<Transaction> = ArrayList()
|
||||||
|
for (debtor in members) {
|
||||||
|
for (creditor in members) {
|
||||||
|
if (debtor.id == creditor.id) continue
|
||||||
|
val net = (owed[debtor.id]?.get(creditor.id) ?: 0.0) -
|
||||||
|
(owed[creditor.id]?.get(debtor.id) ?: 0.0)
|
||||||
|
if (round2(net) > 0.0) {
|
||||||
|
results.add(Transaction(debtor.id, creditor.id, round2(net)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun settleBills(
|
fun settleBills(
|
||||||
|
|||||||
@@ -209,6 +209,7 @@
|
|||||||
|
|
||||||
<!-- Settlement -->
|
<!-- Settlement -->
|
||||||
<string name="center_none">Keine (optimal)</string>
|
<string name="center_none">Keine (optimal)</string>
|
||||||
|
<string name="center_none_direct">Keine (direkt)</string>
|
||||||
<string name="settle_who">Wer zahlt</string>
|
<string name="settle_who">Wer zahlt</string>
|
||||||
<string name="settle_to_whom">An wen</string>
|
<string name="settle_to_whom">An wen</string>
|
||||||
<string name="settle_how_much">Betrag</string>
|
<string name="settle_how_much">Betrag</string>
|
||||||
|
|||||||
@@ -210,6 +210,7 @@
|
|||||||
|
|
||||||
<!-- Settlement -->
|
<!-- Settlement -->
|
||||||
<string name="center_none">Ninguno (óptimo)</string>
|
<string name="center_none">Ninguno (óptimo)</string>
|
||||||
|
<string name="center_none_direct">Ninguno (directo)</string>
|
||||||
<string name="settle_who">Quién paga</string>
|
<string name="settle_who">Quién paga</string>
|
||||||
<string name="settle_to_whom">A quién</string>
|
<string name="settle_to_whom">A quién</string>
|
||||||
<string name="settle_how_much">Importe</string>
|
<string name="settle_how_much">Importe</string>
|
||||||
|
|||||||
@@ -213,6 +213,7 @@
|
|||||||
|
|
||||||
<!-- Settlement -->
|
<!-- Settlement -->
|
||||||
<string name="center_none">Aucun (optimal)</string>
|
<string name="center_none">Aucun (optimal)</string>
|
||||||
|
<string name="center_none_direct">Aucun (direct)</string>
|
||||||
<string name="settle_who">Qui paie</string>
|
<string name="settle_who">Qui paie</string>
|
||||||
<string name="settle_to_whom">À qui</string>
|
<string name="settle_to_whom">À qui</string>
|
||||||
<string name="settle_how_much">Montant</string>
|
<string name="settle_how_much">Montant</string>
|
||||||
|
|||||||
@@ -1,266 +1,259 @@
|
|||||||
<?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">
|
||||||
|
|
||||||
<!--
|
|
||||||
Untranslated: these are the English strings, kept here so a translation can be
|
|
||||||
contributed by simply replacing the values. Strings marked translatable="false" in
|
|
||||||
values/strings.xml (app and product names, preference keys) are deliberately absent
|
|
||||||
and must not be added.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<!-- Actions -->
|
<!-- Actions -->
|
||||||
<string name="action_new_bill">New bill</string>
|
<string name="action_new_bill">Новый расход</string>
|
||||||
<string name="action_add_project">Add project</string>
|
<string name="action_add_project">Добавить проект</string>
|
||||||
<string name="action_save">Save</string>
|
<string name="action_save">Сохранить</string>
|
||||||
<string name="action_edit">Edit</string>
|
<string name="action_edit">Редактировать</string>
|
||||||
<string name="action_share">Share</string>
|
<string name="action_share">Поделиться</string>
|
||||||
<string name="action_search">Search</string>
|
<string name="action_search">Поиск</string>
|
||||||
<string name="action_open_menu">Open menu</string>
|
<string name="action_open_menu">Открыть меню</string>
|
||||||
<string name="action_close_search">Close search</string>
|
<string name="action_close_search">Закрыть поиск</string>
|
||||||
<string name="action_clear_search">Clear search</string>
|
<string name="action_clear_search">Очистить поиск</string>
|
||||||
<string name="action_delete">Delete</string>
|
<string name="action_delete">Удалить</string>
|
||||||
<string name="simple_back">Back</string>
|
<string name="simple_back">Назад</string>
|
||||||
<string name="action_archive">Archive</string>
|
<string name="action_archive">Архивировать</string>
|
||||||
<string name="action_unarchive">Unarchive</string>
|
<string name="action_unarchive">Разархивировать</string>
|
||||||
<string name="action_export">Export</string>
|
<string name="action_export">Экспорт</string>
|
||||||
<string name="action_stats">Stats</string>
|
<string name="action_stats">Статистика</string>
|
||||||
<string name="action_settle">Settle</string>
|
<string name="action_settle">Свести баланс</string>
|
||||||
<string name="action_scan_qrcode">Scan QR Code</string>
|
<string name="action_scan_qrcode">Отсканировать QR-код</string>
|
||||||
<string name="action_settings">Settings</string>
|
<string name="action_settings">Настройки</string>
|
||||||
<string name="action_label_bills">Label missing categories</string>
|
<string name="action_label_bills">Указать отсутствующие категории</string>
|
||||||
<string name="action_logout">Logout</string>
|
<string name="action_logout">Выйти</string>
|
||||||
<string name="action_connect">Connect</string>
|
<string name="action_connect">Подключиться</string>
|
||||||
<string name="action_discard">Discard</string>
|
<string name="action_discard">Отменить</string>
|
||||||
<string name="action_members">Members</string>
|
<string name="action_members">Участники</string>
|
||||||
<string name="action_labels">Labels</string>
|
<string name="action_labels">Метки</string>
|
||||||
<string name="action_currencies">Currencies</string>
|
<string name="action_currencies">Валюты</string>
|
||||||
|
|
||||||
<!-- Titles -->
|
<!-- Titles -->
|
||||||
<string name="title_stats">Statistics</string>
|
<string name="title_stats">Статистика</string>
|
||||||
<string name="title_edit_project">Edit project</string>
|
<string name="title_edit_project">Редактировать проект</string>
|
||||||
<string name="title_label_bills">Label Bills</string>
|
<string name="title_label_bills">Указать категории</string>
|
||||||
<string name="title_labels">Manage Labels</string>
|
<string name="title_labels">Управление метками</string>
|
||||||
<string name="title_about">About</string>
|
<string name="title_about">О программе</string>
|
||||||
<string name="title_settle">Settle Project</string>
|
<string name="title_settle">Сведение баланса</string>
|
||||||
<string name="title_share">Share Project</string>
|
<string name="title_share">Поделиться проектом</string>
|
||||||
<string name="title_add_project">Add Project</string>
|
<string name="title_add_project">Добавить проект</string>
|
||||||
<string name="title_add_category">Add Category</string>
|
<string name="title_add_category">Добавить категорию</string>
|
||||||
<string name="title_add_payment_mode">Add Payment Mode</string>
|
<string name="title_add_payment_mode">Добавить способ оплаты</string>
|
||||||
<string name="title_account">Nextcloud Account</string>
|
<string name="title_account">Учётная запись Nextcloud</string>
|
||||||
<string name="title_share_web">Web link</string>
|
<string name="title_share_web">Веб-ссылка</string>
|
||||||
<string name="title_share_qr">Cowspent link</string>
|
<string name="title_share_qr">Ссылка Cowspent</string>
|
||||||
<string name="title_confirm">Are you sure?</string>
|
<string name="title_confirm">Вы уверены?</string>
|
||||||
|
|
||||||
<!-- Labels and Fields -->
|
<!-- Labels and Fields -->
|
||||||
<string name="label_all_bills">All bills</string>
|
<string name="label_all_bills">Все расходы</string>
|
||||||
<string name="label_categories">Categories</string>
|
<string name="label_categories">Категории</string>
|
||||||
<string name="label_payment_modes">Payment Modes</string>
|
<string name="label_payment_modes">Способы оплаты</string>
|
||||||
<string name="label_name">Name</string>
|
<string name="label_name">Название</string>
|
||||||
<string name="label_icon">Icon / Emoji</string>
|
<string name="label_icon">Значок / Смайлик</string>
|
||||||
<string name="label_color">Color</string>
|
<string name="label_color">Цвет</string>
|
||||||
<string name="label_weight">Weight</string>
|
<string name="label_weight">Вес</string>
|
||||||
<string name="label_activated">Activated</string>
|
<string name="label_activated">Активировано</string>
|
||||||
<string name="label_password">Password</string>
|
<string name="label_password">Пароль</string>
|
||||||
<string name="label_email">E-mail</string>
|
<string name="label_email">Электронная почта</string>
|
||||||
<string name="label_url">Server address</string>
|
<string name="label_url">Адрес сервера</string>
|
||||||
<string name="label_username">Username</string>
|
<string name="label_username">Имя пользователя</string>
|
||||||
<string name="label_comment">Comment</string>
|
<string name="label_comment">Комментарий</string>
|
||||||
<string name="label_what">What?</string>
|
<string name="label_what">Что?</string>
|
||||||
<string name="label_payer">Who paid?</string>
|
<string name="label_payer">Кто оплатил?</string>
|
||||||
<string name="label_owers">For whom?</string>
|
<string name="label_owers">Для кого?</string>
|
||||||
<string name="label_repeat">Repeat every</string>
|
<string name="label_repeat">Повторять каждые</string>
|
||||||
<string name="label_mode">Mode</string>
|
<string name="label_mode">Режим</string>
|
||||||
<string name="label_category">Category</string>
|
<string name="label_category">Категория</string>
|
||||||
<string name="label_project_id">Project ID/name</string>
|
<string name="label_project_id">Идентификатор/название проекта</string>
|
||||||
<string name="label_project_title">Project title</string>
|
<string name="label_project_title">Название проекта</string>
|
||||||
<string name="label_use_sso">Use Nextcloud App Account</string>
|
<string name="label_use_sso">Использовать учётную запись приложения Nextcloud</string>
|
||||||
|
|
||||||
<!-- Dialogs and Messages -->
|
<!-- Dialogs and Messages -->
|
||||||
<string name="dialog_unsaved_changes_title">Unsaved changes</string>
|
<string name="dialog_unsaved_changes_title">Несохраненные изменения</string>
|
||||||
<string name="dialog_unsaved_changes_msg">Save changes before leaving?</string>
|
<string name="dialog_unsaved_changes_msg">Сохранить изменения перед выходом?</string>
|
||||||
<string name="dialog_confirm_remove_project_msg">The remote project will not be deleted.</string>
|
<string name="dialog_confirm_remove_project_msg">Проект на сервере не будет удален.</string>
|
||||||
<string name="dialog_sync_error_title">Sync error</string>
|
<string name="dialog_sync_error_title">Ошибка синхронизации</string>
|
||||||
<string name="dialog_sync_error_msg">Sync failed for %1$s.\n\n%2$s</string>
|
<string name="dialog_sync_error_msg">Не удалось синхронизировать %1$s.\n\n%2$s</string>
|
||||||
<string name="dialog_balanced_msg">Expenses are already balanced.</string>
|
<string name="dialog_balanced_msg">Расходы уже сбалансированы.</string>
|
||||||
<string name="msg_project_added">Project %1$s added</string>
|
<string name="msg_project_added">Проект %1$s добавлен</string>
|
||||||
<string name="msg_bill_labeled_done">All bills labeled</string>
|
<string name="msg_bill_labeled_done">Все расходы помечены</string>
|
||||||
<string name="msg_no_suggestions">No suggestions</string>
|
<string name="msg_no_suggestions">Нет предложений</string>
|
||||||
<string name="msg_auth_warning">Requires Cospend v0.3.4+.</string>
|
<string name="msg_auth_warning">Требуется Cospend v0.3.4+.</string>
|
||||||
<string name="msg_link_copied">Link copied to clipboard</string>
|
<string name="msg_link_copied">Ссылка скопирована в буфер обмена</string>
|
||||||
<string name="msg_share_qr">Scan QR code or share the link to join.</string>
|
<string name="msg_share_qr">Отсканируйте QR-код или поделитесь ссылкой, чтобы присоединиться.</string>
|
||||||
<string name="msg_share_web">Link for web browser access.</string>
|
<string name="msg_share_web">Ссылка для доступа через веб-браузер.</string>
|
||||||
<string name="msg_share_qr_warn">Share this link with a Cowspent user.</string>
|
<string name="msg_share_qr_warn">Поделитесь этой ссылкой с пользователем Cowspent.</string>
|
||||||
<string name="msg_settle_intro">Settlement for %1$s:</string>
|
<string name="msg_settle_intro">Расчет по %1$s:</string>
|
||||||
<string name="msg_settle_sentence">%1$s owes %3$.2f to %2$s</string>
|
<string name="msg_settle_sentence">%1$s должен %2$s %3$.2f</string>
|
||||||
<string name="msg_stats_intro">Stats for %1$s:</string>
|
<string name="msg_stats_intro">Статистика для %1$s:</string>
|
||||||
<string name="msg_stats_header">Member (Paid | Spent | Balance)</string>
|
<string name="msg_stats_header">Участник (Оплачено | Потрачено | Баланс)</string>
|
||||||
<string name="msg_logged_in_as">Logged in as %1$s</string>
|
<string name="msg_logged_in_as">Вошёл под именем %1$s</string>
|
||||||
|
|
||||||
<!-- Errors -->
|
<!-- Errors -->
|
||||||
<string name="error_generic">Error</string>
|
<string name="error_generic">Ошибка</string>
|
||||||
<string name="error_loading">Loading</string>
|
<string name="error_loading">Загрузка</string>
|
||||||
<string name="error_no_projects">No projects found</string>
|
<string name="error_no_projects">Проекты не найдены</string>
|
||||||
<string name="error_no_members">No members found</string>
|
<string name="error_no_members">Участники не найдены</string>
|
||||||
<string name="error_no_bills">No bills found</string>
|
<string name="error_no_bills">Расходы не найдены</string>
|
||||||
<string name="error_no_member">At least one member required</string>
|
<string name="error_no_member">Требуется как минимум один участник</string>
|
||||||
<string name="error_maintenance_mode">Server is in maintenance mode</string>
|
<string name="error_maintenance_mode">Сервер находится в режиме технического обслуживания</string>
|
||||||
<string name="error_400">400 Bad request</string>
|
<string name="error_400">400 Неверный запрос</string>
|
||||||
<string name="error_401">401 Unauthorized</string>
|
<string name="error_401">401 Неавторизованный доступ</string>
|
||||||
<string name="error_403">403 Forbidden</string>
|
<string name="error_403">403 Доступ запрещен</string>
|
||||||
<string name="error_404">404 Not Found</string>
|
<string name="error_404">404 Не найдено</string>
|
||||||
<string name="error_sync">Sync failed: %1$s</string>
|
<string name="error_sync">Сбой синхронизации: %1$s</string>
|
||||||
<string name="error_invalid_login">Invalid login: %1$s</string>
|
<string name="error_invalid_login">Неверный логин: %1$s</string>
|
||||||
<string name="error_auth">Wrong username or password</string>
|
<string name="error_auth">Неверное имя пользователя или пароль</string>
|
||||||
<string name="error_json">Invalid server response</string>
|
<string name="error_json">Неверный ответ сервера</string>
|
||||||
<string name="error_req_failed">Request failed</string>
|
<string name="error_req_failed">Ошибка запроса</string>
|
||||||
<string name="error_invalid_email">Invalid e-mail</string>
|
<string name="error_invalid_email">Неверный адрес электронной почты</string>
|
||||||
<string name="error_invalid_project_id">Invalid project ID</string>
|
<string name="error_invalid_project_id">Неверный идентификатор проекта</string>
|
||||||
<string name="error_invalid_project_name">Invalid project title</string>
|
<string name="error_invalid_project_name">Неверное название проекта</string>
|
||||||
<string name="error_invalid_bill_name">Invalid bill name</string>
|
<string name="error_invalid_bill_name">Неверное название расхода</string>
|
||||||
<string name="error_invalid_bill_date">Invalid bill date</string>
|
<string name="error_invalid_bill_date">Неверная дата расхода</string>
|
||||||
<string name="error_invalid_bill_payer">Payer required</string>
|
<string name="error_invalid_bill_payer">Требуется указать плательщика</string>
|
||||||
<string name="error_invalid_bill_owers">Owers required</string>
|
<string name="error_invalid_bill_owers">Требуется указать участников</string>
|
||||||
<string name="error_no_network">No network connection</string>
|
<string name="error_no_network">Отсутствует подключение к сети</string>
|
||||||
<string name="error_server">Server error</string>
|
<string name="error_server">Ошибка сервера</string>
|
||||||
<string name="error_io">Server connection broken</string>
|
<string name="error_io">Сбой соединения с сервером</string>
|
||||||
<string name="error_share_impossible">Cannot share this project</string>
|
<string name="error_share_impossible">Невозможно поделиться этим проектом</string>
|
||||||
|
|
||||||
<!-- Drawer / Common UI -->
|
<!-- Drawer / Common UI -->
|
||||||
<string name="drawer_no_account">Connect to Nextcloud account</string>
|
<string name="drawer_no_account">Подключиться к учетной записи Nextcloud</string>
|
||||||
<string name="drawer_last_sync">Last sync: %1$02d:%2$02d</string>
|
<string name="drawer_last_sync">Последняя синхронизация: %1$02d:%2$02d</string>
|
||||||
<string name="simple_cancel">Cancel</string>
|
<string name="simple_cancel">Отменить</string>
|
||||||
<string name="simple_ok" tools:ignore="ButtonCase,Typos">Ok</string>
|
<string name="simple_ok" tools:ignore="ButtonCase,Typos">ОК</string>
|
||||||
<string name="simple_yes">Yes</string>
|
<string name="simple_yes">Да</string>
|
||||||
<string name="simple_no">No</string>
|
<string name="simple_no">Нет</string>
|
||||||
<string name="simple_close">Close</string>
|
<string name="simple_close">Закрыть</string>
|
||||||
|
|
||||||
<!-- Settings -->
|
<!-- Settings -->
|
||||||
<string name="settings_appearance">Appearance</string>
|
<string name="settings_appearance">Внешний вид</string>
|
||||||
<string name="settings_network">Network</string>
|
<string name="settings_network">Сеть</string>
|
||||||
<string name="settings_other">Other</string>
|
<string name="settings_other">Прочее</string>
|
||||||
<string name="settings_night_mode">Theme</string>
|
<string name="settings_night_mode">Тема</string>
|
||||||
<string name="settings_offline_mode">Offline mode</string>
|
<string name="settings_offline_mode">Офлайн-режим</string>
|
||||||
<string name="settings_offline_mode_summary">Only sync manually.</string>
|
<string name="settings_offline_mode_summary">Синхронизация только вручную</string>
|
||||||
<string name="settings_color_custom">Custom color</string>
|
<string name="settings_color_custom">Пользовательский цвет</string>
|
||||||
<string name="settings_color_mode">Color Selection</string>
|
<string name="settings_color_mode">Выбор цвета</string>
|
||||||
<string name="settings_show_archived">Show archived projects</string>
|
<string name="settings_show_archived">Показывать заархивированные проекты</string>
|
||||||
<string name="settings_fill_new_bill_from_last">Auto-fill from last bill</string>
|
<string name="settings_fill_new_bill_from_last">Автозаполнение на основе последнего расхода</string>
|
||||||
<string name="settings_fill_new_bill_from_last_summary">Pre-fill payer, category, mode and owers from the last bill created in the project.</string>
|
<string name="settings_fill_new_bill_from_last_summary">Предварительное заполнение полей "Плательщик", "Категория", "Способ оплаты" и "Для кого" на основе последнего расхода, созданного в проекте.</string>
|
||||||
<string name="pref_value_sync_1h">1 hour</string>
|
<string name="pref_value_sync_1h">1 час</string>
|
||||||
<string name="pref_value_sync_1d">1 day</string>
|
<string name="pref_value_sync_1d">1 день</string>
|
||||||
<string name="settings_url_warn_http">WARNING: "http" is unsafe. Use "https".</string>
|
<string name="settings_url_warn_http">ПРЕДУПРЕЖДЕНИЕ: "http" небезопасен. Используйте "https".</string>
|
||||||
<string name="settings_colorpicker_title">Choose Color</string>
|
<string name="settings_colorpicker_title">Выбрать цвет</string>
|
||||||
|
<string name="pref_value_color_system">Системная</string>
|
||||||
<string name="pref_value_color_system">System</string>
|
<string name="pref_value_color_manual">Вручную</string>
|
||||||
<string name="pref_value_color_manual">Manual</string>
|
<string name="pref_value_theme_light">Светлая</string>
|
||||||
<string name="pref_value_theme_light">Light</string>
|
<string name="pref_value_theme_dark">Тёмная</string>
|
||||||
<string name="pref_value_theme_dark">Dark</string>
|
<string name="pref_value_theme_system">По системе</string>
|
||||||
<string name="pref_value_theme_system">Follow system</string>
|
|
||||||
|
|
||||||
<!-- Enums and Lists -->
|
<!-- Enums and Lists -->
|
||||||
<string name="repeat_no">No repeat</string>
|
<string name="repeat_no">Без повторения</string>
|
||||||
<string name="repeat_day">Daily</string>
|
<string name="repeat_day">Ежедневно</string>
|
||||||
<string name="repeat_week">Weekly</string>
|
<string name="repeat_week">Еженедельно</string>
|
||||||
<string name="repeat_fortnight">Fortnightly</string>
|
<string name="repeat_fortnight">Раз в две недели</string>
|
||||||
<string name="repeat_month">Monthly</string>
|
<string name="repeat_month">Ежемесячно</string>
|
||||||
<string name="repeat_year">Yearly</string>
|
<string name="repeat_year">Ежегодно</string>
|
||||||
|
|
||||||
<string name="payment_mode_none">None</string>
|
<string name="payment_mode_none">Нет</string>
|
||||||
<string name="payment_mode_all">All</string>
|
<string name="payment_mode_all">Все</string>
|
||||||
<string name="payment_mode_credit_card">Credit card</string>
|
<string name="payment_mode_credit_card">Кредитная карта</string>
|
||||||
<string name="payment_mode_cash">Cash</string>
|
<string name="payment_mode_cash">Наличные</string>
|
||||||
<string name="payment_mode_check">Check</string>
|
<string name="payment_mode_check">Чек</string>
|
||||||
<string name="payment_mode_online">Online</string>
|
<string name="payment_mode_online">Онлайн</string>
|
||||||
<string name="payment_mode_transfer">Transfer</string>
|
<string name="payment_mode_transfer">Перевод</string>
|
||||||
|
|
||||||
<string name="category_none">None</string>
|
<string name="category_none">Нет</string>
|
||||||
<string name="category_all">All</string>
|
<string name="category_all">Все</string>
|
||||||
<string name="category_all_except_reimbursement">All except reimbursement</string>
|
<string name="category_all_except_reimbursement">Все, кроме возмещения</string>
|
||||||
<string name="category_groceries">Grocery</string>
|
<string name="category_groceries">Продукты</string>
|
||||||
<string name="category_leisure">Bar/Party</string>
|
<string name="category_leisure">Бар/Вечеринка</string>
|
||||||
<string name="category_rent">Rent</string>
|
<string name="category_rent">Аренда</string>
|
||||||
<string name="category_bills">Bill</string>
|
<string name="category_bills">Счета</string>
|
||||||
<string name="category_excursion">Excursion/Culture</string>
|
<string name="category_excursion">Экскурсии/Культура</string>
|
||||||
<string name="category_health">Health</string>
|
<string name="category_health">Здоровье</string>
|
||||||
<string name="category_shopping">Shopping</string>
|
<string name="category_shopping">Покупки</string>
|
||||||
<string name="category_reimbursement">Reimbursement</string>
|
<string name="category_reimbursement">Возмещение расходов</string>
|
||||||
<string name="category_restaurant">Restaurant</string>
|
<string name="category_restaurant">Ресторан</string>
|
||||||
<string name="category_accomodation">Accommodation</string>
|
<string name="category_accomodation">Проживание</string>
|
||||||
<string name="category_transport">Transport</string>
|
<string name="category_transport">Транспорт</string>
|
||||||
<string name="category_sport">Sport</string>
|
<string name="category_sport">Спорт</string>
|
||||||
|
|
||||||
<!-- Project specific -->
|
<!-- Project specific -->
|
||||||
<string name="new_project_action">What</string>
|
<string name="new_project_action">Что</string>
|
||||||
<string name="new_project_where">Where</string>
|
<string name="new_project_where">Где</string>
|
||||||
<string name="where_local">Local only</string>
|
<string name="where_local">Только локально</string>
|
||||||
<string name="todo_join">Join existing project</string>
|
<string name="todo_join">Присоединиться к существующему проекту</string>
|
||||||
<string name="todo_create">Create new project</string>
|
<string name="todo_create">Создать новый проект</string>
|
||||||
<string name="import_tooltip">Import from file</string>
|
<string name="import_tooltip">Импортировать из файла</string>
|
||||||
<string name="choose_project_management_action">Project</string>
|
<string name="choose_project_management_action">Проект</string>
|
||||||
<string name="project_added_success">Project added successfully.</string>
|
<string name="project_added_success">Проект добавлен успешно.</string>
|
||||||
<string name="no_projects_text">You have no projects yet.</string>
|
<string name="no_projects_text">У вас пока нет проектов.</string>
|
||||||
<string name="configure_account_choice">Configure Nextcloud account</string>
|
<string name="configure_account_choice">Настроить учётную запись Nextcloud</string>
|
||||||
<string name="add_project_choice">Add project manually</string>
|
<string name="add_project_choice">Добавить проект вручную</string>
|
||||||
<string name="no_members_text">No members in this project.</string>
|
<string name="no_members_text">В этом проекте нет участников.</string>
|
||||||
<string name="no_bills_text">No bills found.</string>
|
<string name="no_bills_text">Расходов не найдено.</string>
|
||||||
<string name="member_already_exists">Member already exists.</string>
|
<string name="member_already_exists">Участник уже существует.</string>
|
||||||
<string name="activity_dialog_title">Project: %1$s</string>
|
<string name="activity_dialog_title">Проект: %1$s</string>
|
||||||
<string name="remove_project_confirmation">Project %1$s removed.</string>
|
<string name="remove_project_confirmation">Проект %1$s удален.</string>
|
||||||
<string name="file_saved_success">File saved: %1$s</string>
|
<string name="file_saved_success">Файл сохранен: %1$s</string>
|
||||||
<string name="import_error_header">Import failed at row %d</string>
|
<string name="import_error_header">Ошибка импорта в строке %d</string>
|
||||||
<string name="import_error_date">Invalid date format at row %d</string>
|
<string name="import_error_date">Недопустимый формат даты в строке %d</string>
|
||||||
<string name="import_error_owers">Invalid owers at row %d</string>
|
<string name="import_error_owers">Недопустимые участники в строке %d</string>
|
||||||
<string name="add_member_dialog_title">Add Member</string>
|
<string name="add_member_dialog_title">Добавить участника</string>
|
||||||
<string name="edit_member_dialog_title">Edit Member</string>
|
<string name="edit_member_dialog_title">Редактировать участника</string>
|
||||||
<string name="member_edit_delete">Delete</string>
|
<string name="member_edit_delete">Удалить</string>
|
||||||
<string name="project_edition_no_change">No changes to save.</string>
|
<string name="project_edition_no_change">Изменений для сохранения нет.</string>
|
||||||
|
|
||||||
<!-- Settlement -->
|
<!-- Settlement -->
|
||||||
<string name="center_none">None (Optimal)</string>
|
<string name="center_none">Нет (оптимально)</string>
|
||||||
<string name="settle_who">Who pays</string>
|
<string name="center_none_direct">Нет (напрямую)</string>
|
||||||
<string name="settle_to_whom">To whom</string>
|
<string name="settle_who">Кто платит</string>
|
||||||
<string name="settle_how_much">Amount</string>
|
<string name="settle_to_whom">Кому</string>
|
||||||
<string name="simple_settle_share">Share</string>
|
<string name="settle_how_much">Сумма</string>
|
||||||
<string name="simple_create_bills">Create bills</string>
|
<string name="simple_settle_share">Поделиться</string>
|
||||||
<string name="settle_bill_what">Settlement</string>
|
<string name="simple_create_bills">Создать расходы</string>
|
||||||
|
<string name="settle_bill_what">Расход</string>
|
||||||
|
|
||||||
<!-- Currencies -->
|
<!-- Currencies -->
|
||||||
<string name="currency_dialog_title">Choose Currency (%s)</string>
|
<string name="currency_dialog_title">Выбрать валюту (%s)</string>
|
||||||
<string name="setting_none">None</string>
|
<string name="setting_none">Нет</string>
|
||||||
<string name="setting_all">All</string>
|
<string name="setting_all">Все</string>
|
||||||
<string name="currency_saved_success">Currency settings saved.</string>
|
<string name="currency_saved_success">Настройки валюты сохранены.</string>
|
||||||
<string name="main_currency">Main Currency</string>
|
<string name="main_currency">Основная валюта</string>
|
||||||
|
|
||||||
<!-- Statistics -->
|
<!-- Statistics -->
|
||||||
<string name="label_bills_suggested">Suggested Categories</string>
|
<string name="label_bills_suggested">Рекомендуемые категории</string>
|
||||||
<string name="label_bills_skip">Skip</string>
|
<string name="label_bills_skip">Пропустить</string>
|
||||||
<string name="stats_date_min">From</string>
|
<string name="stats_date_min">С</string>
|
||||||
<string name="stats_date_max">To</string>
|
<string name="stats_date_max">По</string>
|
||||||
<string name="stats_who">Member</string>
|
<string name="stats_who">Участник</string>
|
||||||
<string name="stats_paid">Paid</string>
|
<string name="stats_paid">Оплачено</string>
|
||||||
<string name="stats_spent">Spent</string>
|
<string name="stats_spent">Потрачено</string>
|
||||||
<string name="stats_balance">Balance</string>
|
<string name="stats_balance">Баланс</string>
|
||||||
<string name="total">Total: %1$s</string>
|
<string name="total">Итого: %1$s</string>
|
||||||
|
|
||||||
<!-- Errors Extra -->
|
<!-- Errors Extra -->
|
||||||
<string name="error_project_connect_check">Connection failed: %1$s</string>
|
<string name="error_project_connect_check">Ошибка подключения: %1$s</string>
|
||||||
<string name="error_create_remote_project_helper">Creation failed: %1$s</string>
|
<string name="error_create_remote_project_helper">Ошибка создания: %1$s</string>
|
||||||
<string name="error_edit_remote_project_helper">Error updating remote project: %1$s</string>
|
<string name="error_edit_remote_project_helper">Ошибка обновления удаленного проекта: %1$s</string>
|
||||||
<string name="remote_project_operation_no_network">Network unavailable for remote operation.</string>
|
<string name="remote_project_operation_no_network">Отсутствует подключение к сети для операций на сервере.</string>
|
||||||
<string name="error_scanning_bill_qr_code">Failed to parse QR code.</string>
|
<string name="error_scanning_bill_qr_code">Не удалось распознать QR-код.</string>
|
||||||
<string name="error_token_mismatch">Authentication token mismatch. Please log in again.</string>
|
<string name="error_token_mismatch">Ошибка токена аутентификации. Пожалуйста, войдите заново.</string>
|
||||||
<string name="insufficient_access_level">You don\'t have permission to perform this action.</string>
|
<string name="insufficient_access_level">У вас нет прав на выполнение этого действия.</string>
|
||||||
<string name="delete_label_confirmation_title">Delete Label</string>
|
<string name="delete_label_confirmation_title">Удалить метку</string>
|
||||||
<string name="delete_label_confirmation_message">Are you sure you want to delete this label?</string>
|
<string name="delete_label_confirmation_message">Вы действительно хотите удалить эту метку?</string>
|
||||||
|
|
||||||
<!-- About -->
|
<!-- About -->
|
||||||
<string name="about_version">Version %1$s</string>
|
<string name="about_version">Версия %1$s</string>
|
||||||
<string name="about_maintainer_title">Maintainer</string>
|
<string name="about_maintainer_title">Сопровождающий</string>
|
||||||
<string name="about_license_title">License</string>
|
<string name="about_license_title">Лицензия</string>
|
||||||
<string name="about_source_title">Source code</string>
|
<string name="about_source_title">Исходный код</string>
|
||||||
|
|
||||||
<!-- New constants for backward compatibility or shared use -->
|
<!-- New constants for backward compatibility or shared use -->
|
||||||
<string name="share_intent_title">Project %1$s</string>
|
<string name="share_intent_title">Проект %1$s</string>
|
||||||
<string name="share_chooser_title">Share %1$s</string>
|
<string name="share_chooser_title">Поделиться %1$s</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -247,6 +247,7 @@
|
|||||||
|
|
||||||
<!-- Settlement -->
|
<!-- Settlement -->
|
||||||
<string name="center_none">None (Optimal)</string>
|
<string name="center_none">None (Optimal)</string>
|
||||||
|
<string name="center_none_direct">None (Direct)</string>
|
||||||
<string name="settle_who">Who pays</string>
|
<string name="settle_who">Who pays</string>
|
||||||
<string name="settle_to_whom">To whom</string>
|
<string name="settle_to_whom">To whom</string>
|
||||||
<string name="settle_how_much">Amount</string>
|
<string name="settle_how_much">Amount</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user