Compare commits
39
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b46971d6b7 | ||
|
|
1034e961ab | ||
|
|
ff93fdd4d7 | ||
|
|
726c4a73dc
|
||
|
|
191f221faa
|
||
|
|
553e33d441 | ||
|
|
eec95cdcb0 | ||
|
|
106c4e27fc
|
||
|
|
47d0471b48
|
||
|
|
56968dbe3b | ||
|
|
56036d5840
|
||
|
|
4fa29da79d
|
||
|
|
c2c1017633 | ||
|
|
3aeabf4dd3 | ||
|
|
937c3db1f5 | ||
|
|
5b3c47045c | ||
|
|
7581985abd
|
||
|
|
810ca20201
|
||
|
|
678a90ead9
|
||
|
|
88469fa96c
|
||
|
|
119ae757b9
|
||
|
|
c3d380ddb4
|
||
|
|
a8ba00269b
|
||
|
|
756a37de23
|
||
|
|
5f7266013d
|
||
|
|
e98dc4a86b
|
||
|
|
afb14ff7b9
|
||
|
|
1bee1dabbe
|
||
|
|
84432fe68d
|
||
|
|
7e00b14d27
|
||
|
|
ec4d60897a
|
||
|
|
5c74336c10
|
||
|
|
d617c5c7be
|
||
|
|
a9a303478c
|
||
|
|
110ab397b8 | ||
|
|
3675e87651 | ||
|
|
65799496f9 | ||
|
|
2967dbd001
|
||
|
|
c2f59fa1df |
Binary file not shown.
|
After Width: | Height: | Size: 6.4 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
@@ -52,7 +52,8 @@ jobs:
|
|||||||
- name: Build APK
|
- name: Build APK
|
||||||
run: |
|
run: |
|
||||||
VERSION_CODE=$(git rev-list --count HEAD)
|
VERSION_CODE=$(git rev-list --count HEAD)
|
||||||
./gradlew assembleSignedRelease -PVERSION_CODE=$VERSION_CODE
|
VERSION_NAME=$(git describe --tags --always)
|
||||||
|
./gradlew assembleSignedRelease -PVERSION_CODE=$VERSION_CODE -PVERSION_NAME=$VERSION_NAME
|
||||||
|
|
||||||
- name: Release
|
- name: Release
|
||||||
uses: softprops/action-gh-release@v3
|
uses: softprops/action-gh-release@v3
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
<!--suppress ALL -->
|
<!--suppress ALL -->
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<h1>Cowspent</h1>
|
<h1>Cowspent</h1>
|
||||||
<img width="100px" src="./metadata/en-US/images/icon.webp" alt="Logo">
|
<img width="100px" src="./metadata/en-US/images/icon.png" alt="Logo">
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
|
|
||||||
Shared budget manager able to sync with [Nextcloud Cospend](https://github.com/julien-nc/cospend-nc).
|
Shared budget manager able to sync with [Nextcloud Cospend](https://github.com/julien-nc/cospend-nc).
|
||||||
|
|
||||||
This work is based upon [MoneyBuster](https://gitlab.com/eneiluj/moneybuster).
|
This work is based upon [MoneyBuster](https://gitlab.com/eneiluj/moneybuster).
|
||||||
|
|||||||
+21
-7
@@ -1,7 +1,7 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application' version '9.2.1'
|
id 'com.android.application' version '9.2.1'
|
||||||
id 'org.jetbrains.kotlin.plugin.serialization' version '2.3.21'
|
id 'org.jetbrains.kotlin.plugin.serialization' version '2.4.0'
|
||||||
id 'org.jetbrains.kotlin.plugin.compose' version '2.3.21'
|
id 'org.jetbrains.kotlin.plugin.compose' version '2.4.0'
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
@@ -14,7 +14,7 @@ android {
|
|||||||
applicationId "net.helcel.cowspent"
|
applicationId "net.helcel.cowspent"
|
||||||
minSdk = 26
|
minSdk = 26
|
||||||
targetSdk = 37
|
targetSdk = 37
|
||||||
versionName "1.0"
|
versionName project.hasProperty('VERSION_NAME') ? project.property('VERSION_NAME') : "1.4"
|
||||||
versionCode project.hasProperty('VERSION_CODE') ? project.property('VERSION_CODE').toInteger() : 1
|
versionCode project.hasProperty('VERSION_CODE') ? project.property('VERSION_CODE').toInteger() : 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,11 +82,22 @@ android {
|
|||||||
androidResources {
|
androidResources {
|
||||||
generateLocaleConfig = true
|
generateLocaleConfig = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
packaging {
|
||||||
|
jniLibs.keepDebugSymbols.add("**/*.so")
|
||||||
|
}
|
||||||
|
|
||||||
|
dependenciesInfo {
|
||||||
|
// Disables dependency metadata when building APKs (for IzzyOnDroid/F-Droid)
|
||||||
|
includeInApk = false
|
||||||
|
// Disables dependency metadata when building Android App Bundles (for Google Play)
|
||||||
|
includeInBundle = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'androidx.compose.foundation:foundation:1.11.2'
|
implementation 'androidx.compose.foundation:foundation:1.11.4'
|
||||||
implementation 'androidx.compose.runtime:runtime:1.11.2'
|
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'
|
||||||
@@ -99,7 +110,7 @@ dependencies {
|
|||||||
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.05.01')
|
implementation platform('androidx.compose:compose-bom:2026.08.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'
|
||||||
@@ -107,5 +118,8 @@ dependencies {
|
|||||||
debugImplementation 'androidx.compose.ui:ui-tooling'
|
debugImplementation 'androidx.compose.ui:ui-tooling'
|
||||||
implementation 'androidx.activity:activity-compose:1.13.0'
|
implementation 'androidx.activity:activity-compose:1.13.0'
|
||||||
implementation 'androidx.activity:activity-ktx:1.13.0'
|
implementation 'androidx.activity:activity-ktx:1.13.0'
|
||||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.10.0'
|
implementation 'androidx.security:security-crypto:1.1.0'
|
||||||
|
implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.11.0'
|
||||||
|
implementation "androidx.datastore:datastore-preferences:1.2.1"
|
||||||
|
implementation "com.google.crypto.tink:tink-android:1.22.0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,11 +20,14 @@
|
|||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/AppTheme">
|
android:theme="@style/AppTheme">
|
||||||
|
|
||||||
<activity android:name="net.helcel.cowspent.android.currencies.ManageCurrenciesActivity" />
|
<activity
|
||||||
|
android:name="net.helcel.cowspent.android.currencies.ManageCurrenciesActivity"
|
||||||
|
android:windowSoftInputMode="adjustResize" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="net.helcel.cowspent.android.main.BillsListViewActivity"
|
android:name="net.helcel.cowspent.android.main.BillsListViewActivity"
|
||||||
android:theme="@style/AppTheme.NoActionBar"
|
android:theme="@style/AppTheme.NoActionBar"
|
||||||
|
android:windowSoftInputMode="adjustResize"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
@@ -43,9 +46,9 @@
|
|||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="net.helcel.cowspent.android.account.AccountActivity"
|
android:name="net.helcel.cowspent.android.account.AccountActivity"
|
||||||
android:label="@string/settings_server_settings"
|
android:label="@string/title_account"
|
||||||
android:parentActivityName="net.helcel.cowspent.android.settings.PreferencesActivity"
|
android:parentActivityName="net.helcel.cowspent.android.settings.PreferencesActivity"
|
||||||
android:windowSoftInputMode="stateVisible"
|
android:windowSoftInputMode="adjustResize|stateVisible"
|
||||||
android:launchMode="standard"
|
android:launchMode="standard"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -64,9 +67,9 @@
|
|||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="net.helcel.cowspent.android.project.create.NewProjectActivity"
|
android:name="net.helcel.cowspent.android.project.create.NewProjectActivity"
|
||||||
android:label="@string/simple_add_project"
|
android:label="@string/title_add_project"
|
||||||
android:parentActivityName="net.helcel.cowspent.android.main.BillsListViewActivity"
|
android:parentActivityName="net.helcel.cowspent.android.main.BillsListViewActivity"
|
||||||
android:windowSoftInputMode="stateHidden"
|
android:windowSoftInputMode="adjustResize"
|
||||||
android:launchMode="standard"
|
android:launchMode="standard"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
@@ -91,36 +94,48 @@
|
|||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="net.helcel.cowspent.android.project.edit.EditProjectActivity"
|
android:name="net.helcel.cowspent.android.project.edit.EditProjectActivity"
|
||||||
android:label="@string/simple_edit_project"
|
android:label="@string/title_edit_project"
|
||||||
android:parentActivityName="net.helcel.cowspent.android.main.BillsListViewActivity"
|
android:parentActivityName="net.helcel.cowspent.android.main.BillsListViewActivity"
|
||||||
android:windowSoftInputMode="stateHidden"
|
android:windowSoftInputMode="adjustResize"
|
||||||
android:launchMode="standard">
|
android:launchMode="standard">
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="net.helcel.cowspent.android.bill_edit.EditBillActivity"
|
android:name="net.helcel.cowspent.android.bill_edit.EditBillActivity"
|
||||||
android:label="@string/simple_edit_bill"
|
android:label="@string/action_edit"
|
||||||
android:parentActivityName="net.helcel.cowspent.android.main.BillsListViewActivity"
|
android:parentActivityName="net.helcel.cowspent.android.main.BillsListViewActivity"
|
||||||
android:windowSoftInputMode="stateHidden"
|
android:windowSoftInputMode="adjustResize"
|
||||||
android:launchMode="standard">
|
android:launchMode="standard">
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="net.helcel.cowspent.android.about.AboutActivity"
|
android:name="net.helcel.cowspent.android.about.AboutActivity"
|
||||||
android:label="@string/simple_about"
|
android:label="@string/title_about"
|
||||||
android:parentActivityName="net.helcel.cowspent.android.main.BillsListViewActivity" />
|
android:parentActivityName="net.helcel.cowspent.android.main.BillsListViewActivity" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="net.helcel.cowspent.android.statistics.ProjectStatisticsActivity"
|
android:name="net.helcel.cowspent.android.statistics.ProjectStatisticsActivity"
|
||||||
android:label="@string/statistic_title"
|
android:label="@string/title_stats"
|
||||||
android:parentActivityName="net.helcel.cowspent.android.main.BillsListViewActivity"
|
android:parentActivityName="net.helcel.cowspent.android.main.BillsListViewActivity"
|
||||||
android:theme="@style/AppTheme.NoActionBar" />
|
android:theme="@style/AppTheme.NoActionBar" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="net.helcel.cowspent.android.bill_label.LabelBillsActivity"
|
android:name="net.helcel.cowspent.android.bill_label.LabelBillsActivity"
|
||||||
android:label="@string/label_bills_title"
|
android:label="@string/title_label_bills"
|
||||||
android:parentActivityName="net.helcel.cowspent.android.main.BillsListViewActivity"
|
android:parentActivityName="net.helcel.cowspent.android.main.BillsListViewActivity"
|
||||||
android:theme="@style/AppTheme.NoActionBar" />
|
android:theme="@style/AppTheme.NoActionBar" />
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name="net.helcel.cowspent.android.label.LabelManagementActivity"
|
||||||
|
android:label="@string/title_labels"
|
||||||
|
android:parentActivityName="net.helcel.cowspent.android.main.BillsListViewActivity"
|
||||||
|
android:windowSoftInputMode="adjustResize" />
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name="net.helcel.cowspent.android.project.member.MemberManagementActivity"
|
||||||
|
android:label="@string/action_members"
|
||||||
|
android:parentActivityName="net.helcel.cowspent.android.main.BillsListViewActivity"
|
||||||
|
android:windowSoftInputMode="adjustResize" />
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package net.helcel.cowspent.android.about
|
package net.helcel.cowspent.android.about
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import net.helcel.cowspent.theme.ThemeUtils
|
import net.helcel.cowspent.theme.ThemeUtils
|
||||||
@@ -8,6 +9,7 @@ import net.helcel.cowspent.theme.ThemeUtils
|
|||||||
class AboutActivity : AppCompatActivity() {
|
class AboutActivity : AppCompatActivity() {
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
enableEdgeToEdge()
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ fun AboutScreen(
|
|||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = { Text(stringResource(R.string.simple_about)) },
|
title = { Text(stringResource(R.string.title_about)) },
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
IconButton(onClick = onBack) {
|
IconButton(onClick = onBack) {
|
||||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)
|
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import android.webkit.WebViewClient
|
|||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.OnBackPressedCallback
|
import androidx.activity.OnBackPressedCallback
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
@@ -32,7 +33,6 @@ import androidx.lifecycle.lifecycleScope
|
|||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.nextcloud.android.sso.AccountImporter
|
import com.nextcloud.android.sso.AccountImporter
|
||||||
import com.nextcloud.android.sso.helper.SingleAccountHelper
|
import com.nextcloud.android.sso.helper.SingleAccountHelper
|
||||||
import com.nextcloud.android.sso.model.SingleSignOnAccount
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
@@ -41,6 +41,7 @@ import net.helcel.cowspent.android.main.MainConstants
|
|||||||
import net.helcel.cowspent.theme.ThemeUtils
|
import net.helcel.cowspent.theme.ThemeUtils
|
||||||
import net.helcel.cowspent.util.CospendClientUtil
|
import net.helcel.cowspent.util.CospendClientUtil
|
||||||
import net.helcel.cowspent.util.CospendClientUtil.LoginStatus
|
import net.helcel.cowspent.util.CospendClientUtil.LoginStatus
|
||||||
|
import net.helcel.cowspent.util.SecureStorage
|
||||||
import java.net.URLDecoder
|
import java.net.URLDecoder
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
@@ -68,11 +69,11 @@ class AccountActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private lateinit var preferences: SharedPreferences
|
private lateinit var preferences: SharedPreferences
|
||||||
private var oldPassword = ""
|
|
||||||
private var useWebLogin = true
|
private var useWebLogin = true
|
||||||
private var showLoginDialog by mutableStateOf(false)
|
private var showLoginDialog by mutableStateOf(false)
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
enableEdgeToEdge()
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
preferences = PreferenceManager.getDefaultSharedPreferences(applicationContext)
|
preferences = PreferenceManager.getDefaultSharedPreferences(applicationContext)
|
||||||
@@ -127,8 +128,6 @@ class AccountActivity : AppCompatActivity() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
oldPassword = preferences.getString(SETTINGS_PASSWORD, DEFAULT_SETTINGS) ?: ""
|
|
||||||
viewModel.validateUrl()
|
viewModel.validateUrl()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,11 +221,7 @@ class AccountActivity : AppCompatActivity() {
|
|||||||
private fun legacyLogin() {
|
private fun legacyLogin() {
|
||||||
val url = CospendClientUtil.formatURL(viewModel.serverUrl.trim())
|
val url = CospendClientUtil.formatURL(viewModel.serverUrl.trim())
|
||||||
val username = viewModel.username
|
val username = viewModel.username
|
||||||
var password = viewModel.password
|
val password = viewModel.password
|
||||||
|
|
||||||
if (password.isEmpty()) {
|
|
||||||
password = oldPassword
|
|
||||||
}
|
|
||||||
|
|
||||||
performLogin(url, username, password)
|
performLogin(url, username, password)
|
||||||
}
|
}
|
||||||
@@ -309,10 +304,10 @@ class AccountActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (status == LoginStatus.OK) {
|
if (status == LoginStatus.OK) {
|
||||||
|
SecureStorage.savePassword(applicationContext, SETTINGS_PASSWORD, password)
|
||||||
preferences.edit {
|
preferences.edit {
|
||||||
putString(SETTINGS_URL, url)
|
putString(SETTINGS_URL, url)
|
||||||
putString(SETTINGS_USERNAME, username)
|
putString(SETTINGS_USERNAME, username)
|
||||||
putString(SETTINGS_PASSWORD, password)
|
|
||||||
remove(SETTINGS_KEY_ETAG)
|
remove(SETTINGS_KEY_ETAG)
|
||||||
remove(SETTINGS_KEY_LAST_MODIFIED)
|
remove(SETTINGS_KEY_LAST_MODIFIED)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,39 +4,18 @@
|
|||||||
|
|
||||||
package net.helcel.cowspent.android.account
|
package net.helcel.cowspent.android.account
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.*
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.material.*
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.foundation.rememberScrollState
|
|
||||||
import androidx.compose.foundation.verticalScroll
|
|
||||||
import androidx.compose.material.Button
|
|
||||||
import androidx.compose.material.CircularProgressIndicator
|
|
||||||
import androidx.compose.material.Icon
|
|
||||||
import androidx.compose.material.IconButton
|
|
||||||
import androidx.compose.material.MaterialTheme
|
|
||||||
import androidx.compose.material.OutlinedTextField
|
|
||||||
import androidx.compose.material.Scaffold
|
|
||||||
import androidx.compose.material.Switch
|
|
||||||
import androidx.compose.material.SwitchDefaults
|
|
||||||
import androidx.compose.material.Text
|
|
||||||
import androidx.compose.material.TopAppBar
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
import androidx.compose.material.icons.filled.Check
|
import androidx.compose.material.icons.filled.*
|
||||||
import androidx.compose.material.icons.filled.Link
|
|
||||||
import androidx.compose.material.icons.filled.Lock
|
|
||||||
import androidx.compose.material.icons.filled.Person
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
@@ -99,7 +78,7 @@ fun AccountScreenContent(
|
|||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = { Text(stringResource(R.string.settings_server_settings)) },
|
title = { Text(stringResource(R.string.title_account)) },
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
IconButton(onClick = onBack) {
|
IconButton(onClick = onBack) {
|
||||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)
|
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)
|
||||||
@@ -123,30 +102,70 @@ fun AccountScreenContent(
|
|||||||
}
|
}
|
||||||
} else if (isLoggedIn) {
|
} else if (isLoggedIn) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.account_logged_in_as, username),
|
text = "CURRENT ACCOUNT",
|
||||||
style = MaterialTheme.typography.h6
|
style = MaterialTheme.typography.subtitle1,
|
||||||
)
|
color = MaterialTheme.colors.onSurface,
|
||||||
Text(
|
fontWeight = FontWeight.Bold,
|
||||||
text = serverUrl,
|
modifier = Modifier.padding(bottom = 8.dp)
|
||||||
style = MaterialTheme.typography.body2,
|
|
||||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.6f)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = 8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
Icons.Default.Person,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(24.dp),
|
||||||
|
tint = MaterialTheme.colors.onSurface.copy(alpha = 0.6f)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(32.dp))
|
||||||
|
Column {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.msg_logged_in_as, username),
|
||||||
|
style = MaterialTheme.typography.subtitle1
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = serverUrl,
|
||||||
|
style = MaterialTheme.typography.caption,
|
||||||
|
color = MaterialTheme.colors.onSurface.copy(alpha = 0.6f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
Button(
|
Button(
|
||||||
onClick = onLogout,
|
onClick = onLogout,
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
) {
|
) {
|
||||||
Text(stringResource(R.string.account_logout))
|
Text(stringResource(R.string.action_logout))
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.height(24.dp))
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isValidatingLogin) {
|
if (!isValidatingLogin) {
|
||||||
|
Text(
|
||||||
|
text = "CONNECTION SETTINGS",
|
||||||
|
style = MaterialTheme.typography.subtitle1,
|
||||||
|
color = MaterialTheme.colors.onSurface,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
modifier = Modifier.padding(bottom = 8.dp, top = if (isLoggedIn) 16.dp else 0.dp)
|
||||||
|
)
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
modifier = Modifier.padding(vertical = 8.dp)
|
modifier = Modifier.padding(vertical = 8.dp)
|
||||||
) {
|
) {
|
||||||
Text(stringResource(R.string.use_sso_toggle), modifier = Modifier.weight(1f))
|
Icon(
|
||||||
|
Icons.Default.Sync,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(24.dp),
|
||||||
|
tint = MaterialTheme.colors.onSurface.copy(alpha = 0.6f)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(32.dp))
|
||||||
|
Text(stringResource(R.string.label_use_sso), modifier = Modifier.weight(1f), style = MaterialTheme.typography.subtitle1)
|
||||||
Switch(
|
Switch(
|
||||||
checked = useSso,
|
checked = useSso,
|
||||||
onCheckedChange = { onSsoClick(it) },
|
onCheckedChange = { onSsoClick(it) },
|
||||||
@@ -163,7 +182,7 @@ fun AccountScreenContent(
|
|||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = serverUrl,
|
value = serverUrl,
|
||||||
onValueChange = onServerUrlChange,
|
onValueChange = onServerUrlChange,
|
||||||
label = { Text(stringResource(R.string.settings_url)) },
|
placeholder = { Text(stringResource(R.string.label_url)) },
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
leadingIcon = { Icon(Icons.Default.Link, contentDescription = null) },
|
leadingIcon = { Icon(Icons.Default.Link, contentDescription = null) },
|
||||||
trailingIcon = {
|
trailingIcon = {
|
||||||
@@ -192,7 +211,7 @@ fun AccountScreenContent(
|
|||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = username,
|
value = username,
|
||||||
onValueChange = onUsernameChange,
|
onValueChange = onUsernameChange,
|
||||||
label = { Text(stringResource(R.string.settings_username)) },
|
placeholder = { Text(stringResource(R.string.label_username)) },
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
leadingIcon = { Icon(Icons.Default.Person, contentDescription = null) },
|
leadingIcon = { Icon(Icons.Default.Person, contentDescription = null) },
|
||||||
singleLine = true
|
singleLine = true
|
||||||
@@ -204,7 +223,7 @@ fun AccountScreenContent(
|
|||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = password,
|
value = password,
|
||||||
onValueChange = onPasswordChange,
|
onValueChange = onPasswordChange,
|
||||||
label = { Text(stringResource(R.string.settings_password)) },
|
placeholder = { Text(stringResource(R.string.label_password)) },
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
leadingIcon = { Icon(Icons.Default.Lock, contentDescription = null) },
|
leadingIcon = { Icon(Icons.Default.Lock, contentDescription = null) },
|
||||||
visualTransformation = PasswordVisualTransformation(),
|
visualTransformation = PasswordVisualTransformation(),
|
||||||
@@ -221,7 +240,7 @@ fun AccountScreenContent(
|
|||||||
if (isSubmitting) {
|
if (isSubmitting) {
|
||||||
CustomCircularProgressIndicator(size = 24.dp, color = Color.White)
|
CustomCircularProgressIndicator(size = 24.dp, color = Color.White)
|
||||||
} else {
|
} else {
|
||||||
Text(stringResource(R.string.settings_submit))
|
Text(stringResource(R.string.action_connect))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import net.helcel.cowspent.util.CospendClientUtil
|
import net.helcel.cowspent.util.CospendClientUtil
|
||||||
|
import net.helcel.cowspent.util.SecureStorage
|
||||||
|
|
||||||
class AccountViewModel(application: Application) : AndroidViewModel(application) {
|
class AccountViewModel(application: Application) : AndroidViewModel(application) {
|
||||||
private val preferences: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(application)
|
private val preferences: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(application)
|
||||||
@@ -60,14 +61,15 @@ class AccountViewModel(application: Application) : AndroidViewModel(application)
|
|||||||
} else {
|
} else {
|
||||||
preferences.getString(AccountActivity.SETTINGS_USERNAME, "")
|
preferences.getString(AccountActivity.SETTINGS_USERNAME, "")
|
||||||
}
|
}
|
||||||
val password = if (useSso) {
|
|
||||||
""
|
|
||||||
} else {
|
|
||||||
preferences.getString(AccountActivity.SETTINGS_PASSWORD, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!url.isNullOrEmpty() && !username.isNullOrEmpty()) {
|
if (!url.isNullOrEmpty() && !username.isNullOrEmpty()) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
|
val password = if (useSso) {
|
||||||
|
""
|
||||||
|
} else {
|
||||||
|
SecureStorage.getPassword(getApplication(), AccountActivity.SETTINGS_PASSWORD)
|
||||||
|
}
|
||||||
|
|
||||||
isValidatingLogin = true
|
isValidatingLogin = true
|
||||||
isLoggedIn = withContext(Dispatchers.IO) {
|
isLoggedIn = withContext(Dispatchers.IO) {
|
||||||
if (useSso) {
|
if (useSso) {
|
||||||
@@ -87,6 +89,9 @@ class AccountViewModel(application: Application) : AndroidViewModel(application)
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun logout() {
|
fun logout() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
SecureStorage.removePassword(getApplication(), AccountActivity.SETTINGS_PASSWORD)
|
||||||
|
}
|
||||||
preferences.edit {
|
preferences.edit {
|
||||||
remove(AccountActivity.SETTINGS_USE_SSO)
|
remove(AccountActivity.SETTINGS_USE_SSO)
|
||||||
remove(AccountActivity.SETTINGS_SSO_URL)
|
remove(AccountActivity.SETTINGS_SSO_URL)
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import androidx.compose.material.TextButton
|
|||||||
import androidx.compose.material.contentColorFor
|
import androidx.compose.material.contentColorFor
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
@@ -65,8 +66,10 @@ fun LoginDialogContent(
|
|||||||
) {
|
) {
|
||||||
Column(modifier = Modifier.padding(24.dp)) {
|
Column(modifier = Modifier.padding(24.dp)) {
|
||||||
Text(
|
Text(
|
||||||
text = "Login",
|
text = "LOGIN",
|
||||||
style = MaterialTheme.typography.h6,
|
style = MaterialTheme.typography.subtitle1,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = MaterialTheme.colors.onSurface,
|
||||||
modifier = Modifier.padding(bottom = 16.dp)
|
modifier = Modifier.padding(bottom = 16.dp)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import android.app.TimePickerDialog
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.OnBackPressedCallback
|
import androidx.activity.OnBackPressedCallback
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
@@ -25,7 +26,7 @@ import net.helcel.cowspent.model.ProjectType
|
|||||||
import net.helcel.cowspent.persistence.CowspentSQLiteOpenHelper
|
import net.helcel.cowspent.persistence.CowspentSQLiteOpenHelper
|
||||||
import net.helcel.cowspent.theme.ThemeUtils
|
import net.helcel.cowspent.theme.ThemeUtils
|
||||||
import net.helcel.cowspent.util.BillParser
|
import net.helcel.cowspent.util.BillParser
|
||||||
import net.helcel.cowspent.util.CategoryUtils
|
import net.helcel.cowspent.util.SupportUtil
|
||||||
import java.text.ParseException
|
import java.text.ParseException
|
||||||
import java.time.ZoneId
|
import java.time.ZoneId
|
||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
@@ -39,6 +40,7 @@ class EditBillActivity : AppCompatActivity() {
|
|||||||
private val calendar = Calendar.getInstance()
|
private val calendar = Calendar.getInstance()
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
enableEdgeToEdge()
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
db = CowspentSQLiteOpenHelper.getInstance(this)
|
db = CowspentSQLiteOpenHelper.getInstance(this)
|
||||||
|
|
||||||
@@ -49,23 +51,10 @@ class EditBillActivity : AppCompatActivity() {
|
|||||||
ThemeUtils.CowspentTheme {
|
ThemeUtils.CowspentTheme {
|
||||||
|
|
||||||
val categories = remember {
|
val categories = remember {
|
||||||
val syncedCategories = db.getCategories(bill.projectId)
|
db.getCategories(bill.projectId)
|
||||||
val defaultCategories = CategoryUtils.getDefaultCategories(this@EditBillActivity, bill.projectId)
|
|
||||||
val hardcoded = if (projectType == ProjectType.LOCAL) {
|
|
||||||
defaultCategories
|
|
||||||
} else {
|
|
||||||
listOfNotNull(defaultCategories.find { it.remoteId.toInt() == DBBill.CATEGORY_REIMBURSEMENT })
|
|
||||||
}
|
|
||||||
syncedCategories + hardcoded
|
|
||||||
}
|
}
|
||||||
val paymentModes = remember {
|
val paymentModes = remember {
|
||||||
val syncedPaymentModes = db.getPaymentModes(bill.projectId)
|
db.getPaymentModes(bill.projectId)
|
||||||
val defaultPaymentModes = CategoryUtils.getDefaultPaymentModes(this@EditBillActivity, bill.projectId)
|
|
||||||
if (projectType == ProjectType.LOCAL) {
|
|
||||||
syncedPaymentModes + defaultPaymentModes
|
|
||||||
} else {
|
|
||||||
syncedPaymentModes.ifEmpty { defaultPaymentModes }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
EditBillScreen(
|
EditBillScreen(
|
||||||
@@ -105,7 +94,8 @@ class EditBillActivity : AppCompatActivity() {
|
|||||||
val createIntent = Intent(this@EditBillActivity, QrCodeScannerActivity::class.java)
|
val createIntent = Intent(this@EditBillActivity, QrCodeScannerActivity::class.java)
|
||||||
scanQRCodeLauncher.launch(createIntent)
|
scanQRCodeLauncher.launch(createIntent)
|
||||||
},
|
},
|
||||||
onDelete = if (bill.id > 0) { { deleteBillAsked() } } else null,
|
onDuplicate = if (!viewModel.isNewBill) { { duplicateCurrentBill() } } else null,
|
||||||
|
onDelete = if (!viewModel.isNewBill) { { deleteBillAsked() } } else null,
|
||||||
accessLevel = db.getProject(bill.projectId)?.myAccessLevel ?: DBProject.ACCESS_LEVEL_ADMIN
|
accessLevel = db.getProject(bill.projectId)?.myAccessLevel ?: DBProject.ACCESS_LEVEL_ADMIN
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -140,7 +130,7 @@ class EditBillActivity : AppCompatActivity() {
|
|||||||
bill = DBBill(
|
bill = DBBill(
|
||||||
first.id, 0, first.projectId, first.payerId, totalAmount,
|
first.id, 0, first.projectId, first.payerId, totalAmount,
|
||||||
first.timestamp, first.what, first.state, first.repeat,
|
first.timestamp, first.what, first.state, first.repeat,
|
||||||
first.paymentMode, first.categoryRemoteId, first.comment, first.paymentModeRemoteId
|
first.paymentMode, first.categoryId, first.comment, first.paymentModeId
|
||||||
)
|
)
|
||||||
|
|
||||||
val splits = mutableMapOf<Long, Double>()
|
val splits = mutableMapOf<Long, Double>()
|
||||||
@@ -171,8 +161,8 @@ class EditBillActivity : AppCompatActivity() {
|
|||||||
bill = DBBill(
|
bill = DBBill(
|
||||||
0, 0, projectId, btd.payerId, btd.amount,
|
0, 0, projectId, btd.payerId, btd.amount,
|
||||||
timeNowSeconds, btd.what, DBBill.STATE_ADDED,
|
timeNowSeconds, btd.what, DBBill.STATE_ADDED,
|
||||||
btd.repeat, btd.paymentMode, btd.categoryRemoteId,
|
btd.repeat, btd.paymentMode, btd.categoryId,
|
||||||
btd.comment, btd.paymentModeRemoteId
|
btd.comment, btd.paymentModeId
|
||||||
)
|
)
|
||||||
val btdOwers = btd.billOwers
|
val btdOwers = btd.billOwers
|
||||||
val newBillOwers = btdOwers.filter {
|
val newBillOwers = btdOwers.filter {
|
||||||
@@ -183,12 +173,23 @@ class EditBillActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
calendar.timeInMillis = bill.timestamp * 1000
|
calendar.timeInMillis = bill.timestamp * 1000
|
||||||
val members = db.getMembersOfProject(bill.projectId, null)
|
val allMembers = db.getMembersOfProject(bill.projectId, null)
|
||||||
|
val billOwerIds = bill.billOwersIds
|
||||||
|
val members = allMembers.filter { m ->
|
||||||
|
m.isActivated || m.id == bill.payerId || billOwerIds.contains(m.id)
|
||||||
|
}
|
||||||
|
|
||||||
val project = db.getProject(bill.projectId)
|
val project = db.getProject(bill.projectId)
|
||||||
val currencies = db.getCurrencies(bill.projectId)
|
val currencies = db.getCurrencies(bill.projectId)
|
||||||
|
|
||||||
|
if (project != null) {
|
||||||
|
db.ensureDefaultLabels(bill.projectId, project.type)
|
||||||
|
}
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
viewModel.currencies = currencies
|
viewModel.currencies = currencies
|
||||||
viewModel.mainCurrencyName = project?.currencyName ?: ""
|
viewModel.mainCurrencyName = project?.currencyName ?: ""
|
||||||
|
viewModel.isNewBill = (bill.id == 0L)
|
||||||
viewModel.initFromBill(bill, members, customSplits)
|
viewModel.initFromBill(bill, members, customSplits)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -204,6 +205,7 @@ class EditBillActivity : AppCompatActivity() {
|
|||||||
calendar.timeInMillis = austrianBill.date.time
|
calendar.timeInMillis = austrianBill.date.time
|
||||||
viewModel.timestamp = calendar.timeInMillis / 1000
|
viewModel.timestamp = calendar.timeInMillis / 1000
|
||||||
viewModel.amount = austrianBill.amount.toString()
|
viewModel.amount = austrianBill.amount.toString()
|
||||||
|
viewModel.updateSplits()
|
||||||
return@registerForActivityResult
|
return@registerForActivityResult
|
||||||
} catch (_: ParseException) {
|
} catch (_: ParseException) {
|
||||||
}
|
}
|
||||||
@@ -215,6 +217,7 @@ class EditBillActivity : AppCompatActivity() {
|
|||||||
viewModel.timestamp = calendar.timeInMillis / 1000
|
viewModel.timestamp = calendar.timeInMillis / 1000
|
||||||
}
|
}
|
||||||
viewModel.amount = croatianBill.amount.toString()
|
viewModel.amount = croatianBill.amount.toString()
|
||||||
|
viewModel.updateSplits()
|
||||||
return@registerForActivityResult
|
return@registerForActivityResult
|
||||||
} catch (_: ParseException) {
|
} catch (_: ParseException) {
|
||||||
}
|
}
|
||||||
@@ -223,28 +226,42 @@ class EditBillActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun duplicateCurrentBill() {
|
||||||
|
bill = DBBill(
|
||||||
|
0, 0, bill.projectId, viewModel.payerId, viewModel.amountAsDouble,
|
||||||
|
System.currentTimeMillis() / 1000, viewModel.what, DBBill.STATE_ADDED,
|
||||||
|
viewModel.repeat, bill.paymentMode, viewModel.categoryId,
|
||||||
|
viewModel.getFinalComment(), viewModel.paymentModeId
|
||||||
|
)
|
||||||
|
calendar.timeInMillis = System.currentTimeMillis()
|
||||||
|
viewModel.timestamp = calendar.timeInMillis / 1000
|
||||||
|
viewModel.isNewBill = true
|
||||||
|
|
||||||
|
showToast(this, "Duplicating bill...")
|
||||||
|
}
|
||||||
|
|
||||||
private fun onBack() {
|
private fun onBack() {
|
||||||
if (!valuesHaveChanged()) {
|
if (!valuesHaveChanged()) {
|
||||||
finish()
|
finish()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
viewModel.showDialog(
|
viewModel.showDialog(
|
||||||
title = getString(R.string.save_or_discard_bill_dialog_title),
|
title = getString(R.string.dialog_unsaved_changes_title),
|
||||||
message = getString(R.string.save_or_discard_bill_dialog_message),
|
message = getString(R.string.dialog_unsaved_changes_msg),
|
||||||
positiveText = getString(R.string.save_or_discard_bill_dialog_save),
|
positiveText = getString(R.string.action_save),
|
||||||
onConfirm = { saveBillAsked() },
|
onConfirm = { saveBillAsked() },
|
||||||
negativeText = getString(R.string.save_or_discard_bill_dialog_discard),
|
negativeText = getString(R.string.action_discard),
|
||||||
onCancel = { finish() }
|
onCancel = { finish() }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun saveBillAsked() {
|
private fun saveBillAsked() {
|
||||||
val validationError = viewModel.getValidationError(
|
val validationError = viewModel.getValidationError(
|
||||||
getString(R.string.error_invalid_bill_what),
|
getString(R.string.error_invalid_bill_name),
|
||||||
getString(R.string.error_invalid_bill_date),
|
getString(R.string.error_invalid_bill_date),
|
||||||
getString(R.string.error_invalid_bill_payerid),
|
getString(R.string.error_invalid_bill_payer),
|
||||||
getString(R.string.error_invalid_bill_owers),
|
getString(R.string.error_invalid_bill_owers),
|
||||||
getString(R.string.simple_error)
|
getString(R.string.error_generic)
|
||||||
)
|
)
|
||||||
|
|
||||||
if (validationError != null) {
|
if (validationError != null) {
|
||||||
@@ -262,7 +279,7 @@ class EditBillActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
private fun deleteBillAsked() {
|
private fun deleteBillAsked() {
|
||||||
viewModel.showDialog(
|
viewModel.showDialog(
|
||||||
title = getString(R.string.confirm_remove_project_dialog_title),
|
title = getString(R.string.title_confirm),
|
||||||
message = bill.what,
|
message = bill.what,
|
||||||
positiveText = getString(R.string.action_delete),
|
positiveText = getString(R.string.action_delete),
|
||||||
onConfirm = {
|
onConfirm = {
|
||||||
@@ -296,12 +313,12 @@ class EditBillActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
return !(bill.what == viewModel.what &&
|
return !(bill.what == viewModel.what &&
|
||||||
bill.timestamp == viewModel.timestamp &&
|
bill.timestamp == viewModel.timestamp &&
|
||||||
bill.amount == viewModel.amountAsDouble &&
|
bill.amount == viewModel.getFinalAmount() &&
|
||||||
bill.payerId == viewModel.payerId &&
|
bill.payerId == viewModel.payerId &&
|
||||||
bill.comment == viewModel.comment &&
|
bill.comment == viewModel.getFinalComment() &&
|
||||||
bill.repeat == viewModel.repeat &&
|
bill.repeat == viewModel.repeat &&
|
||||||
bill.categoryRemoteId == viewModel.categoryRemoteId &&
|
bill.categoryId == viewModel.categoryId &&
|
||||||
bill.paymentModeRemoteId == viewModel.paymentModeRemoteId &&
|
bill.paymentModeId == viewModel.paymentModeId &&
|
||||||
!owersChanged)
|
!owersChanged)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -310,13 +327,17 @@ class EditBillActivity : AppCompatActivity() {
|
|||||||
val isCustomSplit = viewModel.isCustomSplit
|
val isCustomSplit = viewModel.isCustomSplit
|
||||||
|
|
||||||
if (isCustomSplit) {
|
if (isCustomSplit) {
|
||||||
val splits = viewModel.owersCustomSplit.filter { (id, amount) ->
|
val splits: Map<Long, Double> = viewModel.owersCustomSplit.filter { (id, amountStr) ->
|
||||||
viewModel.owersSelection[id] == true && (amount.replace(',', '.').toDoubleOrNull()
|
viewModel.owersSelection[id] == true && (amountStr.replace(',', '.').toDoubleOrNull()
|
||||||
?: 0.0) > 0
|
?: 0.0) > 0
|
||||||
}.mapValues { it.value.replace(',', '.').toDoubleOrNull() ?: 0.0 }
|
}.mapValues {
|
||||||
|
val uiAmount = it.value.replace(',', '.').toDoubleOrNull() ?: 0.0
|
||||||
|
SupportUtil.round2(uiAmount / viewModel.selectedCurrencyRate)
|
||||||
|
}
|
||||||
|
|
||||||
if (splits.isEmpty()) return@withContext 0L
|
if (splits.isEmpty()) return@withContext 0L
|
||||||
|
|
||||||
|
val finalComment = viewModel.getFinalComment()
|
||||||
val splitEntries = splits.entries.toList()
|
val splitEntries = splits.entries.toList()
|
||||||
|
|
||||||
// Pool of existing bills in this group that we can potentially reuse
|
// Pool of existing bills in this group that we can potentially reuse
|
||||||
@@ -356,9 +377,9 @@ class EditBillActivity : AppCompatActivity() {
|
|||||||
listOf(memberId),
|
listOf(memberId),
|
||||||
viewModel.repeat,
|
viewModel.repeat,
|
||||||
existingBill.paymentMode,
|
existingBill.paymentMode,
|
||||||
viewModel.paymentModeRemoteId,
|
viewModel.paymentModeId,
|
||||||
viewModel.categoryRemoteId,
|
viewModel.categoryId,
|
||||||
viewModel.comment
|
finalComment
|
||||||
)
|
)
|
||||||
if (firstSavedId == 0L) firstSavedId = billToUseId
|
if (firstSavedId == 0L) firstSavedId = billToUseId
|
||||||
} else {
|
} else {
|
||||||
@@ -366,9 +387,11 @@ class EditBillActivity : AppCompatActivity() {
|
|||||||
val newBill = DBBill(
|
val newBill = DBBill(
|
||||||
0, 0, bill.projectId, viewModel.payerId, amount,
|
0, 0, bill.projectId, viewModel.payerId, amount,
|
||||||
viewModel.timestamp, viewModel.what, DBBill.STATE_ADDED, viewModel.repeat,
|
viewModel.timestamp, viewModel.what, DBBill.STATE_ADDED, viewModel.repeat,
|
||||||
bill.paymentMode, viewModel.categoryRemoteId, viewModel.comment, viewModel.paymentModeRemoteId
|
bill.paymentMode, viewModel.categoryId, finalComment, viewModel.paymentModeId
|
||||||
)
|
)
|
||||||
newBill.billOwers = listOf(DBBillOwer(0, 0, memberId))
|
newBill.billOwers = listOf(DBBillOwer(0, 0, memberId))
|
||||||
|
newBill.categoryId = viewModel.categoryId
|
||||||
|
newBill.paymentModeId = viewModel.paymentModeId
|
||||||
val newId = db.addBill(newBill)
|
val newId = db.addBill(newBill)
|
||||||
if (firstSavedId == 0L) firstSavedId = newId
|
if (firstSavedId == 0L) firstSavedId = newId
|
||||||
}
|
}
|
||||||
@@ -396,7 +419,8 @@ class EditBillActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
return@withContext firstSavedId
|
return@withContext firstSavedId
|
||||||
} else {
|
} else {
|
||||||
val newAmount = viewModel.amountAsDouble
|
val newAmount = viewModel.getFinalAmount()
|
||||||
|
val finalComment = viewModel.getFinalComment()
|
||||||
val newOwersIds = viewModel.getOwersIds()
|
val newOwersIds = viewModel.getOwersIds()
|
||||||
|
|
||||||
if (bill.id != 0L) {
|
if (bill.id != 0L) {
|
||||||
@@ -410,9 +434,9 @@ class EditBillActivity : AppCompatActivity() {
|
|||||||
newOwersIds,
|
newOwersIds,
|
||||||
viewModel.repeat,
|
viewModel.repeat,
|
||||||
bill.paymentMode,
|
bill.paymentMode,
|
||||||
viewModel.paymentModeRemoteId,
|
viewModel.paymentModeId,
|
||||||
viewModel.categoryRemoteId,
|
viewModel.categoryId,
|
||||||
viewModel.comment
|
finalComment
|
||||||
)
|
)
|
||||||
if (groupedBillIds != null) {
|
if (groupedBillIds != null) {
|
||||||
for (id in groupedBillIds) {
|
for (id in groupedBillIds) {
|
||||||
@@ -429,7 +453,7 @@ class EditBillActivity : AppCompatActivity() {
|
|||||||
val newBill = DBBill(
|
val newBill = DBBill(
|
||||||
0, 0, bill.projectId, viewModel.payerId, newAmount,
|
0, 0, bill.projectId, viewModel.payerId, newAmount,
|
||||||
viewModel.timestamp, viewModel.what, DBBill.STATE_ADDED, viewModel.repeat,
|
viewModel.timestamp, viewModel.what, DBBill.STATE_ADDED, viewModel.repeat,
|
||||||
bill.paymentMode, viewModel.categoryRemoteId, viewModel.comment, viewModel.paymentModeRemoteId
|
bill.paymentMode, viewModel.categoryId, finalComment, viewModel.paymentModeId
|
||||||
)
|
)
|
||||||
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)
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ 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.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
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
|
||||||
@@ -30,6 +31,7 @@ import androidx.compose.ui.unit.sp
|
|||||||
import net.helcel.cowspent.R
|
import net.helcel.cowspent.R
|
||||||
import net.helcel.cowspent.android.helper.*
|
import net.helcel.cowspent.android.helper.*
|
||||||
import net.helcel.cowspent.model.*
|
import net.helcel.cowspent.model.*
|
||||||
|
import net.helcel.cowspent.util.CategoryUtils
|
||||||
import net.helcel.cowspent.util.SupportUtil
|
import net.helcel.cowspent.util.SupportUtil
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
@@ -45,6 +47,7 @@ fun EditBillScreen(
|
|||||||
onDateClick: () -> Unit,
|
onDateClick: () -> Unit,
|
||||||
onTimeClick: () -> Unit,
|
onTimeClick: () -> Unit,
|
||||||
onScan: () -> Unit,
|
onScan: () -> Unit,
|
||||||
|
onDuplicate: (() -> Unit)? = null,
|
||||||
onDelete: (() -> Unit)? = null,
|
onDelete: (() -> Unit)? = null,
|
||||||
accessLevel: Int = DBProject.ACCESS_LEVEL_ADMIN
|
accessLevel: Int = DBProject.ACCESS_LEVEL_ADMIN
|
||||||
) {
|
) {
|
||||||
@@ -59,7 +62,7 @@ fun EditBillScreen(
|
|||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = { Text(stringResource(if (viewModel.what.isEmpty()) R.string.simple_new_bill else R.string.simple_edit_bill)) },
|
title = { Text(stringResource(if (viewModel.isNewBill) R.string.action_new_bill else R.string.action_edit)) },
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
IconButton(onClick = onBack) {
|
IconButton(onClick = onBack) {
|
||||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)
|
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)
|
||||||
@@ -70,6 +73,11 @@ fun EditBillScreen(
|
|||||||
IconButton(onClick = onScan) {
|
IconButton(onClick = onScan) {
|
||||||
Icon(Icons.Default.QrCodeScanner, contentDescription = null)
|
Icon(Icons.Default.QrCodeScanner, contentDescription = null)
|
||||||
}
|
}
|
||||||
|
if (onDuplicate != null) {
|
||||||
|
IconButton(onClick = onDuplicate) {
|
||||||
|
Icon(Icons.Default.ContentCopy, contentDescription = "Duplicate")
|
||||||
|
}
|
||||||
|
}
|
||||||
if (onDelete != null) {
|
if (onDelete != null) {
|
||||||
IconButton(onClick = onDelete) {
|
IconButton(onClick = onDelete) {
|
||||||
Icon(Icons.Default.Delete, contentDescription = stringResource(R.string.action_delete))
|
Icon(Icons.Default.Delete, contentDescription = stringResource(R.string.action_delete))
|
||||||
@@ -83,11 +91,11 @@ fun EditBillScreen(
|
|||||||
},
|
},
|
||||||
floatingActionButton = {
|
floatingActionButton = {
|
||||||
if (canEdit) {
|
if (canEdit) {
|
||||||
val errorWhat = stringResource(R.string.error_invalid_bill_what)
|
val errorWhat = stringResource(R.string.error_invalid_bill_name)
|
||||||
val errorDate = stringResource(R.string.error_invalid_bill_date)
|
val errorDate = stringResource(R.string.error_invalid_bill_date)
|
||||||
val errorPayer = stringResource(R.string.error_invalid_bill_payerid)
|
val errorPayer = stringResource(R.string.error_invalid_bill_payer)
|
||||||
val errorOwers = stringResource(R.string.error_invalid_bill_owers)
|
val errorOwers = stringResource(R.string.error_invalid_bill_owers)
|
||||||
val errorInvalidForm = stringResource(R.string.simple_error)
|
val errorInvalidForm = stringResource(R.string.error_generic)
|
||||||
|
|
||||||
FloatingActionButton(onClick = {
|
FloatingActionButton(onClick = {
|
||||||
val validationError = viewModel.getValidationError(
|
val validationError = viewModel.getValidationError(
|
||||||
@@ -101,7 +109,7 @@ fun EditBillScreen(
|
|||||||
}) {
|
}) {
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Default.Done,
|
Icons.Default.Done,
|
||||||
contentDescription = stringResource(R.string.action_save_bill)
|
contentDescription = stringResource(R.string.action_save)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -111,6 +119,7 @@ fun EditBillScreen(
|
|||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(padding)
|
.padding(padding)
|
||||||
|
.imePadding()
|
||||||
.padding(16.dp)
|
.padding(16.dp)
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.verticalScroll(scrollState)
|
.verticalScroll(scrollState)
|
||||||
@@ -157,16 +166,23 @@ fun BillBasicInfoSection(
|
|||||||
onDateClick: () -> Unit,
|
onDateClick: () -> Unit,
|
||||||
onTimeClick: () -> Unit
|
onTimeClick: () -> Unit
|
||||||
) {
|
) {
|
||||||
|
Text(
|
||||||
|
text = "GENERAL",
|
||||||
|
style = MaterialTheme.typography.subtitle1,
|
||||||
|
color = MaterialTheme.colors.onSurface,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
modifier = Modifier.padding(bottom = 8.dp)
|
||||||
|
)
|
||||||
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val currencyDialogTitle =
|
val currencyDialogTitle =
|
||||||
stringResource(R.string.currency_dialog_title, viewModel.mainCurrencyName)
|
stringResource(R.string.currency_dialog_title, viewModel.mainCurrencyName)
|
||||||
val noCurrencyError = stringResource(R.string.no_currency_error)
|
|
||||||
|
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = viewModel.what,
|
value = viewModel.what,
|
||||||
onValueChange = { viewModel.what = it },
|
onValueChange = { viewModel.what = it },
|
||||||
enabled = canEdit,
|
enabled = canEdit,
|
||||||
placeholder = { Text(stringResource(R.string.setting_what)) },
|
placeholder = { Text(stringResource(R.string.label_what)) },
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
leadingIcon = { Icon(Icons.Default.Title, contentDescription = null) }
|
leadingIcon = { Icon(Icons.Default.Title, contentDescription = null) }
|
||||||
)
|
)
|
||||||
@@ -182,25 +198,37 @@ fun BillBasicInfoSection(
|
|||||||
enabled = canEdit,
|
enabled = canEdit,
|
||||||
placeholder = { Text("0") },
|
placeholder = { Text("0") },
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
leadingIcon = { Icon(Icons.Default.AttachMoney, contentDescription = null) },
|
leadingIcon = {
|
||||||
|
val currencyToShow = viewModel.selectedCurrencyName.ifEmpty {
|
||||||
|
viewModel.mainCurrencyName.ifEmpty { "$" }
|
||||||
|
}
|
||||||
|
TextIconDisplay(
|
||||||
|
textIcon = TextIcon.Symbol(currencyToShow),
|
||||||
|
tint = MaterialTheme.colors.onSurface.copy(alpha = 0.6f)
|
||||||
|
)
|
||||||
|
},
|
||||||
trailingIcon = {
|
trailingIcon = {
|
||||||
IconButton(
|
IconButton(
|
||||||
enabled = canEdit,
|
enabled = canEdit,
|
||||||
onClick = {
|
onClick = {
|
||||||
if (viewModel.currencies.isNotEmpty()) {
|
val mainLabel = viewModel.mainCurrencyName.ifEmpty { "$" }
|
||||||
viewModel.showDialog(
|
val options = listOf("$mainLabel | Base") + viewModel.currencies.map {
|
||||||
title = currencyDialogTitle,
|
"${it.name} | 1 $mainLabel = ${it.exchangeRate} ${it.name}"
|
||||||
items = viewModel.currencies.map { "${it.name} (${it.exchangeRate})" },
|
|
||||||
onItemSelected = { index ->
|
|
||||||
viewModel.convertCurrency(viewModel.currencies[index])
|
|
||||||
}
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
showToast(context, noCurrencyError)
|
|
||||||
}
|
}
|
||||||
|
viewModel.showDialog(
|
||||||
|
title = currencyDialogTitle,
|
||||||
|
items = options,
|
||||||
|
onItemSelected = { index ->
|
||||||
|
if (index == 0) {
|
||||||
|
viewModel.resetCurrency()
|
||||||
|
} else {
|
||||||
|
viewModel.convertCurrency(viewModel.currencies[index - 1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
Icon(Icons.Default.CurrencyExchange, contentDescription = null)
|
Icon(Icons.Default.SwapHoriz, contentDescription = "Change Currency")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
|
||||||
@@ -221,7 +249,7 @@ fun BillBasicInfoSection(
|
|||||||
onClick = onDateClick,
|
onClick = onDateClick,
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
enabled = canEdit,
|
enabled = canEdit,
|
||||||
leadingIcon = { Icon(Icons.Default.Event, contentDescription = null) }
|
leadingIcon = { Icon(Icons.Default.Event, contentDescription = "Select Date") }
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
ClickableOutlinedTextField(
|
ClickableOutlinedTextField(
|
||||||
@@ -229,7 +257,7 @@ fun BillBasicInfoSection(
|
|||||||
onClick = onTimeClick,
|
onClick = onTimeClick,
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
enabled = canEdit,
|
enabled = canEdit,
|
||||||
leadingIcon = { Icon(Icons.Default.AccessTime, contentDescription = null) }
|
leadingIcon = { Icon(Icons.Default.AccessTime, contentDescription = "Select Time") }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -244,20 +272,16 @@ fun PayerSection(
|
|||||||
|
|
||||||
EditableExposedDropdownMenu(
|
EditableExposedDropdownMenu(
|
||||||
value = selectedPayer?.name ?: "",
|
value = selectedPayer?.name ?: "",
|
||||||
placeholder = stringResource(R.string.setting_payer),
|
placeholder = stringResource(R.string.label_payer),
|
||||||
expanded = payerExpanded,
|
expanded = payerExpanded,
|
||||||
onExpandedChange = { payerExpanded = it },
|
onExpandedChange = { payerExpanded = it },
|
||||||
onDismissRequest = { payerExpanded = false },
|
onDismissRequest = { payerExpanded = false },
|
||||||
enabled = canEdit,
|
enabled = canEdit,
|
||||||
leadingIcon = {
|
leadingIcon = {
|
||||||
Box(modifier = Modifier.padding(start = 12.dp)) {
|
Box(modifier = Modifier) {
|
||||||
if (selectedPayer != null) {
|
if (selectedPayer != null) {
|
||||||
UserAvatar(
|
MemberAvatar(
|
||||||
name = selectedPayer.name,
|
member = selectedPayer,
|
||||||
r = selectedPayer.r,
|
|
||||||
g = selectedPayer.g,
|
|
||||||
b = selectedPayer.b,
|
|
||||||
disabled = !selectedPayer.isActivated,
|
|
||||||
size = 24.dp
|
size = 24.dp
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@@ -271,12 +295,8 @@ fun PayerSection(
|
|||||||
viewModel.payerId = member.id
|
viewModel.payerId = member.id
|
||||||
payerExpanded = false
|
payerExpanded = false
|
||||||
}) {
|
}) {
|
||||||
UserAvatar(
|
MemberAvatar(
|
||||||
name = member.name,
|
member = member,
|
||||||
r = member.r,
|
|
||||||
g = member.g,
|
|
||||||
b = member.b,
|
|
||||||
disabled = !member.isActivated,
|
|
||||||
size = 24.dp
|
size = 24.dp
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
@@ -330,8 +350,10 @@ fun OwerSelectionSection(
|
|||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
Text(
|
Text(
|
||||||
stringResource(R.string.setting_owers), fontSize = 12.sp,
|
text = stringResource(R.string.label_owers).uppercase(),
|
||||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.6f)
|
style = MaterialTheme.typography.subtitle1,
|
||||||
|
color = MaterialTheme.colors.onSurface,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
)
|
)
|
||||||
Spacer(Modifier.weight(1f))
|
Spacer(Modifier.weight(1f))
|
||||||
|
|
||||||
@@ -381,12 +403,8 @@ fun OwerSelectionSection(
|
|||||||
enabled = canEdit,
|
enabled = canEdit,
|
||||||
onCheckedChange = { viewModel.toggleMember(member.id, it) }
|
onCheckedChange = { viewModel.toggleMember(member.id, it) }
|
||||||
)
|
)
|
||||||
UserAvatar(
|
MemberAvatar(
|
||||||
name = member.name,
|
member = member,
|
||||||
r = member.r,
|
|
||||||
g = member.g,
|
|
||||||
b = member.b,
|
|
||||||
disabled = !member.isActivated,
|
|
||||||
size = 32.dp
|
size = 32.dp
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
@@ -441,19 +459,28 @@ fun BillAdditionalDetailsSection(
|
|||||||
paymentModes: List<DBPaymentMode>,
|
paymentModes: List<DBPaymentMode>,
|
||||||
canEdit: Boolean
|
canEdit: Boolean
|
||||||
) {
|
) {
|
||||||
|
Text(
|
||||||
|
text = "DETAILS",
|
||||||
|
style = MaterialTheme.typography.subtitle1,
|
||||||
|
color = MaterialTheme.colors.onSurface,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
modifier = Modifier.padding(bottom = 8.dp, top = 16.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
val context = LocalContext.current
|
||||||
var categoryExpanded by remember { mutableStateOf(false) }
|
var categoryExpanded by remember { mutableStateOf(false) }
|
||||||
val selectedCategory =
|
val selectedCategory =
|
||||||
categories.find { it.remoteId.toInt() == viewModel.categoryRemoteId }
|
categories.find { it.id == viewModel.categoryId } ?: CategoryUtils.getCategoryById(context, viewModel.categoryId)
|
||||||
|
|
||||||
EditableExposedDropdownMenu(
|
EditableExposedDropdownMenu(
|
||||||
value = selectedCategory?.name ?: "",
|
value = selectedCategory?.name ?: "",
|
||||||
placeholder = stringResource(R.string.setting_category),
|
placeholder = stringResource(R.string.label_category),
|
||||||
expanded = categoryExpanded,
|
expanded = categoryExpanded,
|
||||||
onExpandedChange = { categoryExpanded = it },
|
onExpandedChange = { categoryExpanded = it },
|
||||||
onDismissRequest = { categoryExpanded = false },
|
onDismissRequest = { categoryExpanded = false },
|
||||||
enabled = canEdit,
|
enabled = canEdit,
|
||||||
leadingIcon = {
|
leadingIcon = {
|
||||||
Box(modifier = Modifier.padding(start = 12.dp)) {
|
Box(modifier = Modifier) {
|
||||||
if (selectedCategory != null) {
|
if (selectedCategory != null) {
|
||||||
Text(text = selectedCategory.icon, fontSize = 20.sp)
|
Text(text = selectedCategory.icon, fontSize = 20.sp)
|
||||||
} else {
|
} else {
|
||||||
@@ -463,16 +490,26 @@ fun BillAdditionalDetailsSection(
|
|||||||
},
|
},
|
||||||
content = {
|
content = {
|
||||||
DropdownMenuItem(onClick = {
|
DropdownMenuItem(onClick = {
|
||||||
viewModel.categoryRemoteId = 0
|
viewModel.categoryId = 0
|
||||||
categoryExpanded = false
|
categoryExpanded = false
|
||||||
}) {
|
}) {
|
||||||
Icon(Icons.Default.Close, tint = Color.Red, contentDescription = null)
|
Icon(Icons.Default.Close, tint = Color.Red, contentDescription = null)
|
||||||
Spacer(modifier = Modifier.width(12.dp))
|
Spacer(modifier = Modifier.width(12.dp))
|
||||||
Text(stringResource(R.string.category_none))
|
Text(stringResource(R.string.category_none))
|
||||||
}
|
}
|
||||||
categories.forEach { category ->
|
|
||||||
|
DropdownMenuItem(onClick = {
|
||||||
|
viewModel.categoryId = DBBill.CATEGORY_REIMBURSEMENT
|
||||||
|
categoryExpanded = false
|
||||||
|
}) {
|
||||||
|
Text(text = "\uD83D\uDCB0", fontSize = 20.sp)
|
||||||
|
Spacer(modifier = Modifier.width(12.dp))
|
||||||
|
Text(stringResource(R.string.category_reimbursement))
|
||||||
|
}
|
||||||
|
|
||||||
|
categories.filter { it.remoteId != DBBill.CATEGORY_REIMBURSEMENT }.forEach { category ->
|
||||||
DropdownMenuItem(onClick = {
|
DropdownMenuItem(onClick = {
|
||||||
viewModel.categoryRemoteId = category.remoteId.toInt()
|
viewModel.categoryId = category.id
|
||||||
categoryExpanded = false
|
categoryExpanded = false
|
||||||
}) {
|
}) {
|
||||||
Text(text = category.icon, fontSize = 20.sp)
|
Text(text = category.icon, fontSize = 20.sp)
|
||||||
@@ -487,17 +524,17 @@ fun BillAdditionalDetailsSection(
|
|||||||
|
|
||||||
var pmExpanded by remember { mutableStateOf(false) }
|
var pmExpanded by remember { mutableStateOf(false) }
|
||||||
val selectedPm =
|
val selectedPm =
|
||||||
paymentModes.find { it.remoteId.toInt() == viewModel.paymentModeRemoteId }
|
paymentModes.find { it.id == viewModel.paymentModeId } ?: CategoryUtils.getPaymentModeById(context, viewModel.paymentModeId)
|
||||||
|
|
||||||
EditableExposedDropdownMenu(
|
EditableExposedDropdownMenu(
|
||||||
value = selectedPm?.name ?: "",
|
value = selectedPm?.name ?: "",
|
||||||
placeholder = stringResource(R.string.setting_payment_mode),
|
placeholder = stringResource(R.string.label_mode),
|
||||||
expanded = pmExpanded,
|
expanded = pmExpanded,
|
||||||
onExpandedChange = { pmExpanded = it },
|
onExpandedChange = { pmExpanded = it },
|
||||||
onDismissRequest = { pmExpanded = false },
|
onDismissRequest = { pmExpanded = false },
|
||||||
enabled = canEdit,
|
enabled = canEdit,
|
||||||
leadingIcon = {
|
leadingIcon = {
|
||||||
Box(modifier = Modifier.padding(start = 12.dp)) {
|
Box(modifier = Modifier) {
|
||||||
if (selectedPm != null) {
|
if (selectedPm != null) {
|
||||||
Text(text = selectedPm.icon, fontSize = 20.sp)
|
Text(text = selectedPm.icon, fontSize = 20.sp)
|
||||||
} else {
|
} else {
|
||||||
@@ -507,7 +544,7 @@ fun BillAdditionalDetailsSection(
|
|||||||
},
|
},
|
||||||
content = {
|
content = {
|
||||||
DropdownMenuItem(onClick = {
|
DropdownMenuItem(onClick = {
|
||||||
viewModel.paymentModeRemoteId = 0
|
viewModel.paymentModeId = 0
|
||||||
pmExpanded = false
|
pmExpanded = false
|
||||||
}) {
|
}) {
|
||||||
Icon(Icons.Default.Close, tint = Color.Red, contentDescription = null)
|
Icon(Icons.Default.Close, tint = Color.Red, contentDescription = null)
|
||||||
@@ -516,7 +553,7 @@ fun BillAdditionalDetailsSection(
|
|||||||
}
|
}
|
||||||
paymentModes.forEach { pm ->
|
paymentModes.forEach { pm ->
|
||||||
DropdownMenuItem(onClick = {
|
DropdownMenuItem(onClick = {
|
||||||
viewModel.paymentModeRemoteId = pm.remoteId.toInt()
|
viewModel.paymentModeId = pm.id
|
||||||
pmExpanded = false
|
pmExpanded = false
|
||||||
}) {
|
}) {
|
||||||
Text(text = pm.icon, fontSize = 20.sp)
|
Text(text = pm.icon, fontSize = 20.sp)
|
||||||
@@ -542,7 +579,7 @@ fun BillAdditionalDetailsSection(
|
|||||||
|
|
||||||
EditableExposedDropdownMenu(
|
EditableExposedDropdownMenu(
|
||||||
value = selectedRepeat?.second ?: "",
|
value = selectedRepeat?.second ?: "",
|
||||||
placeholder = stringResource(R.string.setting_project_repetition),
|
placeholder = stringResource(R.string.label_repeat),
|
||||||
expanded = repeatExpanded,
|
expanded = repeatExpanded,
|
||||||
onExpandedChange = { repeatExpanded = it },
|
onExpandedChange = { repeatExpanded = it },
|
||||||
onDismissRequest = { repeatExpanded = false },
|
onDismissRequest = { repeatExpanded = false },
|
||||||
@@ -566,7 +603,7 @@ fun BillAdditionalDetailsSection(
|
|||||||
value = viewModel.comment,
|
value = viewModel.comment,
|
||||||
onValueChange = { viewModel.comment = it },
|
onValueChange = { viewModel.comment = it },
|
||||||
enabled = canEdit,
|
enabled = canEdit,
|
||||||
placeholder = { Text(stringResource(R.string.setting_comment)) },
|
placeholder = { Text(stringResource(R.string.label_comment)) },
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
singleLine = false,
|
singleLine = false,
|
||||||
leadingIcon = {
|
leadingIcon = {
|
||||||
@@ -587,6 +624,7 @@ fun EditBillScreenPreview() {
|
|||||||
viewModel = EditBillViewModel().apply {
|
viewModel = EditBillViewModel().apply {
|
||||||
what = "Pizza"
|
what = "Pizza"
|
||||||
amount = "12.50"
|
amount = "12.50"
|
||||||
|
mainCurrencyName = "EUR"
|
||||||
members = listOf(
|
members = listOf(
|
||||||
DBMember(1, 0, 0, "Alice", true, 1.0, 0, null, null, null, null, null),
|
DBMember(1, 0, 0, "Alice", true, 1.0, 0, null, null, null, null, null),
|
||||||
DBMember(2, 0, 0, "Bob", true, 1.0, 0, null, null, null, null, null)
|
DBMember(2, 0, 0, "Bob", true, 1.0, 0, null, null, null, null, null)
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
package net.helcel.cowspent.android.bill_edit
|
package net.helcel.cowspent.android.bill_edit
|
||||||
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
import androidx.compose.runtime.mutableDoubleStateOf
|
||||||
import androidx.compose.runtime.mutableLongStateOf
|
import androidx.compose.runtime.mutableLongStateOf
|
||||||
import androidx.compose.runtime.mutableStateMapOf
|
import androidx.compose.runtime.mutableStateMapOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import net.helcel.cowspent.model.DBBill
|
|
||||||
import net.helcel.cowspent.model.DBMember
|
|
||||||
import net.helcel.cowspent.android.helper.DialogState
|
import net.helcel.cowspent.android.helper.DialogState
|
||||||
|
import net.helcel.cowspent.android.helper.parseAmount
|
||||||
|
import net.helcel.cowspent.model.DBBill
|
||||||
|
import net.helcel.cowspent.model.DBCurrency
|
||||||
|
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
|
||||||
|
|
||||||
import net.helcel.cowspent.model.DBCurrency
|
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
|
||||||
|
|
||||||
class EditBillViewModel : ViewModel() {
|
class EditBillViewModel : ViewModel() {
|
||||||
var what by mutableStateOf("")
|
var what by mutableStateOf("")
|
||||||
var amount by mutableStateOf("")
|
var amount by mutableStateOf("")
|
||||||
@@ -23,11 +23,14 @@ class EditBillViewModel : ViewModel() {
|
|||||||
var timestamp by mutableLongStateOf(0L)
|
var timestamp by mutableLongStateOf(0L)
|
||||||
var payerId by mutableLongStateOf(0L)
|
var payerId by mutableLongStateOf(0L)
|
||||||
var repeat by mutableStateOf(DBBill.NON_REPEATED)
|
var repeat by mutableStateOf(DBBill.NON_REPEATED)
|
||||||
var paymentModeRemoteId by mutableIntStateOf(0)
|
var paymentModeId by mutableLongStateOf(0L)
|
||||||
var categoryRemoteId by mutableIntStateOf(0)
|
var categoryId by mutableLongStateOf(0L)
|
||||||
|
var isNewBill by mutableStateOf(false)
|
||||||
|
|
||||||
var currencies by mutableStateOf<List<DBCurrency>>(emptyList())
|
var currencies by mutableStateOf<List<DBCurrency>>(emptyList())
|
||||||
var mainCurrencyName by mutableStateOf("")
|
var mainCurrencyName by mutableStateOf("")
|
||||||
|
var selectedCurrencyName by mutableStateOf("")
|
||||||
|
var selectedCurrencyRate by mutableDoubleStateOf(1.0)
|
||||||
var members by mutableStateOf<List<DBMember>>(emptyList())
|
var members by mutableStateOf<List<DBMember>>(emptyList())
|
||||||
|
|
||||||
var owersSelection = mutableStateMapOf<Long, Boolean>()
|
var owersSelection = mutableStateMapOf<Long, Boolean>()
|
||||||
@@ -38,13 +41,8 @@ class EditBillViewModel : ViewModel() {
|
|||||||
|
|
||||||
val amountAsDouble: Double
|
val amountAsDouble: Double
|
||||||
get() {
|
get() {
|
||||||
val amountStr = amount.replace(',', '.')
|
return parseAmount(amount) ?: try {
|
||||||
return try {
|
evalMath(amount.replace(',', '.'))
|
||||||
if (amountStr.matches("[0-9.]+".toRegex())) {
|
|
||||||
amountStr.toDouble()
|
|
||||||
} else {
|
|
||||||
evalMath(amountStr)
|
|
||||||
}
|
|
||||||
} catch (_: Exception) {
|
} catch (_: Exception) {
|
||||||
0.0
|
0.0
|
||||||
}
|
}
|
||||||
@@ -128,46 +126,81 @@ class EditBillViewModel : ViewModel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun convertCurrency(currency: DBCurrency) {
|
fun convertCurrency(currency: DBCurrency) {
|
||||||
val originalAmountStr = amount
|
selectedCurrencyName = currency.name ?: ""
|
||||||
val originalAmount = amountAsDouble
|
selectedCurrencyRate = currency.exchangeRate
|
||||||
if (originalAmount == 0.0) return
|
|
||||||
|
|
||||||
val newAmount = originalAmount / currency.exchangeRate
|
// We don't change 'amount' here anymore as per request.
|
||||||
amount = SupportUtil.round2(newAmount).toString()
|
// It stays as the original amount.
|
||||||
|
|
||||||
val currencyLabel = currency.name ?: ""
|
|
||||||
val conversionNote = "($originalAmountStr $currencyLabel)"
|
|
||||||
if (!comment.contains(conversionNote)) {
|
|
||||||
if (comment.isNotEmpty() && !comment.endsWith(" ")) {
|
|
||||||
comment += " "
|
|
||||||
}
|
|
||||||
comment += conversionNote
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isCustomSplit) {
|
|
||||||
owersCustomSplit.keys.toList().forEach { id ->
|
|
||||||
val value = owersCustomSplit[id] ?: ""
|
|
||||||
val partAmount = value.replace(',', '.').toDoubleOrNull() ?: 0.0
|
|
||||||
if (partAmount != 0.0) {
|
|
||||||
val newPartAmount = partAmount / currency.exchangeRate
|
|
||||||
owersCustomSplit[id] = SupportUtil.round2(newPartAmount).toString()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updateSplits()
|
updateSplits()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun resetCurrency() {
|
||||||
|
selectedCurrencyName = ""
|
||||||
|
selectedCurrencyRate = 1.0
|
||||||
|
updateSplits()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getFinalAmount(): Double {
|
||||||
|
return SupportUtil.round2(amountAsDouble / selectedCurrencyRate)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getFinalComment(): String {
|
||||||
|
var baseComment = comment
|
||||||
|
val regex = "\\s*#[^:]+:[\\d.,]+@[\\d.,]+".toRegex()
|
||||||
|
baseComment = baseComment.replace(regex, "").trim()
|
||||||
|
|
||||||
|
return if (selectedCurrencyName.isNotEmpty() && selectedCurrencyRate != 1.0) {
|
||||||
|
val metadata = "#$selectedCurrencyName:$amount@$selectedCurrencyRate"
|
||||||
|
if (baseComment.isEmpty()) metadata else "$baseComment $metadata"
|
||||||
|
} else {
|
||||||
|
baseComment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun initFromBill(bill: DBBill, members: List<DBMember>, customSplits: Map<Long, Double>? = null) {
|
fun initFromBill(bill: DBBill, members: List<DBMember>, customSplits: Map<Long, Double>? = null) {
|
||||||
this.members = members
|
this.members = members
|
||||||
what = bill.what
|
what = bill.what
|
||||||
amount = if (bill.amount == 0.0) "" else bill.amount.toString()
|
|
||||||
comment = bill.comment ?: ""
|
|
||||||
timestamp = bill.timestamp
|
timestamp = bill.timestamp
|
||||||
payerId = bill.payerId
|
payerId = bill.payerId
|
||||||
repeat = bill.repeat ?: DBBill.NON_REPEATED
|
repeat = bill.repeat ?: DBBill.NON_REPEATED
|
||||||
paymentModeRemoteId = bill.paymentModeRemoteId
|
paymentModeId = bill.paymentModeId
|
||||||
categoryRemoteId = bill.categoryRemoteId
|
categoryId = bill.categoryId
|
||||||
|
|
||||||
|
val rawComment = bill.comment ?: ""
|
||||||
|
|
||||||
|
// Try to parse existing conversion metadata #CURR:ORIG@RATE
|
||||||
|
// Using [^:]+ for currency name to support symbols and names with spaces
|
||||||
|
val regex = "#([^:]+):([\\d.,]+)@([\\d.,]+)".toRegex()
|
||||||
|
val match = regex.find(rawComment)
|
||||||
|
|
||||||
|
if (match != null) {
|
||||||
|
val (currName, origAmount, rate) = match.destructured
|
||||||
|
selectedCurrencyName = currName
|
||||||
|
selectedCurrencyRate = rate.replace(',', '.').toDoubleOrNull() ?: 1.0
|
||||||
|
amount = origAmount
|
||||||
|
comment = rawComment.replace(match.value, "").trim()
|
||||||
|
|
||||||
|
// Check if latest rate is different
|
||||||
|
val latestCurrency = currencies.find { it.name == selectedCurrencyName }
|
||||||
|
if (latestCurrency != null && latestCurrency.exchangeRate != selectedCurrencyRate) {
|
||||||
|
showDialog(
|
||||||
|
title = "Update Exchange Rate?",
|
||||||
|
message = "The exchange rate for $selectedCurrencyName has changed from $selectedCurrencyRate to ${latestCurrency.exchangeRate}. Do you want to update the conversion for the saved total?",
|
||||||
|
positiveText = "Update Rate",
|
||||||
|
negativeText = "Keep Old",
|
||||||
|
onConfirm = {
|
||||||
|
selectedCurrencyRate = latestCurrency.exchangeRate
|
||||||
|
updateSplits()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
selectedCurrencyName = ""
|
||||||
|
selectedCurrencyRate = 1.0
|
||||||
|
amount = if (bill.amount == 0.0) "" else bill.amount.toString()
|
||||||
|
comment = rawComment
|
||||||
|
}
|
||||||
|
|
||||||
owersSelection.clear()
|
owersSelection.clear()
|
||||||
owersCustomSplit.clear()
|
owersCustomSplit.clear()
|
||||||
@@ -178,13 +211,23 @@ class EditBillViewModel : ViewModel() {
|
|||||||
val selected = customSplits.containsKey(member.id)
|
val selected = customSplits.containsKey(member.id)
|
||||||
owersSelection[member.id] = selected
|
owersSelection[member.id] = selected
|
||||||
if (selected) {
|
if (selected) {
|
||||||
owersCustomSplit[member.id] = SupportUtil.round2(customSplits[member.id]!!).toString()
|
// If we have metadata, the custom splits from DB are also converted.
|
||||||
|
// We should show them as "Original" if possible?
|
||||||
|
// Actually, if we use metadata, we should probably store original splits too,
|
||||||
|
// but for now let's just reverse the rate for display.
|
||||||
|
val dbPart = customSplits[member.id]!!
|
||||||
|
val uiPart = if (selectedCurrencyRate != 1.0) dbPart * selectedCurrencyRate else dbPart
|
||||||
|
owersCustomSplit[member.id] = SupportUtil.round2(uiPart).toString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// Even split logic
|
||||||
val billOwerIds = bill.billOwersIds
|
val billOwerIds = bill.billOwersIds
|
||||||
val selectedCount = billOwerIds.size
|
val selectedCount = billOwerIds.size
|
||||||
val evenSplit = if (selectedCount > 0) bill.amount / selectedCount else 0.0
|
|
||||||
|
// Use UI amount for even split calculation
|
||||||
|
val uiAmount = amountAsDouble
|
||||||
|
val evenSplit = if (selectedCount > 0) uiAmount / selectedCount else 0.0
|
||||||
val evenSplitStr = if (evenSplit == 0.0) "" else SupportUtil.round2(evenSplit).toString()
|
val evenSplitStr = if (evenSplit == 0.0) "" else SupportUtil.round2(evenSplit).toString()
|
||||||
|
|
||||||
for (member in members) {
|
for (member in members) {
|
||||||
@@ -198,7 +241,7 @@ class EditBillViewModel : ViewModel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun isFormValid(): Boolean {
|
fun isFormValid(): Boolean {
|
||||||
return what.isNotEmpty() && !what.contains(",") &&
|
return what.isNotEmpty() &&
|
||||||
timestamp != 0L &&
|
timestamp != 0L &&
|
||||||
payerId != 0L &&
|
payerId != 0L &&
|
||||||
owersSelection.any { it.value }
|
owersSelection.any { it.value }
|
||||||
@@ -212,7 +255,7 @@ class EditBillViewModel : ViewModel() {
|
|||||||
errorInvalidForm: String
|
errorInvalidForm: String
|
||||||
): String? {
|
): String? {
|
||||||
return when {
|
return when {
|
||||||
what.isEmpty() || what.contains(",") -> errorWhat
|
what.isEmpty() -> errorWhat
|
||||||
timestamp == 0L -> errorDate
|
timestamp == 0L -> errorDate
|
||||||
payerId == 0L -> errorPayer
|
payerId == 0L -> errorPayer
|
||||||
owersSelection.none { it.value } -> errorOwers
|
owersSelection.none { it.value } -> errorOwers
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package net.helcel.cowspent.android.bill_label
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
@@ -12,7 +13,6 @@ import kotlinx.coroutines.launch
|
|||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import net.helcel.cowspent.persistence.CowspentSQLiteOpenHelper
|
import net.helcel.cowspent.persistence.CowspentSQLiteOpenHelper
|
||||||
import net.helcel.cowspent.theme.ThemeUtils
|
import net.helcel.cowspent.theme.ThemeUtils
|
||||||
import net.helcel.cowspent.util.CategoryUtils
|
|
||||||
import net.helcel.cowspent.model.DBBill
|
import net.helcel.cowspent.model.DBBill
|
||||||
import net.helcel.cowspent.model.ProjectType
|
import net.helcel.cowspent.model.ProjectType
|
||||||
|
|
||||||
@@ -20,6 +20,7 @@ class LabelBillsActivity : AppCompatActivity() {
|
|||||||
private val viewModel: LabelBillsViewModel by viewModels()
|
private val viewModel: LabelBillsViewModel by viewModels()
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
enableEdgeToEdge()
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
val projectId = intent.getLongExtra(EXTRA_PROJECT_ID, -1L)
|
val projectId = intent.getLongExtra(EXTRA_PROJECT_ID, -1L)
|
||||||
@@ -37,24 +38,20 @@ class LabelBillsActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
val members = db.getMembersOfProject(projectId, null)
|
val members = db.getMembersOfProject(projectId, null)
|
||||||
val allBills = db.getBillsOfProject(projectId)
|
val allBills = db.getBillsOfProject(projectId)
|
||||||
val billsToLabel = allBills.filter { it.categoryRemoteId == 0 && it.state != DBBill.STATE_DELETED }
|
val billsToLabel = allBills.filter { it.categoryId == 0L && it.state != DBBill.STATE_DELETED }
|
||||||
val allCategorized = allBills.filter { it.categoryRemoteId != 0 && it.state != DBBill.STATE_DELETED }
|
val allCategorized = allBills.filter { it.categoryId != 0L && it.state != DBBill.STATE_DELETED }
|
||||||
|
|
||||||
val syncedCategories = db.getCategories(projectId)
|
db.ensureDefaultLabels(projectId, projectType)
|
||||||
val defaultCategories = CategoryUtils.getDefaultCategories(this@LabelBillsActivity, projectId)
|
|
||||||
val hardcoded = if (projectType == ProjectType.LOCAL) {
|
// Reload from DB to get real IDs
|
||||||
defaultCategories
|
val categories = db.getCategories(projectId)
|
||||||
} else {
|
|
||||||
listOfNotNull(defaultCategories.find { it.remoteId.toInt() == DBBill.CATEGORY_REIMBURSEMENT })
|
|
||||||
}
|
|
||||||
val categories = syncedCategories + hardcoded
|
|
||||||
|
|
||||||
Quadruple(members, billsToLabel, categories, allCategorized)
|
Quadruple(members, billsToLabel, categories, allCategorized)
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModel.billsToLabel = billsToLabel
|
viewModel.billsToLabel = billsToLabel
|
||||||
viewModel.categories = categories
|
viewModel.categories = categories
|
||||||
viewModel.categoriesMap = categories.associateBy { it.remoteId }
|
viewModel.categoriesMap = categories.associateBy { it.id }
|
||||||
viewModel.allCategorizedBills = allCategorized
|
viewModel.allCategorizedBills = allCategorized
|
||||||
viewModel.updateSuggestions()
|
viewModel.updateSuggestions()
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ fun LabelBillsScreen(
|
|||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = { Text(stringResource(R.string.label_bills_title)) },
|
title = { Text(stringResource(R.string.title_label_bills)) },
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
IconButton(onClick = onBack) {
|
IconButton(onClick = onBack) {
|
||||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)
|
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)
|
||||||
@@ -64,10 +64,11 @@ fun LabelBillsScreen(
|
|||||||
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.label_bills_suggested),
|
text = stringResource(R.string.label_bills_suggested).uppercase(),
|
||||||
style = MaterialTheme.typography.subtitle1,
|
style = MaterialTheme.typography.subtitle1,
|
||||||
|
color = MaterialTheme.colors.onSurface,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth().padding(top = 8.dp)
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
@@ -83,7 +84,7 @@ fun LabelBillsScreen(
|
|||||||
CategoryButton(
|
CategoryButton(
|
||||||
icon = category.icon,
|
icon = category.icon,
|
||||||
name = category.name ?: "",
|
name = category.name ?: "",
|
||||||
onClick = { viewModel.labelCurrentBill(db, category.remoteId.toInt()) }
|
onClick = { viewModel.labelCurrentBill(db, category.id) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -93,7 +94,7 @@ fun LabelBillsScreen(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.label_bills_no_suggestions),
|
text = stringResource(R.string.msg_no_suggestions),
|
||||||
style = MaterialTheme.typography.caption,
|
style = MaterialTheme.typography.caption,
|
||||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.4f),
|
color = MaterialTheme.colors.onSurface.copy(alpha = 0.4f),
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
@@ -105,10 +106,11 @@ fun LabelBillsScreen(
|
|||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.setting_category),
|
text = stringResource(R.string.label_category).uppercase(),
|
||||||
style = MaterialTheme.typography.subtitle1,
|
style = MaterialTheme.typography.subtitle1,
|
||||||
|
color = MaterialTheme.colors.onSurface,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth().padding(top = 8.dp)
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
@@ -124,7 +126,7 @@ fun LabelBillsScreen(
|
|||||||
CategoryButton(
|
CategoryButton(
|
||||||
icon = category.icon,
|
icon = category.icon,
|
||||||
name = category.name ?: "",
|
name = category.name ?: "",
|
||||||
onClick = { viewModel.labelCurrentBill(db, category.remoteId.toInt()) }
|
onClick = { viewModel.labelCurrentBill(db, category.id) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -141,7 +143,7 @@ fun LabelBillsScreen(
|
|||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
} else {
|
} else {
|
||||||
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||||
Text(stringResource(R.string.label_bills_no_more))
|
Text(stringResource(R.string.msg_bill_labeled_done))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -243,16 +245,16 @@ fun CategoryButton(icon: String, name: String, onClick: () -> Unit) {
|
|||||||
fun LabelBillsScreenPreview() {
|
fun LabelBillsScreenPreview() {
|
||||||
val viewModel = LabelBillsViewModel().apply {
|
val viewModel = LabelBillsViewModel().apply {
|
||||||
billsToLabel = listOf(
|
billsToLabel = listOf(
|
||||||
DBBill(1L, 0, 1L, 1L, 120.5, System.currentTimeMillis() / 1000, "Groceries at Aldi", 0, null, null, 0, null, -1)
|
DBBill(1L, 0, 1L, 1L, 120.5, System.currentTimeMillis() / 1000, "Groceries at Aldi", 0, null, null, 0L, null, -1L)
|
||||||
)
|
)
|
||||||
val cats = listOf(
|
val cats = listOf(
|
||||||
DBCategory(1, 1, 1, "Groceries", "🛒", ""),
|
DBCategory(1L, 1L, 1L, "Groceries", "🛒", ""),
|
||||||
DBCategory(2, 2, 1, "Leisure", "🥳", ""),
|
DBCategory(2L, 2L, 1L, "Leisure", "🥳", ""),
|
||||||
DBCategory(3, 3, 1, "Rent", "🏠", ""),
|
DBCategory(3L, 3L, 1L, "Rent", "🏠", ""),
|
||||||
DBCategory(4, 4, 1, "Bills", "💸", "")
|
DBCategory(4L, 4L, 1L, "Bills", "💸", "")
|
||||||
)
|
)
|
||||||
categories = cats
|
categories = cats
|
||||||
categoriesMap = cats.associateBy { it.remoteId }
|
categoriesMap = cats.associateBy { it.id }
|
||||||
}
|
}
|
||||||
val members = listOf(
|
val members = listOf(
|
||||||
DBMember(1L, 0, 1L, "Alice", true, 1.0, 0, 255, 100, 100, null, null),
|
DBMember(1L, 0, 1L, "Alice", true, 1.0, 0, 255, 100, 100, null, null),
|
||||||
|
|||||||
@@ -43,18 +43,18 @@ class LabelBillsViewModel : ViewModel() {
|
|||||||
otherName == name || (name.length > 3 && otherName.contains(name)) || (otherName.length > 3 && name.contains(otherName))
|
otherName == name || (name.length > 3 && otherName.contains(name)) || (otherName.length > 3 && name.contains(otherName))
|
||||||
}
|
}
|
||||||
|
|
||||||
val counts = matches.groupBy { it.categoryRemoteId }
|
val counts = matches.groupBy { it.categoryId }
|
||||||
.mapValues { it.value.size }
|
.mapValues { it.value.size }
|
||||||
.toList()
|
.toList()
|
||||||
.sortedByDescending { it.second }
|
.sortedByDescending { it.second }
|
||||||
.take(2)
|
.take(2)
|
||||||
|
|
||||||
suggestedCategories = counts.mapNotNull { (catId, _) ->
|
suggestedCategories = counts.mapNotNull { (catId, _) ->
|
||||||
categoriesMap[catId.toLong()]
|
categoriesMap[catId]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun labelCurrentBill(db: CowspentSQLiteOpenHelper, categoryId: Int) {
|
fun labelCurrentBill(db: CowspentSQLiteOpenHelper, categoryId: Long) {
|
||||||
currentBill?.let { bill ->
|
currentBill?.let { bill ->
|
||||||
db.updateBillAndSync(
|
db.updateBillAndSync(
|
||||||
bill = bill,
|
bill = bill,
|
||||||
@@ -65,11 +65,11 @@ class LabelBillsViewModel : ViewModel() {
|
|||||||
newOwersIds = bill.billOwersIds,
|
newOwersIds = bill.billOwersIds,
|
||||||
newRepeat = bill.repeat,
|
newRepeat = bill.repeat,
|
||||||
newPaymentMode = bill.paymentMode,
|
newPaymentMode = bill.paymentMode,
|
||||||
newPaymentModeRemoteId = bill.paymentModeRemoteId,
|
newPaymentModeId = bill.paymentModeId,
|
||||||
newCategoryId = categoryId,
|
newCategoryId = categoryId,
|
||||||
newComment = bill.comment
|
newComment = bill.comment
|
||||||
)
|
)
|
||||||
bill.categoryRemoteId = categoryId
|
bill.categoryId = categoryId
|
||||||
onBillProcessed?.invoke(bill.id)
|
onBillProcessed?.invoke(bill.id)
|
||||||
moveToNext()
|
moveToNext()
|
||||||
}
|
}
|
||||||
@@ -87,11 +87,11 @@ class LabelBillsViewModel : ViewModel() {
|
|||||||
|
|
||||||
val start = currentBillIndex
|
val start = currentBillIndex
|
||||||
var next = (start + 1) % billsToLabel.size
|
var next = (start + 1) % billsToLabel.size
|
||||||
while (next != start && billsToLabel[next].categoryRemoteId != 0) {
|
while (next != start && billsToLabel[next].categoryId != 0L) {
|
||||||
next = (next + 1) % billsToLabel.size
|
next = (next + 1) % billsToLabel.size
|
||||||
}
|
}
|
||||||
|
|
||||||
currentBillIndex = if (billsToLabel[next].categoryRemoteId == 0) {
|
currentBillIndex = if (billsToLabel[next].categoryId == 0L) {
|
||||||
next
|
next
|
||||||
} else {
|
} else {
|
||||||
billsToLabel.size
|
billsToLabel.size
|
||||||
|
|||||||
+53
-18
@@ -3,6 +3,7 @@ package net.helcel.cowspent.android.currencies
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
@@ -14,6 +15,7 @@ import net.helcel.cowspent.R
|
|||||||
import net.helcel.cowspent.android.helper.showToast
|
import net.helcel.cowspent.android.helper.showToast
|
||||||
import net.helcel.cowspent.model.DBBill
|
import net.helcel.cowspent.model.DBBill
|
||||||
import net.helcel.cowspent.model.DBCurrency
|
import net.helcel.cowspent.model.DBCurrency
|
||||||
|
import net.helcel.cowspent.model.ProjectType
|
||||||
import net.helcel.cowspent.persistence.CowspentSQLiteOpenHelper
|
import net.helcel.cowspent.persistence.CowspentSQLiteOpenHelper
|
||||||
import net.helcel.cowspent.theme.ThemeUtils
|
import net.helcel.cowspent.theme.ThemeUtils
|
||||||
import net.helcel.cowspent.util.ICallback
|
import net.helcel.cowspent.util.ICallback
|
||||||
@@ -31,15 +33,18 @@ class ManageCurrenciesActivity : AppCompatActivity() {
|
|||||||
if (message.isEmpty()) {
|
if (message.isEmpty()) {
|
||||||
showToast(this@ManageCurrenciesActivity,getString(R.string.currency_saved_success), Toast.LENGTH_LONG)
|
showToast(this@ManageCurrenciesActivity,getString(R.string.currency_saved_success), Toast.LENGTH_LONG)
|
||||||
} else {
|
} else {
|
||||||
viewModel.showDialog(title=getString(R.string.error_edit_remote_project_helper, message),
|
viewModel.showDialog(
|
||||||
message=getString(R.string.currency_manager),
|
title = getString(R.string.dialog_sync_error_title),
|
||||||
positiveText = getString(android.R.string.ok))
|
message = getString(R.string.error_edit_remote_project_helper, message),
|
||||||
|
positiveText = getString(android.R.string.ok)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
override fun onScheduled() {}
|
override fun onScheduled() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
enableEdgeToEdge()
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
intent.extras?.let {
|
intent.extras?.let {
|
||||||
@@ -64,8 +69,10 @@ class ManageCurrenciesActivity : AppCompatActivity() {
|
|||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
onBack = { finish() },
|
onBack = { finish() },
|
||||||
onSaveMain = { saveMainCurrency() },
|
onSaveMain = { saveMainCurrency() },
|
||||||
onAdd = { addCurrency() },
|
onAdd = { addOrUpdateCurrency() },
|
||||||
onDelete = { deleteCurrency(it) }
|
onDelete = { deleteCurrency(it) },
|
||||||
|
onEdit = { startEditing(it) },
|
||||||
|
onCancelEdit = { cancelEditing() }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -84,11 +91,17 @@ class ManageCurrenciesActivity : AppCompatActivity() {
|
|||||||
val project = db!!.getProject(selectedProjectID)
|
val project = db!!.getProject(selectedProjectID)
|
||||||
if (project != null) {
|
if (project != null) {
|
||||||
db!!.syncIfRemote(project)
|
db!!.syncIfRemote(project)
|
||||||
withContext(Dispatchers.Main) {
|
if (project.type == ProjectType.COSPEND) {
|
||||||
if (!db!!.cowspentServerSyncHelper
|
withContext(Dispatchers.Main) {
|
||||||
.editRemoteProject(selectedProjectID, project.name, null, null, newMainCurrencyName, editMainCurrencyCallBack)
|
if (!db!!.cowspentServerSyncHelper
|
||||||
) {
|
.editRemoteProject(selectedProjectID, project.name, null, null, newMainCurrencyName, 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
showToast(this@ManageCurrenciesActivity, getString(R.string.currency_saved_success), Toast.LENGTH_LONG)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -96,22 +109,44 @@ class ManageCurrenciesActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addCurrency() {
|
private fun addOrUpdateCurrency() {
|
||||||
val exchangeRate = try { viewModel.newCurrencyRate.toDouble() } catch (_: Exception) { 0.0 }
|
val exchangeRate = try { viewModel.newCurrencyRate.toDouble() } catch (_: Exception) { 0.0 }
|
||||||
val newCurrency = DBCurrency(
|
val currencyName = viewModel.newCurrencyName
|
||||||
0, 0, selectedProjectID,
|
val editingId = viewModel.editingCurrencyId
|
||||||
viewModel.newCurrencyName, exchangeRate, DBBill.STATE_ADDED
|
|
||||||
)
|
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
db!!.addCurrencyAndSync(newCurrency)
|
if (editingId != null) {
|
||||||
|
db!!.updateCurrency(editingId, currencyName, exchangeRate)
|
||||||
|
val currency = db!!.getCurrency(editingId)
|
||||||
|
if (currency != null) {
|
||||||
|
db!!.setCurrencyStateSync(editingId, DBBill.STATE_EDITED)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val newCurrency = DBCurrency(
|
||||||
|
0, 0, selectedProjectID,
|
||||||
|
currencyName, exchangeRate, DBBill.STATE_ADDED
|
||||||
|
)
|
||||||
|
db!!.addCurrencyAndSync(newCurrency)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
viewModel.newCurrencyName = ""
|
cancelEditing()
|
||||||
viewModel.newCurrencyRate = ""
|
|
||||||
updateCurrenciesList()
|
updateCurrenciesList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun startEditing(currency: DBCurrency) {
|
||||||
|
viewModel.editingCurrencyId = currency.id
|
||||||
|
viewModel.newCurrencyName = currency.name ?: ""
|
||||||
|
viewModel.newCurrencyRate = currency.exchangeRate.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun cancelEditing() {
|
||||||
|
viewModel.editingCurrencyId = null
|
||||||
|
viewModel.newCurrencyName = ""
|
||||||
|
viewModel.newCurrencyRate = ""
|
||||||
|
}
|
||||||
|
|
||||||
private fun deleteCurrency(currency: DBCurrency) {
|
private fun deleteCurrency(currency: DBCurrency) {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
|
|||||||
+244
-55
@@ -1,22 +1,57 @@
|
|||||||
package net.helcel.cowspent.android.currencies
|
package net.helcel.cowspent.android.currencies
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.BorderStroke
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.imePadding
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.material.*
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.Card
|
||||||
|
import androidx.compose.material.Icon
|
||||||
|
import androidx.compose.material.IconButton
|
||||||
|
import androidx.compose.material.LocalTextStyle
|
||||||
|
import androidx.compose.material.MaterialTheme
|
||||||
|
import androidx.compose.material.OutlinedTextField
|
||||||
|
import androidx.compose.material.Scaffold
|
||||||
|
import androidx.compose.material.Surface
|
||||||
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.material.TextFieldDefaults
|
||||||
|
import androidx.compose.material.TopAppBar
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
|
import androidx.compose.material.icons.filled.Add
|
||||||
|
import androidx.compose.material.icons.filled.Close
|
||||||
import androidx.compose.material.icons.filled.Delete
|
import androidx.compose.material.icons.filled.Delete
|
||||||
|
import androidx.compose.material.icons.filled.Done
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import net.helcel.cowspent.R
|
import net.helcel.cowspent.R
|
||||||
import net.helcel.cowspent.android.helper.AlertDialog
|
import net.helcel.cowspent.android.helper.AlertDialog
|
||||||
|
import net.helcel.cowspent.android.helper.formatAmount
|
||||||
import net.helcel.cowspent.model.DBCurrency
|
import net.helcel.cowspent.model.DBCurrency
|
||||||
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ManageCurrenciesScreen(
|
fun ManageCurrenciesScreen(
|
||||||
@@ -24,7 +59,9 @@ fun ManageCurrenciesScreen(
|
|||||||
onBack: () -> Unit,
|
onBack: () -> Unit,
|
||||||
onSaveMain: () -> Unit,
|
onSaveMain: () -> Unit,
|
||||||
onAdd: () -> Unit,
|
onAdd: () -> Unit,
|
||||||
onDelete: (DBCurrency) -> Unit
|
onDelete: (DBCurrency) -> Unit,
|
||||||
|
onEdit: (DBCurrency) -> Unit,
|
||||||
|
onCancelEdit: () -> Unit
|
||||||
) {
|
) {
|
||||||
val dialogState = viewModel.dialogState
|
val dialogState = viewModel.dialogState
|
||||||
if (dialogState != null) {
|
if (dialogState != null) {
|
||||||
@@ -59,7 +96,7 @@ fun ManageCurrenciesScreen(
|
|||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = { Text(stringResource(R.string.currency_manager)) },
|
title = { Text(stringResource(R.string.action_currencies)) },
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
IconButton(onClick = onBack) {
|
IconButton(onClick = onBack) {
|
||||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)
|
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)
|
||||||
@@ -73,55 +110,152 @@ fun ManageCurrenciesScreen(
|
|||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(padding)
|
.padding(padding)
|
||||||
.padding(16.dp)
|
.imePadding()
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
|
.background(MaterialTheme.colors.onSurface.copy(alpha = 0.02f))
|
||||||
) {
|
) {
|
||||||
Text(stringResource(R.string.main_currency), style = MaterialTheme.typography.h6)
|
LaunchedEffect(viewModel.mainCurrencyName) {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
if (viewModel.mainCurrencyName.isNotEmpty()) {
|
||||||
OutlinedTextField(
|
delay(500.milliseconds)
|
||||||
value = viewModel.mainCurrencyName,
|
onSaveMain()
|
||||||
onValueChange = { viewModel.mainCurrencyName = it },
|
|
||||||
label = { Text(stringResource(R.string.currency_edit_name)) },
|
|
||||||
modifier = Modifier.weight(1f)
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
|
||||||
Button(onClick = onSaveMain, enabled = viewModel.mainCurrencyName.isNotEmpty()) {
|
|
||||||
Text(stringResource(R.string.save_or_discard_bill_dialog_save))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(24.dp))
|
Column(modifier = Modifier.padding(16.dp)) {
|
||||||
|
// Section 1: Main Currency
|
||||||
Text(stringResource(R.string.add_currency_title), style = MaterialTheme.typography.h6)
|
Text(
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
text = stringResource(R.string.main_currency).uppercase(),
|
||||||
OutlinedTextField(
|
style = MaterialTheme.typography.subtitle1,
|
||||||
value = viewModel.newCurrencyName,
|
color = MaterialTheme.colors.onSurface,
|
||||||
onValueChange = { viewModel.newCurrencyName = it },
|
fontWeight = FontWeight.Bold,
|
||||||
label = { Text(stringResource(R.string.currency_edit_name)) },
|
modifier = Modifier.padding(bottom = 8.dp)
|
||||||
modifier = Modifier.weight(1f)
|
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
Card(
|
||||||
OutlinedTextField(
|
shape = RoundedCornerShape(12.dp),
|
||||||
value = viewModel.newCurrencyRate,
|
elevation = 2.dp,
|
||||||
onValueChange = { viewModel.newCurrencyRate = it },
|
modifier = Modifier.fillMaxWidth()
|
||||||
label = { Text(stringResource(R.string.currency_rate)) },
|
) {
|
||||||
modifier = Modifier.weight(1f)
|
OutlinedTextField(
|
||||||
|
value = viewModel.mainCurrencyName,
|
||||||
|
onValueChange = { viewModel.mainCurrencyName = it },
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
placeholder = { Text("e.g. EUR", fontSize = 14.sp) },
|
||||||
|
colors = TextFieldDefaults.outlinedTextFieldColors(
|
||||||
|
focusedBorderColor = Color.Transparent,
|
||||||
|
unfocusedBorderColor = Color.Transparent
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
|
||||||
|
// Section 2: Exchange Rates
|
||||||
|
val isEditing = viewModel.editingCurrencyId != null
|
||||||
|
Text(
|
||||||
|
text = (if (isEditing) "Edit exchange rate" else "Add exchange rate").uppercase(),
|
||||||
|
style = MaterialTheme.typography.subtitle1,
|
||||||
|
color = MaterialTheme.colors.onSurface,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
modifier = Modifier.padding(bottom = 8.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Integrated Add/Edit Ribbon
|
||||||
|
Surface(
|
||||||
|
color = MaterialTheme.colors.surface,
|
||||||
|
shape = RoundedCornerShape(12.dp),
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
elevation = 2.dp
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.padding(horizontal = 12.dp, vertical = 6.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Text("1 ", style = MaterialTheme.typography.body2, color = MaterialTheme.colors.onSurface.copy(alpha = 0.6f))
|
||||||
|
Text(viewModel.mainCurrencyName.ifEmpty { "$" }, fontWeight = FontWeight.Bold)
|
||||||
|
Text(
|
||||||
|
text = " = ",
|
||||||
|
style = MaterialTheme.typography.h6,
|
||||||
|
color = if (isEditing) MaterialTheme.colors.secondary else MaterialTheme.colors.primary,
|
||||||
|
modifier = Modifier.padding(horizontal = 4.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
OutlinedTextField(
|
||||||
|
value = viewModel.newCurrencyRate,
|
||||||
|
onValueChange = { viewModel.newCurrencyRate = it },
|
||||||
|
modifier = Modifier.weight(1.2f).height(52.dp),
|
||||||
|
placeholder = { Text("1", fontSize = 12.sp) },
|
||||||
|
singleLine = true,
|
||||||
|
textStyle = LocalTextStyle.current.copy(fontWeight = FontWeight.ExtraBold, fontSize = 14.sp),
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
colors = TextFieldDefaults.outlinedTextFieldColors(
|
||||||
|
focusedBorderColor = if (isEditing) MaterialTheme.colors.secondary else MaterialTheme.colors.primary,
|
||||||
|
unfocusedBorderColor = MaterialTheme.colors.onSurface.copy(alpha = 0.12f),
|
||||||
|
backgroundColor = MaterialTheme.colors.onSurface.copy(alpha = 0.02f)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
OutlinedTextField(
|
||||||
|
value = viewModel.newCurrencyName,
|
||||||
|
onValueChange = { viewModel.newCurrencyName = it },
|
||||||
|
modifier = Modifier.weight(1f).height(52.dp),
|
||||||
|
placeholder = { Text(viewModel.mainCurrencyName.ifEmpty { "$" }, fontSize = 12.sp) },
|
||||||
|
singleLine = true,
|
||||||
|
textStyle = LocalTextStyle.current.copy(fontWeight = FontWeight.Bold, fontSize = 14.sp),
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
colors = TextFieldDefaults.outlinedTextFieldColors(
|
||||||
|
focusedBorderColor = if (isEditing) MaterialTheme.colors.secondary else MaterialTheme.colors.primary,
|
||||||
|
unfocusedBorderColor = MaterialTheme.colors.onSurface.copy(alpha = 0.12f),
|
||||||
|
backgroundColor = MaterialTheme.colors.onSurface.copy(alpha = 0.02f)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
Row(modifier = Modifier.padding(start = 4.dp)) {
|
||||||
|
IconButton(
|
||||||
|
onClick = onAdd,
|
||||||
|
enabled = viewModel.isAddEnabled()
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = if (isEditing) Icons.Default.Done else Icons.Default.Add,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = if (viewModel.isAddEnabled()) {
|
||||||
|
if (isEditing) MaterialTheme.colors.secondary else MaterialTheme.colors.primary
|
||||||
|
} else MaterialTheme.colors.onSurface.copy(alpha = 0.2f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isEditing) {
|
||||||
|
IconButton(onClick = onCancelEdit) {
|
||||||
|
Icon(Icons.Default.Close, contentDescription = null, tint = MaterialTheme.colors.error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "SAVED RATES",
|
||||||
|
style = MaterialTheme.typography.subtitle1,
|
||||||
|
color = MaterialTheme.colors.onSurface,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
modifier = Modifier.padding(bottom = 8.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
|
||||||
Button(
|
LazyColumn(
|
||||||
onClick = onAdd,
|
modifier = Modifier.weight(1f).fillMaxWidth(),
|
||||||
modifier = Modifier.fillMaxWidth(),
|
contentPadding = PaddingValues(16.dp, 0.dp, 16.dp, 16.dp),
|
||||||
enabled = viewModel.isAddEnabled()
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
) {
|
) {
|
||||||
Text(stringResource(R.string.simple_add))
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(24.dp))
|
|
||||||
|
|
||||||
LazyColumn(modifier = Modifier.weight(1f)) {
|
|
||||||
items(viewModel.currencies) { currency ->
|
items(viewModel.currencies) { currency ->
|
||||||
CurrencyRow(currency, onDelete = { onDelete(currency) })
|
CurrencyRow(
|
||||||
|
currency = currency,
|
||||||
|
mainCurrencyName = viewModel.mainCurrencyName,
|
||||||
|
isEditing = viewModel.editingCurrencyId == currency.id,
|
||||||
|
onEdit = { onEdit(currency) },
|
||||||
|
onDelete = { onDelete(currency) }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -129,20 +263,70 @@ fun ManageCurrenciesScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun CurrencyRow(currency: DBCurrency, onDelete: () -> Unit) {
|
fun CurrencyRow(
|
||||||
Row(
|
currency: DBCurrency,
|
||||||
modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp),
|
mainCurrencyName: String,
|
||||||
verticalAlignment = Alignment.CenterVertically
|
isEditing: Boolean,
|
||||||
|
onEdit: () -> Unit,
|
||||||
|
onDelete: () -> Unit
|
||||||
|
) {
|
||||||
|
Card(
|
||||||
|
shape = RoundedCornerShape(12.dp),
|
||||||
|
elevation = if (isEditing) 4.dp else 1.dp,
|
||||||
|
border = if (isEditing) BorderStroke(1.dp, MaterialTheme.colors.secondary.copy(alpha = 0.5f)) else null,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
) {
|
) {
|
||||||
Column(modifier = Modifier.weight(1f)) {
|
Row(
|
||||||
Text(currency.name ?: "", style = MaterialTheme.typography.subtitle1)
|
modifier = Modifier
|
||||||
Text("Rate: ${currency.exchangeRate}", style = MaterialTheme.typography.caption)
|
.fillMaxWidth()
|
||||||
}
|
.clickable(onClick = onEdit)
|
||||||
IconButton(onClick = onDelete) {
|
.padding(16.dp, 12.dp),
|
||||||
Icon(Icons.Default.Delete, contentDescription = null, tint = MaterialTheme.colors.error)
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "1 ",
|
||||||
|
style = MaterialTheme.typography.body2,
|
||||||
|
color = MaterialTheme.colors.onSurface.copy(alpha = 0.6f)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = mainCurrencyName.ifEmpty { "$" },
|
||||||
|
style = MaterialTheme.typography.body1,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = " = ",
|
||||||
|
style = MaterialTheme.typography.h6,
|
||||||
|
color = if (isEditing) MaterialTheme.colors.secondary else MaterialTheme.colors.primary,
|
||||||
|
modifier = Modifier.padding(horizontal = 8.dp)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = formatAmount(currency.exchangeRate),
|
||||||
|
style = MaterialTheme.typography.body1,
|
||||||
|
fontWeight = FontWeight.ExtraBold,
|
||||||
|
color = if (isEditing) MaterialTheme.colors.secondary else MaterialTheme.colors.onSurface
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = " ${currency.name ?: ""}",
|
||||||
|
style = MaterialTheme.typography.body1,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = MaterialTheme.colors.onSurface.copy(alpha = 0.6f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
IconButton(onClick = onDelete) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Delete,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = MaterialTheme.colors.error.copy(alpha = 0.7f),
|
||||||
|
modifier = Modifier.size(20.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Divider()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Preview(showBackground = true)
|
@Preview(showBackground = true)
|
||||||
@@ -151,6 +335,9 @@ fun CurrencyRowPreview() {
|
|||||||
MaterialTheme {
|
MaterialTheme {
|
||||||
CurrencyRow(
|
CurrencyRow(
|
||||||
currency = DBCurrency(1, 0, 0, "USD", 1.0, 0),
|
currency = DBCurrency(1, 0, 0, "USD", 1.0, 0),
|
||||||
|
mainCurrencyName = "EUR",
|
||||||
|
isEditing = false,
|
||||||
|
onEdit = {},
|
||||||
onDelete = {}
|
onDelete = {}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -172,7 +359,9 @@ fun ManageCurrenciesScreenPreview() {
|
|||||||
onBack = {},
|
onBack = {},
|
||||||
onSaveMain = {},
|
onSaveMain = {},
|
||||||
onAdd = {},
|
onAdd = {},
|
||||||
onDelete = {}
|
onDelete = {},
|
||||||
|
onEdit = {},
|
||||||
|
onCancelEdit = {}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ class ManageCurrenciesViewModel : ViewModel() {
|
|||||||
var newCurrencyName by mutableStateOf("")
|
var newCurrencyName by mutableStateOf("")
|
||||||
var newCurrencyRate by mutableStateOf("")
|
var newCurrencyRate by mutableStateOf("")
|
||||||
|
|
||||||
|
var editingCurrencyId by mutableStateOf<Long?>(null)
|
||||||
|
|
||||||
var currencies by mutableStateOf<List<DBCurrency>>(emptyList())
|
var currencies by mutableStateOf<List<DBCurrency>>(emptyList())
|
||||||
|
|
||||||
var dialogState by mutableStateOf<DialogState?>(null)
|
var dialogState by mutableStateOf<DialogState?>(null)
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import androidx.compose.material.*
|
|||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.*
|
import androidx.compose.material.icons.filled.*
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
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.scale
|
import androidx.compose.ui.draw.scale
|
||||||
@@ -23,9 +24,11 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import net.helcel.cowspent.R
|
import net.helcel.cowspent.R
|
||||||
import net.helcel.cowspent.android.helper.UserAvatar
|
import net.helcel.cowspent.android.helper.MemberAvatar
|
||||||
import net.helcel.cowspent.android.helper.formatBalance
|
import net.helcel.cowspent.android.helper.formatBalance
|
||||||
import net.helcel.cowspent.android.helper.lazyVerticalScrollbar
|
import net.helcel.cowspent.android.helper.lazyVerticalScrollbar
|
||||||
|
import net.helcel.cowspent.android.helper.TextIcon
|
||||||
|
import net.helcel.cowspent.android.helper.TextIconDisplay
|
||||||
import net.helcel.cowspent.model.DBMember
|
import net.helcel.cowspent.model.DBMember
|
||||||
import net.helcel.cowspent.model.DBProject
|
import net.helcel.cowspent.model.DBProject
|
||||||
import net.helcel.cowspent.model.ProjectType
|
import net.helcel.cowspent.model.ProjectType
|
||||||
@@ -38,6 +41,7 @@ fun Drawer(
|
|||||||
selectedProjectId: Long,
|
selectedProjectId: Long,
|
||||||
selectedMemberId: Long?,
|
selectedMemberId: Long?,
|
||||||
lastSyncText: String,
|
lastSyncText: String,
|
||||||
|
mainCurrency: String? = null,
|
||||||
showArchived: Boolean = false,
|
showArchived: Boolean = false,
|
||||||
onProjectClick: (Long) -> Unit,
|
onProjectClick: (Long) -> Unit,
|
||||||
onProjectOptionsClick: (Long) -> Unit,
|
onProjectOptionsClick: (Long) -> Unit,
|
||||||
@@ -96,22 +100,40 @@ fun Drawer(
|
|||||||
|
|
||||||
// Members Section
|
// Members Section
|
||||||
val membersState = rememberLazyListState()
|
val membersState = rememberLazyListState()
|
||||||
|
val sortedMembers = remember(members, memberBalances, selectedMemberId) {
|
||||||
|
members.filter {
|
||||||
|
val balance = memberBalances[it.id] ?: 0.0
|
||||||
|
it.isActivated || balance > 0.01 || balance < -0.01 || it.id == selectedMemberId
|
||||||
|
}.sortedWith(compareBy<DBMember> {
|
||||||
|
val balance = memberBalances[it.id] ?: 0.0
|
||||||
|
when {
|
||||||
|
balance > 0.01 -> 0
|
||||||
|
balance < -0.01 -> 1
|
||||||
|
else -> 2
|
||||||
|
}
|
||||||
|
}.thenBy {
|
||||||
|
val balance = memberBalances[it.id] ?: 0.0
|
||||||
|
if (balance > 0.01) -balance else balance
|
||||||
|
}.thenBy { it.name })
|
||||||
|
}
|
||||||
|
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
state = membersState,
|
state = membersState,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.heightIn(max = maxBottomHeight)
|
.heightIn(max = maxBottomHeight)
|
||||||
.lazyVerticalScrollbar(membersState)
|
.lazyVerticalScrollbar(membersState)
|
||||||
) {
|
) {
|
||||||
if (members.isNotEmpty()) {
|
if (sortedMembers.isNotEmpty()) {
|
||||||
item {
|
item {
|
||||||
DrawerItem(
|
DrawerItem(
|
||||||
icon = Icons.Default.Receipt,
|
icon = Icons.Default.Receipt,
|
||||||
text = stringResource(R.string.label_all_bills),
|
text = stringResource(R.string.label_all_bills),
|
||||||
|
secondaryText = mainCurrency,
|
||||||
selected = selectedMemberId == null,
|
selected = selectedMemberId == null,
|
||||||
onClick = { onMemberClick(null) }
|
onClick = { onMemberClick(null) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
items(members) { member ->
|
items(sortedMembers) { member ->
|
||||||
val balance = memberBalances[member.id] ?: 0.0
|
val balance = memberBalances[member.id] ?: 0.0
|
||||||
MemberDrawerItem(
|
MemberDrawerItem(
|
||||||
member = member,
|
member = member,
|
||||||
@@ -138,20 +160,27 @@ private fun DrawerHeader(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.background(MaterialTheme.colors.primary)
|
.background(MaterialTheme.colors.primary)
|
||||||
.padding(16.dp, 4.dp)
|
.padding(4.dp, 4.dp)
|
||||||
) {
|
) {
|
||||||
Column {
|
Column {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
Image(
|
Box(modifier = Modifier.size(48.dp)) {
|
||||||
painter = painterResource(id = R.drawable.ic_launcher_foreground),
|
Image(
|
||||||
contentDescription = null,
|
painter = painterResource(id = R.drawable.ic_launcher_background),
|
||||||
modifier = Modifier.size(48.dp)
|
contentDescription = null,
|
||||||
)
|
modifier = Modifier.fillMaxSize()
|
||||||
|
)
|
||||||
|
Image(
|
||||||
|
painter = painterResource(id = R.drawable.ic_launcher_foreground),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.fillMaxSize()
|
||||||
|
)
|
||||||
|
}
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
Text(
|
Text(
|
||||||
stringResource(id = R.string.app_name),
|
text = stringResource(id = R.string.app_name),
|
||||||
color = MaterialTheme.colors.onPrimary,
|
color = MaterialTheme.colors.onPrimary,
|
||||||
fontSize = 18.sp,
|
style = MaterialTheme.typography.subtitle1,
|
||||||
fontWeight = FontWeight.Bold
|
fontWeight = FontWeight.Bold
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
@@ -178,6 +207,7 @@ private fun DrawerHeader(
|
|||||||
}
|
}
|
||||||
if (lastSyncText.isNotEmpty()) {
|
if (lastSyncText.isNotEmpty()) {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Spacer(Modifier.width(16.dp))
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Default.Sync,
|
Icons.Default.Sync,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
@@ -241,6 +271,7 @@ fun DrawerItem(
|
|||||||
member: DBMember? = null,
|
member: DBMember? = null,
|
||||||
text: String? = null,
|
text: String? = null,
|
||||||
balanceText: String? = null,
|
balanceText: String? = null,
|
||||||
|
secondaryText: String? = null,
|
||||||
balanceColor: Color = Color.Unspecified,
|
balanceColor: Color = Color.Unspecified,
|
||||||
selected: Boolean = false,
|
selected: Boolean = false,
|
||||||
alpha: Float = 1f,
|
alpha: Float = 1f,
|
||||||
@@ -259,13 +290,8 @@ fun DrawerItem(
|
|||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
if (member != null) {
|
if (member != null) {
|
||||||
UserAvatar(
|
MemberAvatar(
|
||||||
name = member.name,
|
member = member,
|
||||||
r = member.r,
|
|
||||||
g = member.g,
|
|
||||||
b = member.b,
|
|
||||||
avatar = member.avatar,
|
|
||||||
disabled = !member.isActivated,
|
|
||||||
size = 24.dp,
|
size = 24.dp,
|
||||||
alpha = alpha
|
alpha = alpha
|
||||||
)
|
)
|
||||||
@@ -273,7 +299,7 @@ fun DrawerItem(
|
|||||||
Icon(icon, contentDescription = null, tint = contentColor.copy(alpha = 0.6f * alpha))
|
Icon(icon, contentDescription = null, tint = contentColor.copy(alpha = 0.6f * alpha))
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
Spacer(modifier = Modifier.width(32.dp))
|
||||||
|
|
||||||
val itemText = text ?: member?.name ?: ""
|
val itemText = text ?: member?.name ?: ""
|
||||||
Text(
|
Text(
|
||||||
@@ -295,6 +321,17 @@ fun DrawerItem(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (secondaryText != null) {
|
||||||
|
Box(modifier = Modifier
|
||||||
|
.padding(end = 8.dp)
|
||||||
|
) {
|
||||||
|
TextIconDisplay(
|
||||||
|
textIcon = TextIcon.Symbol(secondaryText),
|
||||||
|
tint = contentColor.copy(alpha = 0.6f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (onSecondaryClick != null) {
|
if (onSecondaryClick != null) {
|
||||||
IconButton(onClick = onSecondaryClick) {
|
IconButton(onClick = onSecondaryClick) {
|
||||||
Icon(
|
Icon(
|
||||||
@@ -340,6 +377,7 @@ fun DrawerPreview() {
|
|||||||
selectedProjectId = 1,
|
selectedProjectId = 1,
|
||||||
selectedMemberId = null,
|
selectedMemberId = null,
|
||||||
lastSyncText = "Last sync: 5 mins ago",
|
lastSyncText = "Last sync: 5 mins ago",
|
||||||
|
mainCurrency = "EUR",
|
||||||
onProjectClick = {},
|
onProjectClick = {},
|
||||||
onProjectOptionsClick = {},
|
onProjectOptionsClick = {},
|
||||||
onMemberClick = {},
|
onMemberClick = {},
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
@@ -110,28 +111,55 @@ fun AlertDialog(
|
|||||||
if (message != null) Spacer(Modifier.height(8.dp))
|
if (message != null) Spacer(Modifier.height(8.dp))
|
||||||
LazyColumn {
|
LazyColumn {
|
||||||
itemsIndexed(items) { index, item ->
|
itemsIndexed(items) { index, item ->
|
||||||
Row(
|
Column {
|
||||||
modifier = Modifier
|
Row(
|
||||||
.fillMaxWidth()
|
modifier = Modifier
|
||||||
.clickable {
|
.fillMaxWidth()
|
||||||
onItemSelected?.invoke(index)
|
.clickable {
|
||||||
|
onItemSelected?.invoke(index)
|
||||||
|
}
|
||||||
|
.padding(vertical = 16.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
if (itemIcons != null && index < itemIcons.size) {
|
||||||
|
Icon(
|
||||||
|
itemIcons[index],
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(24.dp),
|
||||||
|
tint = MaterialTheme.colors.primary.copy(alpha = 0.7f)
|
||||||
|
)
|
||||||
|
Spacer(Modifier.width(16.dp))
|
||||||
|
}
|
||||||
|
|
||||||
|
val text = item.toString()
|
||||||
|
if (text.contains("|")) {
|
||||||
|
val parts = text.split("|")
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = parts[0].trim(),
|
||||||
|
style = MaterialTheme.typography.body1,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = parts[1].trim(),
|
||||||
|
style = MaterialTheme.typography.body2,
|
||||||
|
color = MaterialTheme.colors.onSurface.copy(alpha = 0.6f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Text(
|
||||||
|
text = text,
|
||||||
|
style = MaterialTheme.typography.body1
|
||||||
|
)
|
||||||
}
|
}
|
||||||
.padding(vertical = 12.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
if (itemIcons != null && index < itemIcons.size) {
|
|
||||||
Icon(
|
|
||||||
itemIcons[index],
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.size(24.dp),
|
|
||||||
tint = MaterialTheme.colors.primary.copy(alpha = 0.7f)
|
|
||||||
)
|
|
||||||
Spacer(Modifier.width(16.dp))
|
|
||||||
}
|
}
|
||||||
Text(
|
if (index < items.size - 1) {
|
||||||
text = item.toString(),
|
Divider(color = MaterialTheme.colors.onSurface.copy(alpha = 0.08f))
|
||||||
style = MaterialTheme.typography.body1
|
}
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -202,7 +230,12 @@ fun AlertDialogContent(
|
|||||||
Spacer(Modifier.width(8.dp))
|
Spacer(Modifier.width(8.dp))
|
||||||
}
|
}
|
||||||
if (title != null) {
|
if (title != null) {
|
||||||
Text(text = title, style = MaterialTheme.typography.h6)
|
Text(
|
||||||
|
text = title.uppercase(),
|
||||||
|
style = MaterialTheme.typography.subtitle1,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = MaterialTheme.colors.onSurface
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -216,28 +249,55 @@ fun AlertDialogContent(
|
|||||||
if (message != null) Spacer(Modifier.height(8.dp))
|
if (message != null) Spacer(Modifier.height(8.dp))
|
||||||
LazyColumn {
|
LazyColumn {
|
||||||
itemsIndexed(items) { index, item ->
|
itemsIndexed(items) { index, item ->
|
||||||
Row(
|
Column {
|
||||||
modifier = Modifier
|
Row(
|
||||||
.fillMaxWidth()
|
modifier = Modifier
|
||||||
.clickable {
|
.fillMaxWidth()
|
||||||
onItemSelected?.invoke(index)
|
.clickable {
|
||||||
|
onItemSelected?.invoke(index)
|
||||||
|
}
|
||||||
|
.padding(vertical = 12.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
if (itemIcons != null && index < itemIcons.size) {
|
||||||
|
Icon(
|
||||||
|
itemIcons[index],
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(24.dp),
|
||||||
|
tint = MaterialTheme.colors.primary.copy(alpha = 0.7f)
|
||||||
|
)
|
||||||
|
Spacer(Modifier.width(16.dp))
|
||||||
|
}
|
||||||
|
|
||||||
|
val text = item.toString()
|
||||||
|
if (text.contains("|")) {
|
||||||
|
val parts = text.split("|")
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = parts[0].trim(),
|
||||||
|
style = MaterialTheme.typography.body1,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = parts[1].trim(),
|
||||||
|
style = MaterialTheme.typography.body2,
|
||||||
|
color = MaterialTheme.colors.onSurface.copy(alpha = 0.6f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Text(
|
||||||
|
text = text,
|
||||||
|
style = MaterialTheme.typography.body1
|
||||||
|
)
|
||||||
}
|
}
|
||||||
.padding(vertical = 8.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
if (itemIcons != null && index < itemIcons.size) {
|
|
||||||
Icon(
|
|
||||||
itemIcons[index],
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.size(24.dp),
|
|
||||||
tint = MaterialTheme.colors.primary.copy(alpha = 0.7f)
|
|
||||||
)
|
|
||||||
Spacer(Modifier.width(16.dp))
|
|
||||||
}
|
}
|
||||||
Text(
|
if (index < items.size - 1) {
|
||||||
text = item.toString(),
|
Divider(color = MaterialTheme.colors.onSurface.copy(alpha = 0.08f))
|
||||||
style = MaterialTheme.typography.body1
|
}
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -188,12 +188,13 @@ fun ColorPicker(
|
|||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
val hueBrush = remember {
|
val hueBrush = remember(lightness, chroma) {
|
||||||
Brush.horizontalGradient(
|
val hueSteps = 36 // More steps for smoother gradient
|
||||||
listOf(
|
val colors = (0..hueSteps).map { step ->
|
||||||
Color.Red, Color.Yellow, Color.Green, Color.Cyan, Color.Blue, Color.Magenta, Color.Red
|
val h = (step.toFloat() / hueSteps) * 360f
|
||||||
)
|
Color(mLCHtoRBG(lightness, chroma, h))
|
||||||
)
|
}
|
||||||
|
Brush.horizontalGradient(colors)
|
||||||
}
|
}
|
||||||
LchSlider(
|
LchSlider(
|
||||||
label = "H",
|
label = "H",
|
||||||
|
|||||||
@@ -1,20 +1,80 @@
|
|||||||
package net.helcel.cowspent.android.helper
|
package net.helcel.cowspent.android.helper
|
||||||
|
|
||||||
import net.helcel.cowspent.util.SupportUtil
|
import java.text.NumberFormat
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
import kotlin.math.round
|
import kotlin.math.round
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats amount using k/M notation (e.g., 1.5k for 1500).
|
||||||
|
*/
|
||||||
fun formatShortValue(value: Double): String {
|
fun formatShortValue(value: Double): String {
|
||||||
|
val absValue = abs(value)
|
||||||
|
val sign = if (value < 0) "-" else ""
|
||||||
return when {
|
return when {
|
||||||
value >= 1_000_000 -> String.format(Locale.ROOT, "%.1fM", value / 1_000_000).replace(".0", "")
|
absValue >= 1_000_000 -> sign + formatAmount(absValue / 1_000_000) + "M"
|
||||||
value >= 1_000 -> String.format(Locale.ROOT, "%.1fk", value / 1_000).replace(".0", "")
|
absValue >= 1_000 -> sign + formatAmount(absValue / 1_000) + "k"
|
||||||
else -> String.format(Locale.ROOT, "%.0f", value)
|
else -> sign + formatAmount(absValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats balance with sign and locale-aware number formatting.
|
||||||
|
*/
|
||||||
fun formatBalance(balance: Double): String {
|
fun formatBalance(balance: Double): String {
|
||||||
val rbalance = round(abs(balance) * 100.0) / 100.0
|
val rbalance = round(abs(balance) * 100.0) / 100.0
|
||||||
val balanceSign = if (balance > 0.01) "+" else if (balance < -0.01) "-" else ""
|
val balanceSign = if (balance > 0.01) "+" else if (balance < -0.01) "-" else ""
|
||||||
return if (rbalance == 0.0) "" else "$balanceSign${SupportUtil.normalNumberFormat.format(rbalance)}"
|
return if (rbalance == 0.0) "" else "$balanceSign${formatAmount(rbalance)}"
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats amount using system locale or specified locale.
|
||||||
|
*/
|
||||||
|
fun formatAmount(value: Double, locale: Locale = Locale.getDefault()): String {
|
||||||
|
val formatter = NumberFormat.getNumberInstance(locale)
|
||||||
|
formatter.maximumFractionDigits = 2
|
||||||
|
formatter.minimumFractionDigits = 0
|
||||||
|
return formatter.format(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses amount from string robustly.
|
||||||
|
* Tries common separators and cleans up non-numeric characters (except separators).
|
||||||
|
*/
|
||||||
|
fun parseAmount(input: String?): Double? {
|
||||||
|
if (input.isNullOrBlank()) return null
|
||||||
|
|
||||||
|
// Remove non-numeric characters except for delimiters and minus sign
|
||||||
|
val cleaned = input.replace(Regex("[^0-9,.-]"), "").trim()
|
||||||
|
|
||||||
|
if (cleaned.isEmpty()) return null
|
||||||
|
|
||||||
|
// If there's both a comma and a dot, we assume the last one is the decimal separator
|
||||||
|
val lastDot = cleaned.lastIndexOf('.')
|
||||||
|
val lastComma = cleaned.lastIndexOf(',')
|
||||||
|
|
||||||
|
return if (lastDot != -1 && lastComma != -1) {
|
||||||
|
if (lastDot > lastComma) {
|
||||||
|
// Dot is decimal, comma is thousands
|
||||||
|
cleaned.replace(",", "").toDoubleOrNull()
|
||||||
|
} else {
|
||||||
|
// Comma is decimal, dot is thousands
|
||||||
|
cleaned.replace(".", "").replace(",", ".").toDoubleOrNull()
|
||||||
|
}
|
||||||
|
} else if (lastComma != -1) {
|
||||||
|
// Only comma. Could be decimal or thousands.
|
||||||
|
if (cleaned.count { it == ',' } == 1) {
|
||||||
|
cleaned.replace(',', '.').toDoubleOrNull()
|
||||||
|
} else {
|
||||||
|
// Multiple commas -> thousands
|
||||||
|
cleaned.replace(",", "").toDoubleOrNull()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Only dots or no separators
|
||||||
|
if (cleaned.count { it == '.' } > 1) {
|
||||||
|
cleaned.replace(".", "").toDoubleOrNull()
|
||||||
|
} else {
|
||||||
|
cleaned.toDoubleOrNull()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package net.helcel.cowspent.android.helper
|
||||||
|
|
||||||
|
import androidx.compose.material.LocalContentColor
|
||||||
|
import androidx.compose.material.MaterialTheme
|
||||||
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
|
||||||
|
sealed class TextIcon {
|
||||||
|
data class Symbol(val text: String) : TextIcon()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A helper Composable to display a [TextIcon].
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun TextIconDisplay(
|
||||||
|
textIcon: TextIcon,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
tint: Color = LocalContentColor.current
|
||||||
|
) {
|
||||||
|
when (textIcon) {
|
||||||
|
is TextIcon.Symbol -> Text(
|
||||||
|
text = textIcon.text,
|
||||||
|
modifier = modifier,
|
||||||
|
color = tint,
|
||||||
|
style = MaterialTheme.typography.body1,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,36 +4,49 @@ import android.Manifest
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.VibrationEffect
|
|
||||||
import android.os.Vibrator
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.annotation.OptIn
|
import androidx.annotation.OptIn
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import net.helcel.cowspent.theme.ThemeUtils
|
import androidx.camera.core.CameraSelector
|
||||||
import androidx.camera.core.*
|
import androidx.camera.core.ExperimentalGetImage
|
||||||
|
import androidx.camera.core.ImageAnalysis
|
||||||
|
import androidx.camera.core.ImageProxy
|
||||||
import androidx.camera.lifecycle.ProcessCameraProvider
|
import androidx.camera.lifecycle.ProcessCameraProvider
|
||||||
import androidx.camera.view.PreviewView
|
import androidx.camera.view.PreviewView
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material.*
|
import androidx.compose.material.Icon
|
||||||
|
import androidx.compose.material.IconButton
|
||||||
|
import androidx.compose.material.MaterialTheme
|
||||||
|
import androidx.compose.material.Scaffold
|
||||||
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.material.TopAppBar
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.lifecycle.compose.LocalLifecycleOwner
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
import androidx.core.app.ActivityCompat
|
import androidx.core.app.ActivityCompat
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import com.google.zxing.*
|
import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||||
|
import com.google.zxing.BarcodeFormat
|
||||||
|
import com.google.zxing.BinaryBitmap
|
||||||
|
import com.google.zxing.DecodeHintType
|
||||||
|
import com.google.zxing.MultiFormatReader
|
||||||
|
import com.google.zxing.PlanarYUVLuminanceSource
|
||||||
|
import com.google.zxing.Result
|
||||||
import com.google.zxing.common.HybridBinarizer
|
import com.google.zxing.common.HybridBinarizer
|
||||||
import net.helcel.cowspent.R
|
import net.helcel.cowspent.R
|
||||||
import net.helcel.cowspent.android.main.MainConstants
|
import net.helcel.cowspent.android.main.MainConstants
|
||||||
|
import net.helcel.cowspent.theme.ThemeUtils
|
||||||
import java.util.concurrent.ExecutorService
|
import java.util.concurrent.ExecutorService
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
|
|
||||||
@@ -42,6 +55,7 @@ class QrCodeScannerActivity : AppCompatActivity() {
|
|||||||
private var isScanning = true
|
private var isScanning = true
|
||||||
|
|
||||||
override fun onCreate(state: Bundle?) {
|
override fun onCreate(state: Bundle?) {
|
||||||
|
enableEdgeToEdge()
|
||||||
super.onCreate(state)
|
super.onCreate(state)
|
||||||
|
|
||||||
cameraExecutor = Executors.newSingleThreadExecutor()
|
cameraExecutor = Executors.newSingleThreadExecutor()
|
||||||
@@ -120,7 +134,7 @@ fun QrCodeScannerScreen(
|
|||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = { Text(stringResource(R.string.scan_qrcode)) },
|
title = { Text(stringResource(R.string.action_scan_qrcode)) },
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
IconButton(onClick = onBack) {
|
IconButton(onClick = onBack) {
|
||||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)
|
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
import android.util.Base64
|
import android.util.Base64
|
||||||
|
import net.helcel.cowspent.model.DBMember
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun UserAvatar(
|
fun UserAvatar(
|
||||||
@@ -91,6 +92,26 @@ fun UserAvatar(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MemberAvatar(
|
||||||
|
member: DBMember,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
size: Dp = 40.dp,
|
||||||
|
alpha: Float = 1f
|
||||||
|
) {
|
||||||
|
UserAvatar(
|
||||||
|
name = member.name,
|
||||||
|
modifier = modifier,
|
||||||
|
size = size,
|
||||||
|
r = member.r,
|
||||||
|
g = member.g,
|
||||||
|
b = member.b,
|
||||||
|
avatar = member.avatar,
|
||||||
|
disabled = !member.isActivated,
|
||||||
|
alpha = alpha
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
fun UserAvatarPreview() {
|
fun UserAvatarPreview() {
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package net.helcel.cowspent.android.label
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.activity.compose.setContent
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
|
import androidx.activity.viewModels
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import net.helcel.cowspent.theme.ThemeUtils
|
||||||
|
|
||||||
|
class LabelManagementActivity : AppCompatActivity() {
|
||||||
|
private val viewModel: LabelManagementViewModel by viewModels()
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
enableEdgeToEdge()
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
val projectId = intent.getLongExtra(EXTRA_PROJECT_ID, 0L)
|
||||||
|
if (projectId == 0L) {
|
||||||
|
finish()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.loadLabels(projectId)
|
||||||
|
|
||||||
|
setContent {
|
||||||
|
ThemeUtils.CowspentTheme {
|
||||||
|
LabelManagementScreen(
|
||||||
|
viewModel = viewModel,
|
||||||
|
onBack = { finish() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val EXTRA_PROJECT_ID = "EXTRA_PROJECT_ID"
|
||||||
|
|
||||||
|
fun createIntent(context: Context, projectId: Long): Intent {
|
||||||
|
return Intent(context, LabelManagementActivity::class.java).apply {
|
||||||
|
putExtra(EXTRA_PROJECT_ID, projectId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,399 @@
|
|||||||
|
package net.helcel.cowspent.android.label
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material.*
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
|
import androidx.compose.material.icons.filled.Add
|
||||||
|
import androidx.compose.material.icons.filled.Delete
|
||||||
|
import androidx.compose.material.icons.filled.Edit
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.toArgb
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.core.graphics.toColorInt
|
||||||
|
import net.helcel.cowspent.R
|
||||||
|
import net.helcel.cowspent.android.helper.ColorPicker
|
||||||
|
import net.helcel.cowspent.android.helper.StatefulAlertDialog
|
||||||
|
import net.helcel.cowspent.model.DBBill
|
||||||
|
import net.helcel.cowspent.model.DBCategory
|
||||||
|
import net.helcel.cowspent.model.DBPaymentMode
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LabelManagementScreen(
|
||||||
|
viewModel: LabelManagementViewModel,
|
||||||
|
onBack: () -> Unit
|
||||||
|
) {
|
||||||
|
LabelManagementScreenContent(
|
||||||
|
categories = viewModel.categories,
|
||||||
|
paymentModes = viewModel.paymentModes,
|
||||||
|
dialogState = viewModel.dialogState,
|
||||||
|
onBack = onBack,
|
||||||
|
onAddCategory = viewModel::addCategory,
|
||||||
|
onUpdateCategory = viewModel::updateCategory,
|
||||||
|
onDeleteCategory = viewModel::deleteCategory,
|
||||||
|
onAddPaymentMode = viewModel::addPaymentMode,
|
||||||
|
onUpdatePaymentMode = viewModel::updatePaymentMode,
|
||||||
|
onDeletePaymentMode = viewModel::deletePaymentMode,
|
||||||
|
onDismissDialog = { viewModel.dialogState = null },
|
||||||
|
onShowDialog = { viewModel.dialogState = it }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LabelManagementScreenContent(
|
||||||
|
categories: List<DBCategory>,
|
||||||
|
paymentModes: List<DBPaymentMode>,
|
||||||
|
dialogState: net.helcel.cowspent.android.helper.DialogState?,
|
||||||
|
onBack: () -> Unit,
|
||||||
|
onAddCategory: (String, String, String) -> Unit,
|
||||||
|
onUpdateCategory: (DBCategory, String, String, String) -> Unit,
|
||||||
|
onDeleteCategory: (Long) -> Unit,
|
||||||
|
onAddPaymentMode: (String, String, String) -> Unit,
|
||||||
|
onUpdatePaymentMode: (DBPaymentMode, String, String, String) -> Unit,
|
||||||
|
onDeletePaymentMode: (Long) -> Unit,
|
||||||
|
onDismissDialog: () -> Unit,
|
||||||
|
onShowDialog: (net.helcel.cowspent.android.helper.DialogState) -> Unit,
|
||||||
|
initialTab: Int = 0
|
||||||
|
) {
|
||||||
|
var selectedTab by remember { mutableIntStateOf(initialTab) }
|
||||||
|
val tabs = listOf(
|
||||||
|
stringResource(R.string.label_categories),
|
||||||
|
stringResource(R.string.label_payment_modes)
|
||||||
|
)
|
||||||
|
|
||||||
|
val deleteLabelTitle = stringResource(R.string.delete_label_confirmation_title)
|
||||||
|
val deleteLabelMessage = stringResource(R.string.delete_label_confirmation_message)
|
||||||
|
val yesText = stringResource(R.string.simple_yes)
|
||||||
|
val noText = stringResource(R.string.simple_no)
|
||||||
|
|
||||||
|
var showEditDialog by remember { mutableStateOf(false) }
|
||||||
|
var editingCategory by remember { mutableStateOf<DBCategory?>(null) }
|
||||||
|
var editingPaymentMode by remember { mutableStateOf<DBPaymentMode?>(null) }
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
TopAppBar(
|
||||||
|
title = { Text(stringResource(R.string.title_labels)) },
|
||||||
|
navigationIcon = {
|
||||||
|
IconButton(onClick = onBack) {
|
||||||
|
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
backgroundColor = MaterialTheme.colors.primary,
|
||||||
|
elevation = 0.dp
|
||||||
|
)
|
||||||
|
},
|
||||||
|
floatingActionButton = {
|
||||||
|
FloatingActionButton(onClick = {
|
||||||
|
if (selectedTab == 0) {
|
||||||
|
editingCategory = null
|
||||||
|
showEditDialog = true
|
||||||
|
} else {
|
||||||
|
editingPaymentMode = null
|
||||||
|
showEditDialog = true
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
Icon(Icons.Default.Add, contentDescription = null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) { padding ->
|
||||||
|
Column(modifier = Modifier.padding(padding)) {
|
||||||
|
TabRow(selectedTabIndex = selectedTab) {
|
||||||
|
tabs.forEachIndexed { index, title ->
|
||||||
|
Tab(
|
||||||
|
selected = selectedTab == index,
|
||||||
|
onClick = { selectedTab = index },
|
||||||
|
text = { Text(title) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedTab == 0) {
|
||||||
|
CategoryList(
|
||||||
|
categories = categories.filter { it.remoteId != DBBill.CATEGORY_REIMBURSEMENT },
|
||||||
|
onEdit = {
|
||||||
|
editingCategory = it
|
||||||
|
showEditDialog = true
|
||||||
|
},
|
||||||
|
onDelete = { category ->
|
||||||
|
onShowDialog(net.helcel.cowspent.android.helper.DialogState(
|
||||||
|
title = deleteLabelTitle,
|
||||||
|
message = deleteLabelMessage,
|
||||||
|
positiveText = yesText,
|
||||||
|
negativeText = noText,
|
||||||
|
onConfirm = { onDeleteCategory(category.id) }
|
||||||
|
))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
PaymentModeList(
|
||||||
|
paymentModes = paymentModes,
|
||||||
|
onEdit = {
|
||||||
|
editingPaymentMode = it
|
||||||
|
showEditDialog = true
|
||||||
|
},
|
||||||
|
onDelete = { pm ->
|
||||||
|
onShowDialog(net.helcel.cowspent.android.helper.DialogState(
|
||||||
|
title = deleteLabelTitle,
|
||||||
|
message = deleteLabelMessage,
|
||||||
|
positiveText = yesText,
|
||||||
|
negativeText = noText,
|
||||||
|
onConfirm = { onDeletePaymentMode(pm.id) }
|
||||||
|
))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showEditDialog) {
|
||||||
|
if (selectedTab == 0) {
|
||||||
|
EditLabelDialog(
|
||||||
|
title = if (editingCategory == null) stringResource(R.string.title_add_category) else stringResource(R.string.action_edit),
|
||||||
|
initialName = editingCategory?.name ?: "",
|
||||||
|
initialIcon = editingCategory?.icon ?: "",
|
||||||
|
initialColor = editingCategory?.color ?: "#FF0000",
|
||||||
|
onDismiss = { showEditDialog = false },
|
||||||
|
onSave = { name, icon, color ->
|
||||||
|
if (editingCategory == null) {
|
||||||
|
onAddCategory(name, icon, color)
|
||||||
|
} else {
|
||||||
|
onUpdateCategory(editingCategory!!, name, icon, color)
|
||||||
|
}
|
||||||
|
showEditDialog = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
EditLabelDialog(
|
||||||
|
title = if (editingPaymentMode == null) stringResource(R.string.title_add_payment_mode) else stringResource(R.string.action_edit),
|
||||||
|
initialName = editingPaymentMode?.name ?: "",
|
||||||
|
initialIcon = editingPaymentMode?.icon ?: "",
|
||||||
|
initialColor = editingPaymentMode?.color ?: "#00FF00",
|
||||||
|
onDismiss = { showEditDialog = false },
|
||||||
|
onSave = { name, icon, color ->
|
||||||
|
if (editingPaymentMode == null) {
|
||||||
|
onAddPaymentMode(name, icon, color)
|
||||||
|
} else {
|
||||||
|
onUpdatePaymentMode(editingPaymentMode!!, name, icon, color)
|
||||||
|
}
|
||||||
|
showEditDialog = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StatefulAlertDialog(
|
||||||
|
state = dialogState,
|
||||||
|
onDismissRequest = onDismissDialog
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun CategoryList(
|
||||||
|
categories: List<DBCategory>,
|
||||||
|
onEdit: (DBCategory) -> Unit,
|
||||||
|
onDelete: (DBCategory) -> Unit
|
||||||
|
) {
|
||||||
|
LazyColumn {
|
||||||
|
items(categories) { category ->
|
||||||
|
LabelItem(
|
||||||
|
name = category.name ?: "",
|
||||||
|
icon = category.icon,
|
||||||
|
color = category.color,
|
||||||
|
onEdit = { onEdit(category) },
|
||||||
|
onDelete = { onDelete(category) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun PaymentModeList(
|
||||||
|
paymentModes: List<DBPaymentMode>,
|
||||||
|
onEdit: (DBPaymentMode) -> Unit,
|
||||||
|
onDelete: (DBPaymentMode) -> Unit
|
||||||
|
) {
|
||||||
|
LazyColumn {
|
||||||
|
items(paymentModes) { pm ->
|
||||||
|
LabelItem(
|
||||||
|
name = pm.name ?: "",
|
||||||
|
icon = pm.icon,
|
||||||
|
color = pm.color,
|
||||||
|
onEdit = { onEdit(pm) },
|
||||||
|
onDelete = { onDelete(pm) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LabelItem(
|
||||||
|
name: String,
|
||||||
|
icon: String,
|
||||||
|
color: String,
|
||||||
|
onEdit: () -> Unit,
|
||||||
|
onDelete: () -> Unit
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable { onEdit() }
|
||||||
|
.padding(16.dp, 8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(24.dp)
|
||||||
|
.clip(CircleShape)
|
||||||
|
.background(parseColor(color)),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Text(text = icon, fontSize = 12.sp)
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.width(32.dp))
|
||||||
|
Text(text = name, modifier = Modifier.weight(1f), style = MaterialTheme.typography.subtitle1)
|
||||||
|
IconButton(onClick = onEdit) {
|
||||||
|
Icon(Icons.Default.Edit, contentDescription = null, tint = MaterialTheme.colors.onSurface.copy(alpha = 0.6f))
|
||||||
|
}
|
||||||
|
IconButton(onClick = onDelete) {
|
||||||
|
Icon(Icons.Default.Delete, contentDescription = null, tint = MaterialTheme.colors.error.copy(alpha = 0.6f))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun EditLabelDialog(
|
||||||
|
title: String,
|
||||||
|
initialName: String,
|
||||||
|
initialIcon: String,
|
||||||
|
initialColor: String,
|
||||||
|
onDismiss: () -> Unit,
|
||||||
|
onSave: (String, String, String) -> Unit
|
||||||
|
) {
|
||||||
|
var name by remember { mutableStateOf(initialName) }
|
||||||
|
var icon by remember { mutableStateOf(initialIcon) }
|
||||||
|
var color by remember { mutableStateOf(initialColor) }
|
||||||
|
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = onDismiss,
|
||||||
|
title = { Text(title) },
|
||||||
|
text = {
|
||||||
|
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
|
||||||
|
OutlinedTextField(
|
||||||
|
value = name,
|
||||||
|
onValueChange = { name = it },
|
||||||
|
placeholder = { Text(stringResource(R.string.label_name)) },
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
singleLine = true
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
OutlinedTextField(
|
||||||
|
value = icon,
|
||||||
|
onValueChange = { if (it.length <= 2) icon = it },
|
||||||
|
placeholder = { Text(stringResource(R.string.label_icon)) },
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
singleLine = true
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
Text(text = stringResource(R.string.label_color), style = MaterialTheme.typography.caption)
|
||||||
|
ColorPicker(
|
||||||
|
initialColor = parseColorInt(color),
|
||||||
|
onColorChanged = { color = String.format("#%06X", 0xFFFFFF and it) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
Button(
|
||||||
|
onClick = { onSave(name, icon, color) },
|
||||||
|
enabled = name.isNotBlank()
|
||||||
|
) {
|
||||||
|
Text(stringResource(R.string.action_save))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(onClick = onDismiss) {
|
||||||
|
Text(stringResource(R.string.simple_cancel))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun parseColor(colorString: String): Color {
|
||||||
|
return try {
|
||||||
|
Color(colorString.toColorInt())
|
||||||
|
} catch (_: Exception) {
|
||||||
|
Color.Gray
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun parseColorInt(colorString: String): Int {
|
||||||
|
return try {
|
||||||
|
colorString.toColorInt()
|
||||||
|
} catch (_: Exception) {
|
||||||
|
Color.Gray.toArgb()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(showBackground = true)
|
||||||
|
@Composable
|
||||||
|
fun LabelManagementCategoriesPreview() {
|
||||||
|
MaterialTheme {
|
||||||
|
LabelManagementScreenContent(
|
||||||
|
categories = listOf(
|
||||||
|
DBCategory(1L, 1L, 1L, "Groceries", "🛒", "#FF0000"),
|
||||||
|
DBCategory(2L, 2L, 1L, "Rent", "🏠", "#00FF00")
|
||||||
|
),
|
||||||
|
paymentModes = emptyList(),
|
||||||
|
dialogState = null,
|
||||||
|
onBack = {},
|
||||||
|
onAddCategory = { _, _, _ -> },
|
||||||
|
onUpdateCategory = { _, _, _, _ -> },
|
||||||
|
onDeleteCategory = { _ -> },
|
||||||
|
onAddPaymentMode = { _, _, _ -> },
|
||||||
|
onUpdatePaymentMode = { _, _, _, _ -> },
|
||||||
|
onDeletePaymentMode = { _ -> },
|
||||||
|
onDismissDialog = {},
|
||||||
|
onShowDialog = {},
|
||||||
|
initialTab = 0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(showBackground = true)
|
||||||
|
@Composable
|
||||||
|
fun LabelManagementPaymentModesPreview() {
|
||||||
|
MaterialTheme {
|
||||||
|
LabelManagementScreenContent(
|
||||||
|
categories = emptyList(),
|
||||||
|
paymentModes = listOf(
|
||||||
|
DBPaymentMode(1L, 1L, 1L, "Cash", "💵", "#0000FF"),
|
||||||
|
DBPaymentMode(2L, 2L, 1L, "Credit Card", "💳", "#FFFF00")
|
||||||
|
),
|
||||||
|
dialogState = null,
|
||||||
|
onBack = {},
|
||||||
|
onAddCategory = { _, _, _ -> },
|
||||||
|
onUpdateCategory = { _, _, _, _ -> },
|
||||||
|
onDeleteCategory = { _ -> },
|
||||||
|
onAddPaymentMode = { _, _, _ -> },
|
||||||
|
onUpdatePaymentMode = { _, _, _, _ -> },
|
||||||
|
onDeletePaymentMode = { _ -> },
|
||||||
|
onDismissDialog = {},
|
||||||
|
onShowDialog = {},
|
||||||
|
initialTab = 1
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
package net.helcel.cowspent.android.label
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableLongStateOf
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.lifecycle.AndroidViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import net.helcel.cowspent.android.helper.DialogState
|
||||||
|
import net.helcel.cowspent.model.DBCategory
|
||||||
|
import net.helcel.cowspent.model.DBPaymentMode
|
||||||
|
import net.helcel.cowspent.model.DBBill
|
||||||
|
import net.helcel.cowspent.persistence.CowspentSQLiteOpenHelper
|
||||||
|
|
||||||
|
class LabelManagementViewModel(application: Application) : AndroidViewModel(application) {
|
||||||
|
private val db = CowspentSQLiteOpenHelper.getInstance(application)
|
||||||
|
|
||||||
|
var projectId by mutableLongStateOf(0L)
|
||||||
|
var categories by mutableStateOf<List<DBCategory>>(emptyList())
|
||||||
|
var paymentModes by mutableStateOf<List<DBPaymentMode>>(emptyList())
|
||||||
|
|
||||||
|
var dialogState by mutableStateOf<DialogState?>(null)
|
||||||
|
|
||||||
|
fun loadLabels(projId: Long) {
|
||||||
|
projectId = projId
|
||||||
|
viewModelScope.launch {
|
||||||
|
val project = withContext(Dispatchers.IO) { db.getProject(projId) }
|
||||||
|
if (project != null) {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
db.ensureDefaultLabels(projId, project.type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
categories = withContext(Dispatchers.IO) { db.getCategories(projId) }
|
||||||
|
paymentModes = withContext(Dispatchers.IO) { db.getPaymentModes(projId) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addCategory(name: String, icon: String, color: String) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
db.addCategoryAndSync(DBCategory(0, 0, projectId, name, icon, color, DBBill.STATE_ADDED))
|
||||||
|
}
|
||||||
|
loadLabels(projectId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateCategory(cat: DBCategory, name: String, icon: String, color: String) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
db.updateCategoryAndSync(cat, name, icon, color)
|
||||||
|
}
|
||||||
|
loadLabels(projectId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deleteCategory(id: Long) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
db.deleteCategoryAndSync(id)
|
||||||
|
}
|
||||||
|
loadLabels(projectId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addPaymentMode(name: String, icon: String, color: String) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
db.addPaymentModeAndSync(DBPaymentMode(0, 0, projectId, name, icon, color, DBBill.STATE_ADDED))
|
||||||
|
}
|
||||||
|
loadLabels(projectId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updatePaymentMode(pm: DBPaymentMode, name: String, icon: String, color: String) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
db.updatePaymentModeAndSync(pm, name, icon, color)
|
||||||
|
}
|
||||||
|
loadLabels(projectId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deletePaymentMode(id: Long) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
db.deletePaymentModeAndSync(id)
|
||||||
|
}
|
||||||
|
loadLabels(projectId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,9 +2,25 @@ package net.helcel.cowspent.android.main
|
|||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.offset
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.material.*
|
import androidx.compose.material.Button
|
||||||
|
import androidx.compose.material.Divider
|
||||||
|
import androidx.compose.material.Icon
|
||||||
|
import androidx.compose.material.MaterialTheme
|
||||||
|
import androidx.compose.material.Surface
|
||||||
|
import androidx.compose.material.Text
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Receipt
|
import androidx.compose.material.icons.filled.Receipt
|
||||||
import androidx.compose.material.icons.filled.Repeat
|
import androidx.compose.material.icons.filled.Repeat
|
||||||
@@ -13,10 +29,11 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
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.style.TextAlign
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import net.helcel.cowspent.R
|
import net.helcel.cowspent.R
|
||||||
import net.helcel.cowspent.android.helper.UserAvatar
|
import net.helcel.cowspent.android.helper.MemberAvatar
|
||||||
import net.helcel.cowspent.model.DBBill
|
import net.helcel.cowspent.model.DBBill
|
||||||
import net.helcel.cowspent.model.DBMember
|
import net.helcel.cowspent.model.DBMember
|
||||||
import net.helcel.cowspent.util.SupportUtil
|
import net.helcel.cowspent.util.SupportUtil
|
||||||
@@ -28,15 +45,25 @@ fun EmptyProjectsState(onConfigureNextcloud: () -> Unit, onAddManually: () -> Un
|
|||||||
verticalArrangement = Arrangement.Center,
|
verticalArrangement = Arrangement.Center,
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
) {
|
) {
|
||||||
Text(stringResource(R.string.no_projects_title), style = MaterialTheme.typography.h6)
|
Text(
|
||||||
|
text = stringResource(R.string.error_no_projects).uppercase(),
|
||||||
|
style = MaterialTheme.typography.subtitle1,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = MaterialTheme.colors.onSurface
|
||||||
|
)
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
Text(stringResource(R.string.no_projects_text))
|
Text(
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
text = stringResource(R.string.no_projects_text),
|
||||||
Button(onClick = onConfigureNextcloud) {
|
style = MaterialTheme.typography.body2,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
color = MaterialTheme.colors.onSurface.copy(alpha = 0.6f)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
Button(onClick = onConfigureNextcloud, modifier = Modifier.fillMaxWidth()) {
|
||||||
Text(stringResource(R.string.configure_account_choice))
|
Text(stringResource(R.string.configure_account_choice))
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
Button(onClick = onAddManually) {
|
Button(onClick = onAddManually, modifier = Modifier.fillMaxWidth()) {
|
||||||
Text(stringResource(R.string.add_project_choice))
|
Text(stringResource(R.string.add_project_choice))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -49,9 +76,19 @@ fun EmptyMembersState() {
|
|||||||
verticalArrangement = Arrangement.Center,
|
verticalArrangement = Arrangement.Center,
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
) {
|
) {
|
||||||
Text(stringResource(R.string.no_members_title), style = MaterialTheme.typography.h6)
|
Text(
|
||||||
|
text = stringResource(R.string.error_no_members).uppercase(),
|
||||||
|
style = MaterialTheme.typography.subtitle1,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = MaterialTheme.colors.onSurface
|
||||||
|
)
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
Text(stringResource(R.string.no_members_text))
|
Text(
|
||||||
|
text = stringResource(R.string.no_members_text),
|
||||||
|
style = MaterialTheme.typography.body2,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
color = MaterialTheme.colors.onSurface.copy(alpha = 0.6f)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,9 +99,19 @@ fun EmptyBillsState() {
|
|||||||
verticalArrangement = Arrangement.Center,
|
verticalArrangement = Arrangement.Center,
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
) {
|
) {
|
||||||
Text(stringResource(R.string.no_bills_title), style = MaterialTheme.typography.h6)
|
Text(
|
||||||
|
text = stringResource(R.string.error_no_bills).uppercase(),
|
||||||
|
style = MaterialTheme.typography.subtitle1,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = MaterialTheme.colors.onSurface
|
||||||
|
)
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
Text(stringResource(R.string.no_bills_text))
|
Text(
|
||||||
|
text = stringResource(R.string.no_bills_text),
|
||||||
|
style = MaterialTheme.typography.body2,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
color = MaterialTheme.colors.onSurface.copy(alpha = 0.6f)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,13 +130,8 @@ fun BillItemRow(bill: DBBill, payer: DBMember?, onClick: () -> Unit) {
|
|||||||
) {
|
) {
|
||||||
if (payer != null) {
|
if (payer != null) {
|
||||||
Box {
|
Box {
|
||||||
UserAvatar(
|
MemberAvatar(
|
||||||
name = payer.name,
|
member = payer,
|
||||||
r = payer.r,
|
|
||||||
g = payer.g,
|
|
||||||
b = payer.b,
|
|
||||||
avatar = payer.avatar,
|
|
||||||
disabled = !payer.isActivated,
|
|
||||||
size = 40.dp
|
size = 40.dp
|
||||||
)
|
)
|
||||||
if (bill.repeat != null && bill.repeat != DBBill.NON_REPEATED) {
|
if (bill.repeat != null && bill.repeat != DBBill.NON_REPEATED) {
|
||||||
@@ -111,15 +153,21 @@ fun BillItemRow(bill: DBBill, payer: DBMember?, onClick: () -> Unit) {
|
|||||||
Icons.Default.Receipt,
|
Icons.Default.Receipt,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier.size(40.dp),
|
modifier = Modifier.size(40.dp),
|
||||||
tint = MaterialTheme.colors.primary
|
tint = MaterialTheme.colors.onSurface.copy(alpha = 0.6f)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.width(16.dp))
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
Column(modifier = Modifier.weight(1f)) {
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
Text(bill.formattedWhat.ifEmpty { bill.what }, fontWeight = FontWeight.Bold)
|
Text(
|
||||||
|
text = bill.formattedWhat.ifEmpty { bill.what },
|
||||||
|
style = MaterialTheme.typography.subtitle1,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = MaterialTheme.colors.onSurface
|
||||||
|
)
|
||||||
Text(
|
Text(
|
||||||
text = bill.formattedSubtitle.ifEmpty { bill.comment ?: "" },
|
text = bill.formattedSubtitle.ifEmpty { bill.comment ?: "" },
|
||||||
style = MaterialTheme.typography.caption,
|
style = MaterialTheme.typography.caption,
|
||||||
|
color = MaterialTheme.colors.onSurface.copy(alpha = 0.6f),
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis
|
overflow = TextOverflow.Ellipsis
|
||||||
)
|
)
|
||||||
@@ -140,13 +188,16 @@ fun SectionHeader(title: String) {
|
|||||||
color = MaterialTheme.colors.background,
|
color = MaterialTheme.colors.background,
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
) {
|
) {
|
||||||
Divider(thickness = 2.dp)
|
Column {
|
||||||
Text(
|
Divider(thickness = 1.dp, color = MaterialTheme.colors.onSurface.copy(alpha = 0.08f))
|
||||||
text = title,
|
Text(
|
||||||
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp),
|
text = title.uppercase(),
|
||||||
style = MaterialTheme.typography.caption.copy(fontWeight = FontWeight.Bold),
|
modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||||
color = MaterialTheme.colors.primary
|
style = MaterialTheme.typography.subtitle1,
|
||||||
)
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = MaterialTheme.colors.onSurface
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,7 +208,18 @@ fun EmptyState() {
|
|||||||
verticalArrangement = Arrangement.Center,
|
verticalArrangement = Arrangement.Center,
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
) {
|
) {
|
||||||
Text(stringResource(R.string.no_bills_title), style = MaterialTheme.typography.h6)
|
Text(
|
||||||
Text(stringResource(R.string.no_bills_text), modifier = Modifier.padding(16.dp))
|
text = stringResource(R.string.error_no_bills).uppercase(),
|
||||||
|
style = MaterialTheme.typography.subtitle1,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = MaterialTheme.colors.onSurface
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.no_bills_text),
|
||||||
|
modifier = Modifier.padding(16.dp),
|
||||||
|
style = MaterialTheme.typography.body2,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
color = MaterialTheme.colors.onSurface.copy(alpha = 0.6f)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package net.helcel.cowspent.android.main
|
|||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.widget.Toast
|
|
||||||
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.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
@@ -46,6 +45,7 @@ import androidx.compose.ui.graphics.Color
|
|||||||
import androidx.compose.ui.graphics.RectangleShape
|
import androidx.compose.ui.graphics.RectangleShape
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.window.Dialog
|
import androidx.compose.ui.window.Dialog
|
||||||
@@ -57,14 +57,12 @@ import net.helcel.cowspent.android.drawer.Drawer
|
|||||||
import net.helcel.cowspent.android.helper.StatefulAlertDialog
|
import net.helcel.cowspent.android.helper.StatefulAlertDialog
|
||||||
import net.helcel.cowspent.android.project.ProjectOptionsDialogContent
|
import net.helcel.cowspent.android.project.ProjectOptionsDialogContent
|
||||||
import net.helcel.cowspent.android.project.ProjectShareDialogContent
|
import net.helcel.cowspent.android.project.ProjectShareDialogContent
|
||||||
import net.helcel.cowspent.android.project.member.MemberAddDialogContent
|
|
||||||
import net.helcel.cowspent.android.project.member.MemberEditDialogContent
|
|
||||||
import net.helcel.cowspent.android.project.member.MemberManagementDialogContent
|
|
||||||
import net.helcel.cowspent.android.project.settle.ProjectSettlementDialogContent
|
import net.helcel.cowspent.android.project.settle.ProjectSettlementDialogContent
|
||||||
import net.helcel.cowspent.android.statistics.ProjectStatisticsActivity
|
import net.helcel.cowspent.android.statistics.ProjectStatisticsActivity
|
||||||
import net.helcel.cowspent.model.DBBill
|
import net.helcel.cowspent.model.DBBill
|
||||||
import net.helcel.cowspent.model.DBMember
|
import net.helcel.cowspent.model.DBMember
|
||||||
import net.helcel.cowspent.model.DBProject
|
import net.helcel.cowspent.model.DBProject
|
||||||
|
import net.helcel.cowspent.model.ProjectType
|
||||||
import net.helcel.cowspent.model.SectionItem
|
import net.helcel.cowspent.model.SectionItem
|
||||||
import net.helcel.cowspent.persistence.CowspentSQLiteOpenHelper
|
import net.helcel.cowspent.persistence.CowspentSQLiteOpenHelper
|
||||||
import net.helcel.cowspent.util.IRefreshBillsListCallback
|
import net.helcel.cowspent.util.IRefreshBillsListCallback
|
||||||
@@ -103,9 +101,12 @@ fun BillsListScreen(
|
|||||||
|
|
||||||
val pullRefreshState = rememberPullRefreshState(viewModel.isRefreshing, onRefresh)
|
val pullRefreshState = rememberPullRefreshState(viewModel.isRefreshing, onRefresh)
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val memberAlreadyExistsError = stringResource(R.string.member_already_exists)
|
|
||||||
val sharedPreferences = remember { PreferenceManager.getDefaultSharedPreferences(context) }
|
val sharedPreferences = remember { PreferenceManager.getDefaultSharedPreferences(context) }
|
||||||
val showArchived = sharedPreferences.getBoolean(stringResource(R.string.pref_key_show_archived), false)
|
val showArchived = sharedPreferences.getBoolean(stringResource(R.string.pref_key_show_archived), false)
|
||||||
|
val keyBetaFeatures = stringResource(R.string.pref_key_beta_features)
|
||||||
|
var showBetaFeatures by remember(keyBetaFeatures) {
|
||||||
|
mutableStateOf(sharedPreferences.getBoolean(keyBetaFeatures, false))
|
||||||
|
}
|
||||||
|
|
||||||
StatefulAlertDialog(
|
StatefulAlertDialog(
|
||||||
state = viewModel.dialogState,
|
state = viewModel.dialogState,
|
||||||
@@ -137,6 +138,10 @@ fun BillsListScreen(
|
|||||||
onProjectAction(projectOptionsProjectId, 3)
|
onProjectAction(projectOptionsProjectId, 3)
|
||||||
viewModel.showProjectOptionsDialogByProjectId = null
|
viewModel.showProjectOptionsDialogByProjectId = null
|
||||||
},
|
},
|
||||||
|
onManageLabels = {
|
||||||
|
onProjectAction(projectOptionsProjectId, 8)
|
||||||
|
viewModel.showProjectOptionsDialogByProjectId = null
|
||||||
|
},
|
||||||
onStatistics = {
|
onStatistics = {
|
||||||
onProjectAction(projectOptionsProjectId, 4)
|
onProjectAction(projectOptionsProjectId, 4)
|
||||||
viewModel.showProjectOptionsDialogByProjectId = null
|
viewModel.showProjectOptionsDialogByProjectId = null
|
||||||
@@ -155,8 +160,10 @@ fun BillsListScreen(
|
|||||||
},
|
},
|
||||||
onDismiss = { viewModel.showProjectOptionsDialogByProjectId = null },
|
onDismiss = { viewModel.showProjectOptionsDialogByProjectId = null },
|
||||||
isArchived = proj?.isArchived == true,
|
isArchived = proj?.isArchived == true,
|
||||||
|
projectType = proj?.type ?: ProjectType.LOCAL,
|
||||||
accessLevel = proj?.myAccessLevel ?: DBProject.ACCESS_LEVEL_ADMIN,
|
accessLevel = proj?.myAccessLevel ?: DBProject.ACCESS_LEVEL_ADMIN,
|
||||||
isShareable = proj?.isShareable() ?: true
|
isShareable = proj?.isShareable() ?: true,
|
||||||
|
showBetaFeatures = showBetaFeatures
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -208,114 +215,17 @@ fun BillsListScreen(
|
|||||||
|
|
||||||
val manageMembersProjectId = viewModel.showMemberManagementDialogByProjectId
|
val manageMembersProjectId = viewModel.showMemberManagementDialogByProjectId
|
||||||
if (manageMembersProjectId != null) {
|
if (manageMembersProjectId != null) {
|
||||||
val members = viewModel.members // Use members from ViewModel as they are already loaded
|
viewModel.showMemberManagementDialogByProjectId = null
|
||||||
Dialog(
|
|
||||||
onDismissRequest = { viewModel.showMemberManagementDialogByProjectId = null },
|
|
||||||
properties = DialogProperties(usePlatformDefaultWidth = false)
|
|
||||||
) {
|
|
||||||
MemberManagementDialogContent(
|
|
||||||
members = members,
|
|
||||||
onAddMember = {
|
|
||||||
viewModel.showAddMemberDialogByProjectId = manageMembersProjectId
|
|
||||||
viewModel.showMemberManagementDialogByProjectId = null
|
|
||||||
},
|
|
||||||
onEditMember = { member ->
|
|
||||||
viewModel.showEditMemberDialogByProjectId = member.id
|
|
||||||
viewModel.showMemberManagementDialogByProjectId = null
|
|
||||||
},
|
|
||||||
onDismiss = { viewModel.showMemberManagementDialogByProjectId = null }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val addMemberProjectId = viewModel.showAddMemberDialogByProjectId
|
val addMemberProjectId = viewModel.showAddMemberDialogByProjectId
|
||||||
if (addMemberProjectId != null) {
|
if (addMemberProjectId != null) {
|
||||||
Dialog(
|
viewModel.showAddMemberDialogByProjectId = null
|
||||||
onDismissRequest = { viewModel.showAddMemberDialogByProjectId = null },
|
|
||||||
properties = DialogProperties(usePlatformDefaultWidth = false)
|
|
||||||
) {
|
|
||||||
MemberAddDialogContent(
|
|
||||||
onAdd = { memberName ->
|
|
||||||
val memberNames =
|
|
||||||
db.getMembersOfProject(addMemberProjectId, null).map { it.name }
|
|
||||||
if (memberNames.contains(memberName)) {
|
|
||||||
Toast.makeText(
|
|
||||||
context,
|
|
||||||
memberAlreadyExistsError,
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
} else {
|
|
||||||
val color = net.helcel.cowspent.android.helper.TextDrawable.getColorFromName(memberName)
|
|
||||||
db.addMemberAndSync(
|
|
||||||
DBMember(
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
addMemberProjectId,
|
|
||||||
memberName,
|
|
||||||
true,
|
|
||||||
1.0,
|
|
||||||
DBBill.STATE_ADDED,
|
|
||||||
android.graphics.Color.red(color),
|
|
||||||
android.graphics.Color.green(color),
|
|
||||||
android.graphics.Color.blue(color),
|
|
||||||
null,
|
|
||||||
null
|
|
||||||
)
|
|
||||||
)
|
|
||||||
refreshCallback.refreshLists(false)
|
|
||||||
viewModel.showAddMemberDialogByProjectId = null
|
|
||||||
viewModel.showMemberManagementDialogByProjectId = addMemberProjectId
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onDismiss = {
|
|
||||||
viewModel.showAddMemberDialogByProjectId = null
|
|
||||||
viewModel.showMemberManagementDialogByProjectId = addMemberProjectId
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val editMemberId = viewModel.showEditMemberDialogByProjectId
|
val editMemberId = viewModel.showEditMemberDialogByProjectId
|
||||||
if (editMemberId != null) {
|
if (editMemberId != null) {
|
||||||
val memberToEdit = remember(editMemberId, viewModel.members) {
|
viewModel.showEditMemberDialogByProjectId = null
|
||||||
viewModel.members.find { it.id == editMemberId }
|
|
||||||
}
|
|
||||||
if (memberToEdit != null) {
|
|
||||||
Dialog(
|
|
||||||
onDismissRequest = { viewModel.showEditMemberDialogByProjectId = null },
|
|
||||||
properties = DialogProperties(usePlatformDefaultWidth = false)
|
|
||||||
) {
|
|
||||||
MemberEditDialogContent(
|
|
||||||
member = memberToEdit,
|
|
||||||
onSave = { name, weight, isActivated, r, g, b ->
|
|
||||||
db.updateMemberAndSync(
|
|
||||||
memberToEdit,
|
|
||||||
name,
|
|
||||||
weight,
|
|
||||||
isActivated,
|
|
||||||
r,
|
|
||||||
g,
|
|
||||||
b,
|
|
||||||
"",
|
|
||||||
""
|
|
||||||
)
|
|
||||||
refreshCallback.refreshLists(false)
|
|
||||||
viewModel.showEditMemberDialogByProjectId = null
|
|
||||||
viewModel.showMemberManagementDialogByProjectId = memberToEdit.projectId
|
|
||||||
},
|
|
||||||
onDelete = {
|
|
||||||
db.deleteMember(editMemberId)
|
|
||||||
refreshCallback.refreshLists(false)
|
|
||||||
viewModel.showEditMemberDialogByProjectId = null
|
|
||||||
viewModel.showMemberManagementDialogByProjectId = memberToEdit.projectId
|
|
||||||
},
|
|
||||||
onDismiss = {
|
|
||||||
viewModel.showEditMemberDialogByProjectId = null
|
|
||||||
viewModel.showMemberManagementDialogByProjectId = memberToEdit.projectId
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val shareProjectId = viewModel.showShareDialogByProjectId
|
val shareProjectId = viewModel.showShareDialogByProjectId
|
||||||
@@ -324,8 +234,8 @@ fun BillsListScreen(
|
|||||||
viewModel.projects.find { it.id == shareProjectId }
|
viewModel.projects.find { it.id == shareProjectId }
|
||||||
}
|
}
|
||||||
if (proj != null) {
|
if (proj != null) {
|
||||||
val shareIntentTitle = stringResource(R.string.share_share_intent_title, proj.name)
|
val shareIntentTitle = stringResource(R.string.share_intent_title, proj.name)
|
||||||
val shareChooserTitle = stringResource(R.string.share_share_chooser_title, proj.name)
|
val shareChooserTitle = stringResource(R.string.share_chooser_title, proj.name)
|
||||||
Dialog(
|
Dialog(
|
||||||
onDismissRequest = { viewModel.showShareDialogByProjectId = null },
|
onDismissRequest = { viewModel.showShareDialogByProjectId = null },
|
||||||
properties = DialogProperties(usePlatformDefaultWidth = false)
|
properties = DialogProperties(usePlatformDefaultWidth = false)
|
||||||
@@ -358,7 +268,13 @@ fun BillsListScreen(
|
|||||||
TextField(
|
TextField(
|
||||||
value = viewModel.searchQuery,
|
value = viewModel.searchQuery,
|
||||||
onValueChange = { viewModel.searchQuery = it },
|
onValueChange = { viewModel.searchQuery = it },
|
||||||
placeholder = { Text(stringResource(R.string.action_search), color = MaterialTheme.colors.onPrimary.copy(alpha = 0.7f)) },
|
placeholder = {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.action_search),
|
||||||
|
color = MaterialTheme.colors.onPrimary.copy(alpha = 0.7f),
|
||||||
|
style = MaterialTheme.typography.subtitle1
|
||||||
|
)
|
||||||
|
},
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.focusRequester(focusRequester),
|
.focusRequester(focusRequester),
|
||||||
@@ -381,8 +297,20 @@ fun BillsListScreen(
|
|||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Column {
|
Column {
|
||||||
if (viewModel.title.isNotEmpty()) Text(viewModel.title)
|
if (viewModel.title.isNotEmpty()) {
|
||||||
else Text(stringResource(R.string.app_name))
|
Text(
|
||||||
|
text = viewModel.title,
|
||||||
|
style = MaterialTheme.typography.subtitle1,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.app_name),
|
||||||
|
style = MaterialTheme.typography.subtitle1,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -404,7 +332,7 @@ fun BillsListScreen(
|
|||||||
},
|
},
|
||||||
actions = {
|
actions = {
|
||||||
if (!isSearchExpanded) {
|
if (!isSearchExpanded) {
|
||||||
if (viewModel.hasUnlabeledBills) {
|
if (showBetaFeatures && viewModel.hasUnlabeledBills) {
|
||||||
IconButton(onClick = onLabelBillsClick) {
|
IconButton(onClick = onLabelBillsClick) {
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Default.Category,
|
Icons.Default.Category,
|
||||||
@@ -425,11 +353,12 @@ fun BillsListScreen(
|
|||||||
val selectedProject = viewModel.projects.find { it.id == viewModel.selectedProjectId }
|
val selectedProject = viewModel.projects.find { it.id == viewModel.selectedProjectId }
|
||||||
if (selectedProject != null && !selectedProject.isArchived) {
|
if (selectedProject != null && !selectedProject.isArchived) {
|
||||||
FloatingActionButton(onClick = onAddBillClick) {
|
FloatingActionButton(onClick = onAddBillClick) {
|
||||||
Icon(Icons.Default.Add, contentDescription = stringResource(R.string.action_create_bill))
|
Icon(Icons.Default.Add, contentDescription = stringResource(R.string.action_new_bill))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
drawerContent = {
|
drawerContent = {
|
||||||
|
val selectedProject = viewModel.projects.find { it.id == viewModel.selectedProjectId }
|
||||||
Drawer(
|
Drawer(
|
||||||
projects = viewModel.projects,
|
projects = viewModel.projects,
|
||||||
members = viewModel.members,
|
members = viewModel.members,
|
||||||
@@ -437,6 +366,7 @@ fun BillsListScreen(
|
|||||||
selectedProjectId = viewModel.selectedProjectId,
|
selectedProjectId = viewModel.selectedProjectId,
|
||||||
selectedMemberId = viewModel.selectedMemberId,
|
selectedMemberId = viewModel.selectedMemberId,
|
||||||
lastSyncText = viewModel.lastSyncText,
|
lastSyncText = viewModel.lastSyncText,
|
||||||
|
mainCurrency = selectedProject?.currencyName,
|
||||||
showArchived = showArchived,
|
showArchived = showArchived,
|
||||||
onProjectClick = {
|
onProjectClick = {
|
||||||
viewModel.selectedMemberId = null
|
viewModel.selectedMemberId = null
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import android.content.Intent
|
|||||||
import net.helcel.cowspent.R
|
import net.helcel.cowspent.R
|
||||||
import net.helcel.cowspent.model.*
|
import net.helcel.cowspent.model.*
|
||||||
import net.helcel.cowspent.persistence.CowspentSQLiteOpenHelper
|
import net.helcel.cowspent.persistence.CowspentSQLiteOpenHelper
|
||||||
|
import net.helcel.cowspent.util.CategoryUtils
|
||||||
import net.helcel.cowspent.util.IRefreshBillsListCallback
|
import net.helcel.cowspent.util.IRefreshBillsListCallback
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@@ -65,11 +66,11 @@ object BillsListUtils {
|
|||||||
memberIdToName: Map<Long, String>
|
memberIdToName: Map<Long, String>
|
||||||
) {
|
) {
|
||||||
val projectName = proj.name.ifEmpty { proj.remoteId }
|
val projectName = proj.name.ifEmpty { proj.remoteId }
|
||||||
var text = context.getString(R.string.share_settle_intro, projectName) + "\n"
|
var text = context.getString(R.string.msg_settle_intro, projectName) + "\n"
|
||||||
for (t in transactions) {
|
for (t in transactions) {
|
||||||
val amount = round(t.amount * 100.0) / 100.0
|
val amount = round(t.amount * 100.0) / 100.0
|
||||||
text += "\n" + context.getString(
|
text += "\n" + context.getString(
|
||||||
R.string.share_settle_sentence,
|
R.string.msg_settle_sentence,
|
||||||
memberIdToName[t.owerMemberId],
|
memberIdToName[t.owerMemberId],
|
||||||
memberIdToName[t.receiverMemberId],
|
memberIdToName[t.receiverMemberId],
|
||||||
amount
|
amount
|
||||||
@@ -80,12 +81,12 @@ object BillsListUtils {
|
|||||||
shareIntent.type = "text/plain"
|
shareIntent.type = "text/plain"
|
||||||
shareIntent.putExtra(
|
shareIntent.putExtra(
|
||||||
Intent.EXTRA_SUBJECT,
|
Intent.EXTRA_SUBJECT,
|
||||||
context.getString(R.string.share_settle_title, projectName)
|
context.getString(R.string.title_settle)
|
||||||
)
|
)
|
||||||
shareIntent.putExtra(Intent.EXTRA_TEXT, text)
|
shareIntent.putExtra(Intent.EXTRA_TEXT, text)
|
||||||
val chooserIntent = Intent.createChooser(
|
val chooserIntent = Intent.createChooser(
|
||||||
shareIntent,
|
shareIntent,
|
||||||
context.getString(R.string.share_settle_title, projectName)
|
context.getString(R.string.title_settle)
|
||||||
)
|
)
|
||||||
context.startActivity(chooserIntent)
|
context.startActivity(chooserIntent)
|
||||||
}
|
}
|
||||||
@@ -98,6 +99,10 @@ object BillsListUtils {
|
|||||||
context: Context
|
context: Context
|
||||||
) {
|
) {
|
||||||
val timestamp = System.currentTimeMillis() / 1000
|
val timestamp = System.currentTimeMillis() / 1000
|
||||||
|
val proj = db.getProject(projectId)
|
||||||
|
val categories = db.getCategories(projectId)
|
||||||
|
val reimbursementCategoryId = CategoryUtils.getReimbursementCategoryId(categories, projectId, proj?.remoteId)
|
||||||
|
|
||||||
for (t in transactions) {
|
for (t in transactions) {
|
||||||
val owerId = t.owerMemberId
|
val owerId = t.owerMemberId
|
||||||
val receiverId = t.receiverMemberId
|
val receiverId = t.receiverMemberId
|
||||||
@@ -106,7 +111,7 @@ object BillsListUtils {
|
|||||||
0, 0, projectId, owerId, amount,
|
0, 0, projectId, owerId, amount,
|
||||||
timestamp, context.getString(R.string.settle_bill_what),
|
timestamp, context.getString(R.string.settle_bill_what),
|
||||||
DBBill.STATE_ADDED, DBBill.NON_REPEATED,
|
DBBill.STATE_ADDED, DBBill.NON_REPEATED,
|
||||||
DBBill.PAYMODE_NONE, DBBill.CATEGORY_NONE,
|
DBBill.PAYMODE_NONE, reimbursementCategoryId,
|
||||||
"", DBBill.PAYMODE_ID_NONE
|
"", DBBill.PAYMODE_ID_NONE
|
||||||
)
|
)
|
||||||
bill.billOwers += DBBillOwer(0, 0, receiverId)
|
bill.billOwers += DBBillOwer(0, 0, receiverId)
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
package net.helcel.cowspent.android.main
|
package net.helcel.cowspent.android.main
|
||||||
|
|
||||||
import android.Manifest
|
|
||||||
import android.app.SearchManager
|
import android.app.SearchManager
|
||||||
import android.content.BroadcastReceiver
|
import android.content.BroadcastReceiver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.IntentFilter
|
import android.content.IntentFilter
|
||||||
import android.content.pm.PackageManager
|
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
@@ -17,18 +15,15 @@ import android.util.Log
|
|||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.OnBackPressedCallback
|
import androidx.activity.OnBackPressedCallback
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.appcompat.view.ActionMode
|
import androidx.appcompat.view.ActionMode
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.AddCircleOutline
|
|
||||||
import androidx.compose.material.icons.filled.Sync
|
import androidx.compose.material.icons.filled.Sync
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
|
||||||
import androidx.core.app.ActivityCompat
|
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import androidx.core.net.toUri
|
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.nextcloud.android.sso.helper.SingleAccountHelper
|
import com.nextcloud.android.sso.helper.SingleAccountHelper
|
||||||
@@ -53,6 +48,7 @@ import net.helcel.cowspent.persistence.CowspentSQLiteOpenHelper
|
|||||||
import net.helcel.cowspent.persistence.CowspentServerSyncHelper
|
import net.helcel.cowspent.persistence.CowspentServerSyncHelper
|
||||||
import net.helcel.cowspent.theme.ThemeUtils
|
import net.helcel.cowspent.theme.ThemeUtils
|
||||||
import net.helcel.cowspent.util.BillFormatter
|
import net.helcel.cowspent.util.BillFormatter
|
||||||
|
import net.helcel.cowspent.util.CategoryUtils
|
||||||
import net.helcel.cowspent.util.CospendClientUtil
|
import net.helcel.cowspent.util.CospendClientUtil
|
||||||
import net.helcel.cowspent.util.ExportUtil
|
import net.helcel.cowspent.util.ExportUtil
|
||||||
import net.helcel.cowspent.util.ICallback
|
import net.helcel.cowspent.util.ICallback
|
||||||
@@ -118,18 +114,8 @@ class BillsListViewActivity :
|
|||||||
Log.d(TAG, "CREATED project id: $pid")
|
Log.d(TAG, "CREATED project id: $pid")
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
val addedProj = withContext(Dispatchers.IO) { db.getProject(pid) }
|
val addedProj = withContext(Dispatchers.IO) { db.getProject(pid) }
|
||||||
val message: String
|
val message = getString(R.string.msg_project_added, addedProj?.name?.ifEmpty { addedProj.remoteId } ?: pid.toString())
|
||||||
val title: String
|
showToast(this@BillsListViewActivity, message)
|
||||||
if (created) {
|
|
||||||
Log.e(TAG, "CREATED !!!")
|
|
||||||
title = getString(R.string.project_create_success_title)
|
|
||||||
message = getString(R.string.project_create_success_message, addedProj?.remoteId)
|
|
||||||
} else {
|
|
||||||
Log.e(TAG, "ADDED !!!")
|
|
||||||
title = getString(R.string.project_add_success_title)
|
|
||||||
message = getString(R.string.project_add_success_message, addedProj?.remoteId)
|
|
||||||
}
|
|
||||||
showDialog(message, title, Icons.Default.AddCircleOutline)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -145,7 +131,7 @@ class BillsListViewActivity :
|
|||||||
}
|
}
|
||||||
if (!db.cowspentServerSyncHelper.isSyncPossible) {
|
if (!db.cowspentServerSyncHelper.isSyncPossible) {
|
||||||
if (CowspentServerSyncHelper.isNextcloudAccountConfigured(applicationContext)) {
|
if (CowspentServerSyncHelper.isNextcloudAccountConfigured(applicationContext)) {
|
||||||
Toast.makeText(applicationContext, getString(R.string.error_sync, getString(CospendClientUtil.LoginStatus.NO_NETWORK.str)), Toast.LENGTH_LONG).show()
|
showToast(this@BillsListViewActivity, getString(R.string.error_sync, getString(CospendClientUtil.LoginStatus.NO_NETWORK.str)), Toast.LENGTH_LONG)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -193,6 +179,7 @@ class BillsListViewActivity :
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
enableEdgeToEdge()
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
isActivityVisible = true
|
isActivityVisible = true
|
||||||
if (savedInstanceState != null) {
|
if (savedInstanceState != null) {
|
||||||
@@ -223,7 +210,7 @@ class BillsListViewActivity :
|
|||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
val members = withContext(Dispatchers.IO) { db.getActivatedMembersOfProject(selectedProjectId) }
|
val members = withContext(Dispatchers.IO) { db.getActivatedMembersOfProject(selectedProjectId) }
|
||||||
if (members.isEmpty()) {
|
if (members.isEmpty()) {
|
||||||
showToast(this@BillsListViewActivity, getString(R.string.add_bill_impossible_no_member))
|
showToast(this@BillsListViewActivity, getString(R.string.error_no_members))
|
||||||
} else {
|
} else {
|
||||||
val proj = withContext(Dispatchers.IO) { db.getProject(selectedProjectId) }
|
val proj = withContext(Dispatchers.IO) { db.getProject(selectedProjectId) }
|
||||||
val createIntent = Intent(applicationContext, EditBillActivity::class.java).apply {
|
val createIntent = Intent(applicationContext, EditBillActivity::class.java).apply {
|
||||||
@@ -257,13 +244,23 @@ class BillsListViewActivity :
|
|||||||
onProjectAction = { pid, actionIndex ->
|
onProjectAction = { pid, actionIndex ->
|
||||||
when (actionIndex) {
|
when (actionIndex) {
|
||||||
0 -> onEditProjectClick(pid)
|
0 -> onEditProjectClick(pid)
|
||||||
1 -> onRemoveProjectClick(pid)
|
1 -> {
|
||||||
|
val proj = db.getProject(pid)
|
||||||
|
if (proj?.type == ProjectType.COSPEND) {
|
||||||
|
onArchiveProjectClick(pid)
|
||||||
|
} else {
|
||||||
|
onRemoveProjectClick(pid)
|
||||||
|
}
|
||||||
|
}
|
||||||
2 -> onManageMembersClick(pid)
|
2 -> onManageMembersClick(pid)
|
||||||
3 -> onManageCurrenciesClick(pid)
|
3 -> onManageCurrenciesClick(pid)
|
||||||
4 -> onProjectStatisticsClick(pid)
|
4 -> onProjectStatisticsClick(pid)
|
||||||
5 -> onSettleProjectClick(pid)
|
5 -> onSettleProjectClick(pid)
|
||||||
6 -> onShareProjectClick(pid)
|
6 -> onShareProjectClick(pid)
|
||||||
7 -> onExportProjectClick(pid)
|
7 -> onExportProjectClick(pid)
|
||||||
|
8 -> {
|
||||||
|
startActivity(net.helcel.cowspent.android.label.LabelManagementActivity.createIntent(this@BillsListViewActivity, pid))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onAccountSwitcherClick = {
|
onAccountSwitcherClick = {
|
||||||
@@ -385,17 +382,10 @@ class BillsListViewActivity :
|
|||||||
|
|
||||||
private fun onEditProjectClick(projectId: Long) {
|
private fun onEditProjectClick(projectId: Long) {
|
||||||
if (projectId == 0L) return
|
if (projectId == 0L) return
|
||||||
lifecycleScope.launch {
|
val intent = Intent(applicationContext, EditProjectActivity::class.java).apply {
|
||||||
val proj = withContext(Dispatchers.IO) { db.getProject(projectId) }
|
putExtra(EditProjectActivity.PARAM_PROJECT_ID, projectId)
|
||||||
if (proj?.isLocal == false) {
|
|
||||||
val intent = Intent(applicationContext, EditProjectActivity::class.java).apply {
|
|
||||||
putExtra(EditProjectActivity.PARAM_PROJECT_ID, projectId)
|
|
||||||
}
|
|
||||||
editProjectLauncher.launch(intent)
|
|
||||||
} else {
|
|
||||||
showToast(this@BillsListViewActivity, getString(R.string.edit_project_local_impossible))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
editProjectLauncher.launch(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onRemoveProjectClick(projectId: Long) {
|
private fun onRemoveProjectClick(projectId: Long) {
|
||||||
@@ -404,8 +394,8 @@ class BillsListViewActivity :
|
|||||||
val proj = withContext(Dispatchers.IO) { db.getProject(projectId) } ?: return@launch
|
val proj = withContext(Dispatchers.IO) { db.getProject(projectId) } ?: return@launch
|
||||||
|
|
||||||
viewModel.showDialog(
|
viewModel.showDialog(
|
||||||
title = getString(R.string.confirm_remove_project_dialog_title),
|
title = getString(R.string.title_confirm),
|
||||||
message = if (!proj.isLocal) getString(R.string.confirm_remove_project_dialog_message) else null,
|
message = if (!proj.isLocal) getString(R.string.dialog_confirm_remove_project_msg) else null,
|
||||||
positiveText = getString(R.string.simple_yes),
|
positiveText = getString(R.string.simple_yes),
|
||||||
onConfirm = {
|
onConfirm = {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
@@ -426,6 +416,38 @@ class BillsListViewActivity :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun onArchiveProjectClick(projectId: Long) {
|
||||||
|
if (projectId == 0L) return
|
||||||
|
lifecycleScope.launch {
|
||||||
|
val proj = withContext(Dispatchers.IO) { db.getProject(projectId) } ?: return@launch
|
||||||
|
val isArchiving = !proj.isArchived
|
||||||
|
|
||||||
|
val newArchivedTs = if (isArchiving) System.currentTimeMillis() / 1000 else 0L
|
||||||
|
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
db.updateProject(
|
||||||
|
projId = projectId,
|
||||||
|
newName = null,
|
||||||
|
newEmail = null,
|
||||||
|
newPassword = null,
|
||||||
|
newLastPayerId = null,
|
||||||
|
newLastSyncedTimestamp = null,
|
||||||
|
newCurrencyName = null,
|
||||||
|
newDeletionDisabled = null,
|
||||||
|
newMyAccessLevel = null,
|
||||||
|
newBearerToken = null,
|
||||||
|
newArchivedTs = newArchivedTs
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
setupDrawerProjects()
|
||||||
|
refreshLists()
|
||||||
|
|
||||||
|
val msgRes = if (isArchiving) R.string.action_archive else R.string.action_unarchive
|
||||||
|
showToast(this@BillsListViewActivity, getString(msgRes))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun onManageMembersClick(projectId: Long) {
|
fun onManageMembersClick(projectId: Long) {
|
||||||
if (projectId == 0L) return
|
if (projectId == 0L) return
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
@@ -435,18 +457,18 @@ class BillsListViewActivity :
|
|||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModel.showMemberManagementDialogByProjectId = projectId
|
startActivity(net.helcel.cowspent.android.project.member.MemberManagementActivity.createIntent(this@BillsListViewActivity, projectId))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onManageCurrenciesClick(projectId: Long) {
|
fun onManageCurrenciesClick(projectId: Long) {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
val proj = withContext(Dispatchers.IO) { db.getProject(projectId) }
|
val proj = withContext(Dispatchers.IO) { db.getProject(projectId) }
|
||||||
if (proj != null && proj.type == ProjectType.COSPEND) {
|
if (proj != null) {
|
||||||
startActivity(Intent(applicationContext, ManageCurrenciesActivity::class.java).apply {
|
startActivity(Intent(applicationContext, ManageCurrenciesActivity::class.java).apply {
|
||||||
putExtra(ManageCurrenciesActivity.EXTRA_PROJECT_ID, projectId)
|
putExtra(ManageCurrenciesActivity.EXTRA_PROJECT_ID, projectId)
|
||||||
})
|
})
|
||||||
} else showToast(this@BillsListViewActivity, getString(R.string.currency_management_unavailable))
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -462,7 +484,7 @@ class BillsListViewActivity :
|
|||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
val proj = withContext(Dispatchers.IO) { db.getProject(projectId) }
|
val proj = withContext(Dispatchers.IO) { db.getProject(projectId) }
|
||||||
if (projectId != 0L && proj?.isShareable() == true) viewModel.showShareDialogByProjectId = projectId
|
if (projectId != 0L && proj?.isShareable() == true) viewModel.showShareDialogByProjectId = projectId
|
||||||
else showToast(this@BillsListViewActivity, getString(R.string.share_impossible), Toast.LENGTH_LONG)
|
else showToast(this@BillsListViewActivity, getString(R.string.error_share_impossible), Toast.LENGTH_LONG)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -532,11 +554,13 @@ class BillsListViewActivity :
|
|||||||
|
|
||||||
val members = db.getMembersOfProject(proj.id, null)
|
val members = db.getMembersOfProject(proj.id, null)
|
||||||
val bills = db.getBillsOfProject(proj.id)
|
val bills = db.getBillsOfProject(proj.id)
|
||||||
|
val categories = db.getCategories(proj.id)
|
||||||
|
val reimbursementCategoryId = CategoryUtils.getReimbursementCategoryId(categories, proj.id, proj.remoteId)
|
||||||
val balances = HashMap<Long, Double>()
|
val balances = HashMap<Long, Double>()
|
||||||
SupportUtil.getStats(
|
SupportUtil.getStats(
|
||||||
members, bills,
|
members, bills,
|
||||||
mutableMapOf(), balances, mutableMapOf(), mutableMapOf(),
|
mutableMapOf(), balances, mutableMapOf(), mutableMapOf(),
|
||||||
-1000, -1000, null, null
|
-1000L, -1000L, reimbursementCategoryId, null, null
|
||||||
)
|
)
|
||||||
Triple(proj, members, balances)
|
Triple(proj, members, balances)
|
||||||
}
|
}
|
||||||
@@ -563,7 +587,7 @@ class BillsListViewActivity :
|
|||||||
} else {
|
} else {
|
||||||
val lastSyncTimestamp = proj.lastSyncedTimestamp ?: 0
|
val lastSyncTimestamp = proj.lastSyncedTimestamp ?: 0
|
||||||
val cal = Calendar.getInstance().apply { timeInMillis = lastSyncTimestamp * 1000 }
|
val cal = Calendar.getInstance().apply { timeInMillis = lastSyncTimestamp * 1000 }
|
||||||
val text = getString(R.string.drawer_last_sync_text, cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE))
|
val text = getString(R.string.drawer_last_sync, cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE))
|
||||||
viewModel.lastSyncText = text
|
viewModel.lastSyncText = text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -603,13 +627,13 @@ class BillsListViewActivity :
|
|||||||
mid == null || mid == it.payerId || it.billOwersIds.contains(mid)
|
mid == null || mid == it.payerId || it.billOwersIds.contains(mid)
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModel.hasUnlabeledBills = bills.any { it.categoryRemoteId == 0 && it.state != DBBill.STATE_DELETED }
|
viewModel.hasUnlabeledBills = bills.any { it.categoryId == 0L && it.state != DBBill.STATE_DELETED }
|
||||||
|
|
||||||
val projectMembers = db.getMembersOfProject(projId, null)
|
val projectMembers = db.getMembersOfProject(projId, null)
|
||||||
val memberMap = projectMembers.associateBy { it.id }
|
val memberMap = projectMembers.associateBy { it.id }
|
||||||
|
|
||||||
val projectPaymentModes = db.getPaymentModes(projId).associateBy { it.remoteId }
|
val projectPaymentModes = db.getPaymentModes(projId).associateBy { it.id }
|
||||||
val projectCategories = db.getCategories(projId).associateBy { it.remoteId }
|
val projectCategories = db.getCategories(projId).associateBy { it.id }
|
||||||
|
|
||||||
BillFormatter.formatBills(
|
BillFormatter.formatBills(
|
||||||
bills,
|
bills,
|
||||||
@@ -661,7 +685,7 @@ class BillsListViewActivity :
|
|||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
val members = withContext(Dispatchers.IO) { db.getActivatedMembersOfProject(selectedProjectId) }
|
val members = withContext(Dispatchers.IO) { db.getActivatedMembersOfProject(selectedProjectId) }
|
||||||
if (members.isEmpty()) {
|
if (members.isEmpty()) {
|
||||||
showToast(this@BillsListViewActivity, getString(R.string.add_bill_impossible_no_member))
|
showToast(this@BillsListViewActivity, getString(R.string.error_no_member))
|
||||||
} else {
|
} else {
|
||||||
val projType = withContext(Dispatchers.IO) { db.getProject(selectedProjectId)?.type?.id }
|
val projType = withContext(Dispatchers.IO) { db.getProject(selectedProjectId)?.type?.id }
|
||||||
val intent = Intent(applicationContext, EditBillActivity::class.java).apply {
|
val intent = Intent(applicationContext, EditBillActivity::class.java).apply {
|
||||||
@@ -675,15 +699,6 @@ class BillsListViewActivity :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showDialog(msg: String, title: String, icon: ImageVector) {
|
|
||||||
viewModel.showDialog(
|
|
||||||
title = title,
|
|
||||||
message = msg,
|
|
||||||
positiveText = getString(android.R.string.ok),
|
|
||||||
icon = icon
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateUsernameInDrawer() {
|
private fun updateUsernameInDrawer() {
|
||||||
if (!CowspentServerSyncHelper.isNextcloudAccountConfigured(this)) {
|
if (!CowspentServerSyncHelper.isNextcloudAccountConfigured(this)) {
|
||||||
val text = getString(R.string.drawer_no_account)
|
val text = getString(R.string.drawer_no_account)
|
||||||
@@ -775,8 +790,8 @@ class BillsListViewActivity :
|
|||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
val project = withContext(Dispatchers.IO) { db.getProject(projectId) } ?: return@launch
|
val project = withContext(Dispatchers.IO) { db.getProject(projectId) } ?: return@launch
|
||||||
viewModel.showDialog(
|
viewModel.showDialog(
|
||||||
title = getString(R.string.sync_error_dialog_title),
|
title = getString(R.string.dialog_sync_error_title),
|
||||||
message = getString(R.string.sync_error_dialog_full_content, project.name, errorMessage),
|
message = getString(R.string.dialog_sync_error_msg, project.name, errorMessage),
|
||||||
positiveText = getString(R.string.simple_close),
|
positiveText = getString(R.string.simple_close),
|
||||||
icon = Icons.Default.Sync
|
icon = Icons.Default.Sync
|
||||||
)
|
)
|
||||||
@@ -796,7 +811,7 @@ class BillsListViewActivity :
|
|||||||
}
|
}
|
||||||
MainConstants.BROADCAST_SSO_TOKEN_MISMATCH -> {
|
MainConstants.BROADCAST_SSO_TOKEN_MISMATCH -> {
|
||||||
viewModel.showDialog(
|
viewModel.showDialog(
|
||||||
title = getString(R.string.sync_error_dialog_title),
|
title = getString(R.string.dialog_sync_error_title),
|
||||||
message = getString(R.string.error_token_mismatch),
|
message = getString(R.string.error_token_mismatch),
|
||||||
positiveText = getString(R.string.simple_close),
|
positiveText = getString(R.string.simple_close),
|
||||||
icon = Icons.Default.Sync
|
icon = Icons.Default.Sync
|
||||||
|
|||||||
@@ -118,8 +118,8 @@ object ProjectImportHelper {
|
|||||||
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"
|
||||||
val catId = if (columns.containsKey("categoryid") && line[columns["categoryid"]!!].isNotEmpty()) line[columns["categoryid"]!!].toInt() else 0
|
val catId = if (columns.containsKey("categoryid") && line[columns["categoryid"]!!].isNotEmpty()) line[columns["categoryid"]!!].toLong() else 0L
|
||||||
val pmId = if (columns.containsKey("paymentmodeid") && line[columns["paymentmodeid"]!!].isNotEmpty()) line[columns["paymentmodeid"]!!].toInt() else 0
|
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
|
||||||
|
|
||||||
membersActive[payerName] = payerActive
|
membersActive[payerName] = payerActive
|
||||||
@@ -150,8 +150,16 @@ object ProjectImportHelper {
|
|||||||
val memberNameToId = mutableMapOf<String, Long>()
|
val memberNameToId = mutableMapOf<String, Long>()
|
||||||
val pid = db.addProject(DBProject(0, projectRemoteId, "", projectRemoteId, null, null, null, ProjectType.LOCAL, 0L, mainCurrencyName, false, DBProject.ACCESS_LEVEL_UNKNOWN, null))
|
val pid = db.addProject(DBProject(0, projectRemoteId, "", projectRemoteId, null, null, null, ProjectType.LOCAL, 0L, mainCurrencyName, false, DBProject.ACCESS_LEVEL_UNKNOWN, null))
|
||||||
|
|
||||||
paymentModes.forEach { db.addPaymentMode(DBPaymentMode(0, it.remoteId, pid, it.name, it.icon, it.color)) }
|
val pmRemoteToLocal = mutableMapOf<Long, Long>()
|
||||||
categories.forEach { db.addCategory(DBCategory(0, it.remoteId, pid, it.name, it.icon, it.color)) }
|
paymentModes.forEach {
|
||||||
|
val newId = db.addPaymentMode(DBPaymentMode(0, it.remoteId, pid, it.name, it.icon, it.color))
|
||||||
|
pmRemoteToLocal[it.remoteId] = newId
|
||||||
|
}
|
||||||
|
val catRemoteToLocal = mutableMapOf<Long, Long>()
|
||||||
|
categories.forEach {
|
||||||
|
val newId = db.addCategory(DBCategory(0, it.remoteId, pid, it.name, it.icon, it.color))
|
||||||
|
catRemoteToLocal[it.remoteId] = newId
|
||||||
|
}
|
||||||
currencies.forEach { db.addCurrency(DBCurrency(0, 0, pid, it.name, it.exchangeRate, DBBill.STATE_OK)) }
|
currencies.forEach { db.addCurrency(DBCurrency(0, 0, pid, it.name, it.exchangeRate, DBBill.STATE_OK)) }
|
||||||
|
|
||||||
membersWeight.keys.forEach { mName ->
|
membersWeight.keys.forEach { mName ->
|
||||||
@@ -160,7 +168,9 @@ object ProjectImportHelper {
|
|||||||
|
|
||||||
bills.forEach { b ->
|
bills.forEach { b ->
|
||||||
val payerId = memberNameToId[billRemoteIdToPayerName[b.remoteId]] ?: 0L
|
val payerId = memberNameToId[billRemoteIdToPayerName[b.remoteId]] ?: 0L
|
||||||
val billId = db.addBill(DBBill(0, 0, pid, payerId, b.amount, b.timestamp, b.what, DBBill.STATE_OK, b.repeat, b.paymentMode, b.categoryRemoteId, b.comment, b.paymentModeRemoteId))
|
val localCatId = catRemoteToLocal[b.categoryId] ?: 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))
|
||||||
billRemoteIdToOwerStr[b.remoteId]?.split(", ")?.filter { it.isNotEmpty() }?.forEach { ower ->
|
billRemoteIdToOwerStr[b.remoteId]?.split(", ")?.filter { it.isNotEmpty() }?.forEach { ower ->
|
||||||
memberNameToId[ower.trim()]?.let { owerId -> db.addBillower(billId, owerId) }
|
memberNameToId[ower.trim()]?.let { owerId -> db.addBillower(billId, owerId) }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,19 +4,21 @@ import androidx.compose.foundation.clickable
|
|||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.material.*
|
import androidx.compose.material.*
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.automirrored.filled.Label
|
||||||
import androidx.compose.material.icons.filled.*
|
import androidx.compose.material.icons.filled.*
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
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
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import net.helcel.cowspent.R
|
import net.helcel.cowspent.R
|
||||||
|
|
||||||
import net.helcel.cowspent.model.DBProject
|
import net.helcel.cowspent.model.DBProject
|
||||||
|
import net.helcel.cowspent.model.ProjectType
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ProjectOptionsDialogContent(
|
fun ProjectOptionsDialogContent(
|
||||||
@@ -24,14 +26,17 @@ fun ProjectOptionsDialogContent(
|
|||||||
onRemoveProject: () -> Unit,
|
onRemoveProject: () -> Unit,
|
||||||
onManageMembers: () -> Unit,
|
onManageMembers: () -> Unit,
|
||||||
onManageCurrencies: () -> Unit,
|
onManageCurrencies: () -> Unit,
|
||||||
|
onManageLabels: () -> Unit,
|
||||||
onStatistics: () -> Unit,
|
onStatistics: () -> Unit,
|
||||||
onSettle: () -> Unit,
|
onSettle: () -> Unit,
|
||||||
onShareProject: () -> Unit,
|
onShareProject: () -> Unit,
|
||||||
onExportProject: () -> Unit,
|
onExportProject: () -> Unit,
|
||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
isArchived: Boolean = false,
|
isArchived: Boolean = false,
|
||||||
|
projectType: ProjectType = ProjectType.LOCAL,
|
||||||
accessLevel: Int = DBProject.ACCESS_LEVEL_ADMIN,
|
accessLevel: Int = DBProject.ACCESS_LEVEL_ADMIN,
|
||||||
isShareable: Boolean = true
|
isShareable: Boolean = true,
|
||||||
|
showBetaFeatures: Boolean = false
|
||||||
) {
|
) {
|
||||||
Surface(
|
Surface(
|
||||||
shape = MaterialTheme.shapes.large,
|
shape = MaterialTheme.shapes.large,
|
||||||
@@ -44,46 +49,71 @@ fun ProjectOptionsDialogContent(
|
|||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.choose_project_management_action),
|
text = stringResource(R.string.choose_project_management_action).uppercase(),
|
||||||
style = MaterialTheme.typography.h6,
|
style = MaterialTheme.typography.subtitle1,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = MaterialTheme.colors.onSurface,
|
||||||
modifier = Modifier.padding(bottom = 8.dp)
|
modifier = Modifier.padding(bottom = 8.dp)
|
||||||
)
|
)
|
||||||
|
|
||||||
val options = mutableListOf<ProjectOption>()
|
|
||||||
val isMaintainer = accessLevel >= DBProject.ACCESS_LEVEL_MAINTAINER || accessLevel == DBProject.ACCESS_LEVEL_UNKNOWN
|
val isMaintainer = accessLevel >= DBProject.ACCESS_LEVEL_MAINTAINER || accessLevel == DBProject.ACCESS_LEVEL_UNKNOWN
|
||||||
val isParticipant = accessLevel >= DBProject.ACCESS_LEVEL_PARTICIPANT || accessLevel == DBProject.ACCESS_LEVEL_UNKNOWN
|
val isParticipant = accessLevel >= DBProject.ACCESS_LEVEL_PARTICIPANT || accessLevel == DBProject.ACCESS_LEVEL_UNKNOWN
|
||||||
|
|
||||||
|
val row1 = mutableListOf<ProjectOption>()
|
||||||
|
val row2 = mutableListOf<ProjectOption>()
|
||||||
|
val row3 = mutableListOf<ProjectOption>()
|
||||||
|
|
||||||
if (!isArchived && isMaintainer) {
|
if (!isArchived && isMaintainer) {
|
||||||
options.add(ProjectOption(stringResource(R.string.action_edit_project), Icons.Default.Edit, onEditProject))
|
row1.add(ProjectOption(stringResource(R.string.action_edit), Icons.Default.Edit, onEditProject))
|
||||||
}
|
|
||||||
options.add(ProjectOption(stringResource(R.string.fab_rm_project), Icons.Default.Delete, onRemoveProject))
|
|
||||||
if (!isArchived && isMaintainer) {
|
|
||||||
options.add(ProjectOption(stringResource(R.string.fab_manage_members), Icons.Default.Group, onManageMembers))
|
|
||||||
options.add(ProjectOption(stringResource(R.string.fab_manage_currencies), Icons.Default.MonetizationOn, onManageCurrencies))
|
|
||||||
}
|
|
||||||
options.add(ProjectOption(stringResource(R.string.fab_statistics), Icons.Default.BarChart, onStatistics))
|
|
||||||
if (!isArchived && isParticipant) {
|
|
||||||
options.add(ProjectOption(stringResource(R.string.fab_settle), Icons.Default.Handshake, onSettle))
|
|
||||||
}
|
}
|
||||||
if (isShareable && isParticipant) {
|
if (isShareable && isParticipant) {
|
||||||
options.add(ProjectOption(stringResource(R.string.action_share_project), Icons.Default.Share, onShareProject))
|
row1.add(ProjectOption(stringResource(R.string.action_share), Icons.Default.Share, onShareProject))
|
||||||
|
}
|
||||||
|
if (projectType == ProjectType.COSPEND) {
|
||||||
|
val archiveLabel = if (isArchived) stringResource(R.string.action_unarchive) else stringResource(R.string.action_archive)
|
||||||
|
val archiveIcon = if (isArchived) Icons.Default.Unarchive else Icons.Default.Archive
|
||||||
|
row1.add(ProjectOption(archiveLabel, archiveIcon, onRemoveProject))
|
||||||
|
} else {
|
||||||
|
row1.add(ProjectOption(stringResource(R.string.action_delete), Icons.Default.Delete, onRemoveProject))
|
||||||
}
|
}
|
||||||
options.add(ProjectOption(stringResource(R.string.fab_export_project), Icons.Default.Download, onExportProject))
|
|
||||||
|
|
||||||
// Simple 2-column grid using Rows
|
// Row 2: Manage Member, Manage Labels, Manage Currencies
|
||||||
for (i in options.indices step 2) {
|
if (!isArchived && isMaintainer) {
|
||||||
Row(modifier = Modifier.fillMaxWidth()) {
|
row2.add(ProjectOption(stringResource(R.string.action_members), Icons.Default.Group, onManageMembers))
|
||||||
ProjectOptionItem(
|
if (showBetaFeatures && (projectType == ProjectType.LOCAL || projectType == ProjectType.COSPEND)) {
|
||||||
option = options[i],
|
row2.add(ProjectOption(stringResource(R.string.action_labels), Icons.AutoMirrored.Filled.Label, onManageLabels))
|
||||||
modifier = Modifier.weight(1f)
|
}
|
||||||
)
|
row2.add(ProjectOption(stringResource(R.string.action_currencies), Icons.Default.MonetizationOn, onManageCurrencies))
|
||||||
if (i + 1 < options.size) {
|
}
|
||||||
|
|
||||||
|
// Row 3: Statistics, Settle, Export
|
||||||
|
row3.add(ProjectOption(stringResource(R.string.action_stats), Icons.Default.BarChart, onStatistics))
|
||||||
|
if (!isArchived && isParticipant) {
|
||||||
|
row3.add(ProjectOption(stringResource(R.string.action_settle), Icons.Default.Handshake, onSettle))
|
||||||
|
}
|
||||||
|
row3.add(ProjectOption(stringResource(R.string.action_export), Icons.Default.Download, onExportProject))
|
||||||
|
|
||||||
|
val allRows = listOf(row1, row2, row3).filter { it.isNotEmpty() }
|
||||||
|
|
||||||
|
allRows.forEach { rowOptions ->
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.Center
|
||||||
|
) {
|
||||||
|
val emptySpace = 3 - rowOptions.size
|
||||||
|
if (emptySpace > 0) {
|
||||||
|
Spacer(modifier = Modifier.weight(emptySpace / 2f))
|
||||||
|
}
|
||||||
|
|
||||||
|
rowOptions.forEach { option ->
|
||||||
ProjectOptionItem(
|
ProjectOptionItem(
|
||||||
option = options[i + 1],
|
option = option,
|
||||||
modifier = Modifier.weight(1f)
|
modifier = Modifier.weight(1f)
|
||||||
)
|
)
|
||||||
} else {
|
}
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
|
||||||
|
if (emptySpace > 0) {
|
||||||
|
Spacer(modifier = Modifier.weight(emptySpace / 2f))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -145,12 +175,64 @@ fun ProjectOptionsDialogPreview() {
|
|||||||
onManageCurrencies = {},
|
onManageCurrencies = {},
|
||||||
onStatistics = {},
|
onStatistics = {},
|
||||||
onSettle = {},
|
onSettle = {},
|
||||||
|
onManageLabels = {},
|
||||||
onShareProject = {},
|
onShareProject = {},
|
||||||
onExportProject = {},
|
onExportProject = {},
|
||||||
onDismiss = {},
|
onDismiss = {},
|
||||||
isArchived = false,
|
isArchived = false,
|
||||||
|
projectType = ProjectType.COSPEND,
|
||||||
accessLevel = DBProject.ACCESS_LEVEL_ADMIN,
|
accessLevel = DBProject.ACCESS_LEVEL_ADMIN,
|
||||||
isShareable = true
|
isShareable = true,
|
||||||
|
showBetaFeatures = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Preview(showBackground = true)
|
||||||
|
@Composable
|
||||||
|
fun ProjectOptionsDialogPreview2() {
|
||||||
|
MaterialTheme {
|
||||||
|
ProjectOptionsDialogContent(
|
||||||
|
onEditProject = {},
|
||||||
|
onRemoveProject = {},
|
||||||
|
onManageMembers = {},
|
||||||
|
onManageCurrencies = {},
|
||||||
|
onStatistics = {},
|
||||||
|
onSettle = {},
|
||||||
|
onManageLabels = {},
|
||||||
|
onShareProject = {},
|
||||||
|
onExportProject = {},
|
||||||
|
onDismiss = {},
|
||||||
|
isArchived = true,
|
||||||
|
projectType = ProjectType.COSPEND,
|
||||||
|
accessLevel = DBProject.ACCESS_LEVEL_ADMIN,
|
||||||
|
isShareable = true,
|
||||||
|
showBetaFeatures = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(showBackground = true)
|
||||||
|
@Composable
|
||||||
|
fun ProjectOptionsDialogPreview3() {
|
||||||
|
MaterialTheme {
|
||||||
|
ProjectOptionsDialogContent(
|
||||||
|
onEditProject = {},
|
||||||
|
onRemoveProject = {},
|
||||||
|
onManageMembers = {},
|
||||||
|
onManageCurrencies = {},
|
||||||
|
onStatistics = {},
|
||||||
|
onSettle = {},
|
||||||
|
onManageLabels = {},
|
||||||
|
onShareProject = {},
|
||||||
|
onExportProject = {},
|
||||||
|
onDismiss = {},
|
||||||
|
isArchived = false,
|
||||||
|
projectType = ProjectType.LOCAL,
|
||||||
|
accessLevel = DBProject.ACCESS_LEVEL_ADMIN,
|
||||||
|
isShareable = true,
|
||||||
|
showBetaFeatures = true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import androidx.compose.foundation.layout.Box
|
|||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
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.height
|
||||||
import androidx.compose.foundation.layout.heightIn
|
import androidx.compose.foundation.layout.heightIn
|
||||||
@@ -31,8 +32,6 @@ import androidx.compose.material.Text
|
|||||||
import androidx.compose.material.contentColorFor
|
import androidx.compose.material.contentColorFor
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.ContentCopy
|
import androidx.compose.material.icons.filled.ContentCopy
|
||||||
import androidx.compose.material.icons.filled.Link
|
|
||||||
import androidx.compose.material.icons.filled.QrCode
|
|
||||||
import androidx.compose.material.icons.filled.Share
|
import androidx.compose.material.icons.filled.Share
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
@@ -42,10 +41,8 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.asImageBitmap
|
import androidx.compose.ui.graphics.asImageBitmap
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
|
||||||
import androidx.compose.ui.platform.LocalClipboardManager
|
import androidx.compose.ui.platform.LocalClipboardManager
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.colorResource
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.AnnotatedString
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
@@ -69,7 +66,7 @@ fun ProjectShareDialogContent(
|
|||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val clipboardManager = LocalClipboardManager.current
|
val clipboardManager = LocalClipboardManager.current
|
||||||
val qrCodeLinkWarn = stringResource(R.string.qrcode_link_open_attempt_warning)
|
val qrCodeLinkWarn = stringResource(R.string.msg_share_qr_warn)
|
||||||
|
|
||||||
val shareUrl = remember { proj.getShareUrl() }
|
val shareUrl = remember { proj.getShareUrl() }
|
||||||
val publicWebUrl = remember { proj.getPublicWebUrl() }
|
val publicWebUrl = remember { proj.getPublicWebUrl() }
|
||||||
@@ -93,35 +90,24 @@ fun ProjectShareDialogContent(
|
|||||||
.heightIn(max = 650.dp)
|
.heightIn(max = 650.dp)
|
||||||
.padding(16.dp)
|
.padding(16.dp)
|
||||||
) {
|
) {
|
||||||
Row(
|
Text(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
text = stringResource(R.string.title_share).uppercase(),
|
||||||
modifier = Modifier.padding(bottom = 8.dp)
|
style = MaterialTheme.typography.subtitle1,
|
||||||
) {
|
fontWeight = FontWeight.Bold,
|
||||||
Icon(
|
color = MaterialTheme.colors.onSurface,
|
||||||
Icons.Default.Share,
|
modifier = Modifier.padding(bottom = 16.dp)
|
||||||
contentDescription = null,
|
)
|
||||||
tint = MaterialTheme.colors.primary,
|
|
||||||
modifier = Modifier.size(24.dp)
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.width(12.dp))
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.share_dialog_title),
|
|
||||||
style = MaterialTheme.typography.h6,
|
|
||||||
fontWeight = FontWeight.Bold
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.weight(1f, fill = false)
|
.weight(1f, fill = false)
|
||||||
.verticalScroll(rememberScrollState()),
|
.verticalScroll(rememberScrollState()),
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
verticalArrangement = Arrangement.spacedBy(24.dp)
|
||||||
) {
|
) {
|
||||||
ShareCard(
|
ShareCard(
|
||||||
title = stringResource(R.string.share_project_public_url_title),
|
title = stringResource(R.string.title_share_web),
|
||||||
url = publicWebUrl,
|
url = publicWebUrl,
|
||||||
description = stringResource(R.string.share_project_public_url_dialog_message),
|
description = stringResource(R.string.msg_share_web),
|
||||||
icon = Icons.Default.Link,
|
|
||||||
onUrlClick = {
|
onUrlClick = {
|
||||||
val i = Intent(Intent.ACTION_VIEW).apply {
|
val i = Intent(Intent.ACTION_VIEW).apply {
|
||||||
data = publicWebUrl.toUri()
|
data = publicWebUrl.toUri()
|
||||||
@@ -130,22 +116,21 @@ fun ProjectShareDialogContent(
|
|||||||
},
|
},
|
||||||
onCopyClick = {
|
onCopyClick = {
|
||||||
clipboardManager.setText(AnnotatedString(publicWebUrl))
|
clipboardManager.setText(AnnotatedString(publicWebUrl))
|
||||||
Toast.makeText(context, "Link copied to clipboard", Toast.LENGTH_SHORT).show()
|
Toast.makeText(context, R.string.msg_link_copied, Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
ShareCard(
|
ShareCard(
|
||||||
title = stringResource(R.string.share_project_public_qrcode_title),
|
title = stringResource(R.string.title_share_qr),
|
||||||
url = shareUrl,
|
url = shareUrl,
|
||||||
description = stringResource(R.string.share_project_dialog_message),
|
description = stringResource(R.string.msg_share_qr),
|
||||||
icon = Icons.Default.QrCode,
|
|
||||||
qrBitmap = qrBitmap,
|
qrBitmap = qrBitmap,
|
||||||
onUrlClick = {
|
onUrlClick = {
|
||||||
Toast.makeText(context, qrCodeLinkWarn, Toast.LENGTH_SHORT).show()
|
Toast.makeText(context, qrCodeLinkWarn, Toast.LENGTH_SHORT).show()
|
||||||
},
|
},
|
||||||
onCopyClick = {
|
onCopyClick = {
|
||||||
clipboardManager.setText(AnnotatedString(shareUrl))
|
clipboardManager.setText(AnnotatedString(shareUrl))
|
||||||
Toast.makeText(context, "Link copied to clipboard", Toast.LENGTH_SHORT).show()
|
Toast.makeText(context, R.string.msg_link_copied, Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -165,43 +150,31 @@ private fun ShareCard(
|
|||||||
title: String,
|
title: String,
|
||||||
url: String,
|
url: String,
|
||||||
description: String,
|
description: String,
|
||||||
icon: ImageVector,
|
|
||||||
onUrlClick: () -> Unit,
|
onUrlClick: () -> Unit,
|
||||||
onCopyClick: () -> Unit,
|
onCopyClick: () -> Unit,
|
||||||
qrBitmap: Bitmap? = null
|
qrBitmap: Bitmap? = null
|
||||||
) {
|
) {
|
||||||
Surface(
|
Column(
|
||||||
shape = MaterialTheme.shapes.medium,
|
|
||||||
color = colorResource(R.color.fg_default_low).copy(alpha = 0.04f),
|
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
) {
|
) {
|
||||||
Column(
|
Text(
|
||||||
modifier = Modifier.padding(8.dp),
|
text = title.uppercase(),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
style = MaterialTheme.typography.subtitle1,
|
||||||
) {
|
fontWeight = FontWeight.Bold,
|
||||||
Row(
|
color = MaterialTheme.colors.onSurface,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.padding(bottom = 8.dp)
|
||||||
verticalAlignment = Alignment.CenterVertically
|
)
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = icon,
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.size(18.dp),
|
|
||||||
tint = MaterialTheme.colors.primary.copy(alpha = 0.7f)
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
|
||||||
Text(
|
|
||||||
text = title,
|
|
||||||
style = MaterialTheme.typography.subtitle2,
|
|
||||||
fontWeight = FontWeight.Bold,
|
|
||||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.8f)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (qrBitmap != null) {
|
if (qrBitmap != null) {
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(bottom = 12.dp),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier.size(128.dp)
|
modifier = Modifier
|
||||||
|
.size(128.dp)
|
||||||
.clip(MaterialTheme.shapes.small)
|
.clip(MaterialTheme.shapes.small)
|
||||||
.background(Color.White),
|
.background(Color.White),
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
@@ -209,55 +182,53 @@ private fun ShareCard(
|
|||||||
Image(
|
Image(
|
||||||
bitmap = qrBitmap.asImageBitmap(),
|
bitmap = qrBitmap.asImageBitmap(),
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxSize()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Surface(
|
||||||
|
shape = MaterialTheme.shapes.small,
|
||||||
Surface(
|
color = MaterialTheme.colors.onSurface.copy(alpha = 0.05f),
|
||||||
shape = MaterialTheme.shapes.small,
|
modifier = Modifier.fillMaxWidth()
|
||||||
color = MaterialTheme.colors.surface,
|
) {
|
||||||
modifier = Modifier.fillMaxWidth()
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.clickable { onUrlClick() }
|
||||||
|
.padding(start = 12.dp, end = 4.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Row(
|
Text(
|
||||||
modifier = Modifier
|
text = url,
|
||||||
.clickable { onUrlClick() }
|
modifier = Modifier.weight(1f),
|
||||||
.padding(start = 12.dp, end = 4.dp),
|
color = MaterialTheme.colors.primary,
|
||||||
verticalAlignment = Alignment.CenterVertically
|
style = MaterialTheme.typography.body2,
|
||||||
) {
|
maxLines = 1,
|
||||||
Text(
|
overflow = TextOverflow.Ellipsis,
|
||||||
text = url,
|
fontWeight = FontWeight.Medium
|
||||||
modifier = Modifier.weight(1f),
|
)
|
||||||
color = MaterialTheme.colors.primary,
|
IconButton(onClick = onCopyClick) {
|
||||||
style = MaterialTheme.typography.body2,
|
Icon(
|
||||||
maxLines = 1,
|
Icons.Default.ContentCopy,
|
||||||
overflow = TextOverflow.Ellipsis,
|
contentDescription = "Copy",
|
||||||
fontWeight = FontWeight.Medium
|
modifier = Modifier.size(18.dp),
|
||||||
|
tint = MaterialTheme.colors.onSurface.copy(alpha = 0.4f)
|
||||||
)
|
)
|
||||||
IconButton(onClick = onCopyClick) {
|
|
||||||
Icon(
|
|
||||||
Icons.Default.ContentCopy,
|
|
||||||
contentDescription = "Copy",
|
|
||||||
modifier = Modifier.size(18.dp),
|
|
||||||
tint = colorResource(R.color.fg_default_low)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
|
||||||
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
|
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
|
||||||
Text(
|
Text(
|
||||||
text = description,
|
text = description,
|
||||||
style = MaterialTheme.typography.caption,
|
style = MaterialTheme.typography.caption,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Start,
|
||||||
lineHeight = 16.sp,
|
lineHeight = 16.sp,
|
||||||
modifier = Modifier.padding(horizontal = 8.dp)
|
modifier = Modifier.padding(horizontal = 4.dp)
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -291,7 +262,7 @@ private fun DialogActions(
|
|||||||
Icon(Icons.Default.Share, contentDescription = null, modifier = Modifier.size(16.dp))
|
Icon(Icons.Default.Share, contentDescription = null, modifier = Modifier.size(16.dp))
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.simple_share_share).uppercase(),
|
text = stringResource(R.string.action_share).uppercase(),
|
||||||
style = MaterialTheme.typography.button,
|
style = MaterialTheme.typography.button,
|
||||||
fontWeight = FontWeight.Bold
|
fontWeight = FontWeight.Bold
|
||||||
)
|
)
|
||||||
|
|||||||
+42
-16
@@ -5,6 +5,7 @@ import android.net.Uri
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Patterns
|
import android.util.Patterns
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
@@ -20,6 +21,7 @@ import net.helcel.cowspent.model.*
|
|||||||
import net.helcel.cowspent.persistence.CowspentSQLiteOpenHelper
|
import net.helcel.cowspent.persistence.CowspentSQLiteOpenHelper
|
||||||
import net.helcel.cowspent.theme.ThemeUtils
|
import net.helcel.cowspent.theme.ThemeUtils
|
||||||
import net.helcel.cowspent.util.*
|
import net.helcel.cowspent.util.*
|
||||||
|
import java.util.Locale
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import net.helcel.cowspent.model.ProjectType
|
import net.helcel.cowspent.model.ProjectType
|
||||||
@@ -33,6 +35,7 @@ class NewProjectActivity : AppCompatActivity() {
|
|||||||
private lateinit var db: CowspentSQLiteOpenHelper
|
private lateinit var db: CowspentSQLiteOpenHelper
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
enableEdgeToEdge()
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
db = CowspentSQLiteOpenHelper.getInstance(this)
|
db = CowspentSQLiteOpenHelper.getInstance(this)
|
||||||
|
|
||||||
@@ -58,7 +61,8 @@ class NewProjectActivity : AppCompatActivity() {
|
|||||||
chooseFromNextcloud()
|
chooseFromNextcloud()
|
||||||
},
|
},
|
||||||
onOkPressed = { onPressOk() },
|
onOkPressed = { onPressOk() },
|
||||||
onBack = { finish() }
|
onBack = { finish() },
|
||||||
|
onFieldsChanged = { updateAuthStatus() }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -84,6 +88,8 @@ class NewProjectActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateAuthStatus()
|
||||||
|
|
||||||
val defaultProjectId = intent.getStringExtra(PARAM_DEFAULT_PROJECT_ID)
|
val defaultProjectId = intent.getStringExtra(PARAM_DEFAULT_PROJECT_ID)
|
||||||
if (defaultProjectId != null) {
|
if (defaultProjectId != null) {
|
||||||
viewModel.projectId = defaultProjectId
|
viewModel.projectId = defaultProjectId
|
||||||
@@ -137,6 +143,17 @@ class NewProjectActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun updateAuthStatus() {
|
||||||
|
val url = getFormattedUrl()
|
||||||
|
val fakeProj = DBProject(
|
||||||
|
0, "", "", "", url,
|
||||||
|
"", 0L, viewModel.projectType, 0L,
|
||||||
|
null, false, DBProject.ACCESS_LEVEL_UNKNOWN,
|
||||||
|
""
|
||||||
|
)
|
||||||
|
viewModel.isAuthenticatedAccount = db.cowspentServerSyncHelper.canCreateAuthenticatedProject(fakeProj)
|
||||||
|
}
|
||||||
|
|
||||||
private fun onPressOk() {
|
private fun onPressOk() {
|
||||||
val type = viewModel.projectType
|
val type = viewModel.projectType
|
||||||
val todoCreate = viewModel.whatTodoIsCreate
|
val todoCreate = viewModel.whatTodoIsCreate
|
||||||
@@ -148,15 +165,7 @@ class NewProjectActivity : AppCompatActivity() {
|
|||||||
null, false, DBProject.ACCESS_LEVEL_UNKNOWN,
|
null, false, DBProject.ACCESS_LEVEL_UNKNOWN,
|
||||||
""
|
""
|
||||||
)
|
)
|
||||||
|
createProject()
|
||||||
if (isValidUrl(url) && todoCreate && ProjectType.COSPEND == type &&
|
|
||||||
db.cowspentServerSyncHelper.canCreateAuthenticatedProject(fakeProj) &&
|
|
||||||
!viewModel.showAuthWarningDialog // Avoid infinite loop
|
|
||||||
) {
|
|
||||||
viewModel.showAuthWarningDialog = true
|
|
||||||
} else {
|
|
||||||
createProject()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getFormattedUrl(): String {
|
private fun getFormattedUrl(): String {
|
||||||
@@ -184,15 +193,24 @@ class NewProjectActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun createProject() {
|
private fun createProject() {
|
||||||
val isCospendScheme = isCospendSchemeLink(getFormattedUrl())
|
val url = getFormattedUrl()
|
||||||
|
val isCospendScheme = isCospendSchemeLink(url)
|
||||||
|
|
||||||
|
if (viewModel.whatTodoIsCreate && viewModel.projectId.isEmpty() && viewModel.projectName.isNotEmpty()) {
|
||||||
|
viewModel.projectId = viewModel.projectName.lowercase(Locale.ROOT)
|
||||||
|
.replace("[^a-z0-9]".toRegex(), "-")
|
||||||
|
.replace("-+".toRegex(), "-")
|
||||||
|
.trim('-')
|
||||||
|
}
|
||||||
|
|
||||||
val rid = viewModel.projectId
|
val rid = viewModel.projectId
|
||||||
if (!isCospendScheme && (rid == "" || rid.contains(",") || rid.contains("/"))) {
|
if (!isCospendScheme && (rid == "" || rid.contains(",") || rid.contains("/"))) {
|
||||||
showToast(getString(R.string.error_invalid_project_remote_id), Toast.LENGTH_LONG)
|
showToast(getString(R.string.error_invalid_project_id), Toast.LENGTH_LONG)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (viewModel.projectType != ProjectType.LOCAL && !isCospendScheme) {
|
if (viewModel.projectType != ProjectType.LOCAL && !isCospendScheme) {
|
||||||
if (!isValidUrl(getFormattedUrl())) {
|
if (!isValidUrl(url)) {
|
||||||
showToast("Invalid URL", Toast.LENGTH_SHORT)
|
showToast("Invalid URL", Toast.LENGTH_SHORT)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -215,7 +233,16 @@ class NewProjectActivity : AppCompatActivity() {
|
|||||||
showToast("Invalid project name", Toast.LENGTH_SHORT)
|
showToast("Invalid project name", Toast.LENGTH_SHORT)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (!SupportUtil.isValidEmail(viewModel.projectEmail)) {
|
|
||||||
|
val fakeProj = DBProject(
|
||||||
|
0, rid, "", viewModel.projectName, url,
|
||||||
|
viewModel.projectEmail, 0L, viewModel.projectType, 0L,
|
||||||
|
null, false, DBProject.ACCESS_LEVEL_UNKNOWN,
|
||||||
|
""
|
||||||
|
)
|
||||||
|
val isAuthenticated = db.cowspentServerSyncHelper.canCreateAuthenticatedProject(fakeProj)
|
||||||
|
|
||||||
|
if (!isAuthenticated && !SupportUtil.isValidEmail(viewModel.projectEmail)) {
|
||||||
showToast("Invalid email", Toast.LENGTH_SHORT)
|
showToast("Invalid email", Toast.LENGTH_SHORT)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -224,7 +251,7 @@ class NewProjectActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
if (!db.cowspentServerSyncHelper.createRemoteProject(
|
if (!db.cowspentServerSyncHelper.createRemoteProject(
|
||||||
viewModel.projectId, viewModel.projectName,
|
viewModel.projectId, viewModel.projectName,
|
||||||
viewModel.projectEmail, viewModel.projectPassword, getFormattedUrl(), viewModel.projectType, createRemoteCallBack
|
viewModel.projectEmail, viewModel.projectPassword, url, viewModel.projectType, createRemoteCallBack
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
viewModel.isCreatingRemoteProject = false
|
viewModel.isCreatingRemoteProject = false
|
||||||
@@ -285,7 +312,6 @@ class NewProjectActivity : AppCompatActivity() {
|
|||||||
pid
|
pid
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
showToast(getString(R.string.project_added_success), Toast.LENGTH_LONG)
|
|
||||||
return pid
|
return pid
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ 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.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
@@ -32,12 +33,17 @@ fun NewProjectScreen(
|
|||||||
onImportFile: () -> Unit,
|
onImportFile: () -> Unit,
|
||||||
onChooseFromNextcloud: () -> Unit,
|
onChooseFromNextcloud: () -> Unit,
|
||||||
onOkPressed: () -> Unit,
|
onOkPressed: () -> Unit,
|
||||||
onBack: () -> Unit
|
onBack: () -> Unit,
|
||||||
|
onFieldsChanged: () -> Unit
|
||||||
) {
|
) {
|
||||||
|
LaunchedEffect(viewModel.projectUrl, viewModel.projectType) {
|
||||||
|
onFieldsChanged()
|
||||||
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = { Text(stringResource(R.string.action_add_project)) },
|
title = { Text(stringResource(R.string.title_add_project)) },
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
IconButton(onClick = onBack) {
|
IconButton(onClick = onBack) {
|
||||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)
|
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)
|
||||||
@@ -50,7 +56,7 @@ fun NewProjectScreen(
|
|||||||
floatingActionButton = {
|
floatingActionButton = {
|
||||||
if (viewModel.isFormValid()) {
|
if (viewModel.isFormValid()) {
|
||||||
FloatingActionButton(onClick = onOkPressed) {
|
FloatingActionButton(onClick = onOkPressed) {
|
||||||
Icon(Icons.Default.Done, contentDescription = stringResource(R.string.action_save_bill))
|
Icon(Icons.Default.Done, contentDescription = stringResource(R.string.action_save))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -58,17 +64,18 @@ fun NewProjectScreen(
|
|||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(padding)
|
.padding(padding)
|
||||||
|
.imePadding()
|
||||||
.padding(16.dp)
|
.padding(16.dp)
|
||||||
.verticalScroll(rememberScrollState())
|
.verticalScroll(rememberScrollState())
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
) {
|
) {
|
||||||
// What to do
|
// What to do
|
||||||
SectionRow(
|
SectionRow(
|
||||||
label = stringResource(R.string.new_project_what_todo)
|
label = stringResource(R.string.new_project_action)
|
||||||
) {
|
) {
|
||||||
Row {
|
Row {
|
||||||
ToggleButton(
|
ToggleButton(
|
||||||
text = stringResource(R.string.todo_join_label),
|
text = stringResource(R.string.todo_join),
|
||||||
selected = !viewModel.whatTodoIsCreate,
|
selected = !viewModel.whatTodoIsCreate,
|
||||||
onClick = {
|
onClick = {
|
||||||
viewModel.whatTodoIsCreate = false
|
viewModel.whatTodoIsCreate = false
|
||||||
@@ -78,7 +85,7 @@ fun NewProjectScreen(
|
|||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
ToggleButton(
|
ToggleButton(
|
||||||
text = stringResource(R.string.todo_create_label),
|
text = stringResource(R.string.todo_create),
|
||||||
selected = viewModel.whatTodoIsCreate,
|
selected = viewModel.whatTodoIsCreate,
|
||||||
onClick = { viewModel.whatTodoIsCreate = true }
|
onClick = { viewModel.whatTodoIsCreate = true }
|
||||||
)
|
)
|
||||||
@@ -93,20 +100,20 @@ fun NewProjectScreen(
|
|||||||
Row {
|
Row {
|
||||||
if (viewModel.whatTodoIsCreate) {
|
if (viewModel.whatTodoIsCreate) {
|
||||||
ToggleButton(
|
ToggleButton(
|
||||||
text = stringResource(R.string.where_local_short),
|
text = stringResource(R.string.where_local),
|
||||||
selected = viewModel.projectType == ProjectType.LOCAL,
|
selected = viewModel.projectType == ProjectType.LOCAL,
|
||||||
onClick = { viewModel.projectType = ProjectType.LOCAL }
|
onClick = { viewModel.projectType = ProjectType.LOCAL }
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
}
|
}
|
||||||
ToggleButton(
|
ToggleButton(
|
||||||
text = stringResource(R.string.where_cospend_short),
|
text = stringResource(R.string.where_cospend),
|
||||||
selected = viewModel.projectType == ProjectType.COSPEND,
|
selected = viewModel.projectType == ProjectType.COSPEND,
|
||||||
onClick = { viewModel.projectType = ProjectType.COSPEND }
|
onClick = { viewModel.projectType = ProjectType.COSPEND }
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
ToggleButton(
|
ToggleButton(
|
||||||
text = stringResource(R.string.where_ihatemoney_short),
|
text = stringResource(R.string.where_ihatemoney),
|
||||||
selected = viewModel.projectType == ProjectType.IHATEMONEY,
|
selected = viewModel.projectType == ProjectType.IHATEMONEY,
|
||||||
onClick = { viewModel.projectType = ProjectType.IHATEMONEY }
|
onClick = { viewModel.projectType = ProjectType.IHATEMONEY }
|
||||||
)
|
)
|
||||||
@@ -115,13 +122,7 @@ fun NewProjectScreen(
|
|||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
if (viewModel.whatTodoIsCreate) {
|
if (viewModel.whatTodoIsCreate) {
|
||||||
if (viewModel.projectType == ProjectType.COSPEND) {
|
if (viewModel.projectType == ProjectType.LOCAL) {
|
||||||
Button(
|
|
||||||
onClick = onChooseFromNextcloud
|
|
||||||
) {
|
|
||||||
Text(stringResource(R.string.new_project_from_nextcloud_tooltip))
|
|
||||||
}
|
|
||||||
} else if (viewModel.projectType == ProjectType.LOCAL) {
|
|
||||||
Button(
|
Button(
|
||||||
onClick = onImportFile
|
onClick = onImportFile
|
||||||
) {
|
) {
|
||||||
@@ -131,7 +132,7 @@ fun NewProjectScreen(
|
|||||||
} else {
|
} else {
|
||||||
Spacer(modifier = Modifier.width(16.dp))
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
Button(onClick = onScanQrCode) {
|
Button(onClick = onScanQrCode) {
|
||||||
Text(text = stringResource(R.string.scan_qrcode))
|
Text(text = stringResource(R.string.action_scan_qrcode))
|
||||||
Spacer(modifier = Modifier.width(4.dp))
|
Spacer(modifier = Modifier.width(4.dp))
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Default.QrCode2,
|
Icons.Default.QrCode2,
|
||||||
@@ -148,13 +149,8 @@ fun NewProjectScreen(
|
|||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = viewModel.projectUrl,
|
value = viewModel.projectUrl,
|
||||||
onValueChange = { viewModel.projectUrl = it },
|
onValueChange = { viewModel.projectUrl = it },
|
||||||
label = {
|
placeholder = {
|
||||||
Text(
|
Text(stringResource(R.string.label_url))
|
||||||
stringResource(
|
|
||||||
if (viewModel.projectType == ProjectType.COSPEND) R.string.setting_cospend_project_url
|
|
||||||
else R.string.setting_ihatemoney_project_url
|
|
||||||
)
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
leadingIcon = { Icon(Icons.Default.Link, contentDescription = null) }
|
leadingIcon = { Icon(Icons.Default.Link, contentDescription = null) }
|
||||||
@@ -162,42 +158,47 @@ fun NewProjectScreen(
|
|||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
}
|
}
|
||||||
|
|
||||||
OutlinedTextField(
|
if (!viewModel.whatTodoIsCreate || !viewModel.isAuthenticatedAccount || viewModel.projectType != ProjectType.COSPEND) {
|
||||||
value = viewModel.projectId,
|
OutlinedTextField(
|
||||||
onValueChange = { viewModel.projectId = it },
|
value = viewModel.projectId,
|
||||||
label = { Text(stringResource(R.string.setting_project_id)) },
|
onValueChange = { viewModel.projectId = it },
|
||||||
modifier = Modifier.fillMaxWidth(),
|
placeholder = { Text(stringResource(R.string.label_project_id)) },
|
||||||
leadingIcon = { Icon(Icons.AutoMirrored.Filled.LibraryBooks, contentDescription = null) }
|
modifier = Modifier.fillMaxWidth(),
|
||||||
)
|
leadingIcon = { Icon(Icons.AutoMirrored.Filled.LibraryBooks, contentDescription = null) }
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
}
|
||||||
|
|
||||||
if (viewModel.projectType != ProjectType.LOCAL && (!viewModel.whatTodoIsCreate || viewModel.projectType == ProjectType.IHATEMONEY)) {
|
if (viewModel.projectType != ProjectType.LOCAL && (!viewModel.whatTodoIsCreate || viewModel.projectType == ProjectType.IHATEMONEY)) {
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = viewModel.projectPassword,
|
value = viewModel.projectPassword,
|
||||||
onValueChange = { viewModel.projectPassword = it },
|
onValueChange = { viewModel.projectPassword = it },
|
||||||
label = { Text(stringResource(R.string.setting_new_project_password)) },
|
placeholder = { Text(stringResource(R.string.label_password)) },
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
leadingIcon = { Icon(Icons.Default.Lock, contentDescription = null) }
|
leadingIcon = { Icon(Icons.Default.Lock, contentDescription = null) }
|
||||||
)
|
)
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (viewModel.whatTodoIsCreate && viewModel.projectType != ProjectType.LOCAL) {
|
if (viewModel.whatTodoIsCreate && viewModel.projectType != ProjectType.LOCAL) {
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = viewModel.projectName,
|
value = viewModel.projectName,
|
||||||
onValueChange = { viewModel.projectName = it },
|
onValueChange = { viewModel.projectName = it },
|
||||||
label = { Text(stringResource(R.string.setting_new_project_name)) },
|
placeholder = { Text(stringResource(R.string.label_project_title)) },
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
leadingIcon = { Icon(Icons.Default.Title, contentDescription = null) }
|
leadingIcon = { Icon(Icons.Default.Title, contentDescription = null) }
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
|
||||||
OutlinedTextField(
|
if (!viewModel.isAuthenticatedAccount || viewModel.projectType == ProjectType.IHATEMONEY) {
|
||||||
value = viewModel.projectEmail,
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
onValueChange = { viewModel.projectEmail = it },
|
OutlinedTextField(
|
||||||
label = { Text(stringResource(R.string.setting_new_project_email)) },
|
value = viewModel.projectEmail,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
onValueChange = { viewModel.projectEmail = it },
|
||||||
leadingIcon = { Icon(Icons.Default.Email, contentDescription = null) }
|
placeholder = { Text(stringResource(R.string.label_email)) },
|
||||||
)
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
leadingIcon = { Icon(Icons.Default.Email, contentDescription = null) }
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -205,8 +206,8 @@ fun NewProjectScreen(
|
|||||||
if (viewModel.showAuthWarningDialog) {
|
if (viewModel.showAuthWarningDialog) {
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = { viewModel.showAuthWarningDialog = false },
|
onDismissRequest = { viewModel.showAuthWarningDialog = false },
|
||||||
title = { Text(stringResource(R.string.auth_project_creation_title)) },
|
title = { Text(stringResource(R.string.app_name)) },
|
||||||
text = { Text(stringResource(R.string.warning_auth_project_creation)) },
|
text = { Text(stringResource(R.string.msg_auth_warning)) },
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
TextButton(onClick = {
|
TextButton(onClick = {
|
||||||
viewModel.showAuthWarningDialog = false
|
viewModel.showAuthWarningDialog = false
|
||||||
@@ -260,12 +261,12 @@ fun NewProjectScreen(
|
|||||||
if (viewModel.isCreatingRemoteProject) {
|
if (viewModel.isCreatingRemoteProject) {
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = { },
|
onDismissRequest = { },
|
||||||
title = { Text(stringResource(R.string.simple_loading)) },
|
title = { Text(stringResource(R.string.error_loading)) },
|
||||||
text = {
|
text = {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
CircularProgressIndicator()
|
CircularProgressIndicator()
|
||||||
Spacer(modifier = Modifier.width(16.dp))
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
Text(stringResource(R.string.creating_remote_project))
|
Text(stringResource(R.string.action_add_project))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
confirmButton = {}
|
confirmButton = {}
|
||||||
@@ -275,7 +276,7 @@ fun NewProjectScreen(
|
|||||||
if (viewModel.errorDialogMessage != null) {
|
if (viewModel.errorDialogMessage != null) {
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = { viewModel.errorDialogMessage = null },
|
onDismissRequest = { viewModel.errorDialogMessage = null },
|
||||||
title = { Text(stringResource(R.string.simple_error)) },
|
title = { Text(stringResource(R.string.error_generic)) },
|
||||||
text = { Text(viewModel.errorDialogMessage!!) },
|
text = { Text(viewModel.errorDialogMessage!!) },
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
TextButton(onClick = { viewModel.errorDialogMessage = null }) {
|
TextButton(onClick = { viewModel.errorDialogMessage = null }) {
|
||||||
@@ -291,11 +292,15 @@ fun SectionRow(
|
|||||||
label: String,
|
label: String,
|
||||||
content: @Composable () -> Unit
|
content: @Composable () -> Unit
|
||||||
) {
|
) {
|
||||||
Row(verticalAlignment = Alignment.Top) {
|
Column(modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp)) {
|
||||||
Column {
|
Text(
|
||||||
Text(label, fontSize = 12.sp, color = MaterialTheme.colors.onSurface)
|
text = label.uppercase(),
|
||||||
content()
|
style = MaterialTheme.typography.subtitle1,
|
||||||
}
|
color = MaterialTheme.colors.onSurface,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
modifier = Modifier.padding(bottom = 8.dp)
|
||||||
|
)
|
||||||
|
content()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -334,6 +339,7 @@ fun NewProjectScreenPreview() {
|
|||||||
onImportFile = {},
|
onImportFile = {},
|
||||||
onChooseFromNextcloud = {},
|
onChooseFromNextcloud = {},
|
||||||
onOkPressed = {},
|
onOkPressed = {},
|
||||||
onBack = {}
|
onBack = {},
|
||||||
|
onFieldsChanged = {}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-4
@@ -30,6 +30,8 @@ class NewProjectViewModel : ViewModel() {
|
|||||||
var projectName by mutableStateOf("")
|
var projectName by mutableStateOf("")
|
||||||
var projectEmail by mutableStateOf("")
|
var projectEmail by mutableStateOf("")
|
||||||
|
|
||||||
|
var isAuthenticatedAccount by mutableStateOf(false)
|
||||||
|
|
||||||
var showAuthWarningDialog by mutableStateOf(false)
|
var showAuthWarningDialog by mutableStateOf(false)
|
||||||
var showNextcloudProjectDialog by mutableStateOf(false)
|
var showNextcloudProjectDialog by mutableStateOf(false)
|
||||||
var nextcloudProjects by mutableStateOf<List<DBAccountProject>>(emptyList())
|
var nextcloudProjects by mutableStateOf<List<DBAccountProject>>(emptyList())
|
||||||
@@ -39,12 +41,23 @@ class NewProjectViewModel : ViewModel() {
|
|||||||
|
|
||||||
fun isFormValid(): Boolean {
|
fun isFormValid(): Boolean {
|
||||||
if (whatTodoIsCreate) {
|
if (whatTodoIsCreate) {
|
||||||
if (projectId.isEmpty()) return false
|
if (projectType == ProjectType.LOCAL) {
|
||||||
if (projectType != ProjectType.LOCAL) {
|
return projectId.isNotEmpty()
|
||||||
|
} else {
|
||||||
if (projectUrl.isEmpty()) return false
|
if (projectUrl.isEmpty()) return false
|
||||||
if (projectName.isEmpty()) return false
|
if (projectName.isEmpty()) return false
|
||||||
if (projectEmail.isEmpty()) return false
|
|
||||||
if (projectType == ProjectType.IHATEMONEY && projectPassword.isEmpty()) return false
|
if (projectType == ProjectType.COSPEND && isAuthenticatedAccount) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (projectId.isEmpty()) return false
|
||||||
|
if (projectType == ProjectType.IHATEMONEY) {
|
||||||
|
if (projectEmail.isEmpty()) return false
|
||||||
|
if (projectPassword.isEmpty()) return false
|
||||||
|
} else {
|
||||||
|
if (projectEmail.isEmpty()) return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Join
|
// Join
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package net.helcel.cowspent.android.project.edit
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
@@ -27,6 +28,7 @@ class EditProjectActivity : AppCompatActivity() {
|
|||||||
private lateinit var project: DBProject
|
private lateinit var project: DBProject
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
enableEdgeToEdge()
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
db = CowspentSQLiteOpenHelper.getInstance(this)
|
db = CowspentSQLiteOpenHelper.getInstance(this)
|
||||||
|
|
||||||
@@ -135,7 +137,7 @@ class EditProjectActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
private fun onDeleteRemote() {
|
private fun onDeleteRemote() {
|
||||||
viewModel.showDialog(
|
viewModel.showDialog(
|
||||||
message = getString(R.string.confirm_delete_project_dialog_title),
|
message = getString(R.string.title_confirm),
|
||||||
positiveText = getString(R.string.simple_yes),
|
positiveText = getString(R.string.simple_yes),
|
||||||
onConfirm = {
|
onConfirm = {
|
||||||
if (!db.cowspentServerSyncHelper.deleteRemoteProject(project.id, deleteCallBack)) {
|
if (!db.cowspentServerSyncHelper.deleteRemoteProject(project.id, deleteCallBack)) {
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package net.helcel.cowspent.android.project.edit
|
|||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.*
|
import androidx.compose.material.*
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
@@ -14,6 +16,7 @@ import androidx.compose.material.icons.filled.Title
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import net.helcel.cowspent.R
|
import net.helcel.cowspent.R
|
||||||
@@ -59,70 +62,91 @@ fun EditProjectScreen(
|
|||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = { Text(stringResource(R.string.simple_edit_project)) },
|
title = { Text(stringResource(R.string.title_edit_project)) },
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
IconButton(onClick = onBack) {
|
IconButton(onClick = onBack) {
|
||||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)
|
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions = {
|
actions = {
|
||||||
IconButton(onClick = onDeleteRemote) {
|
if (!viewModel.isLocal) {
|
||||||
Icon(Icons.Default.DeleteForever, contentDescription = stringResource(R.string.menu_delete_project_remote))
|
IconButton(onClick = onDeleteRemote) {
|
||||||
|
Icon(Icons.Default.DeleteForever, contentDescription = stringResource(R.string.action_delete))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
backgroundColor = MaterialTheme.colors.surface,
|
backgroundColor = MaterialTheme.colors.primary,
|
||||||
elevation = 0.dp
|
elevation = 0.dp
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
floatingActionButton = {
|
floatingActionButton = {
|
||||||
FloatingActionButton(onClick = onSave) {
|
FloatingActionButton(onClick = onSave) {
|
||||||
Icon(Icons.Default.Done, contentDescription = stringResource(R.string.menu_save_project))
|
Icon(Icons.Default.Done, contentDescription = stringResource(R.string.action_save))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
) { padding ->
|
) { padding ->
|
||||||
|
val scrollState = rememberScrollState()
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(padding)
|
.padding(padding)
|
||||||
|
.imePadding()
|
||||||
.padding(16.dp)
|
.padding(16.dp)
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
|
.verticalScroll(scrollState)
|
||||||
) {
|
) {
|
||||||
|
Text(
|
||||||
|
text = "GENERAL",
|
||||||
|
style = MaterialTheme.typography.subtitle1,
|
||||||
|
color = MaterialTheme.colors.onSurface,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
modifier = Modifier.padding(bottom = 8.dp)
|
||||||
|
)
|
||||||
|
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = viewModel.name,
|
value = viewModel.name,
|
||||||
onValueChange = { viewModel.name = it },
|
onValueChange = { viewModel.name = it },
|
||||||
label = { Text(stringResource(R.string.setting_new_project_name)) },
|
placeholder = { Text(stringResource(R.string.label_project_title)) },
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
leadingIcon = { Icon(Icons.Default.Title, contentDescription = null) }
|
leadingIcon = { Icon(Icons.Default.Title, contentDescription = null) }
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
if (!viewModel.isLocal) {
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = viewModel.password,
|
value = viewModel.email,
|
||||||
onValueChange = { viewModel.password = it },
|
onValueChange = { viewModel.email = it },
|
||||||
label = { Text(stringResource(R.string.setting_password)) },
|
placeholder = { Text(stringResource(R.string.label_email)) },
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
leadingIcon = { Icon(Icons.Default.Lock, contentDescription = null) }
|
leadingIcon = { Icon(Icons.Default.Email, contentDescription = null) }
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Text(
|
||||||
|
text = "SECURITY",
|
||||||
|
style = MaterialTheme.typography.subtitle1,
|
||||||
|
color = MaterialTheme.colors.onSurface,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
modifier = Modifier.padding(bottom = 8.dp, top = 24.dp)
|
||||||
|
)
|
||||||
|
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = viewModel.newPassword,
|
value = viewModel.password,
|
||||||
onValueChange = { viewModel.newPassword = it },
|
onValueChange = { viewModel.password = it },
|
||||||
label = { Text(stringResource(R.string.setting_new_project_password)) },
|
placeholder = { Text("Old " + stringResource(R.string.label_password)) },
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
leadingIcon = { Icon(Icons.Default.LockOpen, contentDescription = null) }
|
leadingIcon = { Icon(Icons.Default.Lock, contentDescription = null) }
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = viewModel.email,
|
value = viewModel.newPassword,
|
||||||
onValueChange = { viewModel.email = it },
|
onValueChange = { viewModel.newPassword = it },
|
||||||
label = { Text(stringResource(R.string.setting_new_project_email)) },
|
placeholder = { Text("New " + stringResource(R.string.label_password)) },
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
leadingIcon = { Icon(Icons.Default.Email, contentDescription = null) }
|
leadingIcon = { Icon(Icons.Default.LockOpen, contentDescription = null) }
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ class EditProjectViewModel : ViewModel() {
|
|||||||
var password by mutableStateOf("")
|
var password by mutableStateOf("")
|
||||||
var newPassword by mutableStateOf("")
|
var newPassword by mutableStateOf("")
|
||||||
var email by mutableStateOf("")
|
var email by mutableStateOf("")
|
||||||
|
var isLocal by mutableStateOf(false)
|
||||||
|
|
||||||
var dialogState by mutableStateOf<DialogState?>(null)
|
var dialogState by mutableStateOf<DialogState?>(null)
|
||||||
|
|
||||||
@@ -53,5 +54,6 @@ class EditProjectViewModel : ViewModel() {
|
|||||||
password = project.password
|
password = project.password
|
||||||
newPassword = project.password
|
newPassword = project.password
|
||||||
email = project.email?.let { if (it == "null") "" else it } ?: ""
|
email = project.email?.let { if (it == "null") "" else it } ?: ""
|
||||||
|
isLocal = project.isLocal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import androidx.compose.material.*
|
|||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
@@ -29,15 +30,17 @@ fun MemberAddDialogContent(
|
|||||||
.padding(16.dp)
|
.padding(16.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.add_member_dialog_title),
|
text = stringResource(R.string.add_member_dialog_title).uppercase(),
|
||||||
style = MaterialTheme.typography.h6,
|
style = MaterialTheme.typography.subtitle1,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = MaterialTheme.colors.onSurface,
|
||||||
modifier = Modifier.padding(bottom = 16.dp)
|
modifier = Modifier.padding(bottom = 16.dp)
|
||||||
)
|
)
|
||||||
|
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = name,
|
value = name,
|
||||||
onValueChange = { name = it },
|
onValueChange = { name = it },
|
||||||
label = { Text(stringResource(R.string.member_edit_name)) },
|
placeholder = { Text(stringResource(R.string.label_name)) },
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
keyboardOptions = KeyboardOptions(capitalization = KeyboardCapitalization.Sentences),
|
keyboardOptions = KeyboardOptions(capitalization = KeyboardCapitalization.Sentences),
|
||||||
singleLine = true
|
singleLine = true
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ 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.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||||
import androidx.compose.ui.text.input.KeyboardType
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@@ -57,8 +58,10 @@ fun MemberEditDialogContent(
|
|||||||
.verticalScroll(rememberScrollState())
|
.verticalScroll(rememberScrollState())
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.edit_member_dialog_title),
|
text = stringResource(R.string.edit_member_dialog_title).uppercase(),
|
||||||
style = MaterialTheme.typography.h6,
|
style = MaterialTheme.typography.subtitle1,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = MaterialTheme.colors.onSurface,
|
||||||
modifier = Modifier.padding(bottom = 16.dp)
|
modifier = Modifier.padding(bottom = 16.dp)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -66,7 +69,7 @@ fun MemberEditDialogContent(
|
|||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = name,
|
value = name,
|
||||||
onValueChange = { name = it },
|
onValueChange = { name = it },
|
||||||
label = { Text(stringResource(R.string.member_edit_name)) },
|
placeholder = { Text(stringResource(R.string.label_name)) },
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
leadingIcon = {
|
leadingIcon = {
|
||||||
Icon(Icons.Default.Person, contentDescription = null)
|
Icon(Icons.Default.Person, contentDescription = null)
|
||||||
@@ -80,7 +83,7 @@ fun MemberEditDialogContent(
|
|||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = weight,
|
value = weight,
|
||||||
onValueChange = { weight = it },
|
onValueChange = { weight = it },
|
||||||
label = { Text(stringResource(R.string.member_edit_weight)) },
|
placeholder = { Text(stringResource(R.string.label_weight)) },
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
leadingIcon = {
|
leadingIcon = {
|
||||||
Icon(Icons.Default.LineWeight, contentDescription = null)
|
Icon(Icons.Default.LineWeight, contentDescription = null)
|
||||||
@@ -98,9 +101,17 @@ fun MemberEditDialogContent(
|
|||||||
.clickable { isActivated = !isActivated }
|
.clickable { isActivated = !isActivated }
|
||||||
.padding(vertical = 8.dp)
|
.padding(vertical = 8.dp)
|
||||||
) {
|
) {
|
||||||
Icon(Icons.Default.Block, contentDescription = null)
|
Icon(
|
||||||
Spacer(modifier = Modifier.width(16.dp))
|
imageVector = Icons.Default.Block,
|
||||||
Text(stringResource(R.string.member_edit_toggle), modifier = Modifier.weight(1f))
|
contentDescription = null,
|
||||||
|
tint = MaterialTheme.colors.onSurface.copy(alpha = 0.6f)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(32.dp))
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.label_activated),
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
style = MaterialTheme.typography.subtitle1
|
||||||
|
)
|
||||||
Checkbox(checked = isActivated, onCheckedChange = { isActivated = it })
|
Checkbox(checked = isActivated, onCheckedChange = { isActivated = it })
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,9 +124,17 @@ fun MemberEditDialogContent(
|
|||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(vertical = 8.dp)
|
.padding(vertical = 8.dp)
|
||||||
) {
|
) {
|
||||||
Icon(Icons.Default.Palette, contentDescription = null)
|
Icon(
|
||||||
Spacer(modifier = Modifier.width(16.dp))
|
imageVector = Icons.Default.Palette,
|
||||||
Text(stringResource(R.string.member_edit_color), modifier = Modifier.weight(1f))
|
contentDescription = null,
|
||||||
|
tint = MaterialTheme.colors.onSurface.copy(alpha = 0.6f)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(32.dp))
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.label_color),
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
style = MaterialTheme.typography.subtitle1
|
||||||
|
)
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(40.dp)
|
.size(40.dp)
|
||||||
|
|||||||
+122
@@ -0,0 +1,122 @@
|
|||||||
|
package net.helcel.cowspent.android.project.member
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.activity.compose.setContent
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
|
import androidx.activity.viewModels
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.window.Dialog
|
||||||
|
import androidx.compose.ui.window.DialogProperties
|
||||||
|
import net.helcel.cowspent.R
|
||||||
|
import net.helcel.cowspent.model.DBBill
|
||||||
|
import net.helcel.cowspent.model.DBMember
|
||||||
|
import net.helcel.cowspent.persistence.CowspentSQLiteOpenHelper
|
||||||
|
import net.helcel.cowspent.theme.ThemeUtils
|
||||||
|
|
||||||
|
class MemberManagementActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
private val viewModel: MemberManagementViewModel by viewModels()
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
enableEdgeToEdge()
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
val projectId = intent.getLongExtra(EXTRA_PROJECT_ID, 0)
|
||||||
|
if (projectId == 0L) {
|
||||||
|
finish()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val db = CowspentSQLiteOpenHelper.getInstance(this)
|
||||||
|
viewModel.loadMembers(projectId)
|
||||||
|
|
||||||
|
setContent {
|
||||||
|
ThemeUtils.CowspentTheme {
|
||||||
|
var showAddMemberDialog by remember { mutableStateOf(false) }
|
||||||
|
var editingMember by remember { mutableStateOf<DBMember?>(null) }
|
||||||
|
|
||||||
|
MemberManagementScreen(
|
||||||
|
members = viewModel.members,
|
||||||
|
onAddMember = { showAddMemberDialog = true },
|
||||||
|
onEditMember = { editingMember = it },
|
||||||
|
onToggleMember = { member, isActivated ->
|
||||||
|
db.updateMemberAndSync(member, member.name, member.weight, isActivated, member.r, member.g, member.b, "", "")
|
||||||
|
viewModel.loadMembers(projectId)
|
||||||
|
},
|
||||||
|
onBack = { finish() }
|
||||||
|
)
|
||||||
|
|
||||||
|
if (showAddMemberDialog) {
|
||||||
|
Dialog(
|
||||||
|
onDismissRequest = { showAddMemberDialog = false },
|
||||||
|
properties = DialogProperties(usePlatformDefaultWidth = false)
|
||||||
|
) {
|
||||||
|
MemberAddDialogContent(
|
||||||
|
onAdd = { memberName ->
|
||||||
|
val memberNames = db.getMembersOfProject(projectId, null).map { it.name }
|
||||||
|
if (memberNames.contains(memberName)) {
|
||||||
|
Toast.makeText(this, R.string.member_already_exists, Toast.LENGTH_SHORT).show()
|
||||||
|
} else {
|
||||||
|
val color = net.helcel.cowspent.android.helper.TextDrawable.getColorFromName(memberName)
|
||||||
|
db.addMemberAndSync(
|
||||||
|
DBMember(
|
||||||
|
0, 0, projectId, memberName, true, 1.0,
|
||||||
|
DBBill.STATE_ADDED,
|
||||||
|
android.graphics.Color.red(color),
|
||||||
|
android.graphics.Color.green(color),
|
||||||
|
android.graphics.Color.blue(color),
|
||||||
|
null, null
|
||||||
|
)
|
||||||
|
)
|
||||||
|
viewModel.loadMembers(projectId)
|
||||||
|
showAddMemberDialog = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDismiss = { showAddMemberDialog = false }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (editingMember != null) {
|
||||||
|
val member = editingMember!!
|
||||||
|
Dialog(
|
||||||
|
onDismissRequest = { editingMember = null },
|
||||||
|
properties = DialogProperties(usePlatformDefaultWidth = false)
|
||||||
|
) {
|
||||||
|
MemberEditDialogContent(
|
||||||
|
member = member,
|
||||||
|
onSave = { name, weight, isActivated, r, g, b ->
|
||||||
|
db.updateMemberAndSync(member, name, weight, isActivated, r, g, b, "", "")
|
||||||
|
viewModel.loadMembers(projectId)
|
||||||
|
editingMember = null
|
||||||
|
},
|
||||||
|
onDelete = {
|
||||||
|
db.deleteMember(member.id)
|
||||||
|
viewModel.loadMembers(projectId)
|
||||||
|
editingMember = null
|
||||||
|
},
|
||||||
|
onDismiss = { editingMember = null }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val EXTRA_PROJECT_ID = "project_id"
|
||||||
|
|
||||||
|
fun createIntent(context: Context, projectId: Long): Intent {
|
||||||
|
return Intent(context, MemberManagementActivity::class.java).apply {
|
||||||
|
putExtra(EXTRA_PROJECT_ID, projectId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
-132
@@ -1,132 +0,0 @@
|
|||||||
package net.helcel.cowspent.android.project.member
|
|
||||||
|
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.heightIn
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.foundation.layout.width
|
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
|
||||||
import androidx.compose.foundation.lazy.items
|
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
|
||||||
import androidx.compose.material.Icon
|
|
||||||
import androidx.compose.material.IconButton
|
|
||||||
import androidx.compose.material.MaterialTheme
|
|
||||||
import androidx.compose.material.Surface
|
|
||||||
import androidx.compose.material.Text
|
|
||||||
import androidx.compose.material.TextButton
|
|
||||||
import androidx.compose.material.contentColorFor
|
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.filled.Add
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import net.helcel.cowspent.R
|
|
||||||
import net.helcel.cowspent.android.helper.UserAvatar
|
|
||||||
import net.helcel.cowspent.android.helper.lazyVerticalScrollbar
|
|
||||||
import net.helcel.cowspent.model.DBMember
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun MemberManagementDialogContent(
|
|
||||||
members: List<DBMember>,
|
|
||||||
onAddMember: () -> Unit,
|
|
||||||
onEditMember: (DBMember) -> Unit,
|
|
||||||
onDismiss: () -> Unit
|
|
||||||
) {
|
|
||||||
val listState = rememberLazyListState()
|
|
||||||
Surface(
|
|
||||||
shape = MaterialTheme.shapes.large,
|
|
||||||
color = MaterialTheme.colors.surface,
|
|
||||||
contentColor = contentColorFor(MaterialTheme.colors.surface)
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.heightIn(max = 500.dp)
|
|
||||||
.padding(16.dp)
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.fab_manage_members),
|
|
||||||
style = MaterialTheme.typography.h6
|
|
||||||
)
|
|
||||||
IconButton(onClick = onAddMember) {
|
|
||||||
Icon(Icons.Default.Add,modifier=Modifier.size(32.dp),
|
|
||||||
contentDescription = stringResource(R.string.fab_add_member))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
|
||||||
|
|
||||||
LazyColumn(
|
|
||||||
state = listState,
|
|
||||||
modifier = Modifier
|
|
||||||
.weight(1f, fill = false)
|
|
||||||
.lazyVerticalScrollbar(listState)
|
|
||||||
) {
|
|
||||||
items(members) { member ->
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.clickable { onEditMember(member) }
|
|
||||||
.padding(vertical = 12.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
UserAvatar(
|
|
||||||
name = member.name,
|
|
||||||
r = member.r,
|
|
||||||
g = member.g,
|
|
||||||
b = member.b,
|
|
||||||
avatar = member.avatar,
|
|
||||||
disabled = !member.isActivated,
|
|
||||||
size = 40.dp
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.width(16.dp))
|
|
||||||
Text(
|
|
||||||
text = member.name,
|
|
||||||
style = MaterialTheme.typography.body1
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
|
||||||
|
|
||||||
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) {
|
|
||||||
TextButton(onClick = onDismiss) {
|
|
||||||
Text(stringResource(R.string.simple_close).uppercase())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Preview(showBackground = true)
|
|
||||||
@Composable
|
|
||||||
fun MemberManagementDialogContentPreview() {
|
|
||||||
MaterialTheme {
|
|
||||||
MemberManagementDialogContent(
|
|
||||||
members = listOf(
|
|
||||||
DBMember(1, 0, 1, "Alice", true, 1.0, 0, 255, 100, 100, null, null),
|
|
||||||
DBMember(2, 0, 1, "Bob", true, 1.0, 0, 100, 255, 100, null, null),
|
|
||||||
DBMember(3, 0, 1, "Charlie", false, 1.0, 0, 100, 100, 255, null, null)
|
|
||||||
),
|
|
||||||
onAddMember = {},
|
|
||||||
onEditMember = {},
|
|
||||||
onDismiss = {}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
+162
@@ -0,0 +1,162 @@
|
|||||||
|
package net.helcel.cowspent.android.project.member
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
|
import androidx.compose.material.*
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
|
import androidx.compose.material.icons.filled.Add
|
||||||
|
import androidx.compose.material.icons.filled.Delete
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import net.helcel.cowspent.R
|
||||||
|
import net.helcel.cowspent.android.helper.MemberAvatar
|
||||||
|
import net.helcel.cowspent.android.helper.lazyVerticalScrollbar
|
||||||
|
import net.helcel.cowspent.model.DBMember
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MemberManagementScreen(
|
||||||
|
members: List<DBMember>,
|
||||||
|
onAddMember: () -> Unit,
|
||||||
|
onEditMember: (DBMember) -> Unit,
|
||||||
|
onToggleMember: (DBMember, Boolean) -> Unit,
|
||||||
|
onBack: () -> Unit
|
||||||
|
) {
|
||||||
|
val listState = rememberLazyListState()
|
||||||
|
val activatedMembers = members.filter { it.isActivated }
|
||||||
|
val deactivatedMembers = members.filter { !it.isActivated }
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
TopAppBar(
|
||||||
|
title = { Text(stringResource(R.string.action_members)) },
|
||||||
|
navigationIcon = {
|
||||||
|
IconButton(onClick = onBack) {
|
||||||
|
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
backgroundColor = MaterialTheme.colors.primary,
|
||||||
|
elevation = 0.dp
|
||||||
|
)
|
||||||
|
},
|
||||||
|
floatingActionButton = {
|
||||||
|
FloatingActionButton(onClick = onAddMember) {
|
||||||
|
Icon(Icons.Default.Add, contentDescription = stringResource(R.string.action_save))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) { padding ->
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(padding)
|
||||||
|
) {
|
||||||
|
LazyColumn(
|
||||||
|
state = listState,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.lazyVerticalScrollbar(listState)
|
||||||
|
) {
|
||||||
|
item {
|
||||||
|
Text(
|
||||||
|
text = "ACTIVATED MEMBERS",
|
||||||
|
style = MaterialTheme.typography.subtitle1,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = MaterialTheme.colors.onSurface,
|
||||||
|
modifier = Modifier.padding(16.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
items(activatedMembers) { member ->
|
||||||
|
MemberRow(
|
||||||
|
member = member,
|
||||||
|
onEditMember = onEditMember,
|
||||||
|
onToggleMember = { onToggleMember(member, it) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deactivatedMembers.isNotEmpty()) {
|
||||||
|
item {
|
||||||
|
Text(
|
||||||
|
text = "DEACTIVATED MEMBERS",
|
||||||
|
style = MaterialTheme.typography.subtitle1,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = MaterialTheme.colors.onSurface.copy(alpha = 0.6f),
|
||||||
|
modifier = Modifier.padding(start = 16.dp, top = 24.dp, end = 16.dp, bottom = 16.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
items(deactivatedMembers) { member ->
|
||||||
|
MemberRow(
|
||||||
|
member = member,
|
||||||
|
onEditMember = onEditMember,
|
||||||
|
onToggleMember = { onToggleMember(member, it) },
|
||||||
|
alpha = 0.6f
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MemberRow(
|
||||||
|
member: DBMember,
|
||||||
|
onEditMember: (DBMember) -> Unit,
|
||||||
|
onToggleMember: (Boolean) -> Unit,
|
||||||
|
alpha: Float = 1f
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable { onEditMember(member) }
|
||||||
|
.padding(horizontal = 16.dp, vertical = 0.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
MemberAvatar(
|
||||||
|
member = member,
|
||||||
|
size = 24.dp,
|
||||||
|
alpha = alpha
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(32.dp))
|
||||||
|
Text(
|
||||||
|
text = member.name,
|
||||||
|
style = MaterialTheme.typography.subtitle1,
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
color = MaterialTheme.colors.onSurface.copy(alpha = alpha)
|
||||||
|
)
|
||||||
|
IconButton(onClick = { onToggleMember(!member.isActivated) }) {
|
||||||
|
Icon(
|
||||||
|
imageVector = if (member.isActivated) Icons.Default.Delete else Icons.Default.Add,
|
||||||
|
contentDescription = if (member.isActivated) "Deactivate" else "Activate",
|
||||||
|
tint = if (member.isActivated) MaterialTheme.colors.error.copy(alpha = 0.6f) else MaterialTheme.colors.primary.copy(alpha = 0.6f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(showBackground = true)
|
||||||
|
@Composable
|
||||||
|
fun MemberManagementScreenPreview() {
|
||||||
|
MaterialTheme {
|
||||||
|
MemberManagementScreen(
|
||||||
|
members = listOf(
|
||||||
|
DBMember(1, 0, 1, "Alice", true, 1.0, 0, 255, 100, 100, null, null),
|
||||||
|
DBMember(2, 0, 1, "Bob", true, 1.0, 0, 100, 255, 100, null, null),
|
||||||
|
DBMember(3, 0, 1, "Charlie", false, 1.0, 0, 100, 100, 255, null, null)
|
||||||
|
),
|
||||||
|
onAddMember = {},
|
||||||
|
onEditMember = {},
|
||||||
|
onToggleMember = { _, _ -> },
|
||||||
|
onBack = {}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
+40
@@ -0,0 +1,40 @@
|
|||||||
|
package net.helcel.cowspent.android.project.member
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableLongStateOf
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.lifecycle.AndroidViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import net.helcel.cowspent.android.helper.DialogState
|
||||||
|
import net.helcel.cowspent.model.DBMember
|
||||||
|
import net.helcel.cowspent.persistence.CowspentSQLiteOpenHelper
|
||||||
|
|
||||||
|
class MemberManagementViewModel(application: Application) : AndroidViewModel(application) {
|
||||||
|
private val db = CowspentSQLiteOpenHelper.getInstance(application)
|
||||||
|
|
||||||
|
var projectId by mutableLongStateOf(0L)
|
||||||
|
var members by mutableStateOf<List<DBMember>>(emptyList())
|
||||||
|
|
||||||
|
var dialogState by mutableStateOf<DialogState?>(null)
|
||||||
|
|
||||||
|
fun loadMembers(projId: Long) {
|
||||||
|
projectId = projId
|
||||||
|
viewModelScope.launch {
|
||||||
|
members = withContext(Dispatchers.IO) { db.getMembersOfProject(projId, null) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deleteMember(id: Long) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
db.deleteMember(id)
|
||||||
|
}
|
||||||
|
loadMembers(projectId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+7
-5
@@ -129,8 +129,10 @@ fun ProjectSettlementUI(
|
|||||||
.padding(16.dp)
|
.padding(16.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.settle_dialog_title),
|
text = stringResource(R.string.title_settle).uppercase(),
|
||||||
style = MaterialTheme.typography.h6,
|
style = MaterialTheme.typography.subtitle1,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = MaterialTheme.colors.onSurface,
|
||||||
modifier = Modifier.padding(bottom = 16.dp)
|
modifier = Modifier.padding(bottom = 16.dp)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -174,7 +176,7 @@ private fun BalancedStateMessage() {
|
|||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.settle_dialog_balanced),
|
text = stringResource(R.string.dialog_balanced_msg),
|
||||||
style = MaterialTheme.typography.body1
|
style = MaterialTheme.typography.body1
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -279,9 +281,9 @@ private fun TableHeaderText(
|
|||||||
Text(
|
Text(
|
||||||
text = text.uppercase(),
|
text = text.uppercase(),
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
fontWeight = FontWeight.SemiBold,
|
fontWeight = FontWeight.Bold,
|
||||||
style = MaterialTheme.typography.overline,
|
style = MaterialTheme.typography.overline,
|
||||||
color = colorResource(R.color.fg_default_low),
|
color = MaterialTheme.colors.onSurface.copy(alpha = 0.6f),
|
||||||
textAlign = textAlign,
|
textAlign = textAlign,
|
||||||
letterSpacing = 0.8.sp
|
letterSpacing = 0.8.sp
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package net.helcel.cowspent.android.settings
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.OnBackPressedCallback
|
import androidx.activity.OnBackPressedCallback
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
@@ -25,6 +26,7 @@ import net.helcel.cowspent.util.ColorUtils
|
|||||||
class PreferencesActivity : AppCompatActivity() {
|
class PreferencesActivity : AppCompatActivity() {
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
enableEdgeToEdge()
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
|
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ fun SettingsScreen(
|
|||||||
val keyColor = stringResource(R.string.pref_key_color)
|
val keyColor = stringResource(R.string.pref_key_color)
|
||||||
val keyOfflineMode = stringResource(R.string.pref_key_offline_mode)
|
val keyOfflineMode = stringResource(R.string.pref_key_offline_mode)
|
||||||
val keyShowArchived = stringResource(R.string.pref_key_show_archived)
|
val keyShowArchived = stringResource(R.string.pref_key_show_archived)
|
||||||
|
val keyBetaFeatures = stringResource(R.string.pref_key_beta_features)
|
||||||
|
|
||||||
// States for preferences
|
// States for preferences
|
||||||
var nightMode by remember(keyNightMode) {
|
var nightMode by remember(keyNightMode) {
|
||||||
@@ -113,6 +114,9 @@ fun SettingsScreen(
|
|||||||
var showArchived by remember(keyShowArchived) {
|
var showArchived by remember(keyShowArchived) {
|
||||||
mutableStateOf(sharedPreferences.getBoolean(keyShowArchived, false))
|
mutableStateOf(sharedPreferences.getBoolean(keyShowArchived, false))
|
||||||
}
|
}
|
||||||
|
var betaFeatures by remember(keyBetaFeatures) {
|
||||||
|
mutableStateOf(sharedPreferences.getBoolean(keyBetaFeatures, false))
|
||||||
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
@@ -135,10 +139,10 @@ fun SettingsScreen(
|
|||||||
.verticalScroll(rememberScrollState())
|
.verticalScroll(rememberScrollState())
|
||||||
) {
|
) {
|
||||||
// Appearance
|
// Appearance
|
||||||
SettingsCategory(stringResource(R.string.settings_appearance_category))
|
SettingsCategory(stringResource(R.string.settings_appearance))
|
||||||
|
|
||||||
SettingsSwitchPreference(
|
SettingsSwitchPreference(
|
||||||
title = stringResource(R.string.setting_show_archived),
|
title = stringResource(R.string.settings_show_archived),
|
||||||
icon = Icons.Default.Archive,
|
icon = Icons.Default.Archive,
|
||||||
checked = showArchived,
|
checked = showArchived,
|
||||||
onCheckedChange = {
|
onCheckedChange = {
|
||||||
@@ -160,7 +164,7 @@ fun SettingsScreen(
|
|||||||
)
|
)
|
||||||
|
|
||||||
SettingsListPreference(
|
SettingsListPreference(
|
||||||
title = stringResource(R.string.settings_night_mode_title),
|
title = stringResource(R.string.settings_night_mode),
|
||||||
icon = Icons.Default.Brightness2,
|
icon = Icons.Default.Brightness2,
|
||||||
value = nightMode,
|
value = nightMode,
|
||||||
entries = mapOf(
|
entries = mapOf(
|
||||||
@@ -178,7 +182,7 @@ fun SettingsScreen(
|
|||||||
)
|
)
|
||||||
|
|
||||||
SettingsListPreference(
|
SettingsListPreference(
|
||||||
title = stringResource(R.string.settings_color_mode_title),
|
title = stringResource(R.string.settings_color_mode),
|
||||||
icon = Icons.Default.Palette,
|
icon = Icons.Default.Palette,
|
||||||
value = colorMode,
|
value = colorMode,
|
||||||
entries = mapOf(
|
entries = mapOf(
|
||||||
@@ -203,8 +207,8 @@ fun SettingsScreen(
|
|||||||
|
|
||||||
if (colorMode == "manual") {
|
if (colorMode == "manual") {
|
||||||
SettingsColorPreference(
|
SettingsColorPreference(
|
||||||
title = stringResource(R.string.settings_color_title),
|
title = stringResource(R.string.settings_color_custom),
|
||||||
summary = stringResource(R.string.settings_color_summary),
|
summary = stringResource(R.string.settings_color_custom),
|
||||||
icon = Icons.Default.Palette,
|
icon = Icons.Default.Palette,
|
||||||
initialColor = appColor,
|
initialColor = appColor,
|
||||||
onColorSelected = {
|
onColorSelected = {
|
||||||
@@ -218,10 +222,10 @@ fun SettingsScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Network
|
// Network
|
||||||
SettingsCategory(stringResource(R.string.settings_network_category))
|
SettingsCategory(stringResource(R.string.settings_network))
|
||||||
|
|
||||||
SettingsSwitchPreference(
|
SettingsSwitchPreference(
|
||||||
title = stringResource(R.string.settings_offline_mode_title),
|
title = stringResource(R.string.settings_offline_mode),
|
||||||
summary = stringResource(R.string.settings_offline_mode_summary),
|
summary = stringResource(R.string.settings_offline_mode_summary),
|
||||||
icon = Icons.Default.Sync,
|
icon = Icons.Default.Sync,
|
||||||
checked = offlineMode,
|
checked = offlineMode,
|
||||||
@@ -234,16 +238,29 @@ fun SettingsScreen(
|
|||||||
)
|
)
|
||||||
|
|
||||||
SettingsPreference(
|
SettingsPreference(
|
||||||
title = stringResource(R.string.settings_server_settings),
|
title = stringResource(R.string.title_account),
|
||||||
icon = Icons.Default.AccountCircle,
|
icon = Icons.Default.AccountCircle,
|
||||||
onClick = onAccountSettingsClick
|
onClick = onAccountSettingsClick
|
||||||
)
|
)
|
||||||
|
|
||||||
// Other
|
// Other
|
||||||
SettingsCategory(stringResource(R.string.settings_other_category))
|
SettingsCategory(stringResource(R.string.settings_other))
|
||||||
|
|
||||||
|
SettingsSwitchPreference(
|
||||||
|
title = stringResource(R.string.settings_beta_features),
|
||||||
|
summary = stringResource(R.string.settings_beta_features_summary),
|
||||||
|
icon = Icons.Default.Info,
|
||||||
|
checked = betaFeatures,
|
||||||
|
onCheckedChange = {
|
||||||
|
betaFeatures = it
|
||||||
|
sharedPreferences.edit {
|
||||||
|
putBoolean(keyBetaFeatures, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
SettingsPreference(
|
SettingsPreference(
|
||||||
title = stringResource(R.string.settings_about),
|
title = stringResource(R.string.title_about),
|
||||||
icon = Icons.Default.Info,
|
icon = Icons.Default.Info,
|
||||||
onClick = onAboutClick
|
onClick = onAboutClick
|
||||||
)
|
)
|
||||||
@@ -255,9 +272,9 @@ fun SettingsScreen(
|
|||||||
fun SettingsCategory(title: String) {
|
fun SettingsCategory(title: String) {
|
||||||
Text(
|
Text(
|
||||||
text = title.uppercase(),
|
text = title.uppercase(),
|
||||||
modifier = Modifier.padding(start = 16.dp, top = 16.dp, bottom = 8.dp),
|
modifier = Modifier.padding(start = 16.dp, top = 24.dp, bottom = 8.dp),
|
||||||
color = MaterialTheme.colors.primary,
|
color = MaterialTheme.colors.onSurface,
|
||||||
style = MaterialTheme.typography.overline,
|
style = MaterialTheme.typography.subtitle1,
|
||||||
fontWeight = FontWeight.Bold
|
fontWeight = FontWeight.Bold
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+311
-372
@@ -1,469 +1,408 @@
|
|||||||
package net.helcel.cowspent.android.statistics
|
package net.helcel.cowspent.android.statistics
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import androidx.compose.animation.core.Animatable
|
||||||
|
import androidx.compose.animation.core.Spring
|
||||||
|
import androidx.compose.animation.core.spring
|
||||||
import androidx.compose.foundation.Canvas
|
import androidx.compose.foundation.Canvas
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.material.*
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.gestures.Orientation
|
||||||
|
import androidx.compose.foundation.gestures.draggable
|
||||||
|
import androidx.compose.foundation.gestures.rememberDraggableState
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.offset
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.layout.wrapContentWidth
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.DropdownMenuItem
|
||||||
|
import androidx.compose.material.ExperimentalMaterialApi
|
||||||
|
import androidx.compose.material.Icon
|
||||||
|
import androidx.compose.material.IconButton
|
||||||
|
import androidx.compose.material.LocalContentColor
|
||||||
|
import androidx.compose.material.MaterialTheme
|
||||||
|
import androidx.compose.material.Text
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Group
|
import androidx.compose.material.icons.filled.Group
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.material.icons.filled.ViewColumn
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableLongStateOf
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.geometry.CornerRadius
|
import androidx.compose.ui.geometry.Rect
|
||||||
import androidx.compose.ui.geometry.Offset
|
|
||||||
import androidx.compose.ui.geometry.Size
|
|
||||||
import androidx.compose.ui.graphics.Brush
|
import androidx.compose.ui.graphics.Brush
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.Path
|
import androidx.compose.ui.graphics.Path
|
||||||
import androidx.compose.ui.graphics.drawscope.DrawScope
|
import androidx.compose.ui.graphics.drawscope.DrawScope
|
||||||
|
import androidx.compose.ui.layout.LayoutCoordinates
|
||||||
|
import androidx.compose.ui.layout.onGloballyPositioned
|
||||||
|
import androidx.compose.ui.layout.positionInRoot
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
|
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.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.core.graphics.toColorInt
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import net.helcel.cowspent.R
|
import net.helcel.cowspent.R
|
||||||
import net.helcel.cowspent.android.helper.*
|
import net.helcel.cowspent.android.helper.EditableExposedDropdownMenu
|
||||||
|
import net.helcel.cowspent.android.helper.MemberAvatar
|
||||||
|
import net.helcel.cowspent.android.helper.formatShortValue
|
||||||
import net.helcel.cowspent.model.DBBill
|
import net.helcel.cowspent.model.DBBill
|
||||||
import net.helcel.cowspent.model.DBCategory
|
import net.helcel.cowspent.model.DBCategory
|
||||||
import net.helcel.cowspent.model.DBMember
|
import net.helcel.cowspent.model.DBMember
|
||||||
import androidx.core.graphics.toColorInt
|
import net.helcel.cowspent.util.CategoryUtils
|
||||||
|
import kotlin.math.abs
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
private object SankeyDimens {
|
||||||
|
val NodeHeight = 76.dp
|
||||||
|
val TotalNodeHeight = 42.dp
|
||||||
|
val NormalGap = 8.dp
|
||||||
|
val FocusGap = 16.dp
|
||||||
|
val ActiveMinWidth = 160.dp
|
||||||
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterialApi::class)
|
@OptIn(ExperimentalMaterialApi::class)
|
||||||
@SuppressLint("UseKtx")
|
@SuppressLint("UseKtx")
|
||||||
@Composable
|
@Composable
|
||||||
fun ProjectSankeyDiagram(
|
fun ProjectSankeyDiagram(
|
||||||
projectName: String,
|
projectName: String,
|
||||||
|
projectId: Long,
|
||||||
|
projectRemoteId: String?,
|
||||||
allMembers: List<DBMember>,
|
allMembers: List<DBMember>,
|
||||||
allBills: List<DBBill>,
|
allBills: List<DBBill>,
|
||||||
customCategories: List<DBCategory>,
|
customCategories: List<DBCategory>,
|
||||||
onShareReady: (String) -> Unit
|
onShareReady: (String) -> Unit
|
||||||
) {
|
) {
|
||||||
val shareStatsIntro = stringResource(R.string.share_stats_intro, projectName)
|
val shareStatsIntro = stringResource(R.string.msg_stats_intro, projectName)
|
||||||
|
|
||||||
var selectedMemberId by remember { mutableLongStateOf(-1L) }
|
var selectedMemberId by remember { mutableLongStateOf(-1L) }
|
||||||
var expanded by remember { mutableStateOf(false) }
|
var expanded by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
val activeBills = remember(allBills) {
|
val reimbursementCategoryId = remember(customCategories, projectId, projectRemoteId) {
|
||||||
allBills.filter {
|
CategoryUtils.getReimbursementCategoryId(customCategories, projectId, projectRemoteId)
|
||||||
it.state != DBBill.STATE_DELETED &&
|
}
|
||||||
it.categoryRemoteId != DBBill.CATEGORY_REIMBURSEMENT
|
val activeBills = remember(allBills, reimbursementCategoryId) {
|
||||||
}
|
allBills.filter { it.state != DBBill.STATE_DELETED && it.categoryId != reimbursementCategoryId }
|
||||||
}
|
}
|
||||||
|
|
||||||
val membersMap = remember(allMembers) { allMembers.associateBy { it.id } }
|
val membersMap = remember(allMembers) { allMembers.associateBy { it.id } }
|
||||||
val categoriesMap = remember(customCategories) { customCategories.associateBy { it.remoteId.toInt() } }
|
val categoriesMap = remember(customCategories) { customCategories.associateBy { it.id } }
|
||||||
|
|
||||||
if (activeBills.isEmpty()) {
|
if (activeBills.isEmpty()) {
|
||||||
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||||
Text("No data to display", style = MaterialTheme.typography.h6)
|
Text("No data to display", style = MaterialTheme.typography.subtitle1, fontWeight = FontWeight.Bold, color = MaterialTheme.colors.onSurface.copy(alpha = 0.6f))
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Consolidated spending calculation
|
|
||||||
val spendings = remember(activeBills, selectedMemberId, membersMap) {
|
val spendings = remember(activeBills, selectedMemberId, membersMap) {
|
||||||
val spentMap = mutableMapOf<Long, Double>()
|
val spentMap = mutableMapOf<Long, Double>().apply { membersMap.keys.forEach { put(it, 0.0) } }
|
||||||
val catMap = mutableMapOf<Int, Double>()
|
val catMap = mutableMapOf<Long, Double>()
|
||||||
membersMap.keys.forEach { spentMap[it] = 0.0 }
|
|
||||||
|
|
||||||
activeBills.forEach { bill ->
|
activeBills.forEach { bill ->
|
||||||
val totalWeight = bill.billOwers.sumOf { bo ->
|
val totalWeight = bill.billOwers.sumOf { membersMap[it.memberId]?.weight ?: 1.0 }
|
||||||
membersMap[bo.memberId]?.weight ?: 1.0
|
|
||||||
}
|
|
||||||
if (totalWeight > 0) {
|
if (totalWeight > 0) {
|
||||||
if (selectedMemberId == -1L) {
|
if (selectedMemberId == -1L) {
|
||||||
catMap[bill.categoryRemoteId] = (catMap[bill.categoryRemoteId] ?: 0.0) + bill.amount
|
catMap[bill.categoryId] = (catMap[bill.categoryId] ?: 0.0) + bill.amount
|
||||||
bill.billOwers.forEach { bo ->
|
bill.billOwers.forEach { bo ->
|
||||||
val weight = membersMap[bo.memberId]?.weight ?: 1.0
|
val weight = membersMap[bo.memberId]?.weight ?: 1.0
|
||||||
val share = (bill.amount / totalWeight) * weight
|
spentMap[bo.memberId] = (spentMap[bo.memberId] ?: 0.0) + (bill.amount / totalWeight) * weight
|
||||||
spentMap[bo.memberId] = (spentMap[bo.memberId] ?: 0.0) + share
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
bill.billOwers.find { it.memberId == selectedMemberId }?.let { bo ->
|
bill.billOwers.find { it.memberId == selectedMemberId }?.let { bo ->
|
||||||
val weight = membersMap[bo.memberId]?.weight ?: 1.0
|
val weight = membersMap[bo.memberId]?.weight ?: 1.0
|
||||||
val share = (bill.amount / totalWeight) * weight
|
catMap[bill.categoryId] = (catMap[bill.categoryId] ?: 0.0) + (bill.amount / totalWeight) * weight
|
||||||
catMap[bill.categoryRemoteId] = (catMap[bill.categoryRemoteId] ?: 0.0) + share
|
spentMap[selectedMemberId] = (spentMap[selectedMemberId] ?: 0.0) + (bill.amount / totalWeight) * weight
|
||||||
spentMap[selectedMemberId] = (spentMap[selectedMemberId] ?: 0.0) + share
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
spentMap.toList().filter { it.second > 0 }.sortedByDescending { it.second } to catMap.toList().sortedByDescending { it.second }
|
||||||
val memberList = spentMap.toList().filter { it.second > 0 }.sortedByDescending { it.second }
|
|
||||||
val categoryList = catMap.toList().sortedByDescending { it.second }
|
|
||||||
memberList to categoryList
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val displayMemberSpendings = spendings.first
|
val displayMemberSpendings = spendings.first
|
||||||
val displayCategorySpendings = spendings.second
|
val displayCategorySpendings = spendings.second
|
||||||
val totalAmount = remember(displayMemberSpendings) { displayMemberSpendings.sumOf { it.second } }
|
val totalAmount = remember(displayMemberSpendings) { displayMemberSpendings.sumOf { it.second } }
|
||||||
|
var isSpecialMode by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
LaunchedEffect(displayMemberSpendings, displayCategorySpendings, totalAmount, projectName) {
|
LaunchedEffect(displayMemberSpendings, displayCategorySpendings, totalAmount, projectName) {
|
||||||
val statsText = StringBuilder()
|
val statsText = StringBuilder().apply {
|
||||||
statsText.append("// ").append(shareStatsIntro.replace("\n", "\n// ")).append("\n\n")
|
append("// ").append(shareStatsIntro.replace("\n", "\n// ")).append("\n\n")
|
||||||
|
val middleNode = if (selectedMemberId == -1L) "Total" else "Spent"
|
||||||
val middleNode = if (selectedMemberId == -1L) "Total" else "Spent"
|
displayMemberSpendings.forEach { (id, amount) -> append("${membersMap[id]?.name ?: "???"} [${formatShortValue(amount)}] $middleNode\n") }
|
||||||
|
append("\n")
|
||||||
displayMemberSpendings.forEach { (memberId, amount) ->
|
displayCategorySpendings.forEach { (id, amount) -> append("$middleNode [${formatShortValue(amount)}] ${categoriesMap[id]?.name ?: "Other"}\n") }
|
||||||
val name = membersMap[memberId]?.name ?: "???"
|
append("\n")
|
||||||
statsText.append("$name [${formatShortValue(amount)}] $middleNode\n")
|
displayMemberSpendings.forEach { (id, _) ->
|
||||||
}
|
membersMap[id]?.let { append(":${it.name} ${String.format("#%02x%02x%02x", it.r ?: 128, it.g ?: 128, it.b ?: 128)}\n") }
|
||||||
|
|
||||||
statsText.append("\n")
|
|
||||||
|
|
||||||
displayCategorySpendings.forEach { (catRemoteId, amount) ->
|
|
||||||
val name = categoriesMap[catRemoteId]?.name ?: "Other"
|
|
||||||
statsText.append("$middleNode [${formatShortValue(amount)}] $name\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
statsText.append("\n")
|
|
||||||
|
|
||||||
displayMemberSpendings.forEach { (memberId, _) ->
|
|
||||||
val member = membersMap[memberId]
|
|
||||||
if (member != null) {
|
|
||||||
val hexColor = String.format("#%02x%02x%02x", member.r ?: 128, member.g ?: 128, member.b ?: 128)
|
|
||||||
statsText.append(":${member.name} $hexColor\n")
|
|
||||||
}
|
}
|
||||||
|
displayCategorySpendings.forEach { (id, _) -> categoriesMap[id]?.let { append(":${it.name ?: "Other"} ${it.color}\n") } }
|
||||||
}
|
}
|
||||||
|
|
||||||
displayCategorySpendings.forEach { (catRemoteId, _) ->
|
|
||||||
val category = categoriesMap[catRemoteId]
|
|
||||||
if (category != null) {
|
|
||||||
statsText.append(":${category.name ?: "Other"} ${category.color}\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onShareReady(statsText.toString())
|
onShareReady(statsText.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
Column(modifier = Modifier.fillMaxSize().padding(16.dp)) {
|
Column(modifier = Modifier.fillMaxSize()) {
|
||||||
val selectedMember = membersMap[selectedMemberId]
|
Box(modifier = Modifier.padding(16.dp)) {
|
||||||
EditableExposedDropdownMenu(
|
val selectedMember = membersMap[selectedMemberId]
|
||||||
value = selectedMember?.name ?: "All Members",
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
placeholder = "Filter by member",
|
EditableExposedDropdownMenu(
|
||||||
expanded = expanded,
|
value = selectedMember?.name ?: "All Members",
|
||||||
onExpandedChange = { expanded = it },
|
placeholder = "Filter by member",
|
||||||
onDismissRequest = { expanded = false },
|
expanded = expanded,
|
||||||
modifier = Modifier.fillMaxWidth().padding(bottom = 16.dp),
|
onExpandedChange = { expanded = it },
|
||||||
leadingIcon = {
|
onDismissRequest = { expanded = false },
|
||||||
Box(modifier = Modifier.padding(start = 12.dp)) {
|
modifier = Modifier.weight(1f).padding(bottom = 16.dp),
|
||||||
if (selectedMember != null) {
|
leadingIcon = {
|
||||||
UserAvatar(
|
Box(modifier = Modifier.padding(start = 12.dp)) {
|
||||||
name = selectedMember.name,
|
if (selectedMember != null) {
|
||||||
r = selectedMember.r,
|
MemberAvatar(member = selectedMember, size = 24.dp)
|
||||||
g = selectedMember.g,
|
} else Icon(Icons.Default.Group, contentDescription = null)
|
||||||
b = selectedMember.b,
|
}
|
||||||
disabled = !selectedMember.isActivated,
|
},
|
||||||
size = 24.dp
|
content = {
|
||||||
)
|
DropdownMenuItem(onClick = { selectedMemberId = -1L; expanded = false }) {
|
||||||
} else {
|
Icon(Icons.Default.Group, contentDescription = null)
|
||||||
Icon(Icons.Default.Group, contentDescription = null)
|
Spacer(modifier = Modifier.width(12.dp))
|
||||||
}
|
Text("All Members")
|
||||||
}
|
}
|
||||||
},
|
allMembers.forEach { member ->
|
||||||
content = {
|
DropdownMenuItem(onClick = { selectedMemberId = member.id; expanded = false }) {
|
||||||
DropdownMenuItem(onClick = {
|
MemberAvatar(member = member, size = 24.dp)
|
||||||
selectedMemberId = -1L
|
Spacer(modifier = Modifier.width(12.dp))
|
||||||
expanded = false
|
Text(member.name)
|
||||||
}) {
|
}
|
||||||
Icon(Icons.Default.Group, contentDescription = null)
|
}
|
||||||
Spacer(modifier = Modifier.width(12.dp))
|
|
||||||
Text("All Members")
|
|
||||||
}
|
|
||||||
allMembers.forEach { member ->
|
|
||||||
DropdownMenuItem(onClick = {
|
|
||||||
selectedMemberId = member.id
|
|
||||||
expanded = false
|
|
||||||
}) {
|
|
||||||
UserAvatar(
|
|
||||||
name = member.name,
|
|
||||||
r = member.r,
|
|
||||||
g = member.g,
|
|
||||||
b = member.b,
|
|
||||||
disabled = !member.isActivated,
|
|
||||||
size = 24.dp
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.width(12.dp))
|
|
||||||
Text(member.name)
|
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
IconButton(onClick = { isSpecialMode = !isSpecialMode }, modifier = Modifier.padding(bottom = 16.dp)) {
|
||||||
|
Icon(Icons.Default.ViewColumn, contentDescription = "Focus Mode", tint = if (isSpecialMode) MaterialTheme.colors.primary else LocalContentColor.current)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
|
|
||||||
if (totalAmount <= 0) {
|
if (totalAmount <= 0) {
|
||||||
Box(modifier = Modifier.weight(1f).fillMaxWidth(), contentAlignment = Alignment.Center) {
|
Box(modifier = Modifier.weight(1f).fillMaxWidth(), contentAlignment = Alignment.Center) {
|
||||||
Text("No spending data for selected filter", style = MaterialTheme.typography.body1)
|
Text("No spending data for selected filter", style = MaterialTheme.typography.body1)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
BoxWithConstraints(modifier = Modifier.weight(1f).fillMaxWidth()) {
|
SankeyContent(isSpecialMode, selectedMemberId, totalAmount, displayMemberSpendings, displayCategorySpendings, membersMap, categoriesMap)
|
||||||
val nodeHeightMember = 76.dp
|
|
||||||
val nodeHeightCategory = 76.dp
|
|
||||||
val nodeHeightTotal = 42.dp
|
|
||||||
val horizontalGap = 8.dp
|
|
||||||
|
|
||||||
val memberCount = displayMemberSpendings.size
|
|
||||||
val categoryCount = displayCategorySpendings.size
|
|
||||||
|
|
||||||
val maxGaps = maxOf(memberCount - 1, categoryCount - 1, 0)
|
|
||||||
val usableWidth = (maxWidth.value - horizontalGap.value * maxGaps).coerceAtLeast(0f)
|
|
||||||
val moneyScale = if (totalAmount > 0) usableWidth / totalAmount.toFloat() else 0f
|
|
||||||
|
|
||||||
Canvas(modifier = Modifier.fillMaxSize()) {
|
|
||||||
val nodeHeightMemberPx = nodeHeightMember.toPx()
|
|
||||||
val nodeHeightCategoryPx = nodeHeightCategory.toPx()
|
|
||||||
val nodeHeightTotalPx = nodeHeightTotal.toPx()
|
|
||||||
val gapPx = horizontalGap.toPx()
|
|
||||||
|
|
||||||
val totalWidthPx = size.width
|
|
||||||
val usableWidthPx = totalWidthPx - gapPx * maxGaps
|
|
||||||
val moneyScalePx = if (totalAmount > 0) usableWidthPx / totalAmount else 0.0
|
|
||||||
|
|
||||||
val totalNodeWidthPx = (totalAmount * moneyScalePx).toFloat()
|
|
||||||
val totalNodeXPx = (totalWidthPx - totalNodeWidthPx) / 2
|
|
||||||
|
|
||||||
val topY = 0f
|
|
||||||
val middleY = (size.height - nodeHeightTotalPx) / 2
|
|
||||||
val bottomY = size.height - nodeHeightCategoryPx
|
|
||||||
|
|
||||||
val totalNodeColor = Color(0xFF333333)
|
|
||||||
|
|
||||||
// Flows
|
|
||||||
var currentXTop = (totalWidthPx - (totalAmount * moneyScalePx + (memberCount - 1).coerceAtLeast(0) * gapPx).toFloat()) / 2
|
|
||||||
var currentXInTotalTop = totalNodeXPx
|
|
||||||
displayMemberSpendings.forEach { (memberId, amount) ->
|
|
||||||
val member = membersMap[memberId]
|
|
||||||
val boxWidth = (amount * moneyScalePx).toFloat()
|
|
||||||
val color = member?.let {
|
|
||||||
Color(it.r ?: 128, it.g ?: 128, it.b ?: 128)
|
|
||||||
} ?: Color.Gray
|
|
||||||
|
|
||||||
if (boxWidth > 0.5f) {
|
|
||||||
drawSankeyFlow(
|
|
||||||
startX = currentXTop,
|
|
||||||
startY = topY + nodeHeightMemberPx * 0.5f,
|
|
||||||
startWidth = boxWidth,
|
|
||||||
endX = currentXInTotalTop,
|
|
||||||
endY = middleY + nodeHeightTotalPx * 0.5f,
|
|
||||||
endWidth = boxWidth,
|
|
||||||
startColor = color.copy(alpha = 0.5f),
|
|
||||||
endColor = totalNodeColor.copy(alpha = 0.35f)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
currentXTop += boxWidth + gapPx
|
|
||||||
currentXInTotalTop += boxWidth
|
|
||||||
}
|
|
||||||
|
|
||||||
var currentXBottom = (totalWidthPx - (totalAmount * moneyScalePx + (categoryCount - 1).coerceAtLeast(0) * gapPx).toFloat()) / 2
|
|
||||||
var currentXInTotalBottom = totalNodeXPx
|
|
||||||
displayCategorySpendings.forEach { (catRemoteId, amount) ->
|
|
||||||
val category = categoriesMap[catRemoteId]
|
|
||||||
val boxWidth = (amount * moneyScalePx).toFloat()
|
|
||||||
val color = category?.color?.let {
|
|
||||||
try { Color(it.toColorInt()) } catch (_: Exception) { Color(0xFF999999) }
|
|
||||||
} ?: Color(0xFF999999)
|
|
||||||
|
|
||||||
if (boxWidth > 0.5f) {
|
|
||||||
drawSankeyFlow(
|
|
||||||
startX = currentXInTotalBottom,
|
|
||||||
startY = middleY + nodeHeightTotalPx * 0.5f,
|
|
||||||
startWidth = boxWidth,
|
|
||||||
endX = currentXBottom,
|
|
||||||
endY = bottomY + nodeHeightCategoryPx * 0.5f,
|
|
||||||
endWidth = boxWidth,
|
|
||||||
startColor = totalNodeColor.copy(alpha = 0.35f),
|
|
||||||
endColor = color.copy(alpha = 0.5f)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
currentXInTotalBottom += boxWidth
|
|
||||||
currentXBottom += boxWidth + gapPx
|
|
||||||
}
|
|
||||||
|
|
||||||
// Nodes
|
|
||||||
currentXTop = (totalWidthPx - (totalAmount * moneyScalePx + (memberCount - 1).coerceAtLeast(0) * gapPx).toFloat()) / 2
|
|
||||||
displayMemberSpendings.forEach { (memberId, amount) ->
|
|
||||||
val member = membersMap[memberId]
|
|
||||||
val width = (amount * moneyScalePx).toFloat()
|
|
||||||
val color = member?.let {
|
|
||||||
Color(it.r ?: 128, it.g ?: 128, it.b ?: 128)
|
|
||||||
} ?: Color.Gray
|
|
||||||
|
|
||||||
if (width > 0.5f) {
|
|
||||||
drawRoundRect(
|
|
||||||
color = color,
|
|
||||||
topLeft = Offset(currentXTop, topY),
|
|
||||||
size = Size(width, nodeHeightMemberPx),
|
|
||||||
cornerRadius = CornerRadius(10.dp.toPx())
|
|
||||||
)
|
|
||||||
}
|
|
||||||
currentXTop += width + gapPx
|
|
||||||
}
|
|
||||||
|
|
||||||
drawRect(
|
|
||||||
color = totalNodeColor,
|
|
||||||
topLeft = Offset(totalNodeXPx, middleY),
|
|
||||||
size = Size(totalNodeWidthPx, nodeHeightTotalPx)
|
|
||||||
)
|
|
||||||
|
|
||||||
currentXBottom = (totalWidthPx - (totalAmount * moneyScalePx + (categoryCount - 1).coerceAtLeast(0) * gapPx).toFloat()) / 2
|
|
||||||
displayCategorySpendings.forEach { (catRemoteId, amount) ->
|
|
||||||
val category = categoriesMap[catRemoteId]
|
|
||||||
val width = (amount * moneyScalePx).toFloat()
|
|
||||||
val color = category?.color?.let {
|
|
||||||
try { Color(it.toColorInt()) } catch (_: Exception) { Color(0xFF999999) }
|
|
||||||
} ?: Color(0xFF999999)
|
|
||||||
|
|
||||||
if (width > 0.5f) {
|
|
||||||
drawRoundRect(
|
|
||||||
color = color,
|
|
||||||
topLeft = Offset(currentXBottom, bottomY),
|
|
||||||
size = Size(width, nodeHeightCategoryPx),
|
|
||||||
cornerRadius = CornerRadius(10.dp.toPx())
|
|
||||||
)
|
|
||||||
}
|
|
||||||
currentXBottom += width + gapPx
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Member labels
|
|
||||||
var currentXTopLabel = (maxWidth.value - (totalAmount.toFloat() * moneyScale + (memberCount - 1).coerceAtLeast(0) * horizontalGap.value)) / 2
|
|
||||||
displayMemberSpendings.forEach { (memberId, amount) ->
|
|
||||||
val member = membersMap[memberId]
|
|
||||||
val widthValue = amount.toFloat() * moneyScale
|
|
||||||
val width = widthValue.dp
|
|
||||||
if (widthValue > 12f) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.offset(x = currentXTopLabel.dp, y = 0.dp)
|
|
||||||
.size(width, nodeHeightMember),
|
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
if (width >= 36.dp) {
|
|
||||||
Column(
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = member?.name ?: "???",
|
|
||||||
fontSize = 16.sp,
|
|
||||||
color = Color.White,
|
|
||||||
maxLines = 1,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
fontWeight = FontWeight.Bold
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = formatShortValue(amount),
|
|
||||||
fontSize = 14.sp,
|
|
||||||
color = Color.White.copy(alpha = 0.9f)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
currentXTopLabel += widthValue + horizontalGap.value
|
|
||||||
}
|
|
||||||
|
|
||||||
// Total label
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.align(Alignment.Center)
|
|
||||||
.fillMaxWidth()
|
|
||||||
.height(nodeHeightTotal),
|
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = if (selectedMemberId == -1L) formatShortValue(totalAmount) else "SPENT: ${formatShortValue(totalAmount)}",
|
|
||||||
fontSize = 16.sp,
|
|
||||||
color = Color.White,
|
|
||||||
textAlign = TextAlign.Center,
|
|
||||||
fontWeight = FontWeight.ExtraBold
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Category labels
|
|
||||||
var currentXBottomLabel = (maxWidth.value - (totalAmount.toFloat() * moneyScale + (categoryCount - 1).coerceAtLeast(0) * horizontalGap.value)) / 2
|
|
||||||
displayCategorySpendings.forEach { (catRemoteId, amount) ->
|
|
||||||
val category = categoriesMap[catRemoteId]
|
|
||||||
val widthValue = amount.toFloat() * moneyScale
|
|
||||||
val width = widthValue.dp
|
|
||||||
if (widthValue > 12f) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.align(Alignment.BottomStart)
|
|
||||||
.offset(x = currentXBottomLabel.dp)
|
|
||||||
.size(width, nodeHeightCategory),
|
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = category?.icon ?: "❔",
|
|
||||||
fontSize = 20.sp
|
|
||||||
)
|
|
||||||
if (width >= 32.dp) {
|
|
||||||
Text(
|
|
||||||
text = formatShortValue(amount),
|
|
||||||
fontSize = 14.sp,
|
|
||||||
color = Color.White.copy(alpha = 0.9f)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
currentXBottomLabel += widthValue + horizontalGap.value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Spacer(Modifier.fillMaxWidth().height(32.dp))
|
Spacer(Modifier.fillMaxWidth().height(32.dp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun DrawScope.drawSankeyFlow(
|
private data class NodeLayout(val centerX: Float, val visualWidth: Float)
|
||||||
startX: Float,
|
|
||||||
startY: Float,
|
@SuppressLint("FrequentlyChangingValue")
|
||||||
startWidth: Float,
|
@Composable
|
||||||
endX: Float,
|
private fun SankeyContent(
|
||||||
endY: Float,
|
isSpecialMode: Boolean,
|
||||||
endWidth: Float,
|
selectedMemberId: Long,
|
||||||
startColor: Color,
|
totalAmount: Double,
|
||||||
endColor: Color
|
displayMemberSpendings: List<Pair<Long, Double>>,
|
||||||
|
displayCategorySpendings: List<Pair<Long, Double>>,
|
||||||
|
membersMap: Map<Long, DBMember>,
|
||||||
|
categoriesMap: Map<Long, DBCategory>
|
||||||
) {
|
) {
|
||||||
|
val density = LocalDensity.current
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
val topFocalIndex = remember { Animatable(0f) }
|
||||||
|
val bottomFocalIndex = remember { Animatable(0f) }
|
||||||
|
|
||||||
|
var diagramAreaCoordinates by remember { mutableStateOf<LayoutCoordinates?>(null) }
|
||||||
|
var totalNodeCoordinates by remember { mutableStateOf<LayoutCoordinates?>(null) }
|
||||||
|
|
||||||
|
LaunchedEffect(isSpecialMode) {
|
||||||
|
if (!isSpecialMode) {
|
||||||
|
topFocalIndex.snapTo(0f)
|
||||||
|
bottomFocalIndex.snapTo(0f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BoxWithConstraints(modifier = Modifier.fillMaxSize().onGloballyPositioned { diagramAreaCoordinates = it }) {
|
||||||
|
val totalMaxWidthPx = with(density) { maxWidth.toPx() }
|
||||||
|
val normalGapPx = with(density) { SankeyDimens.NormalGap.toPx() }
|
||||||
|
val focusGapPx = with(density) { SankeyDimens.FocusGap.toPx() }
|
||||||
|
val activeMinPx = with(density) { SankeyDimens.ActiveMinWidth.toPx() }
|
||||||
|
|
||||||
|
val moneyScalePx = if (totalAmount > 0) (totalMaxWidthPx - (maxOf(displayMemberSpendings.size, displayCategorySpendings.size) - 1) * normalGapPx).coerceAtLeast(0f) / totalAmount else 0.0
|
||||||
|
|
||||||
|
fun calculateRowLayout(spendings: List<Pair<*, Double>>, focalIndex: Float): List<NodeLayout> {
|
||||||
|
val visualWidths = spendings.mapIndexed { i, (_, amount) ->
|
||||||
|
val propWidthPx = (amount * moneyScalePx).toFloat()
|
||||||
|
if (!isSpecialMode) propWidthPx
|
||||||
|
else {
|
||||||
|
val focusAmount = (1f - abs(i - focalIndex)).coerceIn(0f, 1f)
|
||||||
|
maxOf(propWidthPx, activeMinPx * focusAmount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return if (!isSpecialMode) {
|
||||||
|
val totalWidthPx = visualWidths.sum() + (spendings.size - 1).coerceAtLeast(0) * normalGapPx
|
||||||
|
var currentX = (totalMaxWidthPx - totalWidthPx) / 2
|
||||||
|
visualWidths.map { w ->
|
||||||
|
NodeLayout(currentX + w / 2, w).also { currentX += w + normalGapPx }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val centers = mutableListOf<Float>()
|
||||||
|
var currentX = 0f
|
||||||
|
visualWidths.forEachIndexed { i, w ->
|
||||||
|
if (i > 0) currentX += visualWidths[i - 1] / 2 + focusGapPx + w / 2
|
||||||
|
else currentX = w / 2
|
||||||
|
centers.add(currentX)
|
||||||
|
}
|
||||||
|
|
||||||
|
val focalPointX = if (spendings.isEmpty()) 0f else {
|
||||||
|
val idx = focalIndex.coerceIn(0f, (spendings.size - 1).toFloat())
|
||||||
|
val low = idx.toInt()
|
||||||
|
val high = (low + 1).coerceAtMost(spendings.size - 1)
|
||||||
|
val fract = idx - low
|
||||||
|
centers[low] * (1 - fract) + centers[high] * fract
|
||||||
|
}
|
||||||
|
|
||||||
|
centers.mapIndexed { i, c ->
|
||||||
|
NodeLayout(totalMaxWidthPx / 2 + (c - focalPointX), visualWidths[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val topLayout = calculateRowLayout(displayMemberSpendings, topFocalIndex.value)
|
||||||
|
val bottomLayout = calculateRowLayout(displayCategorySpendings, bottomFocalIndex.value)
|
||||||
|
|
||||||
|
Canvas(modifier = Modifier.fillMaxSize()) {
|
||||||
|
val root = diagramAreaCoordinates ?: return@Canvas
|
||||||
|
val totalRect = totalNodeCoordinates?.let {
|
||||||
|
val pos = it.positionInRoot() - root.positionInRoot()
|
||||||
|
Rect(pos.x, pos.y, pos.x + it.size.width, pos.y + it.size.height)
|
||||||
|
} ?: return@Canvas
|
||||||
|
val flowMoneyScalePx = totalRect.width / totalAmount
|
||||||
|
val totalNodeColor = Color(0xFF333333)
|
||||||
|
|
||||||
|
displayMemberSpendings.forEachIndexed { index, (_, amount) ->
|
||||||
|
val flowWidth = (amount * flowMoneyScalePx).toFloat()
|
||||||
|
val layout = topLayout[index]
|
||||||
|
if (flowWidth > 0.5f) {
|
||||||
|
val color = membersMap[displayMemberSpendings[index].first]?.let { Color(it.r ?: 128, it.g ?: 128, it.b ?: 128) } ?: Color.Gray
|
||||||
|
drawSankeyFlow(layout.centerX - layout.visualWidth / 2, SankeyDimens.NodeHeight.toPx() / 2, layout.visualWidth, totalRect.left + displayMemberSpendings.take(index).sumOf { it.second * flowMoneyScalePx }.toFloat(), totalRect.top + totalRect.height / 2, flowWidth, color.copy(alpha = 0.5f), totalNodeColor.copy(alpha = 0.35f))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
displayCategorySpendings.forEachIndexed { index, (_, amount) ->
|
||||||
|
val flowWidth = (amount * flowMoneyScalePx).toFloat()
|
||||||
|
val layout = bottomLayout[index]
|
||||||
|
if (flowWidth > 0.5f) {
|
||||||
|
val color = categoriesMap[displayCategorySpendings[index].first]?.color?.let { try { Color(it.toColorInt()) } catch (_: Exception) { Color(0xFF999999) } } ?: Color(0xFF999999)
|
||||||
|
drawSankeyFlow(totalRect.left + displayCategorySpendings.take(index).sumOf { it.second * flowMoneyScalePx }.toFloat(), totalRect.top + totalRect.height / 2, flowWidth, layout.centerX - layout.visualWidth / 2, size.height - SankeyDimens.NodeHeight.toPx() / 2, layout.visualWidth, totalNodeColor.copy(alpha = 0.35f), color.copy(alpha = 0.5f))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(modifier = Modifier.fillMaxSize()) {
|
||||||
|
// TOP Row
|
||||||
|
Box(modifier = Modifier.fillMaxWidth().height(SankeyDimens.NodeHeight).then(if (isSpecialMode) Modifier.draggable(
|
||||||
|
orientation = Orientation.Horizontal,
|
||||||
|
state = rememberDraggableState { delta ->
|
||||||
|
scope.launch {
|
||||||
|
val currentIdx = topFocalIndex.value.roundToInt().coerceIn(displayMemberSpendings.indices)
|
||||||
|
val step = topLayout[currentIdx].visualWidth + focusGapPx
|
||||||
|
topFocalIndex.snapTo((topFocalIndex.value - delta / step).coerceIn(0f, (displayMemberSpendings.size - 1).toFloat()))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDragStopped = {
|
||||||
|
topFocalIndex.animateTo(topFocalIndex.value.roundToInt().toFloat(), spring(Spring.DampingRatioLowBouncy, Spring.StiffnessLow))
|
||||||
|
}
|
||||||
|
) else Modifier)) {
|
||||||
|
displayMemberSpendings.forEachIndexed { index, (id, amount) ->
|
||||||
|
val member = membersMap[id]
|
||||||
|
val layout = topLayout[index]
|
||||||
|
val wDp = with(density) { layout.visualWidth.toDp() }
|
||||||
|
val xDp = with(density) { (layout.centerX - layout.visualWidth / 2).toDp() }
|
||||||
|
|
||||||
|
Box(modifier = Modifier.offset(x = xDp).width(wDp).fillMaxHeight()
|
||||||
|
.then(if (isSpecialMode) Modifier.clickable(interactionSource = remember { MutableInteractionSource() }, indication = null) {
|
||||||
|
scope.launch { topFocalIndex.animateTo(index.toFloat(), spring(Spring.DampingRatioLowBouncy, Spring.StiffnessLow)) }
|
||||||
|
} else Modifier), contentAlignment = Alignment.Center) {
|
||||||
|
Column(modifier = Modifier.fillMaxSize().background(color = member?.let { Color(it.r ?: 128, it.g ?: 128, it.b ?: 128) } ?: Color.Gray, shape = RoundedCornerShape(10.dp)), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center) {
|
||||||
|
Text(text = member?.name ?: "???", color = Color.White, fontWeight = FontWeight.Bold, maxLines = 1, overflow = TextOverflow.Ellipsis, textAlign = TextAlign.Center, modifier = Modifier.wrapContentWidth(unbounded = true))
|
||||||
|
if (wDp >= 40.dp) Text(text = formatShortValue(amount), fontSize = 12.sp, color = Color.White.copy(alpha = 0.9f), textAlign = TextAlign.Center)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Box(modifier = Modifier.weight(1f).fillMaxWidth(), contentAlignment = Alignment.Center) {
|
||||||
|
Box(modifier = Modifier.width(with(density) { (totalAmount * moneyScalePx).toFloat().toDp() }).height(SankeyDimens.TotalNodeHeight).background(Color(0xFF333333)).onGloballyPositioned { totalNodeCoordinates = it }, contentAlignment = Alignment.Center) {
|
||||||
|
Text(text = if (selectedMemberId == -1L) formatShortValue(totalAmount) else "SPENT: ${formatShortValue(totalAmount)}", color = Color.White, fontWeight = FontWeight.ExtraBold, fontSize = 16.sp, modifier = Modifier.padding(horizontal = 8.dp), maxLines = 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BOTTOM Row
|
||||||
|
Box(modifier = Modifier.fillMaxWidth().height(SankeyDimens.NodeHeight).then(if (isSpecialMode) Modifier.draggable(
|
||||||
|
orientation = Orientation.Horizontal,
|
||||||
|
state = rememberDraggableState { delta ->
|
||||||
|
scope.launch {
|
||||||
|
val currentIdx = bottomFocalIndex.value.roundToInt().coerceIn(displayCategorySpendings.indices)
|
||||||
|
val step = bottomLayout[currentIdx].visualWidth + focusGapPx
|
||||||
|
bottomFocalIndex.snapTo((bottomFocalIndex.value - delta / step).coerceIn(0f, (displayCategorySpendings.size - 1).toFloat()))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDragStopped = {
|
||||||
|
bottomFocalIndex.animateTo(bottomFocalIndex.value.roundToInt().toFloat(), spring(Spring.DampingRatioLowBouncy, Spring.StiffnessLow))
|
||||||
|
}
|
||||||
|
) else Modifier)) {
|
||||||
|
displayCategorySpendings.forEachIndexed { index, (id, amount) ->
|
||||||
|
val category = categoriesMap[id]
|
||||||
|
val layout = bottomLayout[index]
|
||||||
|
val wDp = with(density) { layout.visualWidth.toDp() }
|
||||||
|
val xDp = with(density) { (layout.centerX - layout.visualWidth / 2).toDp() }
|
||||||
|
|
||||||
|
Box(modifier = Modifier.offset(x = xDp).width(wDp).fillMaxHeight()
|
||||||
|
.then(if (isSpecialMode) Modifier.clickable(interactionSource = remember { MutableInteractionSource() }, indication = null) {
|
||||||
|
scope.launch { bottomFocalIndex.animateTo(index.toFloat(), spring(Spring.DampingRatioLowBouncy, Spring.StiffnessLow)) }
|
||||||
|
} else Modifier), contentAlignment = Alignment.Center) {
|
||||||
|
Column(modifier = Modifier.fillMaxSize().background(color = category?.color?.let { try { Color(it.toColorInt()) } catch (_: Exception) { Color(0xFF999999) } } ?: Color(0xFF999999), shape = RoundedCornerShape(10.dp)), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center) {
|
||||||
|
Text(text = category?.icon ?: "❔", fontSize = 20.sp, textAlign = TextAlign.Center, modifier = Modifier.wrapContentWidth(unbounded = true))
|
||||||
|
if (wDp >= 40.dp) Text(text = formatShortValue(amount), fontSize = 12.sp, color = Color.White.copy(alpha = 0.9f), textAlign = TextAlign.Center)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun DrawScope.drawSankeyFlow(startX: Float, startY: Float, startWidth: Float, endX: Float, endY: Float, endWidth: Float, startColor: Color, endColor: Color) {
|
||||||
val path = Path().apply {
|
val path = Path().apply {
|
||||||
moveTo(startX, startY)
|
moveTo(startX, startY)
|
||||||
cubicTo(
|
cubicTo(startX, startY + (endY - startY) * 0.5f, endX, endY - (endY - startY) * 0.5f, endX, endY)
|
||||||
startX, startY + (endY - startY) * 0.5f,
|
|
||||||
endX, endY - (endY - startY) * 0.5f,
|
|
||||||
endX, endY
|
|
||||||
)
|
|
||||||
lineTo(endX + endWidth, endY)
|
lineTo(endX + endWidth, endY)
|
||||||
cubicTo(
|
cubicTo(endX + endWidth, endY - (endY - startY) * 0.5f, startX + startWidth, startY + (endY - startY) * 0.5f, startX + startWidth, startY)
|
||||||
endX + endWidth, endY - (endY - startY) * 0.5f,
|
|
||||||
startX + startWidth, startY + (endY - startY) * 0.5f,
|
|
||||||
startX + startWidth, startY
|
|
||||||
)
|
|
||||||
close()
|
close()
|
||||||
}
|
}
|
||||||
drawPath(
|
drawPath(path = path, brush = Brush.verticalGradient(colors = listOf(startColor, endColor), startY = startY, endY = endY))
|
||||||
path = path,
|
|
||||||
brush = Brush.verticalGradient(
|
|
||||||
colors = listOf(startColor, endColor),
|
|
||||||
startY = startY,
|
|
||||||
endY = endY
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Preview(showBackground = true, widthDp = 360, heightDp = 640)
|
@Preview(showBackground = true, widthDp = 360, heightDp = 640)
|
||||||
@Composable
|
@Composable
|
||||||
fun ProjectSankeyDiagramPreview() {
|
fun ProjectSankeyDiagramPreview() = MaterialTheme { ProjectSankeyDiagram("Test Project", 1L, "1", StatisticsMockData.members, StatisticsMockData.bills, emptyList()) {} }
|
||||||
MaterialTheme {
|
|
||||||
ProjectSankeyDiagram(
|
|
||||||
projectName = "Test Project",
|
|
||||||
allMembers = StatisticsMockData.members,
|
|
||||||
allBills = StatisticsMockData.bills,
|
|
||||||
customCategories = emptyList(),
|
|
||||||
onShareReady = {}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -43,10 +43,10 @@ fun ProjectSpendingGraph(
|
|||||||
allBills: List<DBBill>,
|
allBills: List<DBBill>,
|
||||||
onShareReady: (String) -> Unit
|
onShareReady: (String) -> Unit
|
||||||
) {
|
) {
|
||||||
val shareStatsIntro = stringResource(R.string.share_stats_intro, projectName)
|
val shareStatsIntro = stringResource(R.string.msg_stats_intro, projectName)
|
||||||
if (allBills.isEmpty()) {
|
if (allBills.isEmpty()) {
|
||||||
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||||
Text("No data to display", style = MaterialTheme.typography.h6)
|
Text("No data to display", style = MaterialTheme.typography.subtitle1, fontWeight = FontWeight.Bold, color = MaterialTheme.colors.onSurface.copy(alpha = 0.6f))
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package net.helcel.cowspent.android.statistics
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
@@ -15,6 +16,7 @@ import net.helcel.cowspent.theme.ThemeUtils
|
|||||||
class ProjectStatisticsActivity : AppCompatActivity() {
|
class ProjectStatisticsActivity : AppCompatActivity() {
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
enableEdgeToEdge()
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
val projectId = intent.getLongExtra(EXTRA_PROJECT_ID, -1L)
|
val projectId = intent.getLongExtra(EXTRA_PROJECT_ID, -1L)
|
||||||
|
|||||||
+23
-13
@@ -35,7 +35,7 @@ fun ProjectStatisticsScreen(
|
|||||||
}
|
}
|
||||||
var currentShareText by remember { mutableStateOf("") }
|
var currentShareText by remember { mutableStateOf("") }
|
||||||
val tabs = listOf(
|
val tabs = listOf(
|
||||||
stringResource(R.string.statistic_title),
|
stringResource(R.string.title_stats),
|
||||||
"Trend",
|
"Trend",
|
||||||
"Sankey"
|
"Sankey"
|
||||||
)
|
)
|
||||||
@@ -43,7 +43,7 @@ fun ProjectStatisticsScreen(
|
|||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = { Text(stringResource(R.string.statistic_title)) },
|
title = { Text(stringResource(R.string.title_stats)) },
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
IconButton(onClick = onBack) {
|
IconButton(onClick = onBack) {
|
||||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)
|
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)
|
||||||
@@ -84,6 +84,21 @@ fun ProjectStatisticsScreen(
|
|||||||
|
|
||||||
val statsData by produceState<StatisticsData?>(null, proj.id) {
|
val statsData by produceState<StatisticsData?>(null, proj.id) {
|
||||||
value = withContext(Dispatchers.IO) {
|
value = withContext(Dispatchers.IO) {
|
||||||
|
val syncedCategories = db.getCategories(proj.id)
|
||||||
|
val defaultCategories = CategoryUtils.getDefaultCategories(context, proj.id)
|
||||||
|
|
||||||
|
val toEnsure = if (proj.type == ProjectType.LOCAL) {
|
||||||
|
defaultCategories
|
||||||
|
} else {
|
||||||
|
listOfNotNull(defaultCategories.find { it.remoteId == DBBill.CATEGORY_REIMBURSEMENT })
|
||||||
|
}
|
||||||
|
|
||||||
|
toEnsure.forEach { def ->
|
||||||
|
if (syncedCategories.none { it.remoteId == def.remoteId }) {
|
||||||
|
db.addCategory(def)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val members = db.getMembersOfProject(proj.id, null)
|
val members = db.getMembersOfProject(proj.id, null)
|
||||||
val bills = db.getBillsOfProject(proj.id)
|
val bills = db.getBillsOfProject(proj.id)
|
||||||
val categories = db.getCategories(proj.id)
|
val categories = db.getCategories(proj.id)
|
||||||
@@ -94,19 +109,12 @@ fun ProjectStatisticsScreen(
|
|||||||
|
|
||||||
if (statsData != null) {
|
if (statsData != null) {
|
||||||
val data = statsData!!
|
val data = statsData!!
|
||||||
val defaultCategories = remember(proj.id) { CategoryUtils.getDefaultCategories(context, proj.id) }
|
val categories = data.categories
|
||||||
val categories = remember(proj.type, data.categories, defaultCategories) {
|
|
||||||
val hardcoded = if (proj.type == ProjectType.LOCAL) {
|
|
||||||
defaultCategories
|
|
||||||
} else {
|
|
||||||
listOfNotNull(defaultCategories.find { it.remoteId.toInt() == DBBill.CATEGORY_REIMBURSEMENT })
|
|
||||||
}
|
|
||||||
(data.categories + hardcoded).distinctBy { it.remoteId }
|
|
||||||
}
|
|
||||||
val categoryNoneLabel = stringResource(R.string.category_none)
|
val categoryNoneLabel = stringResource(R.string.category_none)
|
||||||
val sankeyCategories = remember(proj.id, data.categories, defaultCategories, categoryNoneLabel) {
|
val sankeyCategories = remember(proj.id, data.categories, categoryNoneLabel) {
|
||||||
val noneCategory = DBCategory(0, 0, proj.id, categoryNoneLabel, "❌", "#9E9E9E")
|
val noneCategory = DBCategory(0, 0, proj.id, categoryNoneLabel, "❌", "#9E9E9E")
|
||||||
(data.categories + defaultCategories + noneCategory).distinctBy { it.remoteId }
|
(data.categories + noneCategory).distinctBy { if (it.id == 0L) "none" else it.id.toString() }
|
||||||
}
|
}
|
||||||
|
|
||||||
when (selectedTab) {
|
when (selectedTab) {
|
||||||
@@ -131,6 +139,8 @@ fun ProjectStatisticsScreen(
|
|||||||
2 -> {
|
2 -> {
|
||||||
ProjectSankeyDiagram(
|
ProjectSankeyDiagram(
|
||||||
projectName = proj.name.ifEmpty { proj.remoteId },
|
projectName = proj.name.ifEmpty { proj.remoteId },
|
||||||
|
projectId = proj.id,
|
||||||
|
projectRemoteId = proj.remoteId,
|
||||||
allMembers = data.members,
|
allMembers = data.members,
|
||||||
allBills = data.bills,
|
allBills = data.bills,
|
||||||
customCategories = sankeyCategories,
|
customCategories = sankeyCategories,
|
||||||
|
|||||||
+26
-26
@@ -44,8 +44,8 @@ fun ProjectStatisticsTable(
|
|||||||
val sdf = remember { SimpleDateFormat("yyyy-MM-dd", Locale.ROOT) }
|
val sdf = remember { SimpleDateFormat("yyyy-MM-dd", Locale.ROOT) }
|
||||||
val dateFormat = remember { android.text.format.DateFormat.getDateFormat(context) }
|
val dateFormat = remember { android.text.format.DateFormat.getDateFormat(context) }
|
||||||
|
|
||||||
var categoryId by remember { mutableIntStateOf(-1000) }
|
var categoryId by remember { mutableLongStateOf(-1000L) }
|
||||||
var paymentModeId by remember { mutableIntStateOf(-1000) }
|
var paymentModeId by remember { mutableLongStateOf(-1000L) }
|
||||||
var dateMin by remember { mutableStateOf<String?>(null) }
|
var dateMin by remember { mutableStateOf<String?>(null) }
|
||||||
var dateMax by remember { mutableStateOf<String?>(null) }
|
var dateMax by remember { mutableStateOf<String?>(null) }
|
||||||
|
|
||||||
@@ -57,14 +57,14 @@ fun ProjectStatisticsTable(
|
|||||||
val paymentModeAll = stringResource(R.string.payment_mode_all)
|
val paymentModeAll = stringResource(R.string.payment_mode_all)
|
||||||
val paymentModeNone = stringResource(R.string.payment_mode_none)
|
val paymentModeNone = stringResource(R.string.payment_mode_none)
|
||||||
|
|
||||||
val shareStatsHeader = stringResource(R.string.share_stats_header)
|
val shareStatsHeader = stringResource(R.string.msg_stats_header)
|
||||||
val shareStatsIntro = stringResource(R.string.share_stats_intro, proj.name.ifEmpty { proj.remoteId })
|
val shareStatsIntro = stringResource(R.string.msg_stats_intro, proj.name.ifEmpty { proj.remoteId })
|
||||||
|
|
||||||
val categories = remember(proj.id, customCategories, categoryAll, categoryNone, categoryReimbursement, categoryAllExceptReimbursement) {
|
val categories = remember(proj.id, customCategories, categoryAll, categoryNone, categoryReimbursement, categoryAllExceptReimbursement) {
|
||||||
val list = mutableListOf<Triple<Int, String, String>>()
|
val list = mutableListOf<Triple<Long, String, String>>()
|
||||||
list.add(Triple(-1000, "📋", categoryAll))
|
list.add(Triple(-1000L, "📋", categoryAll))
|
||||||
list.add(Triple(-100, "🧾", categoryAllExceptReimbursement))
|
list.add(Triple(-100L, "🧾", categoryAllExceptReimbursement))
|
||||||
list.add(Triple(0, "❌", categoryNone))
|
list.add(Triple(0L, "❌", categoryNone))
|
||||||
|
|
||||||
val catsToUse = if (proj.type == ProjectType.LOCAL) {
|
val catsToUse = if (proj.type == ProjectType.LOCAL) {
|
||||||
CategoryUtils.getDefaultCategories(context, proj.id)
|
CategoryUtils.getDefaultCategories(context, proj.id)
|
||||||
@@ -75,15 +75,15 @@ fun ProjectStatisticsTable(
|
|||||||
}
|
}
|
||||||
|
|
||||||
catsToUse.forEach {
|
catsToUse.forEach {
|
||||||
list.add(Triple(it.remoteId.toInt(), it.icon, it.name ?: ""))
|
list.add(Triple(it.id, it.icon, it.name ?: ""))
|
||||||
}
|
}
|
||||||
list.distinctBy { it.first }
|
list.distinctBy { it.first }
|
||||||
}
|
}
|
||||||
|
|
||||||
val paymentModes = remember(proj.id, customPaymentModes, paymentModeAll, paymentModeNone) {
|
val paymentModes = remember(proj.id, customPaymentModes, paymentModeAll, paymentModeNone) {
|
||||||
val list = mutableListOf<Triple<Int, String, String>>()
|
val list = mutableListOf<Triple<Long, String, String>>()
|
||||||
list.add(Triple(-1000, "💳", paymentModeAll))
|
list.add(Triple(-1000L, "💳", paymentModeAll))
|
||||||
list.add(Triple(0, "❌", paymentModeNone))
|
list.add(Triple(0L, "❌", paymentModeNone))
|
||||||
|
|
||||||
val pmsToUse = if (proj.type == ProjectType.LOCAL) {
|
val pmsToUse = if (proj.type == ProjectType.LOCAL) {
|
||||||
CategoryUtils.getDefaultPaymentModes(context, proj.id)
|
CategoryUtils.getDefaultPaymentModes(context, proj.id)
|
||||||
@@ -94,7 +94,7 @@ fun ProjectStatisticsTable(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pmsToUse.forEach {
|
pmsToUse.forEach {
|
||||||
list.add(Triple(it.remoteId.toInt(), it.icon, it.name ?: ""))
|
list.add(Triple(it.id, it.icon, it.name ?: ""))
|
||||||
}
|
}
|
||||||
list.distinctBy { it.first }
|
list.distinctBy { it.first }
|
||||||
}
|
}
|
||||||
@@ -105,10 +105,12 @@ fun ProjectStatisticsTable(
|
|||||||
val membersPaid = HashMap<Long, Double>()
|
val membersPaid = HashMap<Long, Double>()
|
||||||
val membersSpent = HashMap<Long, Double>()
|
val membersSpent = HashMap<Long, Double>()
|
||||||
|
|
||||||
|
val reimbursementCategoryId = CategoryUtils.getReimbursementCategoryId(customCategories, proj.id, proj.remoteId)
|
||||||
|
|
||||||
SupportUtil.getStats(
|
SupportUtil.getStats(
|
||||||
allMembers, allBills,
|
allMembers, allBills,
|
||||||
membersNbBills, membersBalance, membersPaid, membersSpent,
|
membersNbBills, membersBalance, membersPaid, membersSpent,
|
||||||
categoryId, paymentModeId, dateMin, dateMax
|
categoryId, paymentModeId, reimbursementCategoryId, dateMin, dateMax
|
||||||
)
|
)
|
||||||
|
|
||||||
var statsText = shareStatsIntro + "\n\n"
|
var statsText = shareStatsIntro + "\n\n"
|
||||||
@@ -164,12 +166,12 @@ fun ProjectStatisticsTable(
|
|||||||
|
|
||||||
EditableExposedDropdownMenu(
|
EditableExposedDropdownMenu(
|
||||||
value = selectedCategory?.third ?: "",
|
value = selectedCategory?.third ?: "",
|
||||||
placeholder = stringResource(R.string.setting_category),
|
placeholder = stringResource(R.string.label_category),
|
||||||
expanded = categoryExpanded,
|
expanded = categoryExpanded,
|
||||||
onExpandedChange = { categoryExpanded = it },
|
onExpandedChange = { categoryExpanded = it },
|
||||||
onDismissRequest = { categoryExpanded = false },
|
onDismissRequest = { categoryExpanded = false },
|
||||||
leadingIcon = {
|
leadingIcon = {
|
||||||
Box(modifier = Modifier.padding(start = 12.dp)) {
|
Box(modifier = Modifier) {
|
||||||
if (selectedCategory != null) {
|
if (selectedCategory != null) {
|
||||||
Text(text = selectedCategory.second, fontSize = 20.sp)
|
Text(text = selectedCategory.second, fontSize = 20.sp)
|
||||||
} else {
|
} else {
|
||||||
@@ -198,12 +200,12 @@ fun ProjectStatisticsTable(
|
|||||||
|
|
||||||
EditableExposedDropdownMenu(
|
EditableExposedDropdownMenu(
|
||||||
value = selectedPm?.third ?: "",
|
value = selectedPm?.third ?: "",
|
||||||
placeholder = stringResource(R.string.setting_payment_mode),
|
placeholder = stringResource(R.string.label_mode),
|
||||||
expanded = pmExpanded,
|
expanded = pmExpanded,
|
||||||
onExpandedChange = { pmExpanded = it },
|
onExpandedChange = { pmExpanded = it },
|
||||||
onDismissRequest = { pmExpanded = false },
|
onDismissRequest = { pmExpanded = false },
|
||||||
leadingIcon = {
|
leadingIcon = {
|
||||||
Box(modifier = Modifier.padding(start = 12.dp)) {
|
Box(modifier = Modifier) {
|
||||||
if (selectedPm != null) {
|
if (selectedPm != null) {
|
||||||
Text(text = selectedPm.second, fontSize = 20.sp)
|
Text(text = selectedPm.second, fontSize = 20.sp)
|
||||||
} else {
|
} else {
|
||||||
@@ -269,17 +271,15 @@ fun ProjectStatisticsTable(
|
|||||||
|
|
||||||
Spacer(modifier = Modifier.height(24.dp))
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
|
||||||
// Table Header
|
|
||||||
Surface(
|
Surface(
|
||||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.05f),
|
color = MaterialTheme.colors.background,
|
||||||
shape = MaterialTheme.shapes.small,
|
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
) {
|
) {
|
||||||
Row(modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp, vertical = 8.dp)) {
|
Row(modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp, vertical = 12.dp)) {
|
||||||
Text(stringResource(R.string.stats_who), modifier = Modifier.weight(2f), fontWeight = FontWeight.Bold, color = MaterialTheme.colors.onSurface.copy(alpha = 0.6f), fontSize = 12.sp)
|
Text(stringResource(R.string.stats_who).uppercase(), modifier = Modifier.weight(2f), fontWeight = FontWeight.Bold, color = MaterialTheme.colors.onSurface, style = MaterialTheme.typography.overline)
|
||||||
Text(stringResource(R.string.stats_paid), modifier = Modifier.weight(1.5f), fontWeight = FontWeight.Bold, textAlign = TextAlign.End, color = MaterialTheme.colors.onSurface.copy(alpha = 0.6f), fontSize = 12.sp)
|
Text(stringResource(R.string.stats_paid).uppercase(), modifier = Modifier.weight(1.5f), fontWeight = FontWeight.Bold, textAlign = TextAlign.End, color = MaterialTheme.colors.onSurface, style = MaterialTheme.typography.overline)
|
||||||
Text(stringResource(R.string.stats_spent), modifier = Modifier.weight(1.5f), fontWeight = FontWeight.Bold, textAlign = TextAlign.End, color = MaterialTheme.colors.onSurface.copy(alpha = 0.6f), fontSize = 12.sp)
|
Text(stringResource(R.string.stats_spent).uppercase(), modifier = Modifier.weight(1.5f), fontWeight = FontWeight.Bold, textAlign = TextAlign.End, color = MaterialTheme.colors.onSurface, style = MaterialTheme.typography.overline)
|
||||||
Text(stringResource(R.string.stats_balance), modifier = Modifier.weight(1.5f), fontWeight = FontWeight.Bold, textAlign = TextAlign.End, color = MaterialTheme.colors.onSurface.copy(alpha = 0.6f), fontSize = 12.sp)
|
Text(stringResource(R.string.stats_balance).uppercase(), modifier = Modifier.weight(1.5f), fontWeight = FontWeight.Bold, textAlign = TextAlign.End, color = MaterialTheme.colors.onSurface, style = MaterialTheme.typography.overline)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,6 @@ class DBAccountProject(
|
|||||||
) : Serializable {
|
) : Serializable {
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "#DBAccountProject$id/$remoteId,$name, $ncUrl, $password, archivedTs=$archivedTs"
|
return "#DBAccountProject$id/$remoteId,$name, $ncUrl, [SECURE], archivedTs=$archivedTs"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package net.helcel.cowspent.model
|
package net.helcel.cowspent.model
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
@@ -16,9 +15,9 @@ open class DBBill(
|
|||||||
var state: Int,
|
var state: Int,
|
||||||
var repeat: String?,
|
var repeat: String?,
|
||||||
var paymentMode: String?,
|
var paymentMode: String?,
|
||||||
var categoryRemoteId: Int,
|
var categoryId: Long,
|
||||||
var comment: String?,
|
var comment: String?,
|
||||||
var paymentModeRemoteId: Int
|
var paymentModeId: Long
|
||||||
) : Item, Serializable {
|
) : Item, Serializable {
|
||||||
|
|
||||||
var formattedWhat: String = ""
|
var formattedWhat: String = ""
|
||||||
@@ -39,7 +38,6 @@ open class DBBill(
|
|||||||
get() {
|
get() {
|
||||||
val cal = Calendar.getInstance()
|
val cal = Calendar.getInstance()
|
||||||
cal.timeInMillis = timestamp * 1000
|
cal.timeInMillis = timestamp * 1000
|
||||||
Log.v("ll", "[$what] get date ts $timestamp year ${cal[Calendar.YEAR]}")
|
|
||||||
val month = cal[Calendar.MONTH] + 1
|
val month = cal[Calendar.MONTH] + 1
|
||||||
val day = cal[Calendar.DAY_OF_MONTH]
|
val day = cal[Calendar.DAY_OF_MONTH]
|
||||||
return "${cal[Calendar.YEAR]}-${String.format(Locale.ROOT, "%02d", month)}-${
|
return "${cal[Calendar.YEAR]}-${String.format(Locale.ROOT, "%02d", month)}-${
|
||||||
@@ -58,7 +56,7 @@ open class DBBill(
|
|||||||
|
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "#DBBill$id/$remoteId,$projectId, $payerId, $amount, $timestamp, $what, $state, $repeat, $paymentMode, $categoryRemoteId"
|
return "#DBBill$id/$remoteId,$projectId, $payerId, $amount, $timestamp, $what, $state, $repeat, $paymentMode, $categoryId"
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun isSection(): Boolean {
|
override fun isSection(): Boolean {
|
||||||
@@ -73,15 +71,15 @@ open class DBBill(
|
|||||||
const val PAYMODE_TRANSFER = "t"
|
const val PAYMODE_TRANSFER = "t"
|
||||||
const val PAYMODE_ONLINE_SERVICE = "o"
|
const val PAYMODE_ONLINE_SERVICE = "o"
|
||||||
|
|
||||||
const val PAYMODE_ID_NONE = 0
|
const val PAYMODE_ID_NONE = 0L
|
||||||
const val PAYMODE_ID_CARD = -1
|
const val PAYMODE_ID_CARD = -1L
|
||||||
const val PAYMODE_ID_CASH = -2
|
const val PAYMODE_ID_CASH = -2L
|
||||||
const val PAYMODE_ID_CHECK = -3
|
const val PAYMODE_ID_CHECK = -3L
|
||||||
const val PAYMODE_ID_TRANSFER = -4
|
const val PAYMODE_ID_TRANSFER = -4L
|
||||||
const val PAYMODE_ID_ONLINE_SERVICE = -5
|
const val PAYMODE_ID_ONLINE_SERVICE = -5L
|
||||||
|
|
||||||
@JvmField
|
@JvmField
|
||||||
val oldPmIdToNew: Map<String, Int> = object : HashMap<String, Int>() {
|
val oldPmIdToNew: Map<String, Long> = object : HashMap<String, Long>() {
|
||||||
init {
|
init {
|
||||||
put(PAYMODE_NONE, PAYMODE_ID_NONE)
|
put(PAYMODE_NONE, PAYMODE_ID_NONE)
|
||||||
put(PAYMODE_CARD, PAYMODE_ID_CARD)
|
put(PAYMODE_CARD, PAYMODE_ID_CARD)
|
||||||
@@ -92,19 +90,19 @@ open class DBBill(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const val CATEGORY_NONE = 0
|
const val CATEGORY_NONE = 0L
|
||||||
const val CATEGORY_GROCERIES = -1
|
const val CATEGORY_GROCERIES = -1L
|
||||||
const val CATEGORY_LEISURE = -2
|
const val CATEGORY_LEISURE = -2L
|
||||||
const val CATEGORY_RENT = -3
|
const val CATEGORY_RENT = -3L
|
||||||
const val CATEGORY_BILLS = -4
|
const val CATEGORY_BILLS = -4L
|
||||||
const val CATEGORY_CULTURE = -5
|
const val CATEGORY_CULTURE = -5L
|
||||||
const val CATEGORY_HEALTH = -6
|
const val CATEGORY_HEALTH = -6L
|
||||||
const val CATEGORY_SHOPPING = -10
|
const val CATEGORY_SHOPPING = -10L
|
||||||
const val CATEGORY_REIMBURSEMENT = -11
|
const val CATEGORY_REIMBURSEMENT = -11L
|
||||||
const val CATEGORY_RESTAURANT = -12
|
const val CATEGORY_RESTAURANT = -12L
|
||||||
const val CATEGORY_ACCOMMODATION = -13
|
const val CATEGORY_ACCOMMODATION = -13L
|
||||||
const val CATEGORY_TRANSPORT = -14
|
const val CATEGORY_TRANSPORT = -14L
|
||||||
const val CATEGORY_SPORT = -15
|
const val CATEGORY_SPORT = -15L
|
||||||
|
|
||||||
const val STATE_OK = 0
|
const val STATE_OK = 0
|
||||||
const val STATE_ADDED = 1
|
const val STATE_ADDED = 1
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ class DBCategory(
|
|||||||
var projectId: Long,
|
var projectId: Long,
|
||||||
var name: String?,
|
var name: String?,
|
||||||
var icon: String,
|
var icon: String,
|
||||||
var color: String
|
var color: String,
|
||||||
|
var state: Int = DBBill.STATE_OK
|
||||||
) : Serializable {
|
) : Serializable {
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ class DBPaymentMode(
|
|||||||
var projectId: Long,
|
var projectId: Long,
|
||||||
var name: String?,
|
var name: String?,
|
||||||
var icon: String,
|
var icon: String,
|
||||||
var color: String
|
var color: String,
|
||||||
|
var state: Int = DBBill.STATE_OK
|
||||||
) : Serializable {
|
) : Serializable {
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ class GroupedBill(
|
|||||||
sourceBills.first().state,
|
sourceBills.first().state,
|
||||||
sourceBills.first().repeat,
|
sourceBills.first().repeat,
|
||||||
sourceBills.first().paymentMode,
|
sourceBills.first().paymentMode,
|
||||||
sourceBills.first().categoryRemoteId,
|
sourceBills.first().categoryId,
|
||||||
sourceBills.first().comment,
|
sourceBills.first().comment,
|
||||||
sourceBills.first().paymentModeRemoteId
|
sourceBills.first().paymentModeId
|
||||||
), Serializable {
|
), Serializable {
|
||||||
init {
|
init {
|
||||||
this.formattedWhat = sourceBills.first().formattedWhat
|
this.formattedWhat = sourceBills.first().formattedWhat
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -265,6 +265,121 @@ class CowspentServerSyncHelper private constructor(private val dbHelper: Cowspen
|
|||||||
val members = dbHelper.getMembersOfProject(project.id, null)
|
val members = dbHelper.getMembersOfProject(project.id, null)
|
||||||
val memberIdToRemoteId = members.associate { it.id to it.remoteId }
|
val memberIdToRemoteId = members.associate { it.id to it.remoteId }
|
||||||
|
|
||||||
|
val categoriesToAdd = dbHelper.getCategoriesOfProjectWithState(project.id, DBBill.STATE_ADDED)
|
||||||
|
for (catToAdd in categoriesToAdd) {
|
||||||
|
try {
|
||||||
|
val categoriesResponse = client!!.getCategories(project)
|
||||||
|
val remoteCategories = categoriesResponse.getCategories(project.id)
|
||||||
|
val matchingRemote = remoteCategories.find { it.name == catToAdd.name }
|
||||||
|
|
||||||
|
if (matchingRemote != null) {
|
||||||
|
dbHelper.updateCategory(catToAdd.id, null, null, null, DBBill.STATE_OK, matchingRemote.remoteId)
|
||||||
|
catToAdd.remoteId = matchingRemote.remoteId
|
||||||
|
} else {
|
||||||
|
val createResponse = client!!.createRemoteCategory(project, catToAdd)
|
||||||
|
val newRemoteId = createResponse.remoteCategoryId
|
||||||
|
if (newRemoteId > 0) {
|
||||||
|
dbHelper.updateCategory(catToAdd.id, null, null, null, DBBill.STATE_OK, newRemoteId)
|
||||||
|
catToAdd.remoteId = newRemoteId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "CATEGORY SYNC FAILED for ${catToAdd.name}", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val categoriesToEdit = dbHelper.getCategoriesOfProjectWithState(project.id, DBBill.STATE_EDITED)
|
||||||
|
for (catToEdit in categoriesToEdit) {
|
||||||
|
try {
|
||||||
|
client!!.editRemoteCategory(project, catToEdit)
|
||||||
|
dbHelper.updateCategory(catToEdit.id, null, null, null, DBBill.STATE_OK)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "EDIT CATEGORY FAILED for ${catToEdit.name}, might not exist remotely", e)
|
||||||
|
// If it fails, we keep the state as EDITED so it tries again next time,
|
||||||
|
// or we could set it to OK if we think it's a permanent mismatch.
|
||||||
|
// For now just log it.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val categoriesToDelete = dbHelper.getCategoriesOfProjectWithState(project.id, DBBill.STATE_DELETED)
|
||||||
|
for (catToDel in categoriesToDelete) {
|
||||||
|
try {
|
||||||
|
client!!.deleteRemoteCategory(project, catToDel.remoteId)
|
||||||
|
dbHelper.deleteCategory(catToDel.id)
|
||||||
|
} catch (e: NextcloudHttpRequestFailedException) {
|
||||||
|
if (e.statusCode == 404 || e.statusCode == 400) {
|
||||||
|
Log.d(TAG, "failed to delete category on remote project (code ${e.statusCode}) : delete it locally anyway")
|
||||||
|
dbHelper.deleteCategory(catToDel.id)
|
||||||
|
} else {
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "DELETE CATEGORY FAILED for ${catToDel.name}", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val paymentModesToAdd = dbHelper.getPaymentModesOfProjectWithState(project.id, DBBill.STATE_ADDED)
|
||||||
|
if (paymentModesToAdd.isNotEmpty()) {
|
||||||
|
try {
|
||||||
|
val pmsResponse = client!!.getPaymentModes(project)
|
||||||
|
val remotePms = pmsResponse.getPaymentModes(project.id)
|
||||||
|
val remotePmsNames = remotePms.map { it.name }
|
||||||
|
for (pmToAdd in paymentModesToAdd) {
|
||||||
|
val searchIndex = remotePmsNames.indexOf(pmToAdd.name)
|
||||||
|
if (searchIndex != -1) {
|
||||||
|
val remotePm = remotePms[searchIndex]
|
||||||
|
dbHelper.updatePaymentMode(pmToAdd.id, null, null, null, DBBill.STATE_OK, remotePm.remoteId)
|
||||||
|
pmToAdd.remoteId = remotePm.remoteId
|
||||||
|
} else {
|
||||||
|
val createRemotePaymentModeResponse = client!!.createRemotePaymentMode(project, pmToAdd)
|
||||||
|
val newRemoteId = createRemotePaymentModeResponse.remotePaymentModeId
|
||||||
|
if (newRemoteId > 0) {
|
||||||
|
dbHelper.updatePaymentMode(pmToAdd.id, null, null, null, DBBill.STATE_OK, newRemoteId)
|
||||||
|
pmToAdd.remoteId = newRemoteId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: NextcloudHttpRequestFailedException) {
|
||||||
|
Log.e(TAG, "GET PAYMENT MODES FAILED : " + e.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val paymentModesToEdit = dbHelper.getPaymentModesOfProjectWithState(project.id, DBBill.STATE_EDITED)
|
||||||
|
for (pmToEdit in paymentModesToEdit) {
|
||||||
|
try {
|
||||||
|
client!!.editRemotePaymentMode(project, pmToEdit)
|
||||||
|
dbHelper.updatePaymentMode(pmToEdit.id, null, null, null, DBBill.STATE_OK)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "EDIT PAYMENT MODE FAILED for ${pmToEdit.name}, might not exist remotely", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val paymentModesToDelete = dbHelper.getPaymentModesOfProjectWithState(project.id, DBBill.STATE_DELETED)
|
||||||
|
for (pmToDel in paymentModesToDelete) {
|
||||||
|
try {
|
||||||
|
client!!.deleteRemotePaymentMode(project, pmToDel.remoteId)
|
||||||
|
dbHelper.deletePaymentMode(pmToDel.id)
|
||||||
|
} catch (e: NextcloudHttpRequestFailedException) {
|
||||||
|
if (e.statusCode == 404 || e.statusCode == 400) {
|
||||||
|
Log.d(TAG, "failed to delete payment mode on remote project (code ${e.statusCode}) : delete it locally anyway")
|
||||||
|
dbHelper.deletePaymentMode(pmToDel.id)
|
||||||
|
} else {
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
} catch (_: Exception) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val categories = dbHelper.getCategories(project.id)
|
||||||
|
val categoryIdToRemoteId = categories.associate { it.id to it.remoteId }.toMutableMap()
|
||||||
|
// Map hardcoded constants to themselves if not in DB
|
||||||
|
categoryIdToRemoteId[DBBill.CATEGORY_REIMBURSEMENT] = DBBill.CATEGORY_REIMBURSEMENT
|
||||||
|
categories.filter { it.remoteId < 0 }.forEach { categoryIdToRemoteId[it.remoteId] = it.remoteId }
|
||||||
|
|
||||||
|
val paymentModes = dbHelper.getPaymentModes(project.id)
|
||||||
|
val paymentModeIdToRemoteId = paymentModes.associate { it.id to it.remoteId }.toMutableMap()
|
||||||
|
paymentModes.filter { it.remoteId < 0 }.forEach { paymentModeIdToRemoteId[it.remoteId] = it.remoteId }
|
||||||
|
|
||||||
val toDelete = dbHelper.getBillsOfProjectWithState(project.id, DBBill.STATE_DELETED)
|
val toDelete = dbHelper.getBillsOfProjectWithState(project.id, DBBill.STATE_DELETED)
|
||||||
for (bToDel in toDelete) {
|
for (bToDel in toDelete) {
|
||||||
try {
|
try {
|
||||||
@@ -293,12 +408,16 @@ class CowspentServerSyncHelper private constructor(private val dbHelper: Cowspen
|
|||||||
val toEdit = dbHelper.getBillsOfProjectWithState(project.id, DBBill.STATE_EDITED)
|
val toEdit = dbHelper.getBillsOfProjectWithState(project.id, DBBill.STATE_EDITED)
|
||||||
for (bToEdit in toEdit) {
|
for (bToEdit in toEdit) {
|
||||||
try {
|
try {
|
||||||
val editRemoteBillResponse = client!!.editRemoteBill(project, bToEdit, memberIdToRemoteId)
|
val editRemoteBillResponse = client!!.editRemoteBill(project, bToEdit, memberIdToRemoteId, categoryIdToRemoteId, paymentModeIdToRemoteId)
|
||||||
if (editRemoteBillResponse.stringContent == bToEdit.remoteId.toString()) {
|
val returnedRemoteId = editRemoteBillResponse.remoteBillId
|
||||||
|
if (returnedRemoteId == bToEdit.remoteId || (returnedRemoteId == 0L && !project.getRequestBaseUrl(true).contains("/ocs/v2.php"))) {
|
||||||
dbHelper.setBillState(bToEdit.id, DBBill.STATE_OK)
|
dbHelper.setBillState(bToEdit.id, DBBill.STATE_OK)
|
||||||
Log.d(TAG, "SUCCESSFUL remote bill edition (${editRemoteBillResponse.stringContent})")
|
Log.d(TAG, "SUCCESSFUL remote bill edition ($returnedRemoteId)")
|
||||||
|
} else if (returnedRemoteId > 0) {
|
||||||
|
dbHelper.setBillState(bToEdit.id, DBBill.STATE_OK)
|
||||||
|
Log.d(TAG, "SUCCESSFUL remote bill edition ($returnedRemoteId)")
|
||||||
} else {
|
} else {
|
||||||
Log.d(TAG, "FAILED to edit remote bill (${editRemoteBillResponse.stringContent})")
|
Log.d(TAG, "FAILED to edit remote bill ($returnedRemoteId)")
|
||||||
}
|
}
|
||||||
} catch (_: Exception) {
|
} catch (_: Exception) {
|
||||||
Log.d(TAG, "FAILED to edit remote bill: it probably does not exist remotely")
|
Log.d(TAG, "FAILED to edit remote bill: it probably does not exist remotely")
|
||||||
@@ -307,8 +426,8 @@ class CowspentServerSyncHelper private constructor(private val dbHelper: Cowspen
|
|||||||
|
|
||||||
val toAdd = dbHelper.getBillsOfProjectWithState(project.id, DBBill.STATE_ADDED)
|
val toAdd = dbHelper.getBillsOfProjectWithState(project.id, DBBill.STATE_ADDED)
|
||||||
for (bToAdd in toAdd) {
|
for (bToAdd in toAdd) {
|
||||||
val createRemoteBillResponse = client!!.createRemoteBill(project, bToAdd, memberIdToRemoteId)
|
val createRemoteBillResponse = client!!.createRemoteBill(project, bToAdd, memberIdToRemoteId, categoryIdToRemoteId, paymentModeIdToRemoteId)
|
||||||
val newRemoteId = createRemoteBillResponse.stringContent.toLong()
|
val newRemoteId = createRemoteBillResponse.remoteBillId
|
||||||
if (newRemoteId > 0) {
|
if (newRemoteId > 0) {
|
||||||
dbHelper.updateBill(
|
dbHelper.updateBill(
|
||||||
bToAdd.id, newRemoteId, null,
|
bToAdd.id, newRemoteId, null,
|
||||||
@@ -320,48 +439,69 @@ class CowspentServerSyncHelper private constructor(private val dbHelper: Cowspen
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val currenciesToDelete = dbHelper.getCurrenciesOfProjectWithState(project.id, DBBill.STATE_DELETED)
|
if (project.type == ProjectType.COSPEND) {
|
||||||
for (cToDel in currenciesToDelete) {
|
val currenciesToDelete = dbHelper.getCurrenciesOfProjectWithState(project.id, DBBill.STATE_DELETED)
|
||||||
try {
|
for (cToDel in currenciesToDelete) {
|
||||||
val deleteRemoteCurrencyResponse = client!!.deleteRemoteCurrency(project, cToDel.remoteId)
|
try {
|
||||||
if (deleteRemoteCurrencyResponse.stringContent == "OK") {
|
val deleteRemoteCurrencyResponse = client!!.deleteRemoteCurrency(project, cToDel.remoteId)
|
||||||
Log.d(TAG, "successfully deleted currency on remote project : delete it locally")
|
if (deleteRemoteCurrencyResponse.stringContent == "OK") {
|
||||||
dbHelper.deleteCurrency(cToDel.id)
|
Log.d(TAG, "successfully deleted currency on remote project : delete it locally")
|
||||||
}
|
dbHelper.deleteCurrency(cToDel.id)
|
||||||
} catch (e: IOException) {
|
}
|
||||||
if (e.message == "\"Not Found\"") {
|
} catch (e: IOException) {
|
||||||
Log.d(TAG, "failed to delete currency on remote project : delete it locally anyway")
|
if (e.message == "\"Not Found\"") {
|
||||||
dbHelper.deleteCurrency(cToDel.id)
|
Log.d(TAG, "failed to delete currency on remote project : delete it locally anyway")
|
||||||
} else {
|
dbHelper.deleteCurrency(cToDel.id)
|
||||||
throw e
|
} else {
|
||||||
|
throw e
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
val currenciesToDelete = dbHelper.getCurrenciesOfProjectWithState(project.id, DBBill.STATE_DELETED)
|
||||||
|
for (cToDel in currenciesToDelete) {
|
||||||
|
dbHelper.deleteCurrency(cToDel.id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val currenciesToEdit = dbHelper.getCurrenciesOfProjectWithState(project.id, DBBill.STATE_EDITED)
|
if (project.type == ProjectType.COSPEND) {
|
||||||
for (cToEdit in currenciesToEdit) {
|
val currenciesToEdit = dbHelper.getCurrenciesOfProjectWithState(project.id, DBBill.STATE_EDITED)
|
||||||
try {
|
for (cToEdit in currenciesToEdit) {
|
||||||
val editRemoteCurrencyResponse = client!!.editRemoteCurrency(project, cToEdit)
|
try {
|
||||||
if (editRemoteCurrencyResponse.stringContent == cToEdit.remoteId.toString()) {
|
val editRemoteCurrencyResponse = client!!.editRemoteCurrency(project, cToEdit)
|
||||||
dbHelper.setCurrencyState(cToEdit.id, DBBill.STATE_OK)
|
if (editRemoteCurrencyResponse.stringContent == cToEdit.remoteId.toString()) {
|
||||||
Log.d(TAG, "SUCCESSFUL remote currency edition (${editRemoteCurrencyResponse.stringContent})")
|
dbHelper.setCurrencyState(cToEdit.id, DBBill.STATE_OK)
|
||||||
} else {
|
Log.d(TAG, "SUCCESSFUL remote currency edition (${editRemoteCurrencyResponse.stringContent})")
|
||||||
Log.d(TAG, "FAILED to edit remote currency (${editRemoteCurrencyResponse.stringContent})")
|
} else {
|
||||||
}
|
Log.d(TAG, "FAILED to edit remote currency (${editRemoteCurrencyResponse.stringContent})")
|
||||||
} catch (e: IOException) {
|
}
|
||||||
if (e.message == "{\"message\": \"Internal Server Error\"}") {
|
} catch (e: IOException) {
|
||||||
Log.d(TAG, "FAILED to edit remote currency : it does not exist remotely")
|
if (e.message == "{\"message\": \"Internal Server Error\"}") {
|
||||||
} else {
|
Log.d(TAG, "FAILED to edit remote currency : it does not exist remotely")
|
||||||
throw e
|
} else {
|
||||||
|
throw e
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
val currenciesToEdit = dbHelper.getCurrenciesOfProjectWithState(project.id, DBBill.STATE_EDITED)
|
||||||
|
for (cToEdit in currenciesToEdit) {
|
||||||
|
dbHelper.setCurrencyState(cToEdit.id, DBBill.STATE_OK)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val currencyToAdd = dbHelper.getCurrenciesOfProjectWithState(project.id, DBBill.STATE_ADDED)
|
if (project.type == ProjectType.COSPEND) {
|
||||||
for (cToAdd in currencyToAdd) {
|
val currencyToAdd = dbHelper.getCurrenciesOfProjectWithState(project.id, DBBill.STATE_ADDED)
|
||||||
val createRemoteCurrencyResponse = client!!.createRemoteCurrency(project, cToAdd)
|
for (cToAdd in currencyToAdd) {
|
||||||
val newRemoteId = createRemoteCurrencyResponse.stringContent.toLong()
|
val createRemoteCurrencyResponse = client!!.createRemoteCurrency(project, cToAdd)
|
||||||
if (newRemoteId > 0) {
|
val newRemoteId = createRemoteCurrencyResponse.remoteCurrencyId
|
||||||
|
if (newRemoteId > 0) {
|
||||||
|
dbHelper.setCurrencyState(cToAdd.id, DBBill.STATE_OK)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val currencyToAdd = dbHelper.getCurrenciesOfProjectWithState(project.id, DBBill.STATE_ADDED)
|
||||||
|
for (cToAdd in currencyToAdd) {
|
||||||
dbHelper.setCurrencyState(cToAdd.id, DBBill.STATE_OK)
|
dbHelper.setCurrencyState(cToAdd.id, DBBill.STATE_OK)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -450,7 +590,7 @@ class CowspentServerSyncHelper private constructor(private val dbHelper: Cowspen
|
|||||||
|
|
||||||
val localPaymentModes = dbHelper.getPaymentModes(project.id)
|
val localPaymentModes = dbHelper.getPaymentModes(project.id)
|
||||||
for (localPaymentMode in localPaymentModes) {
|
for (localPaymentMode in localPaymentModes) {
|
||||||
if (!remotePaymentModesByRemoteId.containsKey(localPaymentMode.remoteId)) {
|
if (localPaymentMode.state == DBBill.STATE_OK && !remotePaymentModesByRemoteId.containsKey(localPaymentMode.remoteId)) {
|
||||||
dbHelper.deletePaymentMode(localPaymentMode.id)
|
dbHelper.deletePaymentMode(localPaymentMode.id)
|
||||||
Log.d(TAG, "Delete local pm : $localPaymentMode")
|
Log.d(TAG, "Delete local pm : $localPaymentMode")
|
||||||
}
|
}
|
||||||
@@ -460,6 +600,7 @@ class CowspentServerSyncHelper private constructor(private val dbHelper: Cowspen
|
|||||||
val remoteCategoriesByRemoteId = remoteCategories.associateBy { it.remoteId }
|
val remoteCategoriesByRemoteId = remoteCategories.associateBy { it.remoteId }
|
||||||
|
|
||||||
for (c in remoteCategories) {
|
for (c in remoteCategories) {
|
||||||
|
if (c.remoteId == DBBill.CATEGORY_REIMBURSEMENT) continue
|
||||||
val localCategory = dbHelper.getCategory(c.remoteId, project.id)
|
val localCategory = dbHelper.getCategory(c.remoteId, project.id)
|
||||||
if (localCategory == null) {
|
if (localCategory == null) {
|
||||||
Log.d(TAG, "Add local category : $c")
|
Log.d(TAG, "Add local category : $c")
|
||||||
@@ -479,7 +620,7 @@ class CowspentServerSyncHelper private constructor(private val dbHelper: Cowspen
|
|||||||
|
|
||||||
val localCategories = dbHelper.getCategories(project.id)
|
val localCategories = dbHelper.getCategories(project.id)
|
||||||
for (localCategory in localCategories) {
|
for (localCategory in localCategories) {
|
||||||
if (!remoteCategoriesByRemoteId.containsKey(localCategory.remoteId)) {
|
if (localCategory.state == DBBill.STATE_OK && !remoteCategoriesByRemoteId.containsKey(localCategory.remoteId)) {
|
||||||
dbHelper.deleteCategory(localCategory.id)
|
dbHelper.deleteCategory(localCategory.id)
|
||||||
Log.d(TAG, "Delete local category : $localCategory")
|
Log.d(TAG, "Delete local category : $localCategory")
|
||||||
}
|
}
|
||||||
@@ -507,7 +648,7 @@ class CowspentServerSyncHelper private constructor(private val dbHelper: Cowspen
|
|||||||
|
|
||||||
val localCurrencies = dbHelper.getCurrencies(project.id)
|
val localCurrencies = dbHelper.getCurrencies(project.id)
|
||||||
for (localCurrency in localCurrencies) {
|
for (localCurrency in localCurrencies) {
|
||||||
if (!remoteCurrenciesByRemoteId.containsKey(localCurrency.remoteId)) {
|
if (localCurrency.state == DBBill.STATE_OK && !remoteCurrenciesByRemoteId.containsKey(localCurrency.remoteId)) {
|
||||||
dbHelper.deleteCurrency(localCurrency.id)
|
dbHelper.deleteCurrency(localCurrency.id)
|
||||||
Log.d(TAG, "Delete local currency : $localCurrencies")
|
Log.d(TAG, "Delete local currency : $localCurrencies")
|
||||||
}
|
}
|
||||||
@@ -574,13 +715,23 @@ class CowspentServerSyncHelper private constructor(private val dbHelper: Cowspen
|
|||||||
val dbMembers = dbHelper.getMembersOfProject(project.id, null)
|
val dbMembers = dbHelper.getMembersOfProject(project.id, null)
|
||||||
val memberRemoteIdToId = dbMembers.associate { it.remoteId to it.id }
|
val memberRemoteIdToId = dbMembers.associate { it.remoteId to it.id }
|
||||||
|
|
||||||
|
val dbCategories = dbHelper.getCategories(project.id)
|
||||||
|
val categoriesRemoteIdToId = dbCategories.associate { it.remoteId to it.id }.toMutableMap()
|
||||||
|
// Map hardcoded constants to their local IDs if they exist in DB, else to themselves
|
||||||
|
categoriesRemoteIdToId[DBBill.CATEGORY_REIMBURSEMENT] = DBBill.CATEGORY_REIMBURSEMENT
|
||||||
|
dbCategories.filter { it.remoteId < 0 }.forEach { categoriesRemoteIdToId[it.remoteId] = it.id }
|
||||||
|
|
||||||
|
val dbPaymentModes = dbHelper.getPaymentModes(project.id)
|
||||||
|
val paymentModesRemoteIdToId = dbPaymentModes.associate { it.remoteId to it.id }.toMutableMap()
|
||||||
|
dbPaymentModes.filter { it.remoteId < 0 }.forEach { paymentModesRemoteIdToId[it.remoteId] = it.id }
|
||||||
|
|
||||||
val billsResponse = client!!.getBills(project)
|
val billsResponse = client!!.getBills(project)
|
||||||
val isIHM = project.type == ProjectType.IHATEMONEY
|
val isIHM = project.type == ProjectType.IHATEMONEY
|
||||||
val serverSyncTimestamp = if (isIHM) 0L else billsResponse.syncTimestamp
|
val serverSyncTimestamp = if (isIHM) 0L else billsResponse.syncTimestamp
|
||||||
val remoteBills: List<DBBill> = if (isIHM) {
|
val remoteBills: List<DBBill> = if (isIHM) {
|
||||||
billsResponse.getBillsIHM(project.id, memberRemoteIdToId)
|
billsResponse.getBillsIHM(project.id, memberRemoteIdToId, categoriesRemoteIdToId, paymentModesRemoteIdToId)
|
||||||
} else {
|
} else {
|
||||||
billsResponse.getBillsCospend(project.id, memberRemoteIdToId)
|
billsResponse.getBillsCospend(project.id, memberRemoteIdToId, categoriesRemoteIdToId, paymentModesRemoteIdToId)
|
||||||
}
|
}
|
||||||
val remoteAllBillIds: List<Long> = if (isIHM) {
|
val remoteAllBillIds: List<Long> = if (isIHM) {
|
||||||
remoteBills.map { it.remoteId }
|
remoteBills.map { it.remoteId }
|
||||||
@@ -597,7 +748,6 @@ class CowspentServerSyncHelper private constructor(private val dbHelper: Cowspen
|
|||||||
dbHelper.addBill(remoteBill)
|
dbHelper.addBill(remoteBill)
|
||||||
nbPulledNewBills++
|
nbPulledNewBills++
|
||||||
newBillsDialogText += "+ ${remoteBill.what}\n"
|
newBillsDialogText += "+ ${remoteBill.what}\n"
|
||||||
Log.d(TAG, "Add local bill : $remoteBill")
|
|
||||||
} else {
|
} else {
|
||||||
val localBill = localBillsByRemoteId[remoteBill.remoteId]!!
|
val localBill = localBillsByRemoteId[remoteBill.remoteId]!!
|
||||||
if (hasChanged(localBill, remoteBill)) {
|
if (hasChanged(localBill, remoteBill)) {
|
||||||
@@ -605,12 +755,11 @@ class CowspentServerSyncHelper private constructor(private val dbHelper: Cowspen
|
|||||||
localBill.id, null, remoteBill.payerId,
|
localBill.id, null, remoteBill.payerId,
|
||||||
remoteBill.amount, remoteBill.timestamp,
|
remoteBill.amount, remoteBill.timestamp,
|
||||||
remoteBill.what, DBBill.STATE_OK, remoteBill.repeat,
|
remoteBill.what, DBBill.STATE_OK, remoteBill.repeat,
|
||||||
remoteBill.paymentMode, remoteBill.paymentModeRemoteId,
|
remoteBill.paymentMode, remoteBill.paymentModeId,
|
||||||
remoteBill.categoryRemoteId, remoteBill.comment
|
remoteBill.categoryId, remoteBill.comment
|
||||||
)
|
)
|
||||||
nbPulledUpdatedBills++
|
nbPulledUpdatedBills++
|
||||||
updatedBillsDialogText += "✏ ${remoteBill.what}\n"
|
updatedBillsDialogText += "✏ ${remoteBill.what}\n"
|
||||||
Log.d(TAG, "Update local bill : $remoteBill")
|
|
||||||
} else {
|
} else {
|
||||||
Log.d(TAG, "Nothing to do for bill : $localBill")
|
Log.d(TAG, "Nothing to do for bill : $localBill")
|
||||||
}
|
}
|
||||||
@@ -1063,8 +1212,8 @@ class CowspentServerSyncHelper private constructor(private val dbHelper: Cowspen
|
|||||||
localBill.what == remoteBill.what &&
|
localBill.what == remoteBill.what &&
|
||||||
localBill.comment == remoteBill.comment &&
|
localBill.comment == remoteBill.comment &&
|
||||||
localBill.paymentMode == remoteBill.paymentMode &&
|
localBill.paymentMode == remoteBill.paymentMode &&
|
||||||
localBill.paymentModeRemoteId == remoteBill.paymentModeRemoteId &&
|
localBill.paymentModeId == remoteBill.paymentModeId &&
|
||||||
localBill.categoryRemoteId == remoteBill.categoryRemoteId
|
localBill.categoryId == remoteBill.categoryId
|
||||||
) {
|
) {
|
||||||
val localRepeat = localBill.repeat ?: DBBill.NON_REPEATED
|
val localRepeat = localBill.repeat ?: DBBill.NON_REPEATED
|
||||||
val remoteRepeat = remoteBill.repeat ?: DBBill.NON_REPEATED
|
val remoteRepeat = remoteBill.repeat ?: DBBill.NON_REPEATED
|
||||||
|
|||||||
@@ -100,14 +100,19 @@ object ThemeUtils {
|
|||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
color = MaterialTheme.colors.background
|
color = MaterialTheme.colors.background
|
||||||
) {
|
) {
|
||||||
Column {
|
Column(modifier = Modifier.fillMaxSize()) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.background(MaterialTheme.colors.primary)
|
.background(MaterialTheme.colors.primary)
|
||||||
.statusBarsPadding()
|
.statusBarsPadding()
|
||||||
)
|
)
|
||||||
Box(modifier = Modifier.fillMaxSize()) {
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.navigationBarsPadding()
|
||||||
|
.imePadding()
|
||||||
|
) {
|
||||||
content()
|
content()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,11 +14,11 @@ object BillFormatter {
|
|||||||
) {
|
) {
|
||||||
for (bill in bills) {
|
for (bill in bills) {
|
||||||
var whatPrefix = ""
|
var whatPrefix = ""
|
||||||
val pm = paymentModesMap[bill.paymentModeRemoteId.toLong()]
|
val pm = paymentModesMap[bill.paymentModeId]
|
||||||
if (pm != null) {
|
if (pm != null) {
|
||||||
whatPrefix += pm.icon + " "
|
whatPrefix += pm.icon + " "
|
||||||
} else {
|
} else {
|
||||||
when (bill.paymentModeRemoteId) {
|
when (bill.paymentModeId) {
|
||||||
DBBill.PAYMODE_ID_CARD -> whatPrefix += "\uD83D\uDCB3 "
|
DBBill.PAYMODE_ID_CARD -> whatPrefix += "\uD83D\uDCB3 "
|
||||||
DBBill.PAYMODE_ID_CASH -> whatPrefix += "\uD83D\uDCB5 "
|
DBBill.PAYMODE_ID_CASH -> whatPrefix += "\uD83D\uDCB5 "
|
||||||
DBBill.PAYMODE_ID_CHECK -> whatPrefix += "\uD83C\uDFAB "
|
DBBill.PAYMODE_ID_CHECK -> whatPrefix += "\uD83C\uDFAB "
|
||||||
@@ -27,11 +27,11 @@ object BillFormatter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val cat = categoriesMap[bill.categoryRemoteId.toLong()]
|
val cat = categoriesMap[bill.categoryId]
|
||||||
if (cat != null) {
|
if (cat != null) {
|
||||||
whatPrefix += cat.icon + " "
|
whatPrefix += cat.icon + " "
|
||||||
} else {
|
} else {
|
||||||
when (bill.categoryRemoteId) {
|
when (bill.categoryId) {
|
||||||
DBBill.CATEGORY_GROCERIES -> whatPrefix += "\uD83D\uDED2 "
|
DBBill.CATEGORY_GROCERIES -> whatPrefix += "\uD83D\uDED2 "
|
||||||
DBBill.CATEGORY_LEISURE -> whatPrefix += "\uD83C\uDF89 "
|
DBBill.CATEGORY_LEISURE -> whatPrefix += "\uD83C\uDF89 "
|
||||||
DBBill.CATEGORY_RENT -> whatPrefix += "\uD83C\uDFE0 "
|
DBBill.CATEGORY_RENT -> whatPrefix += "\uD83C\uDFE0 "
|
||||||
|
|||||||
@@ -10,28 +10,57 @@ object CategoryUtils {
|
|||||||
|
|
||||||
fun getDefaultCategories(context: Context, projectId: Long): List<DBCategory> {
|
fun getDefaultCategories(context: Context, projectId: Long): List<DBCategory> {
|
||||||
return listOf(
|
return listOf(
|
||||||
DBCategory(0, DBBill.CATEGORY_GROCERIES.toLong(), projectId, context.getString(R.string.category_groceries), "\uD83D\uDED2", "#ffaa00"),
|
DBCategory(DBBill.CATEGORY_GROCERIES,
|
||||||
DBCategory(0, DBBill.CATEGORY_LEISURE.toLong(), projectId, context.getString(R.string.category_leisure), "\uD83C\uDF89", "#aa55ff"),
|
DBBill.CATEGORY_GROCERIES, projectId, context.getString(R.string.category_groceries), "\uD83D\uDED2", "#ffaa00"),
|
||||||
DBCategory(0, DBBill.CATEGORY_RENT.toLong(), projectId, context.getString(R.string.category_rent), "\uD83C\uDFE0", "#da8733"),
|
DBCategory(DBBill.CATEGORY_LEISURE,
|
||||||
DBCategory(0, DBBill.CATEGORY_BILLS.toLong(), projectId, context.getString(R.string.category_bills), "\uD83C\uDF29", "#4aa6b0"),
|
DBBill.CATEGORY_LEISURE, projectId, context.getString(R.string.category_leisure), "\uD83C\uDF89", "#aa55ff"),
|
||||||
DBCategory(0, DBBill.CATEGORY_CULTURE.toLong(), projectId, context.getString(R.string.category_excursion), "\uD83D\uDEB8", "#0055ff"),
|
DBCategory(DBBill.CATEGORY_RENT,
|
||||||
DBCategory(0, DBBill.CATEGORY_HEALTH.toLong(), projectId, context.getString(R.string.category_health), "\uD83D\uDC9A", "#bf090c"),
|
DBBill.CATEGORY_RENT, projectId, context.getString(R.string.category_rent), "\uD83C\uDFE0", "#da8733"),
|
||||||
DBCategory(0, DBBill.CATEGORY_SHOPPING.toLong(), projectId, context.getString(R.string.category_shopping), "\uD83D\uDECD", "#e167d1"),
|
DBCategory(DBBill.CATEGORY_BILLS,
|
||||||
DBCategory(0, DBBill.CATEGORY_REIMBURSEMENT.toLong(), projectId, context.getString(R.string.category_reimbursement), "\uD83D\uDCB0", "#00ced1"),
|
DBBill.CATEGORY_BILLS, projectId, context.getString(R.string.category_bills), "\uD83C\uDF29", "#4aa6b0"),
|
||||||
DBCategory(0, DBBill.CATEGORY_RESTAURANT.toLong(), projectId, context.getString(R.string.category_restaurant), "\uD83C\uDF74", "#d0d5e1"),
|
DBCategory(DBBill.CATEGORY_CULTURE,
|
||||||
DBCategory(0, DBBill.CATEGORY_ACCOMMODATION.toLong(), projectId, context.getString(R.string.category_accomodation), "\uD83D\uDECC", "#5de1a3"),
|
DBBill.CATEGORY_CULTURE, projectId, context.getString(R.string.category_excursion), "\uD83D\uDEB8", "#0055ff"),
|
||||||
DBCategory(0, DBBill.CATEGORY_TRANSPORT.toLong(), projectId, context.getString(R.string.category_transport), "\uD83D\uDE8C", "#6f2ee1"),
|
DBCategory(DBBill.CATEGORY_HEALTH,
|
||||||
DBCategory(0, DBBill.CATEGORY_SPORT.toLong(), projectId, context.getString(R.string.category_sport), "\uD83C\uDFBE", "#69e177")
|
DBBill.CATEGORY_HEALTH, projectId, context.getString(R.string.category_health), "\uD83D\uDC9A", "#bf090c"),
|
||||||
|
DBCategory(DBBill.CATEGORY_SHOPPING,
|
||||||
|
DBBill.CATEGORY_SHOPPING, projectId, context.getString(R.string.category_shopping), "\uD83D\uDECD", "#e167d1"),
|
||||||
|
DBCategory(DBBill.CATEGORY_REIMBURSEMENT,
|
||||||
|
DBBill.CATEGORY_REIMBURSEMENT, projectId, context.getString(R.string.category_reimbursement), "\uD83D\uDCB0", "#00ced1"),
|
||||||
|
DBCategory(DBBill.CATEGORY_RESTAURANT,
|
||||||
|
DBBill.CATEGORY_RESTAURANT, projectId, context.getString(R.string.category_restaurant), "\uD83C\uDF74", "#d0d5e1"),
|
||||||
|
DBCategory(DBBill.CATEGORY_ACCOMMODATION,
|
||||||
|
DBBill.CATEGORY_ACCOMMODATION, projectId, context.getString(R.string.category_accomodation), "\uD83D\uDECC", "#5de1a3"),
|
||||||
|
DBCategory(DBBill.CATEGORY_TRANSPORT,
|
||||||
|
DBBill.CATEGORY_TRANSPORT, projectId, context.getString(R.string.category_transport), "\uD83D\uDE8C", "#6f2ee1"),
|
||||||
|
DBCategory(DBBill.CATEGORY_SPORT,
|
||||||
|
DBBill.CATEGORY_SPORT, projectId, context.getString(R.string.category_sport), "\uD83C\uDFBE", "#69e177")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getDefaultPaymentModes(context: Context, projectId: Long): List<DBPaymentMode> {
|
fun getDefaultPaymentModes(context: Context, projectId: Long): List<DBPaymentMode> {
|
||||||
return listOf(
|
return listOf(
|
||||||
DBPaymentMode(0, DBBill.PAYMODE_ID_CARD.toLong(), projectId, context.getString(R.string.payment_mode_credit_card), "\uD83D\uDCB3", "#ff7f50"),
|
DBPaymentMode(DBBill.PAYMODE_ID_CARD, DBBill.PAYMODE_ID_CARD, projectId, context.getString(R.string.payment_mode_credit_card), "\uD83D\uDCB3", "#ff7f50"),
|
||||||
DBPaymentMode(0, DBBill.PAYMODE_ID_CASH.toLong(), projectId, context.getString(R.string.payment_mode_cash), "\uD83D\uDCB5", "#556b2f"),
|
DBPaymentMode(DBBill.PAYMODE_ID_CASH, DBBill.PAYMODE_ID_CASH, projectId, context.getString(R.string.payment_mode_cash), "\uD83D\uDCB5", "#556b2f"),
|
||||||
DBPaymentMode(0, DBBill.PAYMODE_ID_CHECK.toLong(), projectId, context.getString(R.string.payment_mode_check), "\uD83C\uDFAB", "#a9a9a9"),
|
DBPaymentMode(DBBill.PAYMODE_ID_CHECK, DBBill.PAYMODE_ID_CHECK, projectId, context.getString(R.string.payment_mode_check), "\uD83C\uDFAB", "#a9a9a9"),
|
||||||
DBPaymentMode(0, DBBill.PAYMODE_ID_TRANSFER.toLong(), projectId, context.getString(R.string.payment_mode_transfer), "⇄", "#00ced1"),
|
DBPaymentMode(DBBill.PAYMODE_ID_TRANSFER, DBBill.PAYMODE_ID_TRANSFER, projectId, context.getString(R.string.payment_mode_transfer), "⇄", "#00ced1"),
|
||||||
DBPaymentMode(0, DBBill.PAYMODE_ID_ONLINE_SERVICE.toLong(), projectId, context.getString(R.string.payment_mode_online), "\uD83C\uDF0E", "#9932cc")
|
DBPaymentMode(DBBill.PAYMODE_ID_ONLINE_SERVICE, DBBill.PAYMODE_ID_ONLINE_SERVICE, projectId, context.getString(R.string.payment_mode_online), "\uD83C\uDF0E", "#9932cc")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getReimbursementCategoryId(categories: List<DBCategory>, projectId: Long, projectRemoteId: String? = null): Long {
|
||||||
|
return categories.find {
|
||||||
|
it.projectId == projectId && (
|
||||||
|
it.remoteId == DBBill.CATEGORY_REIMBURSEMENT ||
|
||||||
|
(projectRemoteId != null && it.remoteId.toString() == projectRemoteId)
|
||||||
|
)
|
||||||
|
}?.id ?: DBBill.CATEGORY_REIMBURSEMENT
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getCategoryById(context: Context, id: Long, projectId: Long = 0): DBCategory? {
|
||||||
|
return getDefaultCategories(context, projectId).find { it.id == id }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getPaymentModeById(context: Context, id: Long, projectId: Long = 0): DBPaymentMode? {
|
||||||
|
return getDefaultPaymentModes(context, projectId).find { it.id == id }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ object CospendClientUtil {
|
|||||||
|
|
||||||
enum class LoginStatus(@param:StringRes val str: Int) {
|
enum class LoginStatus(@param:StringRes val str: Int) {
|
||||||
OK(0),
|
OK(0),
|
||||||
AUTH_FAILED(R.string.error_username_password_invalid),
|
AUTH_FAILED(R.string.error_auth),
|
||||||
CONNECTION_FAILED(R.string.error_io),
|
CONNECTION_FAILED(R.string.error_io),
|
||||||
NO_NETWORK(R.string.error_no_network),
|
NO_NETWORK(R.string.error_no_network),
|
||||||
JSON_FAILED(R.string.error_json),
|
JSON_FAILED(R.string.error_json),
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ object ExportUtil {
|
|||||||
}
|
}
|
||||||
owersTxt = owersTxt.replace(",$".toRegex(), "")
|
owersTxt = owersTxt.replace(",$".toRegex(), "")
|
||||||
fileContent += "\"${b.what}\",${b.amount},${b.date},${b.timestamp},\"$payerName\"," +
|
fileContent += "\"${b.what}\",${b.amount},${b.date},${b.timestamp},\"$payerName\"," +
|
||||||
"$payerWeight,$payerActive,\"$owersTxt\",${b.repeat},${b.categoryRemoteId}," +
|
"$payerWeight,$payerActive,\"$owersTxt\",${b.repeat},${b.categoryId}," +
|
||||||
"${b.paymentMode}\n"
|
"${b.paymentMode}\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,13 +35,13 @@ class NextcloudClient(
|
|||||||
val method = if (useOcsApi) METHOD_GET else METHOD_POST
|
val method = if (useOcsApi) METHOD_GET else METHOD_POST
|
||||||
return if (nextcloudAPI != null) {
|
return if (nextcloudAPI != null) {
|
||||||
Log.d(javaClass.simpleName, "using SSO to get/sync account projects")
|
Log.d(javaClass.simpleName, "using SSO to get/sync account projects")
|
||||||
Log.d(javaClass.simpleName, "Sync projects target $target")
|
// Log.d(javaClass.simpleName, "Sync projects target $target")
|
||||||
ServerResponse.AccountProjectsResponse(
|
ServerResponse.AccountProjectsResponse(
|
||||||
requestServerWithSSO(nextcloudAPI, target, method, null, useOcsApi),
|
requestServerWithSSO(nextcloudAPI, target, method, null, useOcsApi),
|
||||||
useOcsApi
|
useOcsApi
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Log.d(javaClass.simpleName, "Sync projects target $target")
|
// Log.d(javaClass.simpleName, "Sync projects target $target")
|
||||||
ServerResponse.AccountProjectsResponse(
|
ServerResponse.AccountProjectsResponse(
|
||||||
requestServer(target, method, null, "", true, useOcsApi),
|
requestServer(target, method, null, "", true, useOcsApi),
|
||||||
useOcsApi
|
useOcsApi
|
||||||
@@ -93,6 +93,10 @@ class NextcloudClient(
|
|||||||
params: Collection<QueryParam>?,
|
params: Collection<QueryParam>?,
|
||||||
isOCSRequest: Boolean
|
isOCSRequest: Boolean
|
||||||
): VersatileProjectSyncClient.ResponseData {
|
): VersatileProjectSyncClient.ResponseData {
|
||||||
|
var finalTarget = target
|
||||||
|
if (finalTarget.contains("/ocs/v2.php") && !finalTarget.contains("format=json")) {
|
||||||
|
finalTarget += if (finalTarget.contains("?")) "&format=json" else "?format=json"
|
||||||
|
}
|
||||||
val result = StringBuilder()
|
val result = StringBuilder()
|
||||||
val headers: MutableMap<String, List<String>> = HashMap()
|
val headers: MutableMap<String, List<String>> = HashMap()
|
||||||
if (isOCSRequest) {
|
if (isOCSRequest) {
|
||||||
@@ -103,13 +107,13 @@ class NextcloudClient(
|
|||||||
val nextcloudRequest: NextcloudRequest = if (params == null) {
|
val nextcloudRequest: NextcloudRequest = if (params == null) {
|
||||||
NextcloudRequest.Builder()
|
NextcloudRequest.Builder()
|
||||||
.setMethod(method)
|
.setMethod(method)
|
||||||
.setUrl(target)
|
.setUrl(finalTarget)
|
||||||
.setHeader(headers)
|
.setHeader(headers)
|
||||||
.build()
|
.build()
|
||||||
} else {
|
} else {
|
||||||
NextcloudRequest.Builder()
|
NextcloudRequest.Builder()
|
||||||
.setMethod(method)
|
.setMethod(method)
|
||||||
.setUrl(target)
|
.setUrl(finalTarget)
|
||||||
.setParameter(params)
|
.setParameter(params)
|
||||||
.setHeader(headers)
|
.setHeader(headers)
|
||||||
.build()
|
.build()
|
||||||
@@ -182,9 +186,13 @@ class NextcloudClient(
|
|||||||
target: String,
|
target: String,
|
||||||
method: String, params: JSONObject?, lastETag: String?, needLogin: Boolean, isOCSRequest: Boolean
|
method: String, params: JSONObject?, lastETag: String?, needLogin: Boolean, isOCSRequest: Boolean
|
||||||
): VersatileProjectSyncClient.ResponseData {
|
): VersatileProjectSyncClient.ResponseData {
|
||||||
|
var finalTarget = target
|
||||||
|
if (finalTarget.contains("/ocs/v2.php") && !finalTarget.contains("format=json")) {
|
||||||
|
finalTarget += if (finalTarget.contains("?")) "&format=json" else "?format=json"
|
||||||
|
}
|
||||||
val result = StringBuilder()
|
val result = StringBuilder()
|
||||||
val targetURL = url + target.replace("^/".toRegex(), "")
|
val targetURL = url + finalTarget.replace("^/".toRegex(), "")
|
||||||
Log.d(javaClass.simpleName, "method and target URL: $method $targetURL")
|
// Log.d(javaClass.simpleName, "method and target URL: $method $targetURL")
|
||||||
val httpCon = SupportUtil.getHttpURLConnection(targetURL)
|
val httpCon = SupportUtil.getHttpURLConnection(targetURL)
|
||||||
httpCon.requestMethod = method
|
httpCon.requestMethod = method
|
||||||
if (needLogin) {
|
if (needLogin) {
|
||||||
@@ -194,7 +202,7 @@ class NextcloudClient(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
httpCon.setRequestProperty("Connection", "Close")
|
httpCon.setRequestProperty("Connection", "Close")
|
||||||
httpCon.setRequestProperty("User-Agent", "cowspent-android/" + SupportUtil.getAppVersionName(context))
|
httpCon.setRequestProperty("User-Agent", "Cowspent-android/" + SupportUtil.getAppVersionName(context))
|
||||||
if (lastETag != null && METHOD_GET == method) {
|
if (lastETag != null && METHOD_GET == method) {
|
||||||
httpCon.setRequestProperty("If-None-Match", lastETag)
|
httpCon.setRequestProperty("If-None-Match", lastETag)
|
||||||
}
|
}
|
||||||
@@ -206,7 +214,7 @@ class NextcloudClient(
|
|||||||
var paramData: ByteArray? = null
|
var paramData: ByteArray? = null
|
||||||
if (params != null) {
|
if (params != null) {
|
||||||
paramData = params.toString().toByteArray()
|
paramData = params.toString().toByteArray()
|
||||||
Log.d(javaClass.simpleName, "Params: $params")
|
// Log.d(javaClass.simpleName, "Params: $params")
|
||||||
httpCon.setFixedLengthStreamingMode(paramData.size)
|
httpCon.setFixedLengthStreamingMode(paramData.size)
|
||||||
httpCon.setRequestProperty("Content-Type", application_json)
|
httpCon.setRequestProperty("Content-Type", application_json)
|
||||||
httpCon.doOutput = true
|
httpCon.doOutput = true
|
||||||
@@ -244,8 +252,12 @@ class NextcloudClient(
|
|||||||
target: String,
|
target: String,
|
||||||
method: String, params: JSONObject?, lastETag: String?, needLogin: Boolean, isOCSRequest: Boolean
|
method: String, params: JSONObject?, lastETag: String?, needLogin: Boolean, isOCSRequest: Boolean
|
||||||
): VersatileProjectSyncClient.ResponseData {
|
): VersatileProjectSyncClient.ResponseData {
|
||||||
|
var finalTarget = target
|
||||||
|
if (finalTarget.contains("/ocs/v2.php") && !finalTarget.contains("format=json")) {
|
||||||
|
finalTarget += if (finalTarget.contains("?")) "&format=json" else "?format=json"
|
||||||
|
}
|
||||||
var strBase64: String
|
var strBase64: String
|
||||||
val targetURL = url + target.replace("^/".toRegex(), "")
|
val targetURL = url + finalTarget.replace("^/".toRegex(), "")
|
||||||
val httpCon = SupportUtil.getHttpURLConnection( targetURL)
|
val httpCon = SupportUtil.getHttpURLConnection( targetURL)
|
||||||
httpCon.requestMethod = method
|
httpCon.requestMethod = method
|
||||||
if (needLogin) {
|
if (needLogin) {
|
||||||
@@ -263,11 +275,11 @@ class NextcloudClient(
|
|||||||
httpCon.setRequestProperty("OCS-APIRequest", "true")
|
httpCon.setRequestProperty("OCS-APIRequest", "true")
|
||||||
}
|
}
|
||||||
httpCon.connectTimeout = 10 * 1000 // 10 seconds
|
httpCon.connectTimeout = 10 * 1000 // 10 seconds
|
||||||
Log.d(javaClass.simpleName, "$method $targetURL")
|
// Log.d(javaClass.simpleName, "$method $targetURL")
|
||||||
var paramData: ByteArray?
|
var paramData: ByteArray?
|
||||||
if (params != null) {
|
if (params != null) {
|
||||||
paramData = params.toString().toByteArray()
|
paramData = params.toString().toByteArray()
|
||||||
Log.d(javaClass.simpleName, "Params: $params")
|
// Log.d(javaClass.simpleName, "Params: $params")
|
||||||
httpCon.setFixedLengthStreamingMode(paramData.size)
|
httpCon.setFixedLengthStreamingMode(paramData.size)
|
||||||
httpCon.setRequestProperty("Content-Type", application_json)
|
httpCon.setRequestProperty("Content-Type", application_json)
|
||||||
httpCon.doOutput = true
|
httpCon.doOutput = true
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
package net.helcel.cowspent.util
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.Base64
|
||||||
|
import androidx.datastore.core.DataStore
|
||||||
|
import androidx.datastore.preferences.core.Preferences
|
||||||
|
import androidx.datastore.preferences.core.edit
|
||||||
|
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||||
|
import androidx.datastore.preferences.preferencesDataStore
|
||||||
|
import com.google.crypto.tink.Aead
|
||||||
|
import com.google.crypto.tink.KeyTemplates
|
||||||
|
import com.google.crypto.tink.RegistryConfiguration
|
||||||
|
import com.google.crypto.tink.aead.AeadConfig
|
||||||
|
import com.google.crypto.tink.integration.android.AndroidKeysetManager
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
|
||||||
|
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "secure_prefs")
|
||||||
|
|
||||||
|
object SecureStorage {
|
||||||
|
private var aead: Aead? = null
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun getAead(context: Context): Aead {
|
||||||
|
aead?.let { return it }
|
||||||
|
AeadConfig.register()
|
||||||
|
val masterKeyUri = "android-keystore://secure_storage_master_key"
|
||||||
|
val keysetManager = AndroidKeysetManager.Builder()
|
||||||
|
.withSharedPref(context, "tink_keyset", "secure_storage_keyset_prefs")
|
||||||
|
.withKeyTemplate(KeyTemplates.get("AES256_GCM"))
|
||||||
|
.withMasterKeyUri(masterKeyUri)
|
||||||
|
.build()
|
||||||
|
val newAead = keysetManager.keysetHandle.getPrimitive(RegistryConfiguration.get(), Aead::class.java)
|
||||||
|
aead = newAead
|
||||||
|
return newAead
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun encrypt(context: Context, value: String): String {
|
||||||
|
val encrypted = getAead(context).encrypt(value.toByteArray(), null)
|
||||||
|
return Base64.encodeToString(encrypted, Base64.NO_WRAP)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun decrypt(context: Context, encryptedValue: String): String? {
|
||||||
|
return try {
|
||||||
|
val decoded = Base64.decode(encryptedValue, Base64.NO_WRAP)
|
||||||
|
val decrypted = getAead(context).decrypt(decoded, null)
|
||||||
|
String(decrypted)
|
||||||
|
} catch (_: Exception) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun savePassword(context: Context, key: String, password: String?) {
|
||||||
|
if (password == null) {
|
||||||
|
removePassword(context, key)
|
||||||
|
} else {
|
||||||
|
val encrypted = encrypt(context, password)
|
||||||
|
context.dataStore.edit { it[stringPreferencesKey(key)] = encrypted }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getPassword(context: Context, key: String): String? {
|
||||||
|
val encrypted = context.dataStore.data.map { it[stringPreferencesKey(key)] }.first()
|
||||||
|
return encrypted?.let { decrypt(context, it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun removePassword(context: Context, key: String) {
|
||||||
|
context.dataStore.edit { it.remove(stringPreferencesKey(key)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Synchronous alternatives for legacy code
|
||||||
|
fun savePasswordSync(context: Context, key: String, password: String?) = runBlocking {
|
||||||
|
savePassword(context, key, password)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getPasswordSync(context: Context, key: String): String? = runBlocking {
|
||||||
|
getPassword(context, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removePasswordSync(context: Context, key: String) = runBlocking {
|
||||||
|
removePassword(context, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -137,6 +137,90 @@ open class ServerResponse(
|
|||||||
getResponseStringData().toLong()
|
getResponseStringData().toLong()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class CreateRemoteCategoryResponse(
|
||||||
|
response: VersatileProjectSyncClient.ResponseData,
|
||||||
|
isOcsResponse: Boolean
|
||||||
|
) : ServerResponse(response, isOcsResponse) {
|
||||||
|
|
||||||
|
@get:Throws(JSONException::class)
|
||||||
|
val stringContent: String
|
||||||
|
get() = getResponseStringData()
|
||||||
|
|
||||||
|
@get:Throws(JSONException::class)
|
||||||
|
val remoteCategoryId: Long
|
||||||
|
get() {
|
||||||
|
val dataStr = getResponseStringData()
|
||||||
|
return try {
|
||||||
|
dataStr.toLong()
|
||||||
|
} catch (_: NumberFormatException) {
|
||||||
|
val obj = JSONObject(dataStr)
|
||||||
|
obj.optLong("id", obj.optLong("remoteId", 0L))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class EditRemoteCategoryResponse(
|
||||||
|
response: VersatileProjectSyncClient.ResponseData,
|
||||||
|
isOcsResponse: Boolean
|
||||||
|
) : ServerResponse(response, isOcsResponse) {
|
||||||
|
|
||||||
|
@get:Throws(JSONException::class)
|
||||||
|
val stringContent: String
|
||||||
|
get() = getResponseStringData()
|
||||||
|
}
|
||||||
|
|
||||||
|
class DeleteRemoteCategoryResponse(
|
||||||
|
response: VersatileProjectSyncClient.ResponseData,
|
||||||
|
isOcsResponse: Boolean
|
||||||
|
) : ServerResponse(response, isOcsResponse) {
|
||||||
|
|
||||||
|
@get:Throws(JSONException::class)
|
||||||
|
val stringContent: String
|
||||||
|
get() = getResponseStringData()
|
||||||
|
}
|
||||||
|
|
||||||
|
class CreateRemotePaymentModeResponse(
|
||||||
|
response: VersatileProjectSyncClient.ResponseData,
|
||||||
|
isOcsResponse: Boolean
|
||||||
|
) : ServerResponse(response, isOcsResponse) {
|
||||||
|
|
||||||
|
@get:Throws(JSONException::class)
|
||||||
|
val stringContent: String
|
||||||
|
get() = getResponseStringData()
|
||||||
|
|
||||||
|
@get:Throws(JSONException::class)
|
||||||
|
val remotePaymentModeId: Long
|
||||||
|
get() {
|
||||||
|
val dataStr = getResponseStringData()
|
||||||
|
return try {
|
||||||
|
dataStr.toLong()
|
||||||
|
} catch (_: NumberFormatException) {
|
||||||
|
val obj = JSONObject(dataStr)
|
||||||
|
obj.optLong("id", obj.optLong("remoteId", 0L))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class EditRemotePaymentModeResponse(
|
||||||
|
response: VersatileProjectSyncClient.ResponseData,
|
||||||
|
isOcsResponse: Boolean
|
||||||
|
) : ServerResponse(response, isOcsResponse) {
|
||||||
|
|
||||||
|
@get:Throws(JSONException::class)
|
||||||
|
val stringContent: String
|
||||||
|
get() = getResponseStringData()
|
||||||
|
}
|
||||||
|
|
||||||
|
class DeleteRemotePaymentModeResponse(
|
||||||
|
response: VersatileProjectSyncClient.ResponseData,
|
||||||
|
isOcsResponse: Boolean
|
||||||
|
) : ServerResponse(response, isOcsResponse) {
|
||||||
|
|
||||||
|
@get:Throws(JSONException::class)
|
||||||
|
val stringContent: String
|
||||||
|
get() = getResponseStringData()
|
||||||
|
}
|
||||||
|
|
||||||
class CreateRemoteCurrencyResponse(
|
class CreateRemoteCurrencyResponse(
|
||||||
response: VersatileProjectSyncClient.ResponseData,
|
response: VersatileProjectSyncClient.ResponseData,
|
||||||
isOcsResponse: Boolean
|
isOcsResponse: Boolean
|
||||||
@@ -145,6 +229,18 @@ open class ServerResponse(
|
|||||||
@get:Throws(JSONException::class)
|
@get:Throws(JSONException::class)
|
||||||
val stringContent: String
|
val stringContent: String
|
||||||
get() = getResponseStringData()
|
get() = getResponseStringData()
|
||||||
|
|
||||||
|
@get:Throws(JSONException::class)
|
||||||
|
val remoteCurrencyId: Long
|
||||||
|
get() {
|
||||||
|
val dataStr = getResponseStringData()
|
||||||
|
return try {
|
||||||
|
dataStr.toLong()
|
||||||
|
} catch (_: NumberFormatException) {
|
||||||
|
val obj = JSONObject(dataStr)
|
||||||
|
obj.optLong("id", obj.optLong("remoteId", 0L))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class EditRemoteCurrencyResponse(
|
class EditRemoteCurrencyResponse(
|
||||||
@@ -196,6 +292,18 @@ open class ServerResponse(
|
|||||||
@get:Throws(JSONException::class)
|
@get:Throws(JSONException::class)
|
||||||
val stringContent: String
|
val stringContent: String
|
||||||
get() = getResponseStringData()
|
get() = getResponseStringData()
|
||||||
|
|
||||||
|
@get:Throws(JSONException::class)
|
||||||
|
val remoteBillId: Long
|
||||||
|
get() {
|
||||||
|
val dataStr = getResponseStringData()
|
||||||
|
return try {
|
||||||
|
dataStr.toLong()
|
||||||
|
} catch (_: NumberFormatException) {
|
||||||
|
val obj = JSONObject(dataStr)
|
||||||
|
obj.optLong("id", obj.optLong("remoteId", 0L))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class CreateRemoteBillResponse(
|
class CreateRemoteBillResponse(
|
||||||
@@ -206,6 +314,18 @@ open class ServerResponse(
|
|||||||
@get:Throws(JSONException::class)
|
@get:Throws(JSONException::class)
|
||||||
val stringContent: String
|
val stringContent: String
|
||||||
get() = getResponseStringData()
|
get() = getResponseStringData()
|
||||||
|
|
||||||
|
@get:Throws(JSONException::class)
|
||||||
|
val remoteBillId: Long
|
||||||
|
get() {
|
||||||
|
val dataStr = getResponseStringData()
|
||||||
|
return try {
|
||||||
|
dataStr.toLong()
|
||||||
|
} catch (_: NumberFormatException) {
|
||||||
|
val obj = JSONObject(dataStr)
|
||||||
|
obj.optLong("id", obj.optLong("remoteId", 0L))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DeleteRemoteBillResponse(
|
class DeleteRemoteBillResponse(
|
||||||
@@ -242,13 +362,23 @@ open class ServerResponse(
|
|||||||
ServerResponse(response, isOcsResponse) {
|
ServerResponse(response, isOcsResponse) {
|
||||||
|
|
||||||
@Throws(JSONException::class)
|
@Throws(JSONException::class)
|
||||||
fun getBillsCospend(projId: Long, memberRemoteIdToId: Map<Long, Long>): List<DBBill> {
|
fun getBillsCospend(
|
||||||
return getBillsFromJSONObject(getResponseObjectData(), projId, memberRemoteIdToId)
|
projId: Long,
|
||||||
|
memberRemoteIdToId: Map<Long, Long>,
|
||||||
|
catRemoteIdToId: Map<Long, Long>,
|
||||||
|
pmRemoteIdToId: Map<Long, Long>
|
||||||
|
): List<DBBill> {
|
||||||
|
return getBillsFromJSONObject(projId, memberRemoteIdToId, catRemoteIdToId, pmRemoteIdToId)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(JSONException::class)
|
@Throws(JSONException::class)
|
||||||
fun getBillsIHM(projId: Long, memberRemoteIdToId: Map<Long, Long>): List<DBBill> {
|
fun getBillsIHM(
|
||||||
return getBillsFromJSONArray(JSONArray(content), projId, memberRemoteIdToId)
|
projId: Long,
|
||||||
|
memberRemoteIdToId: Map<Long, Long>,
|
||||||
|
catRemoteIdToId: Map<Long, Long>,
|
||||||
|
pmRemoteIdToId: Map<Long, Long>
|
||||||
|
): List<DBBill> {
|
||||||
|
return getBillsFromJSONArray(JSONArray(content), projId, memberRemoteIdToId, catRemoteIdToId, pmRemoteIdToId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -270,6 +400,32 @@ open class ServerResponse(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class CategoriesResponse(response: VersatileProjectSyncClient.ResponseData, isOcsResponse: Boolean) :
|
||||||
|
ServerResponse(response, isOcsResponse) {
|
||||||
|
|
||||||
|
@Throws(JSONException::class)
|
||||||
|
fun getCategories(projId: Long): List<DBCategory> {
|
||||||
|
return if (isOcsResponse) {
|
||||||
|
getCategoriesFromJSON(getResponseObjectData(), projId)
|
||||||
|
} else {
|
||||||
|
getCategoriesFromJSONArray(getResponseArrayData(), projId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PaymentModesResponse(response: VersatileProjectSyncClient.ResponseData, isOcsResponse: Boolean) :
|
||||||
|
ServerResponse(response, isOcsResponse) {
|
||||||
|
|
||||||
|
@Throws(JSONException::class)
|
||||||
|
fun getPaymentModes(projId: Long): List<DBPaymentMode> {
|
||||||
|
return if (isOcsResponse) {
|
||||||
|
getPaymentModesFromJSON(getResponseObjectData(), projId)
|
||||||
|
} else {
|
||||||
|
getPaymentModesFromJSONArray(getResponseArrayData(), projId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class AccountProjectsResponse(
|
class AccountProjectsResponse(
|
||||||
response: VersatileProjectSyncClient.ResponseData,
|
response: VersatileProjectSyncClient.ResponseData,
|
||||||
isOcsResponse: Boolean
|
isOcsResponse: Boolean
|
||||||
@@ -366,6 +522,26 @@ open class ServerResponse(
|
|||||||
return members
|
return members
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Throws(JSONException::class)
|
||||||
|
protected fun getCategoriesFromJSONArray(jsonCats: JSONArray, projId: Long): List<DBCategory> {
|
||||||
|
val categories: MutableList<DBCategory> = ArrayList()
|
||||||
|
for (i in 0 until jsonCats.length()) {
|
||||||
|
val jsonCat = jsonCats.getJSONObject(i)
|
||||||
|
categories.add(getCategoryFromJSON(jsonCat, projId))
|
||||||
|
}
|
||||||
|
return categories
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(JSONException::class)
|
||||||
|
protected fun getPaymentModesFromJSONArray(jsonPms: JSONArray, projId: Long): List<DBPaymentMode> {
|
||||||
|
val paymentModes: MutableList<DBPaymentMode> = ArrayList()
|
||||||
|
for (i in 0 until jsonPms.length()) {
|
||||||
|
val jsonPm = jsonPms.getJSONObject(i)
|
||||||
|
paymentModes.add(getPaymentModeFromJSON(jsonPm, projId))
|
||||||
|
}
|
||||||
|
return paymentModes
|
||||||
|
}
|
||||||
|
|
||||||
@Throws(JSONException::class)
|
@Throws(JSONException::class)
|
||||||
protected fun getCategoriesFromJSON(json: JSONObject, projId: Long): List<DBCategory> {
|
protected fun getCategoriesFromJSON(json: JSONObject, projId: Long): List<DBCategory> {
|
||||||
val categories: MutableList<DBCategory> = ArrayList()
|
val categories: MutableList<DBCategory> = ArrayList()
|
||||||
@@ -382,6 +558,27 @@ open class ServerResponse(
|
|||||||
return categories
|
return categories
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Throws(JSONException::class)
|
||||||
|
protected fun getCategoryFromJSON(json: JSONObject, projId: Long): DBCategory {
|
||||||
|
var remoteId: Long = 0
|
||||||
|
if (json.has("id") && !json.isNull("id")) {
|
||||||
|
remoteId = json.getLong("id")
|
||||||
|
}
|
||||||
|
var name = ""
|
||||||
|
var color = ""
|
||||||
|
var icon = ""
|
||||||
|
if (json.has("color") && !json.isNull("color")) {
|
||||||
|
color = json.getString("color")
|
||||||
|
}
|
||||||
|
if (json.has("icon") && !json.isNull("icon")) {
|
||||||
|
icon = json.getString("icon")
|
||||||
|
}
|
||||||
|
if (json.has("name") && !json.isNull("name")) {
|
||||||
|
name = json.getString("name")
|
||||||
|
}
|
||||||
|
return DBCategory(0, remoteId, projId, name, icon, color)
|
||||||
|
}
|
||||||
|
|
||||||
@Throws(JSONException::class)
|
@Throws(JSONException::class)
|
||||||
protected fun getCategoryFromJSON(json: JSONObject, remoteIdStr: String, projId: Long): DBCategory {
|
protected fun getCategoryFromJSON(json: JSONObject, remoteIdStr: String, projId: Long): DBCategory {
|
||||||
val remoteId = remoteIdStr.toLong()
|
val remoteId = remoteIdStr.toLong()
|
||||||
@@ -416,6 +613,27 @@ open class ServerResponse(
|
|||||||
return paymentModes
|
return paymentModes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Throws(JSONException::class)
|
||||||
|
protected fun getPaymentModeFromJSON(json: JSONObject, projId: Long): DBPaymentMode {
|
||||||
|
var remoteId: Long = 0
|
||||||
|
if (json.has("id") && !json.isNull("id")) {
|
||||||
|
remoteId = json.getLong("id")
|
||||||
|
}
|
||||||
|
var name = ""
|
||||||
|
var color = ""
|
||||||
|
var icon = ""
|
||||||
|
if (json.has("color") && !json.isNull("color")) {
|
||||||
|
color = json.getString("color")
|
||||||
|
}
|
||||||
|
if (json.has("icon") && !json.isNull("icon")) {
|
||||||
|
icon = json.getString("icon")
|
||||||
|
}
|
||||||
|
if (json.has("name") && !json.isNull("name")) {
|
||||||
|
name = json.getString("name")
|
||||||
|
}
|
||||||
|
return DBPaymentMode(0, remoteId, projId, name, icon, color)
|
||||||
|
}
|
||||||
|
|
||||||
@Throws(JSONException::class)
|
@Throws(JSONException::class)
|
||||||
protected fun getPaymentModeFromJSON(json: JSONObject, remoteIdStr: String, projId: Long): DBPaymentMode {
|
protected fun getPaymentModeFromJSON(json: JSONObject, remoteIdStr: String, projId: Long): DBPaymentMode {
|
||||||
val remoteId = remoteIdStr.toLong()
|
val remoteId = remoteIdStr.toLong()
|
||||||
@@ -556,26 +774,30 @@ open class ServerResponse(
|
|||||||
protected fun getBillsFromJSONArray(
|
protected fun getBillsFromJSONArray(
|
||||||
json: JSONArray,
|
json: JSONArray,
|
||||||
projId: Long,
|
projId: Long,
|
||||||
memberRemoteIdToId: Map<Long, Long>
|
memberRemoteIdToId: Map<Long, Long>,
|
||||||
|
catRemoteIdToId: Map<Long, Long>,
|
||||||
|
pmRemoteIdToId: Map<Long, Long>
|
||||||
): List<DBBill> {
|
): List<DBBill> {
|
||||||
val bills: MutableList<DBBill> = ArrayList()
|
val bills: MutableList<DBBill> = ArrayList()
|
||||||
for (i in 0 until json.length()) {
|
for (i in 0 until json.length()) {
|
||||||
val jsonBill = json.getJSONObject(i)
|
val jsonBill = json.getJSONObject(i)
|
||||||
bills.add(getBillFromJSON(jsonBill, projId, memberRemoteIdToId))
|
bills.add(getBillFromJSON(jsonBill, projId, memberRemoteIdToId, catRemoteIdToId, pmRemoteIdToId))
|
||||||
}
|
}
|
||||||
return bills
|
return bills
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(JSONException::class)
|
@Throws(JSONException::class)
|
||||||
protected fun getBillsFromJSONObject(
|
protected fun getBillsFromJSONObject(
|
||||||
json: JSONObject,
|
|
||||||
projId: Long,
|
projId: Long,
|
||||||
memberRemoteIdToId: Map<Long, Long>
|
memberRemoteIdToId: Map<Long, Long>,
|
||||||
|
catRemoteIdToId: Map<Long, Long>,
|
||||||
|
pmRemoteIdToId: Map<Long, Long>
|
||||||
): List<DBBill> {
|
): List<DBBill> {
|
||||||
val bills: List<DBBill>
|
val bills: List<DBBill>
|
||||||
|
val json = getResponseObjectData()
|
||||||
if (json.has("bills") && !json.isNull("bills")) {
|
if (json.has("bills") && !json.isNull("bills")) {
|
||||||
val jsonBills = json.getJSONArray("bills")
|
val jsonBills = json.getJSONArray("bills")
|
||||||
bills = getBillsFromJSONArray(jsonBills, projId, memberRemoteIdToId)
|
bills = getBillsFromJSONArray(jsonBills, projId, memberRemoteIdToId, catRemoteIdToId, pmRemoteIdToId)
|
||||||
} else {
|
} else {
|
||||||
bills = ArrayList()
|
bills = ArrayList()
|
||||||
}
|
}
|
||||||
@@ -586,7 +808,9 @@ open class ServerResponse(
|
|||||||
protected fun getBillFromJSON(
|
protected fun getBillFromJSON(
|
||||||
json: JSONObject,
|
json: JSONObject,
|
||||||
projId: Long,
|
projId: Long,
|
||||||
memberRemoteIdToId: Map<Long, Long>
|
memberRemoteIdToId: Map<Long, Long>,
|
||||||
|
catRemoteIdToId: Map<Long, Long>,
|
||||||
|
pmRemoteIdToId: Map<Long, Long>
|
||||||
): DBBill {
|
): DBBill {
|
||||||
var remoteId: Long = 0
|
var remoteId: Long = 0
|
||||||
var payerRemoteId: Long
|
var payerRemoteId: Long
|
||||||
@@ -600,7 +824,7 @@ open class ServerResponse(
|
|||||||
var repeat = DBBill.NON_REPEATED
|
var repeat = DBBill.NON_REPEATED
|
||||||
var paymentMode = DBBill.PAYMODE_NONE
|
var paymentMode = DBBill.PAYMODE_NONE
|
||||||
var paymentModeRemoteId = DBBill.PAYMODE_ID_NONE
|
var paymentModeRemoteId = DBBill.PAYMODE_ID_NONE
|
||||||
var categoryId = DBBill.CATEGORY_NONE
|
var categoryRemoteId = DBBill.CATEGORY_NONE
|
||||||
if (!json.isNull("id")) {
|
if (!json.isNull("id")) {
|
||||||
remoteId = json.getLong("id")
|
remoteId = json.getLong("id")
|
||||||
}
|
}
|
||||||
@@ -639,23 +863,29 @@ open class ServerResponse(
|
|||||||
}
|
}
|
||||||
if (json.has("paymentmode") && !json.isNull("paymentmode")) {
|
if (json.has("paymentmode") && !json.isNull("paymentmode")) {
|
||||||
paymentMode = json.getString("paymentmode")
|
paymentMode = json.getString("paymentmode")
|
||||||
|
} else if (json.has("paymentMode") && !json.isNull("paymentMode")) {
|
||||||
|
paymentMode = json.getString("paymentMode")
|
||||||
}
|
}
|
||||||
if (json.has("categoryid") && !json.isNull("categoryid")) {
|
if (json.has("categoryid") && !json.isNull("categoryid")) {
|
||||||
categoryId = json.getInt("categoryid")
|
categoryRemoteId = json.getLong("categoryid")
|
||||||
Log.d("PLOP", "LOADED CATTTTTTTTTTTT $categoryId")
|
} else if (json.has("categoryId") && !json.isNull("categoryId")) {
|
||||||
|
categoryRemoteId = json.getLong("categoryId")
|
||||||
}
|
}
|
||||||
if (json.has("paymentmodeid") && !json.isNull("paymentmodeid")) {
|
if (json.has("paymentmodeid") && !json.isNull("paymentmodeid")) {
|
||||||
paymentModeRemoteId = json.getInt("paymentmodeid")
|
paymentModeRemoteId = json.getLong("paymentmodeid")
|
||||||
|
} else if (json.has("paymentModeId") && !json.isNull("paymentModeId")) {
|
||||||
|
paymentModeRemoteId = json.getLong("paymentModeId")
|
||||||
}
|
}
|
||||||
// old MB, new Cospend is ok as Cospend provides the old pm ID
|
|
||||||
// new MB, old Cospend => set payment mode ID from old one
|
|
||||||
if (DBBill.PAYMODE_NONE != paymentMode && "" != paymentMode && paymentModeRemoteId == DBBill.PAYMODE_ID_NONE) {
|
if (DBBill.PAYMODE_NONE != paymentMode && "" != paymentMode && paymentModeRemoteId == DBBill.PAYMODE_ID_NONE) {
|
||||||
Log.d("PaymentMode", "old: $paymentMode and new: 0")
|
|
||||||
paymentModeRemoteId = DBBill.oldPmIdToNew[paymentMode] ?: DBBill.PAYMODE_ID_NONE
|
paymentModeRemoteId = DBBill.oldPmIdToNew[paymentMode] ?: DBBill.PAYMODE_ID_NONE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val categoryId = catRemoteIdToId[categoryRemoteId] ?: 0L
|
||||||
|
val paymentModeId = pmRemoteIdToId[paymentModeRemoteId] ?: 0L
|
||||||
|
|
||||||
val bill = DBBill(
|
val bill = DBBill(
|
||||||
0, remoteId, projId, payerId, amount, timestamp, what,
|
0, remoteId, projId, payerId, amount, timestamp, what,
|
||||||
DBBill.STATE_OK, repeat, paymentMode, categoryId, comment, paymentModeRemoteId
|
DBBill.STATE_OK, repeat, paymentMode, categoryId, comment, paymentModeId
|
||||||
)
|
)
|
||||||
bill.billOwers = getBillOwersFromJson(json, memberRemoteIdToId)
|
bill.billOwers = getBillOwersFromJson(json, memberRemoteIdToId)
|
||||||
return bill
|
return bill
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
package net.helcel.cowspent.util
|
package net.helcel.cowspent.util
|
||||||
|
|
||||||
import android.app.NotificationChannel
|
|
||||||
import android.app.NotificationManager
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import net.helcel.cowspent.model.*
|
import net.helcel.cowspent.model.CreditDebt
|
||||||
|
import net.helcel.cowspent.model.DBBill
|
||||||
|
import net.helcel.cowspent.model.DBMember
|
||||||
|
import net.helcel.cowspent.model.Transaction
|
||||||
import net.helcel.cowspent.persistence.CowspentSQLiteOpenHelper
|
import net.helcel.cowspent.persistence.CowspentSQLiteOpenHelper
|
||||||
import org.json.JSONException
|
import org.json.JSONException
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
@@ -86,14 +87,17 @@ object SupportUtil {
|
|||||||
membersBalance: MutableMap<Long, Double>,
|
membersBalance: MutableMap<Long, Double>,
|
||||||
membersPaid: MutableMap<Long, Double>,
|
membersPaid: MutableMap<Long, Double>,
|
||||||
membersSpent: MutableMap<Long, Double>,
|
membersSpent: MutableMap<Long, Double>,
|
||||||
catId: Int, paymentModeId: Int,
|
catId: Long, paymentModeId: Long,
|
||||||
dateMin: String?, dateMax: String?
|
dateMin: String?, dateMax: String?
|
||||||
): Int {
|
): Int {
|
||||||
|
val proj = db.getProject(projId)
|
||||||
|
val categories = db.getCategories(projId)
|
||||||
|
val reimbursementCategoryId = CategoryUtils.getReimbursementCategoryId(categories, projId, proj?.remoteId)
|
||||||
return getStats(
|
return getStats(
|
||||||
db.getMembersOfProject(projId, null),
|
db.getMembersOfProject(projId, null),
|
||||||
db.getBillsOfProject(projId),
|
db.getBillsOfProject(projId),
|
||||||
membersNbBills, membersBalance, membersPaid, membersSpent,
|
membersNbBills, membersBalance, membersPaid, membersSpent,
|
||||||
catId, paymentModeId, dateMin, dateMax
|
catId, paymentModeId, reimbursementCategoryId, dateMin, dateMax
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,7 +109,8 @@ object SupportUtil {
|
|||||||
membersBalance: MutableMap<Long, Double>,
|
membersBalance: MutableMap<Long, Double>,
|
||||||
membersPaid: MutableMap<Long, Double>,
|
membersPaid: MutableMap<Long, Double>,
|
||||||
membersSpent: MutableMap<Long, Double>,
|
membersSpent: MutableMap<Long, Double>,
|
||||||
catId: Int, paymentModeId: Int,
|
catId: Long, paymentModeId: Long,
|
||||||
|
reimbursementCategoryId: Long,
|
||||||
dateMin: String?, dateMax: String?
|
dateMin: String?, dateMax: String?
|
||||||
): Int {
|
): Int {
|
||||||
val nbBillsTotal = 0
|
val nbBillsTotal = 0
|
||||||
@@ -123,9 +128,9 @@ object SupportUtil {
|
|||||||
for (b in dbBills) {
|
for (b in dbBills) {
|
||||||
// don't take deleted bills and respect category filter
|
// don't take deleted bills and respect category filter
|
||||||
if (b.state != DBBill.STATE_DELETED &&
|
if (b.state != DBBill.STATE_DELETED &&
|
||||||
((catId == -1000 || catId == -100 || b.categoryRemoteId == catId) &&
|
((catId == -1000L || catId == -100L || b.categoryId == catId) &&
|
||||||
(catId != -100 || b.categoryRemoteId != DBBill.CATEGORY_REIMBURSEMENT) &&
|
(catId != -100L || b.categoryId != reimbursementCategoryId) &&
|
||||||
(paymentModeId == -1000 || b.paymentModeRemoteId == paymentModeId)) &&
|
(paymentModeId == -1000L || b.paymentModeId == paymentModeId)) &&
|
||||||
(dateMin == null || b.date >= dateMin) &&
|
(dateMin == null || b.date >= dateMin) &&
|
||||||
(dateMax == null || b.date <= dateMax)
|
(dateMax == null || b.date <= dateMax)
|
||||||
) {
|
) {
|
||||||
@@ -284,18 +289,6 @@ object SupportUtil {
|
|||||||
return reduceBalance(crediters, debiters, results)
|
return reduceBalance(crediters, debiters, results)
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun getVersionName(context: Context): String {
|
|
||||||
var versionName = "0.0.0"
|
|
||||||
try {
|
|
||||||
val pInfo = context.packageManager.getPackageInfo(context.packageName, 0)
|
|
||||||
versionName = pInfo.versionName ?: "0.0.0"
|
|
||||||
} catch (e: PackageManager.NameNotFoundException) {
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
return versionName
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun getJsonObject(text: String?): JSONObject? {
|
fun getJsonObject(text: String?): JSONObject? {
|
||||||
if (text == null) return null
|
if (text == null) return null
|
||||||
|
|||||||
@@ -11,8 +11,10 @@ import com.nextcloud.android.sso.exceptions.NextcloudHttpRequestFailedException
|
|||||||
import com.nextcloud.android.sso.exceptions.TokenMismatchException
|
import com.nextcloud.android.sso.exceptions.TokenMismatchException
|
||||||
import com.nextcloud.android.sso.model.SingleSignOnAccount
|
import com.nextcloud.android.sso.model.SingleSignOnAccount
|
||||||
import net.helcel.cowspent.model.DBBill
|
import net.helcel.cowspent.model.DBBill
|
||||||
|
import net.helcel.cowspent.model.DBCategory
|
||||||
import net.helcel.cowspent.model.DBCurrency
|
import net.helcel.cowspent.model.DBCurrency
|
||||||
import net.helcel.cowspent.model.DBMember
|
import net.helcel.cowspent.model.DBMember
|
||||||
|
import net.helcel.cowspent.model.DBPaymentMode
|
||||||
import net.helcel.cowspent.model.DBProject
|
import net.helcel.cowspent.model.DBProject
|
||||||
import net.helcel.cowspent.model.ProjectType
|
import net.helcel.cowspent.model.ProjectType
|
||||||
import org.json.JSONException
|
import org.json.JSONException
|
||||||
@@ -81,25 +83,18 @@ class VersatileProjectSyncClient(
|
|||||||
else
|
else
|
||||||
project.getRequestBaseUrl(false) + "/api-priv/projects/" + project.remoteId
|
project.getRequestBaseUrl(false) + "/api-priv/projects/" + project.remoteId
|
||||||
useOcsApiRequest = cospendVersionGT161
|
useOcsApiRequest = cospendVersionGT161
|
||||||
if (cospendVersionGT161) {
|
|
||||||
Log.i(TAG, "using new API (weblogin, $username:$password) for getProjectInfo")
|
|
||||||
}
|
|
||||||
} else if (canAccessProjectWithSSO(project)) {
|
} else if (canAccessProjectWithSSO(project)) {
|
||||||
return if (cospendVersionGT161) {
|
target = if (cospendVersionGT161)
|
||||||
target = "/ocs/v2.php/apps/cospend/api/v1/projects/" + project.remoteId
|
"/ocs/v2.php/apps/cospend/api/v1/projects/" + project.remoteId
|
||||||
Log.i(TAG, "using new API for getProjectInfo")
|
else
|
||||||
ServerResponse.ProjectResponse(requestServerWithSSO(nextcloudAPI!!, target, METHOD_GET, null, null, true), true)
|
"/index.php/apps/cospend/api-priv/projects/" + project.remoteId
|
||||||
} else {
|
return ServerResponse.ProjectResponse(requestServerWithSSO(nextcloudAPI!!, target, METHOD_GET, null, null, cospendVersionGT161), cospendVersionGT161)
|
||||||
target = "/index.php/apps/cospend/api-priv/projects/" + project.remoteId
|
|
||||||
ServerResponse.ProjectResponse(requestServerWithSSO(nextcloudAPI!!, target, METHOD_GET, null, null, false), false)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
useOcsApiRequest = cospendVersionGT161
|
useOcsApiRequest = cospendVersionGT161
|
||||||
target = if (cospendVersionGT161)
|
target = if (cospendVersionGT161)
|
||||||
project.getRequestBaseUrl(true) + "/api/v1/public/projects/" + project.remoteId + "/" + getEncodedPassword(project.password)
|
project.getRequestBaseUrl(true) + "/api/v1/public/projects/" + project.remoteId + "/" + getEncodedPassword(project.password)
|
||||||
else
|
else
|
||||||
project.getRequestBaseUrl(false) + "/api/projects/" + project.remoteId + "/" + getEncodedPassword(project.password)
|
project.getRequestBaseUrl(false) + "/api/projects/" + project.remoteId + "/" + getEncodedPassword(project.password)
|
||||||
Log.i(TAG, "using public API, target is: ${target}for getProjectInfo")
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
target = project.serverUrl!!.replace("/+$".toRegex(), "") + "/api/projects/" + project.remoteId
|
target = project.serverUrl!!.replace("/+$".toRegex(), "") + "/api/projects/" + project.remoteId
|
||||||
@@ -172,13 +167,9 @@ class VersatileProjectSyncClient(
|
|||||||
else
|
else
|
||||||
project.getRequestBaseUrl(false) + "/api-priv/projects/" + project.remoteId
|
project.getRequestBaseUrl(false) + "/api-priv/projects/" + project.remoteId
|
||||||
useOcsApiRequest = cospendVersionGT161
|
useOcsApiRequest = cospendVersionGT161
|
||||||
if (cospendVersionGT161) {
|
|
||||||
Log.i(TAG, "using new API (weblogin, $username:$password) for editRemoteProject")
|
|
||||||
}
|
|
||||||
} else if (canAccessProjectWithSSO(project)) {
|
} else if (canAccessProjectWithSSO(project)) {
|
||||||
return if (cospendVersionGT161) {
|
return if (cospendVersionGT161) {
|
||||||
target = "/ocs/v2.php/apps/cospend/api/v1/projects/" + project.remoteId
|
target = "/ocs/v2.php/apps/cospend/api/v1/projects/" + project.remoteId
|
||||||
Log.i(TAG, "using new API for editRemoteProject")
|
|
||||||
ServerResponse.EditRemoteProjectResponse(requestServerWithSSO(nextcloudAPI!!, target, METHOD_PUT, paramKeys, paramValues, true), true)
|
ServerResponse.EditRemoteProjectResponse(requestServerWithSSO(nextcloudAPI!!, target, METHOD_PUT, paramKeys, paramValues, true), true)
|
||||||
} else {
|
} else {
|
||||||
target = "/index.php/apps/cospend/api-priv/projects/" + project.remoteId
|
target = "/index.php/apps/cospend/api-priv/projects/" + project.remoteId
|
||||||
@@ -190,7 +181,6 @@ class VersatileProjectSyncClient(
|
|||||||
project.getRequestBaseUrl(true) + "/api/v1/public/projects/" + project.remoteId + "/" + getEncodedPassword(project.password)
|
project.getRequestBaseUrl(true) + "/api/v1/public/projects/" + project.remoteId + "/" + getEncodedPassword(project.password)
|
||||||
else
|
else
|
||||||
project.getRequestBaseUrl(false) + "/api/projects/" + project.remoteId + "/" + getEncodedPassword(project.password)
|
project.getRequestBaseUrl(false) + "/api/projects/" + project.remoteId + "/" + getEncodedPassword(project.password)
|
||||||
Log.i(TAG, "using public API, target is: ${target}for editRemoteProject")
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
target = project.serverUrl!!.replace("/+$".toRegex(), "") + "/api/projects/" + project.remoteId
|
target = project.serverUrl!!.replace("/+$".toRegex(), "") + "/api/projects/" + project.remoteId
|
||||||
@@ -239,13 +229,9 @@ class VersatileProjectSyncClient(
|
|||||||
else
|
else
|
||||||
project.getRequestBaseUrl(false) + "/api-priv/projects/" + project.remoteId + "/members/" + member.remoteId
|
project.getRequestBaseUrl(false) + "/api-priv/projects/" + project.remoteId + "/members/" + member.remoteId
|
||||||
useOcsApiRequest = cospendVersionGT161
|
useOcsApiRequest = cospendVersionGT161
|
||||||
if (cospendVersionGT161) {
|
|
||||||
Log.i(TAG, "using new API (weblogin, $username:$password) for editRemoteMember")
|
|
||||||
}
|
|
||||||
} else if (canAccessProjectWithSSO(project)) {
|
} else if (canAccessProjectWithSSO(project)) {
|
||||||
return if (cospendVersionGT161) {
|
return if (cospendVersionGT161) {
|
||||||
target = "/ocs/v2.php/apps/cospend/api/v1/projects/" + project.remoteId + "/members/" + member.remoteId
|
target = "/ocs/v2.php/apps/cospend/api/v1/projects/" + project.remoteId + "/members/" + member.remoteId
|
||||||
Log.i(TAG, "using new API for editRemoteMember")
|
|
||||||
ServerResponse.EditRemoteMemberResponse(requestServerWithSSO(nextcloudAPI!!, target, METHOD_PUT, paramKeys, paramValues, true), true)
|
ServerResponse.EditRemoteMemberResponse(requestServerWithSSO(nextcloudAPI!!, target, METHOD_PUT, paramKeys, paramValues, true), true)
|
||||||
} else {
|
} else {
|
||||||
target = "/index.php/apps/cospend/api-priv/projects/" + project.remoteId + "/members/" + member.remoteId
|
target = "/index.php/apps/cospend/api-priv/projects/" + project.remoteId + "/members/" + member.remoteId
|
||||||
@@ -257,7 +243,6 @@ class VersatileProjectSyncClient(
|
|||||||
project.getRequestBaseUrl(true) + "/api/v1/public/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/members/" + member.remoteId
|
project.getRequestBaseUrl(true) + "/api/v1/public/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/members/" + member.remoteId
|
||||||
else
|
else
|
||||||
project.getRequestBaseUrl(false) + "/api/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/members/" + member.remoteId
|
project.getRequestBaseUrl(false) + "/api/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/members/" + member.remoteId
|
||||||
Log.i(TAG, "using public API, target is: ${target}for editRemoteMember")
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
target = project.serverUrl!!.replace("/+$".toRegex(), "") + "/api/projects/" + project.remoteId + "/members/" + member.remoteId
|
target = project.serverUrl!!.replace("/+$".toRegex(), "") + "/api/projects/" + project.remoteId + "/members/" + member.remoteId
|
||||||
@@ -275,7 +260,13 @@ class VersatileProjectSyncClient(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IOException::class, TokenMismatchException::class, NextcloudHttpRequestFailedException::class)
|
@Throws(IOException::class, TokenMismatchException::class, NextcloudHttpRequestFailedException::class)
|
||||||
fun editRemoteBill(project: DBProject, bill: DBBill, memberIdToRemoteId: Map<Long, Long>): ServerResponse.EditRemoteBillResponse {
|
fun editRemoteBill(
|
||||||
|
project: DBProject,
|
||||||
|
bill: DBBill,
|
||||||
|
memberIdToRemoteId: Map<Long, Long>,
|
||||||
|
categoryIdToRemoteId: Map<Long, Long>,
|
||||||
|
paymentModeIdToRemoteId: Map<Long, Long>
|
||||||
|
): ServerResponse.EditRemoteBillResponse {
|
||||||
val paramKeys: MutableList<String> = ArrayList()
|
val paramKeys: MutableList<String> = ArrayList()
|
||||||
val paramValues: MutableList<String> = ArrayList()
|
val paramValues: MutableList<String> = ArrayList()
|
||||||
paramKeys.add("date")
|
paramKeys.add("date")
|
||||||
@@ -322,8 +313,8 @@ class VersatileProjectSyncClient(
|
|||||||
payedFor = payedFor.replace(",$".toRegex(), "")
|
payedFor = payedFor.replace(",$".toRegex(), "")
|
||||||
paramValues.add(payedFor)
|
paramValues.add(payedFor)
|
||||||
paramValues.add(bill.paymentMode ?: "")
|
paramValues.add(bill.paymentMode ?: "")
|
||||||
paramValues.add(bill.categoryRemoteId.toString())
|
paramValues.add((categoryIdToRemoteId[bill.categoryId] ?: 0L).toString())
|
||||||
paramValues.add(bill.paymentModeRemoteId.toString())
|
paramValues.add((paymentModeIdToRemoteId[bill.paymentModeId] ?: 0L).toString())
|
||||||
|
|
||||||
if (canAccessProjectWithNCLogin(project)) {
|
if (canAccessProjectWithNCLogin(project)) {
|
||||||
username = this.username
|
username = this.username
|
||||||
@@ -333,9 +324,6 @@ class VersatileProjectSyncClient(
|
|||||||
else
|
else
|
||||||
project.getRequestBaseUrl(false) + "/api-priv/projects/" + project.remoteId + "/bills/" + bill.remoteId
|
project.getRequestBaseUrl(false) + "/api-priv/projects/" + project.remoteId + "/bills/" + bill.remoteId
|
||||||
useOcsApiRequest = cospendVersionGT161
|
useOcsApiRequest = cospendVersionGT161
|
||||||
if (cospendVersionGT161) {
|
|
||||||
Log.i(TAG, "using new API (weblogin, $username:$password) for editRemoteBill")
|
|
||||||
}
|
|
||||||
} else if (canAccessProjectWithSSO(project)) {
|
} else if (canAccessProjectWithSSO(project)) {
|
||||||
return if (cospendVersionGT161) {
|
return if (cospendVersionGT161) {
|
||||||
target = "/ocs/v2.php/apps/cospend/api/v1/projects/" + project.remoteId + "/bills/" + bill.remoteId
|
target = "/ocs/v2.php/apps/cospend/api/v1/projects/" + project.remoteId + "/bills/" + bill.remoteId
|
||||||
@@ -351,7 +339,6 @@ class VersatileProjectSyncClient(
|
|||||||
project.getRequestBaseUrl(true) + "/api/v1/public/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/bills/" + bill.remoteId
|
project.getRequestBaseUrl(true) + "/api/v1/public/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/bills/" + bill.remoteId
|
||||||
else
|
else
|
||||||
project.getRequestBaseUrl(false) + "/api/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/bills/" + bill.remoteId
|
project.getRequestBaseUrl(false) + "/api/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/bills/" + bill.remoteId
|
||||||
Log.i(TAG, "using public API, target is: ${target}for editRemoteBill")
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
target = project.serverUrl!!.replace("/+$".toRegex(), "") + "/api/projects/" + project.remoteId + "/bills/" + bill.remoteId
|
target = project.serverUrl!!.replace("/+$".toRegex(), "") + "/api/projects/" + project.remoteId + "/bills/" + bill.remoteId
|
||||||
@@ -389,9 +376,6 @@ class VersatileProjectSyncClient(
|
|||||||
else
|
else
|
||||||
project.getRequestBaseUrl(false) + "/api-priv/projects/" + project.remoteId
|
project.getRequestBaseUrl(false) + "/api-priv/projects/" + project.remoteId
|
||||||
useOcsApiRequest = cospendVersionGT161
|
useOcsApiRequest = cospendVersionGT161
|
||||||
if (cospendVersionGT161) {
|
|
||||||
Log.i(TAG, "using new API (weblogin, $username:$password) for deleteRemoteProject")
|
|
||||||
}
|
|
||||||
} else if (canAccessProjectWithSSO(project)) {
|
} else if (canAccessProjectWithSSO(project)) {
|
||||||
return if (cospendVersionGT161) {
|
return if (cospendVersionGT161) {
|
||||||
target = "/ocs/v2.php/apps/cospend/api/v1/projects/" + project.remoteId
|
target = "/ocs/v2.php/apps/cospend/api/v1/projects/" + project.remoteId
|
||||||
@@ -407,7 +391,6 @@ class VersatileProjectSyncClient(
|
|||||||
project.getRequestBaseUrl(true) + "/api/v1/public/projects/" + project.remoteId + "/" + getEncodedPassword(project.password)
|
project.getRequestBaseUrl(true) + "/api/v1/public/projects/" + project.remoteId + "/" + getEncodedPassword(project.password)
|
||||||
else
|
else
|
||||||
project.getRequestBaseUrl(false) + "/api/projects/" + project.remoteId + "/" + getEncodedPassword(project.password)
|
project.getRequestBaseUrl(false) + "/api/projects/" + project.remoteId + "/" + getEncodedPassword(project.password)
|
||||||
Log.i(TAG, "using public API, target is: ${target}for deleteRemoteProject")
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
target = project.serverUrl!!.replace("/+$".toRegex(), "") + "/api/projects/" + project.remoteId
|
target = project.serverUrl!!.replace("/+$".toRegex(), "") + "/api/projects/" + project.remoteId
|
||||||
@@ -439,9 +422,6 @@ class VersatileProjectSyncClient(
|
|||||||
else
|
else
|
||||||
project.getRequestBaseUrl(false) + "/api-priv/projects/" + project.remoteId + "/bills/" + billRemoteId
|
project.getRequestBaseUrl(false) + "/api-priv/projects/" + project.remoteId + "/bills/" + billRemoteId
|
||||||
useOcsApiRequest = cospendVersionGT161
|
useOcsApiRequest = cospendVersionGT161
|
||||||
if (cospendVersionGT161) {
|
|
||||||
Log.i(TAG, "using new API (weblogin, $username:$password) for deleteRemoteBill")
|
|
||||||
}
|
|
||||||
} else if (canAccessProjectWithSSO(project)) {
|
} else if (canAccessProjectWithSSO(project)) {
|
||||||
return if (cospendVersionGT161) {
|
return if (cospendVersionGT161) {
|
||||||
target = "/ocs/v2.php/apps/cospend/api/v1/projects/" + project.remoteId + "/bills/" + billRemoteId
|
target = "/ocs/v2.php/apps/cospend/api/v1/projects/" + project.remoteId + "/bills/" + billRemoteId
|
||||||
@@ -457,7 +437,6 @@ class VersatileProjectSyncClient(
|
|||||||
project.getRequestBaseUrl(true) + "/api/v1/public/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/bills/" + billRemoteId
|
project.getRequestBaseUrl(true) + "/api/v1/public/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/bills/" + billRemoteId
|
||||||
else
|
else
|
||||||
project.getRequestBaseUrl(false) + "/api/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/bills/" + billRemoteId
|
project.getRequestBaseUrl(false) + "/api/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/bills/" + billRemoteId
|
||||||
Log.i(TAG, "using public API, target is: ${target}for deleteRemoteProject")
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
target = project.serverUrl!!.replace("/+$".toRegex(), "") + "/api/projects/" + project.remoteId + "/bills/" + billRemoteId
|
target = project.serverUrl!!.replace("/+$".toRegex(), "") + "/api/projects/" + project.remoteId + "/bills/" + billRemoteId
|
||||||
@@ -528,9 +507,7 @@ class VersatileProjectSyncClient(
|
|||||||
else
|
else
|
||||||
project.getRequestBaseUrl(false) + "/api-priv/projects"
|
project.getRequestBaseUrl(false) + "/api-priv/projects"
|
||||||
useOcsApiRequest = cospendVersionGT161
|
useOcsApiRequest = cospendVersionGT161
|
||||||
if (cospendVersionGT161) {
|
|
||||||
Log.i(TAG, "using new API (weblogin, $username:$password) for createAuthenticatedRemoteProject")
|
|
||||||
}
|
|
||||||
return ServerResponse.CreateRemoteProjectResponse(
|
return ServerResponse.CreateRemoteProjectResponse(
|
||||||
requestServer(
|
requestServer(
|
||||||
target, METHOD_POST, paramKeys, paramValues,
|
target, METHOD_POST, paramKeys, paramValues,
|
||||||
@@ -541,7 +518,13 @@ class VersatileProjectSyncClient(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IOException::class, TokenMismatchException::class, NextcloudHttpRequestFailedException::class)
|
@Throws(IOException::class, TokenMismatchException::class, NextcloudHttpRequestFailedException::class)
|
||||||
fun createRemoteBill(project: DBProject, bill: DBBill, memberIdToRemoteId: Map<Long, Long>): ServerResponse.CreateRemoteBillResponse {
|
fun createRemoteBill(
|
||||||
|
project: DBProject,
|
||||||
|
bill: DBBill,
|
||||||
|
memberIdToRemoteId: Map<Long, Long>,
|
||||||
|
categoryIdToRemoteId: Map<Long, Long>,
|
||||||
|
paymentModeIdToRemoteId: Map<Long, Long>
|
||||||
|
): ServerResponse.CreateRemoteBillResponse {
|
||||||
val paramKeys: MutableList<String> = ArrayList()
|
val paramKeys: MutableList<String> = ArrayList()
|
||||||
val paramValues: MutableList<String> = ArrayList()
|
val paramValues: MutableList<String> = ArrayList()
|
||||||
paramKeys.add("date")
|
paramKeys.add("date")
|
||||||
@@ -583,8 +566,8 @@ class VersatileProjectSyncClient(
|
|||||||
payedFor = payedFor.replace(",$".toRegex(), "")
|
payedFor = payedFor.replace(",$".toRegex(), "")
|
||||||
paramValues.add(payedFor)
|
paramValues.add(payedFor)
|
||||||
paramValues.add(bill.paymentMode ?: "")
|
paramValues.add(bill.paymentMode ?: "")
|
||||||
paramValues.add(bill.categoryRemoteId.toString())
|
paramValues.add((categoryIdToRemoteId[bill.categoryId] ?: 0L).toString())
|
||||||
paramValues.add(bill.paymentModeRemoteId.toString())
|
paramValues.add((paymentModeIdToRemoteId[bill.paymentModeId] ?: 0L).toString())
|
||||||
|
|
||||||
if (canAccessProjectWithNCLogin(project)) {
|
if (canAccessProjectWithNCLogin(project)) {
|
||||||
username = this.username
|
username = this.username
|
||||||
@@ -594,9 +577,6 @@ class VersatileProjectSyncClient(
|
|||||||
else
|
else
|
||||||
project.getRequestBaseUrl(false) + "/api-priv/projects/" + project.remoteId + "/bills"
|
project.getRequestBaseUrl(false) + "/api-priv/projects/" + project.remoteId + "/bills"
|
||||||
useOcsApiRequest = cospendVersionGT161
|
useOcsApiRequest = cospendVersionGT161
|
||||||
if (cospendVersionGT161) {
|
|
||||||
Log.i(TAG, "using new API (weblogin, $username:$password) for createRemoteBill")
|
|
||||||
}
|
|
||||||
} else if (canAccessProjectWithSSO(project)) {
|
} else if (canAccessProjectWithSSO(project)) {
|
||||||
return if (cospendVersionGT161) {
|
return if (cospendVersionGT161) {
|
||||||
target = "/ocs/v2.php/apps/cospend/api/v1/projects/" + project.remoteId + "/bills"
|
target = "/ocs/v2.php/apps/cospend/api/v1/projects/" + project.remoteId + "/bills"
|
||||||
@@ -612,7 +592,6 @@ class VersatileProjectSyncClient(
|
|||||||
project.getRequestBaseUrl(true) + "/api/v1/public/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/bills"
|
project.getRequestBaseUrl(true) + "/api/v1/public/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/bills"
|
||||||
else
|
else
|
||||||
project.getRequestBaseUrl(false) + "/api/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/bills"
|
project.getRequestBaseUrl(false) + "/api/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/bills"
|
||||||
Log.i(TAG, "using public API, target is: ${target}for createRemoteBill")
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
target = project.serverUrl!!.replace("/+$".toRegex(), "") + "/api/projects/" + project.remoteId + "/bills"
|
target = project.serverUrl!!.replace("/+$".toRegex(), "") + "/api/projects/" + project.remoteId + "/bills"
|
||||||
@@ -664,13 +643,9 @@ class VersatileProjectSyncClient(
|
|||||||
else
|
else
|
||||||
project.getRequestBaseUrl(false) + "/api-priv/projects/" + project.remoteId + "/members"
|
project.getRequestBaseUrl(false) + "/api-priv/projects/" + project.remoteId + "/members"
|
||||||
useOcsApiRequest = cospendVersionGT161
|
useOcsApiRequest = cospendVersionGT161
|
||||||
if (cospendVersionGT161) {
|
|
||||||
Log.i(TAG, "using new API (weblogin, $username:$password) for createRemoteMember")
|
|
||||||
}
|
|
||||||
} else if (canAccessProjectWithSSO(project)) {
|
} else if (canAccessProjectWithSSO(project)) {
|
||||||
return if (cospendVersionGT161) {
|
return if (cospendVersionGT161) {
|
||||||
target = "/ocs/v2.php/apps/cospend/api/v1/projects/" + project.remoteId + "/members"
|
target = "/ocs/v2.php/apps/cospend/api/v1/projects/" + project.remoteId + "/members"
|
||||||
Log.i(TAG, "using new API for createRemoteBill")
|
|
||||||
ServerResponse.CreateRemoteMemberResponse(requestServerWithSSO(nextcloudAPI!!, target, METHOD_POST, paramKeys, paramValues, true), isOcsResponse=true, isJsonMember=true)
|
ServerResponse.CreateRemoteMemberResponse(requestServerWithSSO(nextcloudAPI!!, target, METHOD_POST, paramKeys, paramValues, true), isOcsResponse=true, isJsonMember=true)
|
||||||
} else {
|
} else {
|
||||||
target = "/index.php/apps/cospend/api-priv/projects/" + project.remoteId + "/members"
|
target = "/index.php/apps/cospend/api-priv/projects/" + project.remoteId + "/members"
|
||||||
@@ -682,7 +657,6 @@ class VersatileProjectSyncClient(
|
|||||||
project.getRequestBaseUrl(true) + "/api/v1/public/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/members"
|
project.getRequestBaseUrl(true) + "/api/v1/public/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/members"
|
||||||
else
|
else
|
||||||
project.getRequestBaseUrl(false) + "/api/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/members"
|
project.getRequestBaseUrl(false) + "/api/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/members"
|
||||||
Log.i(TAG, "using public API, target is: ${target}for createRemoteBill")
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
target = project.serverUrl!!.replace("/+$".toRegex(), "") + "/api/projects/" + project.remoteId + "/members"
|
target = project.serverUrl!!.replace("/+$".toRegex(), "") + "/api/projects/" + project.remoteId + "/members"
|
||||||
@@ -717,9 +691,6 @@ class VersatileProjectSyncClient(
|
|||||||
else
|
else
|
||||||
project.getRequestBaseUrl(false) + "/api-priv/projects/" + project.remoteId + "/bills?lastchanged=" + tsLastSync
|
project.getRequestBaseUrl(false) + "/api-priv/projects/" + project.remoteId + "/bills?lastchanged=" + tsLastSync
|
||||||
useOcsApiRequest = cospendVersionGT161
|
useOcsApiRequest = cospendVersionGT161
|
||||||
if (cospendVersionGT161) {
|
|
||||||
Log.i(TAG, "using new API (weblogin, $username:$password) for getBills")
|
|
||||||
}
|
|
||||||
return ServerResponse.BillsResponse(
|
return ServerResponse.BillsResponse(
|
||||||
requestServer(
|
requestServer(
|
||||||
target, METHOD_GET, null, null,
|
target, METHOD_GET, null, null,
|
||||||
@@ -738,7 +709,6 @@ class VersatileProjectSyncClient(
|
|||||||
paramValues.add(tsLastSync.toString())
|
paramValues.add(tsLastSync.toString())
|
||||||
return if (cospendVersionGT161) {
|
return if (cospendVersionGT161) {
|
||||||
target = "/ocs/v2.php/apps/cospend/api/v1/projects/" + project.remoteId + "/bills"
|
target = "/ocs/v2.php/apps/cospend/api/v1/projects/" + project.remoteId + "/bills"
|
||||||
Log.i(TAG, "using new API for getBills")
|
|
||||||
ServerResponse.BillsResponse(requestServerWithSSO(nextcloudAPI!!, target, METHOD_GET, paramKeys, paramValues, true), true)
|
ServerResponse.BillsResponse(requestServerWithSSO(nextcloudAPI!!, target, METHOD_GET, paramKeys, paramValues, true), true)
|
||||||
} else {
|
} else {
|
||||||
target = "/index.php/apps/cospend/api-priv/projects/" + project.remoteId + "/bills"
|
target = "/index.php/apps/cospend/api-priv/projects/" + project.remoteId + "/bills"
|
||||||
@@ -750,7 +720,6 @@ class VersatileProjectSyncClient(
|
|||||||
project.getRequestBaseUrl(true) + "/api/v1/public/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/bills?lastChanged=" + tsLastSync
|
project.getRequestBaseUrl(true) + "/api/v1/public/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/bills?lastChanged=" + tsLastSync
|
||||||
else
|
else
|
||||||
project.getRequestBaseUrl(false) + "/apiv2/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/bills?lastchanged=" + tsLastSync
|
project.getRequestBaseUrl(false) + "/apiv2/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/bills?lastchanged=" + tsLastSync
|
||||||
Log.i(TAG, "using public API, target is: ${target}for getBills")
|
|
||||||
return ServerResponse.BillsResponse(
|
return ServerResponse.BillsResponse(
|
||||||
requestServer(
|
requestServer(
|
||||||
target, METHOD_GET, null, null,
|
target, METHOD_GET, null, null,
|
||||||
@@ -774,6 +743,92 @@ class VersatileProjectSyncClient(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Throws(JSONException::class, IOException::class, TokenMismatchException::class, NextcloudHttpRequestFailedException::class)
|
||||||
|
fun getCategories(project: DBProject): ServerResponse.CategoriesResponse {
|
||||||
|
var target: String
|
||||||
|
var username: String? = null
|
||||||
|
var password: String? = null
|
||||||
|
var bearerToken: String? = null
|
||||||
|
var useOcsApiRequest = false
|
||||||
|
if (ProjectType.COSPEND == project.type) {
|
||||||
|
if (canAccessProjectWithNCLogin(project)) {
|
||||||
|
username = this.username
|
||||||
|
password = this.password
|
||||||
|
target = if (cospendVersionGT161)
|
||||||
|
project.getRequestBaseUrl(true) + "/api/v1/projects/" + project.remoteId
|
||||||
|
else
|
||||||
|
project.getRequestBaseUrl(false) + "/api-priv/projects/" + project.remoteId + "/categories"
|
||||||
|
useOcsApiRequest = cospendVersionGT161
|
||||||
|
} else if (canAccessProjectWithSSO(project)) {
|
||||||
|
target = if (cospendVersionGT161)
|
||||||
|
"/ocs/v2.php/apps/cospend/api/v1/projects/" + project.remoteId
|
||||||
|
else
|
||||||
|
"/index.php/apps/cospend/api-priv/projects/" + project.remoteId + "/categories"
|
||||||
|
return ServerResponse.CategoriesResponse(requestServerWithSSO(nextcloudAPI!!, target, METHOD_GET, null, null, cospendVersionGT161), cospendVersionGT161)
|
||||||
|
} else {
|
||||||
|
useOcsApiRequest = cospendVersionGT161
|
||||||
|
target = if (cospendVersionGT161)
|
||||||
|
project.getRequestBaseUrl(true) + "/api/v1/public/projects/" + project.remoteId + "/" + getEncodedPassword(project.password)
|
||||||
|
else
|
||||||
|
project.getRequestBaseUrl(false) + "/api/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/categories"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
target = project.serverUrl!!.replace("/+$".toRegex(), "") + "/api/projects/" + project.remoteId + "/categories"
|
||||||
|
username = project.remoteId
|
||||||
|
password = project.password
|
||||||
|
bearerToken = project.bearerToken
|
||||||
|
}
|
||||||
|
return ServerResponse.CategoriesResponse(
|
||||||
|
requestServer(
|
||||||
|
target, METHOD_GET, null, null,
|
||||||
|
null, username, password, bearerToken, useOcsApiRequest
|
||||||
|
), useOcsApiRequest
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(JSONException::class, IOException::class, TokenMismatchException::class, NextcloudHttpRequestFailedException::class)
|
||||||
|
fun getPaymentModes(project: DBProject): ServerResponse.PaymentModesResponse {
|
||||||
|
var target: String
|
||||||
|
var username: String? = null
|
||||||
|
var password: String? = null
|
||||||
|
var bearerToken: String? = null
|
||||||
|
var useOcsApiRequest = false
|
||||||
|
if (ProjectType.COSPEND == project.type) {
|
||||||
|
if (canAccessProjectWithNCLogin(project)) {
|
||||||
|
username = this.username
|
||||||
|
password = this.password
|
||||||
|
target = if (cospendVersionGT161)
|
||||||
|
project.getRequestBaseUrl(true) + "/api/v1/projects/" + project.remoteId
|
||||||
|
else
|
||||||
|
project.getRequestBaseUrl(false) + "/api-priv/projects/" + project.remoteId + "/paymentmodes"
|
||||||
|
useOcsApiRequest = cospendVersionGT161
|
||||||
|
} else if (canAccessProjectWithSSO(project)) {
|
||||||
|
target = if (cospendVersionGT161)
|
||||||
|
"/ocs/v2.php/apps/cospend/api/v1/projects/" + project.remoteId
|
||||||
|
else
|
||||||
|
"/index.php/apps/cospend/api-priv/projects/" + project.remoteId + "/paymentmodes"
|
||||||
|
return ServerResponse.PaymentModesResponse(requestServerWithSSO(nextcloudAPI!!, target, METHOD_GET, null, null, cospendVersionGT161), cospendVersionGT161)
|
||||||
|
} else {
|
||||||
|
useOcsApiRequest = cospendVersionGT161
|
||||||
|
target = if (cospendVersionGT161)
|
||||||
|
project.getRequestBaseUrl(true) + "/api/v1/public/projects/" + project.remoteId + "/" + getEncodedPassword(project.password)
|
||||||
|
else
|
||||||
|
project.getRequestBaseUrl(false) + "/api/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/paymentmodes"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
target = project.serverUrl!!.replace("/+$".toRegex(), "") + "/api/projects/" + project.remoteId + "/paymentmodes"
|
||||||
|
username = project.remoteId
|
||||||
|
password = project.password
|
||||||
|
bearerToken = project.bearerToken
|
||||||
|
}
|
||||||
|
return ServerResponse.PaymentModesResponse(
|
||||||
|
requestServer(
|
||||||
|
target, METHOD_GET, null, null,
|
||||||
|
null, username, password, bearerToken, useOcsApiRequest
|
||||||
|
), useOcsApiRequest
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Throws(JSONException::class, IOException::class, TokenMismatchException::class, NextcloudHttpRequestFailedException::class)
|
@Throws(JSONException::class, IOException::class, TokenMismatchException::class, NextcloudHttpRequestFailedException::class)
|
||||||
fun getMembers(project: DBProject): ServerResponse.MembersResponse {
|
fun getMembers(project: DBProject): ServerResponse.MembersResponse {
|
||||||
var target: String
|
var target: String
|
||||||
@@ -790,9 +845,6 @@ class VersatileProjectSyncClient(
|
|||||||
else
|
else
|
||||||
project.getRequestBaseUrl(false) + "/api-priv/projects/" + project.remoteId + "/members"
|
project.getRequestBaseUrl(false) + "/api-priv/projects/" + project.remoteId + "/members"
|
||||||
useOcsApiRequest = cospendVersionGT161
|
useOcsApiRequest = cospendVersionGT161
|
||||||
if (cospendVersionGT161) {
|
|
||||||
Log.i(TAG, "using new API (weblogin, $username:$password) for getMembers, projectId: " + project.remoteId)
|
|
||||||
}
|
|
||||||
} else if (canAccessProjectWithSSO(project)) {
|
} else if (canAccessProjectWithSSO(project)) {
|
||||||
return if (cospendVersionGT161) {
|
return if (cospendVersionGT161) {
|
||||||
target = "/ocs/v2.php/apps/cospend/api/v1/projects/" + project.remoteId + "/members"
|
target = "/ocs/v2.php/apps/cospend/api/v1/projects/" + project.remoteId + "/members"
|
||||||
@@ -808,7 +860,6 @@ class VersatileProjectSyncClient(
|
|||||||
project.getRequestBaseUrl(true) + "/api/v1/public/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/members"
|
project.getRequestBaseUrl(true) + "/api/v1/public/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/members"
|
||||||
else
|
else
|
||||||
project.getRequestBaseUrl(false) + "/api/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/members"
|
project.getRequestBaseUrl(false) + "/api/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/members"
|
||||||
Log.i(TAG, "using public API, target is: ${target}for getMembers")
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
target = project.serverUrl!!.replace("/+$".toRegex(), "") + "/api/projects/" + project.remoteId + "/members"
|
target = project.serverUrl!!.replace("/+$".toRegex(), "") + "/api/projects/" + project.remoteId + "/members"
|
||||||
@@ -824,6 +875,305 @@ class VersatileProjectSyncClient(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class, TokenMismatchException::class, NextcloudHttpRequestFailedException::class)
|
||||||
|
fun createRemoteCategory(project: DBProject, category: DBCategory): ServerResponse.CreateRemoteCategoryResponse {
|
||||||
|
val paramKeys: MutableList<String> = ArrayList()
|
||||||
|
val paramValues: MutableList<String> = ArrayList()
|
||||||
|
paramKeys.add("name")
|
||||||
|
paramValues.add(category.name ?: "")
|
||||||
|
paramKeys.add("icon")
|
||||||
|
paramValues.add(category.icon)
|
||||||
|
paramKeys.add("color")
|
||||||
|
paramValues.add(category.color)
|
||||||
|
|
||||||
|
var target: String
|
||||||
|
var username: String? = null
|
||||||
|
var password: String? = null
|
||||||
|
var bearerToken: String? = null
|
||||||
|
var useOcsApiRequest = false
|
||||||
|
if (ProjectType.COSPEND == project.type) {
|
||||||
|
if (canAccessProjectWithNCLogin(project)) {
|
||||||
|
username = this.username
|
||||||
|
password = this.password
|
||||||
|
target = if (cospendVersionGT161)
|
||||||
|
project.getRequestBaseUrl(true) + "/api/v1/projects/" + project.remoteId + "/category"
|
||||||
|
else
|
||||||
|
project.getRequestBaseUrl(false) + "/api-priv/projects/" + project.remoteId + "/categories"
|
||||||
|
useOcsApiRequest = cospendVersionGT161
|
||||||
|
} else if (canAccessProjectWithSSO(project)) {
|
||||||
|
target = if (cospendVersionGT161)
|
||||||
|
"/ocs/v2.php/apps/cospend/api/v1/projects/" + project.remoteId + "/category"
|
||||||
|
else
|
||||||
|
"/index.php/apps/cospend/api-priv/projects/" + project.remoteId + "/categories"
|
||||||
|
return ServerResponse.CreateRemoteCategoryResponse(requestServerWithSSO(nextcloudAPI!!, target, METHOD_POST, paramKeys, paramValues, cospendVersionGT161), cospendVersionGT161)
|
||||||
|
} else {
|
||||||
|
useOcsApiRequest = cospendVersionGT161
|
||||||
|
target = if (cospendVersionGT161)
|
||||||
|
project.getRequestBaseUrl(true) + "/api/v1/public/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/category"
|
||||||
|
else
|
||||||
|
project.getRequestBaseUrl(false) + "/api/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/categories"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
target = project.serverUrl!!.replace("/+$".toRegex(), "") + "/api/projects/" + project.remoteId + "/categories"
|
||||||
|
username = project.remoteId
|
||||||
|
password = project.password
|
||||||
|
bearerToken = project.bearerToken
|
||||||
|
}
|
||||||
|
|
||||||
|
val response = requestServer(
|
||||||
|
target, METHOD_POST, paramKeys, paramValues, null,
|
||||||
|
username, password, bearerToken, useOcsApiRequest
|
||||||
|
)
|
||||||
|
return ServerResponse.CreateRemoteCategoryResponse(response, useOcsApiRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class, TokenMismatchException::class, NextcloudHttpRequestFailedException::class)
|
||||||
|
fun editRemoteCategory(project: DBProject, category: DBCategory): ServerResponse.EditRemoteCategoryResponse {
|
||||||
|
val paramKeys: MutableList<String> = ArrayList()
|
||||||
|
val paramValues: MutableList<String> = ArrayList()
|
||||||
|
paramKeys.add("name")
|
||||||
|
paramValues.add(category.name ?: "")
|
||||||
|
paramKeys.add("icon")
|
||||||
|
paramValues.add(category.icon)
|
||||||
|
paramKeys.add("color")
|
||||||
|
paramValues.add(category.color)
|
||||||
|
|
||||||
|
var target: String
|
||||||
|
var username: String? = null
|
||||||
|
var password: String? = null
|
||||||
|
var bearerToken: String? = null
|
||||||
|
var useOcsApiRequest = false
|
||||||
|
if (ProjectType.COSPEND == project.type) {
|
||||||
|
if (canAccessProjectWithNCLogin(project)) {
|
||||||
|
username = this.username
|
||||||
|
password = this.password
|
||||||
|
target = if (cospendVersionGT161)
|
||||||
|
project.getRequestBaseUrl(true) + "/api/v1/projects/" + project.remoteId + "/category/" + category.remoteId
|
||||||
|
else
|
||||||
|
project.getRequestBaseUrl(false) + "/api-priv/projects/" + project.remoteId + "/categories/" + category.remoteId
|
||||||
|
useOcsApiRequest = cospendVersionGT161
|
||||||
|
} else if (canAccessProjectWithSSO(project)) {
|
||||||
|
target = if (cospendVersionGT161)
|
||||||
|
"/ocs/v2.php/apps/cospend/api/v1/projects/" + project.remoteId + "/category/" + category.remoteId
|
||||||
|
else
|
||||||
|
"/index.php/apps/cospend/api-priv/projects/" + project.remoteId + "/categories/" + category.remoteId
|
||||||
|
return ServerResponse.EditRemoteCategoryResponse(requestServerWithSSO(nextcloudAPI!!, target, METHOD_PUT, paramKeys, paramValues, cospendVersionGT161), cospendVersionGT161)
|
||||||
|
} else {
|
||||||
|
useOcsApiRequest = cospendVersionGT161
|
||||||
|
target = if (cospendVersionGT161)
|
||||||
|
project.getRequestBaseUrl(true) + "/api/v1/public/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/category/" + category.remoteId
|
||||||
|
else
|
||||||
|
project.getRequestBaseUrl(false) + "/api/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/categories/" + category.remoteId
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
target = project.serverUrl!!.replace("/+$".toRegex(), "") + "/api/projects/" + project.remoteId + "/categories/" + category.remoteId
|
||||||
|
username = project.remoteId
|
||||||
|
password = project.password
|
||||||
|
bearerToken = project.bearerToken
|
||||||
|
}
|
||||||
|
|
||||||
|
return ServerResponse.EditRemoteCategoryResponse(
|
||||||
|
requestServer(
|
||||||
|
target, METHOD_PUT, paramKeys, paramValues, null,
|
||||||
|
username, password, bearerToken, useOcsApiRequest
|
||||||
|
), useOcsApiRequest
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class, TokenMismatchException::class, NextcloudHttpRequestFailedException::class)
|
||||||
|
fun deleteRemoteCategory(project: DBProject, categoryRemoteId: Long): ServerResponse.DeleteRemoteCategoryResponse {
|
||||||
|
var target: String
|
||||||
|
var username: String? = null
|
||||||
|
var password: String? = null
|
||||||
|
var bearerToken: String? = null
|
||||||
|
var useOcsApiRequest = false
|
||||||
|
if (ProjectType.COSPEND == project.type) {
|
||||||
|
if (canAccessProjectWithNCLogin(project)) {
|
||||||
|
username = this.username
|
||||||
|
password = this.password
|
||||||
|
target = if (cospendVersionGT161)
|
||||||
|
project.getRequestBaseUrl(true) + "/api/v1/projects/" + project.remoteId + "/category/" + categoryRemoteId
|
||||||
|
else
|
||||||
|
project.getRequestBaseUrl(false) + "/api-priv/projects/" + project.remoteId + "/categories/" + categoryRemoteId
|
||||||
|
useOcsApiRequest = cospendVersionGT161
|
||||||
|
} else if (canAccessProjectWithSSO(project)) {
|
||||||
|
target = if (cospendVersionGT161)
|
||||||
|
"/ocs/v2.php/apps/cospend/api/v1/projects/" + project.remoteId + "/category/" + categoryRemoteId
|
||||||
|
else
|
||||||
|
"/index.php/apps/cospend/api-priv/projects/" + project.remoteId + "/categories/" + categoryRemoteId
|
||||||
|
return ServerResponse.DeleteRemoteCategoryResponse(requestServerWithSSO(nextcloudAPI!!, target, METHOD_DELETE, null, null, cospendVersionGT161), cospendVersionGT161)
|
||||||
|
} else {
|
||||||
|
useOcsApiRequest = cospendVersionGT161
|
||||||
|
target = if (cospendVersionGT161)
|
||||||
|
project.getRequestBaseUrl(true) + "/api/v1/public/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/category/" + categoryRemoteId
|
||||||
|
else
|
||||||
|
project.getRequestBaseUrl(false) + "/api/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/categories/" + categoryRemoteId
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
target = project.serverUrl!!.replace("/+$".toRegex(), "") + "/api/projects/" + project.remoteId + "/categories/" + categoryRemoteId
|
||||||
|
username = project.remoteId
|
||||||
|
password = project.password
|
||||||
|
bearerToken = project.bearerToken
|
||||||
|
}
|
||||||
|
|
||||||
|
return ServerResponse.DeleteRemoteCategoryResponse(
|
||||||
|
requestServer(
|
||||||
|
target, METHOD_DELETE, null, null,
|
||||||
|
null, username, password, bearerToken, useOcsApiRequest
|
||||||
|
), useOcsApiRequest
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class, TokenMismatchException::class, NextcloudHttpRequestFailedException::class)
|
||||||
|
fun createRemotePaymentMode(project: DBProject, paymentMode: DBPaymentMode): ServerResponse.CreateRemotePaymentModeResponse {
|
||||||
|
val paramKeys: MutableList<String> = ArrayList()
|
||||||
|
val paramValues: MutableList<String> = ArrayList()
|
||||||
|
paramKeys.add("name")
|
||||||
|
paramValues.add(paymentMode.name ?: "")
|
||||||
|
paramKeys.add("icon")
|
||||||
|
paramValues.add(paymentMode.icon)
|
||||||
|
paramKeys.add("color")
|
||||||
|
paramValues.add(paymentMode.color)
|
||||||
|
|
||||||
|
var target: String
|
||||||
|
var username: String? = null
|
||||||
|
var password: String? = null
|
||||||
|
var bearerToken: String? = null
|
||||||
|
var useOcsApiRequest = false
|
||||||
|
if (ProjectType.COSPEND == project.type) {
|
||||||
|
if (canAccessProjectWithNCLogin(project)) {
|
||||||
|
username = this.username
|
||||||
|
password = this.password
|
||||||
|
target = if (cospendVersionGT161)
|
||||||
|
project.getRequestBaseUrl(true) + "/api/v1/projects/" + project.remoteId + "/paymentmode"
|
||||||
|
else
|
||||||
|
project.getRequestBaseUrl(false) + "/api-priv/projects/" + project.remoteId + "/paymentmodes"
|
||||||
|
useOcsApiRequest = cospendVersionGT161
|
||||||
|
} else if (canAccessProjectWithSSO(project)) {
|
||||||
|
target = if (cospendVersionGT161)
|
||||||
|
"/ocs/v2.php/apps/cospend/api/v1/projects/" + project.remoteId + "/paymentmode"
|
||||||
|
else
|
||||||
|
"/index.php/apps/cospend/api-priv/projects/" + project.remoteId + "/paymentmodes"
|
||||||
|
return ServerResponse.CreateRemotePaymentModeResponse(requestServerWithSSO(nextcloudAPI!!, target, METHOD_POST, paramKeys, paramValues, cospendVersionGT161), cospendVersionGT161)
|
||||||
|
} else {
|
||||||
|
useOcsApiRequest = cospendVersionGT161
|
||||||
|
target = if (cospendVersionGT161)
|
||||||
|
project.getRequestBaseUrl(true) + "/api/v1/public/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/paymentmode"
|
||||||
|
else
|
||||||
|
project.getRequestBaseUrl(false) + "/api/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/paymentmodes"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
target = project.serverUrl!!.replace("/+$".toRegex(), "") + "/api/projects/" + project.remoteId + "/paymentmodes"
|
||||||
|
username = project.remoteId
|
||||||
|
password = project.password
|
||||||
|
bearerToken = project.bearerToken
|
||||||
|
}
|
||||||
|
|
||||||
|
return ServerResponse.CreateRemotePaymentModeResponse(
|
||||||
|
requestServer(
|
||||||
|
target, METHOD_POST, paramKeys, paramValues, null,
|
||||||
|
username, password, bearerToken, useOcsApiRequest
|
||||||
|
), useOcsApiRequest
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class, TokenMismatchException::class, NextcloudHttpRequestFailedException::class)
|
||||||
|
fun editRemotePaymentMode(project: DBProject, paymentMode: DBPaymentMode): ServerResponse.EditRemotePaymentModeResponse {
|
||||||
|
val paramKeys: MutableList<String> = ArrayList()
|
||||||
|
val paramValues: MutableList<String> = ArrayList()
|
||||||
|
paramKeys.add("name")
|
||||||
|
paramValues.add(paymentMode.name ?: "")
|
||||||
|
paramKeys.add("icon")
|
||||||
|
paramValues.add(paymentMode.icon)
|
||||||
|
paramKeys.add("color")
|
||||||
|
paramValues.add(paymentMode.color)
|
||||||
|
|
||||||
|
var target: String
|
||||||
|
var username: String? = null
|
||||||
|
var password: String? = null
|
||||||
|
var bearerToken: String? = null
|
||||||
|
var useOcsApiRequest = false
|
||||||
|
if (ProjectType.COSPEND == project.type) {
|
||||||
|
if (canAccessProjectWithNCLogin(project)) {
|
||||||
|
username = this.username
|
||||||
|
password = this.password
|
||||||
|
target = if (cospendVersionGT161)
|
||||||
|
project.getRequestBaseUrl(true) + "/api/v1/projects/" + project.remoteId + "/paymentmode/" + paymentMode.remoteId
|
||||||
|
else
|
||||||
|
project.getRequestBaseUrl(false) + "/api-priv/projects/" + project.remoteId + "/paymentmodes/" + paymentMode.remoteId
|
||||||
|
useOcsApiRequest = cospendVersionGT161
|
||||||
|
} else if (canAccessProjectWithSSO(project)) {
|
||||||
|
target = if (cospendVersionGT161)
|
||||||
|
"/ocs/v2.php/apps/cospend/api/v1/projects/" + project.remoteId + "/paymentmode/" + paymentMode.remoteId
|
||||||
|
else
|
||||||
|
"/index.php/apps/cospend/api-priv/projects/" + project.remoteId + "/paymentmodes/" + paymentMode.remoteId
|
||||||
|
return ServerResponse.EditRemotePaymentModeResponse(requestServerWithSSO(nextcloudAPI!!, target, METHOD_PUT, paramKeys, paramValues, cospendVersionGT161), cospendVersionGT161)
|
||||||
|
} else {
|
||||||
|
useOcsApiRequest = cospendVersionGT161
|
||||||
|
target = if (cospendVersionGT161)
|
||||||
|
project.getRequestBaseUrl(true) + "/api/v1/public/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/paymentmode/" + paymentMode.remoteId
|
||||||
|
else
|
||||||
|
project.getRequestBaseUrl(false) + "/api/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/paymentmodes/" + paymentMode.remoteId
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
target = project.serverUrl!!.replace("/+$".toRegex(), "") + "/api/projects/" + project.remoteId + "/paymentmodes/" + paymentMode.remoteId
|
||||||
|
username = project.remoteId
|
||||||
|
password = project.password
|
||||||
|
bearerToken = project.bearerToken
|
||||||
|
}
|
||||||
|
|
||||||
|
return ServerResponse.EditRemotePaymentModeResponse(
|
||||||
|
requestServer(
|
||||||
|
target, METHOD_PUT, paramKeys, paramValues, null,
|
||||||
|
username, password, bearerToken, useOcsApiRequest
|
||||||
|
), useOcsApiRequest
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class, TokenMismatchException::class, NextcloudHttpRequestFailedException::class)
|
||||||
|
fun deleteRemotePaymentMode(project: DBProject, paymentModeRemoteId: Long): ServerResponse.DeleteRemotePaymentModeResponse {
|
||||||
|
var target: String
|
||||||
|
var username: String? = null
|
||||||
|
var password: String? = null
|
||||||
|
var bearerToken: String? = null
|
||||||
|
var useOcsApiRequest = false
|
||||||
|
if (ProjectType.COSPEND == project.type) {
|
||||||
|
if (canAccessProjectWithNCLogin(project)) {
|
||||||
|
username = this.username
|
||||||
|
password = this.password
|
||||||
|
target = if (cospendVersionGT161)
|
||||||
|
project.getRequestBaseUrl(true) + "/api/v1/projects/" + project.remoteId + "/paymentmode/" + paymentModeRemoteId
|
||||||
|
else
|
||||||
|
project.getRequestBaseUrl(false) + "/api-priv/projects/" + project.remoteId + "/paymentmodes/" + paymentModeRemoteId
|
||||||
|
useOcsApiRequest = cospendVersionGT161
|
||||||
|
} else if (canAccessProjectWithSSO(project)) {
|
||||||
|
target = if (cospendVersionGT161)
|
||||||
|
"/ocs/v2.php/apps/cospend/api/v1/projects/" + project.remoteId + "/paymentmode/" + paymentModeRemoteId
|
||||||
|
else
|
||||||
|
"/index.php/apps/cospend/api-priv/projects/" + project.remoteId + "/paymentmodes/" + paymentModeRemoteId
|
||||||
|
return ServerResponse.DeleteRemotePaymentModeResponse(requestServerWithSSO(nextcloudAPI!!, target, METHOD_DELETE, null, null, cospendVersionGT161), cospendVersionGT161)
|
||||||
|
} else {
|
||||||
|
useOcsApiRequest = cospendVersionGT161
|
||||||
|
target = if (cospendVersionGT161)
|
||||||
|
project.getRequestBaseUrl(true) + "/api/v1/public/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/paymentmode/" + paymentModeRemoteId
|
||||||
|
else
|
||||||
|
project.getRequestBaseUrl(false) + "/api/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/paymentmodes/" + paymentModeRemoteId
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
target = project.serverUrl!!.replace("/+$".toRegex(), "") + "/api/projects/" + project.remoteId + "/paymentmodes/" + paymentModeRemoteId
|
||||||
|
username = project.remoteId
|
||||||
|
password = project.password
|
||||||
|
bearerToken = project.bearerToken
|
||||||
|
}
|
||||||
|
|
||||||
|
return ServerResponse.DeleteRemotePaymentModeResponse(
|
||||||
|
requestServer(
|
||||||
|
target, METHOD_DELETE, null, null,
|
||||||
|
null, username, password, bearerToken, useOcsApiRequest
|
||||||
|
), useOcsApiRequest
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Throws(IOException::class, TokenMismatchException::class, NextcloudHttpRequestFailedException::class)
|
@Throws(IOException::class, TokenMismatchException::class, NextcloudHttpRequestFailedException::class)
|
||||||
fun createRemoteCurrency(project: DBProject, currency: DBCurrency): ServerResponse.CreateRemoteCurrencyResponse {
|
fun createRemoteCurrency(project: DBProject, currency: DBCurrency): ServerResponse.CreateRemoteCurrencyResponse {
|
||||||
val paramKeys: MutableList<String> = ArrayList()
|
val paramKeys: MutableList<String> = ArrayList()
|
||||||
@@ -847,9 +1197,6 @@ class VersatileProjectSyncClient(
|
|||||||
else
|
else
|
||||||
project.getRequestBaseUrl(false) + "/api-priv/projects/" + project.remoteId + "/currency"
|
project.getRequestBaseUrl(false) + "/api-priv/projects/" + project.remoteId + "/currency"
|
||||||
useOcsApiRequest = cospendVersionGT161
|
useOcsApiRequest = cospendVersionGT161
|
||||||
if (cospendVersionGT161) {
|
|
||||||
Log.i(TAG, "using new API (weblogin, $username:$password) for createRemoteCurrency")
|
|
||||||
}
|
|
||||||
} else if (canAccessProjectWithSSO(project)) {
|
} else if (canAccessProjectWithSSO(project)) {
|
||||||
return if (cospendVersionGT161) {
|
return if (cospendVersionGT161) {
|
||||||
target = "/ocs/v2.php/apps/cospend/api/v1/projects/" + project.remoteId + "/currency"
|
target = "/ocs/v2.php/apps/cospend/api/v1/projects/" + project.remoteId + "/currency"
|
||||||
@@ -865,7 +1212,6 @@ class VersatileProjectSyncClient(
|
|||||||
project.getRequestBaseUrl(true) + "/api/v1/public/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/currency"
|
project.getRequestBaseUrl(true) + "/api/v1/public/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/currency"
|
||||||
else
|
else
|
||||||
project.getRequestBaseUrl(false) + "/api/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/currency"
|
project.getRequestBaseUrl(false) + "/api/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/currency"
|
||||||
Log.i(TAG, "using public API, target is: ${target}for createRemoteCurrency")
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
target = project.serverUrl!!.replace("/+$".toRegex(), "") + "/api/projects/" + project.remoteId + "/currency"
|
target = project.serverUrl!!.replace("/+$".toRegex(), "") + "/api/projects/" + project.remoteId + "/currency"
|
||||||
@@ -904,9 +1250,6 @@ class VersatileProjectSyncClient(
|
|||||||
else
|
else
|
||||||
project.getRequestBaseUrl(false) + "/api-priv/projects/" + project.remoteId + "/currency/" + currency.remoteId
|
project.getRequestBaseUrl(false) + "/api-priv/projects/" + project.remoteId + "/currency/" + currency.remoteId
|
||||||
useOcsApiRequest = cospendVersionGT161
|
useOcsApiRequest = cospendVersionGT161
|
||||||
if (cospendVersionGT161) {
|
|
||||||
Log.i(TAG, "using new API (weblogin, $username:$password) for editRemoteCurrency")
|
|
||||||
}
|
|
||||||
} else if (canAccessProjectWithSSO(project)) {
|
} else if (canAccessProjectWithSSO(project)) {
|
||||||
return if (cospendVersionGT161) {
|
return if (cospendVersionGT161) {
|
||||||
target = "/ocs/v2.php/apps/cospend/api/v1/projects/" + project.remoteId + "/currency/" + currency.remoteId
|
target = "/ocs/v2.php/apps/cospend/api/v1/projects/" + project.remoteId + "/currency/" + currency.remoteId
|
||||||
@@ -922,7 +1265,6 @@ class VersatileProjectSyncClient(
|
|||||||
project.getRequestBaseUrl(true) + "/api/v1/public/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/currency/" + currency.remoteId
|
project.getRequestBaseUrl(true) + "/api/v1/public/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/currency/" + currency.remoteId
|
||||||
else
|
else
|
||||||
project.getRequestBaseUrl(false) + "/api/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/currency/" + currency.remoteId
|
project.getRequestBaseUrl(false) + "/api/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/currency/" + currency.remoteId
|
||||||
Log.i(TAG, "using public API, target is: ${target}for createRemoteCurrency")
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
target = project.serverUrl!!.replace("/+$".toRegex(), "") + "/api/projects/" + project.remoteId + "/currency/" + currency.remoteId
|
target = project.serverUrl!!.replace("/+$".toRegex(), "") + "/api/projects/" + project.remoteId + "/currency/" + currency.remoteId
|
||||||
@@ -954,9 +1296,6 @@ class VersatileProjectSyncClient(
|
|||||||
else
|
else
|
||||||
project.getRequestBaseUrl(false) + "/api-priv/projects/" + project.remoteId + "/currency/" + currencyRemoteId
|
project.getRequestBaseUrl(false) + "/api-priv/projects/" + project.remoteId + "/currency/" + currencyRemoteId
|
||||||
useOcsApiRequest = cospendVersionGT161
|
useOcsApiRequest = cospendVersionGT161
|
||||||
if (cospendVersionGT161) {
|
|
||||||
Log.i(TAG, "using new API (weblogin, $username:$password) for deleteRemoteCurrency")
|
|
||||||
}
|
|
||||||
} else if (canAccessProjectWithSSO(project)) {
|
} else if (canAccessProjectWithSSO(project)) {
|
||||||
return if (cospendVersionGT161) {
|
return if (cospendVersionGT161) {
|
||||||
target = "/ocs/v2.php/apps/cospend/api/v1/projects/" + project.remoteId + "/currency/" + currencyRemoteId
|
target = "/ocs/v2.php/apps/cospend/api/v1/projects/" + project.remoteId + "/currency/" + currencyRemoteId
|
||||||
@@ -972,7 +1311,6 @@ class VersatileProjectSyncClient(
|
|||||||
project.getRequestBaseUrl(true) + "/api/v1/public/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/currency/" + currencyRemoteId
|
project.getRequestBaseUrl(true) + "/api/v1/public/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/currency/" + currencyRemoteId
|
||||||
else
|
else
|
||||||
project.getRequestBaseUrl(false) + "/api/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/currency/" + currencyRemoteId
|
project.getRequestBaseUrl(false) + "/api/projects/" + project.remoteId + "/" + getEncodedPassword(project.password) + "/currency/" + currencyRemoteId
|
||||||
Log.i(TAG, "using public API, target is: ${target}for deleteRemoteCurrency")
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
target = project.serverUrl!!.replace("/+$".toRegex(), "") + "/api/projects/" + project.remoteId + "/currency/" + currencyRemoteId
|
target = project.serverUrl!!.replace("/+$".toRegex(), "") + "/api/projects/" + project.remoteId + "/currency/" + currencyRemoteId
|
||||||
@@ -993,6 +1331,10 @@ class VersatileProjectSyncClient(
|
|||||||
nextcloudAPI: NextcloudAPI, target: String, method: String,
|
nextcloudAPI: NextcloudAPI, target: String, method: String,
|
||||||
paramKeys: List<String>?, paramValues: List<String>?, isOCSRequest: Boolean
|
paramKeys: List<String>?, paramValues: List<String>?, isOCSRequest: Boolean
|
||||||
): ResponseData {
|
): ResponseData {
|
||||||
|
var finalTarget = target
|
||||||
|
if (finalTarget.contains("/ocs/v2.php") && !finalTarget.contains("format=json")) {
|
||||||
|
finalTarget += if (finalTarget.contains("?")) "&format=json" else "?format=json"
|
||||||
|
}
|
||||||
val result = StringBuilder()
|
val result = StringBuilder()
|
||||||
var params: MutableList<QueryParam>? = null
|
var params: MutableList<QueryParam>? = null
|
||||||
if (paramKeys != null && paramValues != null) {
|
if (paramKeys != null && paramValues != null) {
|
||||||
@@ -1010,13 +1352,13 @@ class VersatileProjectSyncClient(
|
|||||||
val nextcloudRequest: NextcloudRequest = if (params == null) {
|
val nextcloudRequest: NextcloudRequest = if (params == null) {
|
||||||
NextcloudRequest.Builder()
|
NextcloudRequest.Builder()
|
||||||
.setMethod(method)
|
.setMethod(method)
|
||||||
.setUrl(target)
|
.setUrl(finalTarget)
|
||||||
.setHeader(headers)
|
.setHeader(headers)
|
||||||
.build()
|
.build()
|
||||||
} else {
|
} else {
|
||||||
NextcloudRequest.Builder()
|
NextcloudRequest.Builder()
|
||||||
.setMethod(method)
|
.setMethod(method)
|
||||||
.setUrl(target)
|
.setUrl(finalTarget)
|
||||||
.setParameter(params)
|
.setParameter(params)
|
||||||
.setHeader(headers)
|
.setHeader(headers)
|
||||||
.build()
|
.build()
|
||||||
@@ -1050,8 +1392,12 @@ class VersatileProjectSyncClient(
|
|||||||
lastETag: String?, username: String?, password: String?,
|
lastETag: String?, username: String?, password: String?,
|
||||||
bearerToken: String?, isOCSRequest: Boolean
|
bearerToken: String?, isOCSRequest: Boolean
|
||||||
): ResponseData {
|
): ResponseData {
|
||||||
|
var finalTarget = target
|
||||||
|
if (finalTarget.contains("/ocs/v2.php") && !finalTarget.contains("format=json")) {
|
||||||
|
finalTarget += if (finalTarget.contains("?")) "&format=json" else "?format=json"
|
||||||
|
}
|
||||||
val result = StringBuilder()
|
val result = StringBuilder()
|
||||||
val httpCon = SupportUtil.getHttpURLConnection(target)
|
val httpCon = SupportUtil.getHttpURLConnection(finalTarget)
|
||||||
httpCon.requestMethod = method
|
httpCon.requestMethod = method
|
||||||
if (bearerToken != null) {
|
if (bearerToken != null) {
|
||||||
httpCon.setRequestProperty("Authorization", "Bearer $bearerToken")
|
httpCon.setRequestProperty("Authorization", "Bearer $bearerToken")
|
||||||
@@ -1062,7 +1408,7 @@ class VersatileProjectSyncClient(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
httpCon.setRequestProperty("Connection", "Close")
|
httpCon.setRequestProperty("Connection", "Close")
|
||||||
httpCon.setRequestProperty("User-Agent", "Cowspent/" + SupportUtil.getAppVersionName(context))
|
httpCon.setRequestProperty("User-Agent", "Cowspent-android/" + SupportUtil.getAppVersionName(context))
|
||||||
if (lastETag != null && METHOD_GET == method) {
|
if (lastETag != null && METHOD_GET == method) {
|
||||||
httpCon.setRequestProperty("If-None-Match", lastETag)
|
httpCon.setRequestProperty("If-None-Match", lastETag)
|
||||||
}
|
}
|
||||||
@@ -1071,7 +1417,6 @@ class VersatileProjectSyncClient(
|
|||||||
httpCon.setRequestProperty("Accept", "application/json")
|
httpCon.setRequestProperty("Accept", "application/json")
|
||||||
}
|
}
|
||||||
httpCon.connectTimeout = 10 * 1000 // 10 seconds
|
httpCon.connectTimeout = 10 * 1000 // 10 seconds
|
||||||
Log.d(javaClass.simpleName, "$method $target")
|
|
||||||
if (paramKeys != null && paramValues != null) {
|
if (paramKeys != null && paramValues != null) {
|
||||||
var dataString = ""
|
var dataString = ""
|
||||||
for (i in paramKeys.indices) {
|
for (i in paramKeys.indices) {
|
||||||
@@ -1084,7 +1429,6 @@ class VersatileProjectSyncClient(
|
|||||||
dataString += URLEncoder.encode(value, "UTF-8")
|
dataString += URLEncoder.encode(value, "UTF-8")
|
||||||
}
|
}
|
||||||
val data = dataString.toByteArray()
|
val data = dataString.toByteArray()
|
||||||
Log.d(javaClass.simpleName, "Params: $dataString")
|
|
||||||
httpCon.setFixedLengthStreamingMode(data.size)
|
httpCon.setFixedLengthStreamingMode(data.size)
|
||||||
httpCon.setRequestProperty("Content-Type", "application/x-www-form-urlencoded")
|
httpCon.setRequestProperty("Content-Type", "application/x-www-form-urlencoded")
|
||||||
httpCon.setRequestProperty("Content-Length", data.size.toString())
|
httpCon.setRequestProperty("Content-Length", data.size.toString())
|
||||||
|
|||||||
@@ -2,10 +2,39 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="108dp"
|
android:width="108dp"
|
||||||
android:height="108dp"
|
android:height="108dp"
|
||||||
android:viewportWidth="100"
|
android:viewportWidth="72"
|
||||||
android:viewportHeight="100">
|
android:viewportHeight="72">
|
||||||
<!-- Matches #0F172A Base -->
|
<group android:translateY="-0.33333334">
|
||||||
<path
|
<group android:scaleX="0.63461536"
|
||||||
android:pathData="M0,0h100v100h-100z"
|
android:scaleY="0.63461536"
|
||||||
android:fillColor="#0F172A"/>
|
android:translateX="13.153846"
|
||||||
|
android:translateY="13.153846">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFD700"
|
||||||
|
android:pathData="M36,2 C17.22,2 2,17.22 2,36 C2,54.78 17.22,70 36,70 C54.78,70 70,54.78 70,36 C70,17.22 54.78,2 36,2 Z" />
|
||||||
|
|
||||||
|
<!-- Inner Face (Expanded path) -->
|
||||||
|
<path
|
||||||
|
android:fillColor="#DAA520"
|
||||||
|
android:pathData="M36,8 C20.54,8 8,20.54 8,36 C8,51.46 20.54,64 36,64 C51.46,64 64,51.46 64,36 C64,20.54 51.46,8 36,8 Z" />
|
||||||
|
|
||||||
|
<!-- Center Detail (Pure path replacement for the center circle) -->
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFD700"
|
||||||
|
android:pathData="M36,22 C28.27,22 22,28.27 22,36 C22,43.73 28.27,50 36,50 C43.73,50 50,43.73 50,36 C50,28.27 43.73,22 36,22 Z" />
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Massive Background Coin Base -->
|
||||||
|
<path
|
||||||
|
android:pathData="M 256,76 A 180,180 0 1,1 255.9,76 Z"
|
||||||
|
android:fillColor="#FBBF24"/>
|
||||||
|
|
||||||
|
<!-- Coin Decorative Dotted Inner Rim -->
|
||||||
|
<path
|
||||||
|
android:pathData="M 256,101 A 155,155 0 1,1 255.9,101 Z"
|
||||||
|
android:strokeColor="#D97706"
|
||||||
|
android:strokeWidth="4"/>
|
||||||
|
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
</vector>
|
</vector>
|
||||||
|
|||||||
@@ -9,27 +9,12 @@
|
|||||||
android:scaleY="0.63461536"
|
android:scaleY="0.63461536"
|
||||||
android:translateX="13.153846"
|
android:translateX="13.153846"
|
||||||
android:translateY="13.153846">
|
android:translateY="13.153846">
|
||||||
<path
|
|
||||||
android:fillColor="#FFD700"
|
|
||||||
android:pathData="M36,2 C17.22,2 2,17.22 2,36 C2,54.78 17.22,70 36,70 C54.78,70 70,54.78 70,36 C70,17.22 54.78,2 36,2 Z" />
|
|
||||||
|
|
||||||
<!-- Inner Face (Expanded path) -->
|
|
||||||
<path
|
|
||||||
android:fillColor="#DAA520"
|
|
||||||
android:pathData="M36,8 C20.54,8 8,20.54 8,36 C8,51.46 20.54,64 36,64 C51.46,64 64,51.46 64,36 C64,20.54 51.46,8 36,8 Z" />
|
|
||||||
|
|
||||||
<!-- Center Detail (Pure path replacement for the center circle) -->
|
|
||||||
<path
|
|
||||||
android:fillColor="#FFD700"
|
|
||||||
android:pathData="M36,22 C28.27,22 22,28.27 22,36 C22,43.73 28.27,50 36,50 C43.73,50 50,43.73 50,36 C50,28.27 43.73,22 36,22 Z" />
|
|
||||||
|
|
||||||
|
|
||||||
<group android:name="color">
|
<group android:name="color">
|
||||||
<path android:fillColor="#fff" android:pathData="m15,12.0211h0c0,.6116.3056,1.1827.8145,1.5219l4.2449,2.83c.5764.3843,1.774.8721,1.688,1.5596l-1.7051,6.7284c-.028.2241-.0143.4514.0405.6705l1.8343,7.3371c.0548.2191.0685.4464.0405.6705l-.9152,7.322c-.028.2241-.0143.4514.0405.6705l1.7456,6.9825c.1104.4414.3813.8259.7599,1.0783l1.7312,1.1541c.433.2887.7225.7481.7961,1.2632l-.2598,3.7979s-.2916,3.1088,2.3012,6.051c1.6332,1.8533,5.7148,3.8846,8.2656,3.8847,3.3409,0,5.9547-1.9378,7.3949-3.6465.8914-1.0576,2.5398-2.4978,3.2821-5.8244.2014-.9023-.0996-3.6967-.0996-3.6967,0-.2477.4009-1.311.4985-1.5386l1.3063-2.5077c.0158-.0369.0264-.0595.0389-.0795.5394-.8642.7885-1.8786.8851-2.8927,0,0,.9043-4.9747,1.1585-6.6406.005-.0325-.4996-5.5158-.4953-5.5485l1.5496-6.8172c.0386-.2315.0323-.4683-.0187-.6975l-1.5364-6.9139c-.2151-.9678.3801-1.9308,1.3419-2.1712l1.6416-.4104c.4075-.1019.7676-.341,1.0196-.677l2.2445-2.9927c.2375-.3166.3658-.7017.3658-1.0974v-.1102c0-1.1701-1.1152-1.1434-2.2575-.8896l-6.191.4868c-.3593.0798-.7343.0494-1.0761-.0873l-4.3602-1.7441c-.0767-.0307-.1555-.0562-.2357-.0762l-7.3628-1.8407c-.3375-.0844-.692-.0708-1.022.0393l-5.3714,1.7905c-.0821.0274-.1622.0605-.2396.0992l-3.4104,1.7052c-.3092.1546-.656.2184-1,.184l-7.4623-.7462c-1.0768-.1077-2.0111.7379-2.0111,1.82Z"/>
|
<path android:fillColor="#fff" android:pathData="m15,12.0211h0c0,.6116.3056,1.1827.8145,1.5219l4.2449,2.83c.5764.3843,1.774.8721,1.688,1.5596l-1.7051,6.7284c-.028.2241-.0143.4514.0405.6705l1.8343,7.3371c.0548.2191.0685.4464.0405.6705l-.9152,7.322c-.028.2241-.0143.4514.0405.6705l1.7456,6.9825c.1104.4414.3813.8259.7599,1.0783l1.7312,1.1541c.433.2887.7225.7481.7961,1.2632l-.2598,3.7979s-.2916,3.1088,2.3012,6.051c1.6332,1.8533,5.7148,3.8846,8.2656,3.8847,3.3409,0,5.9547-1.9378,7.3949-3.6465.8914-1.0576,2.5398-2.4978,3.2821-5.8244.2014-.9023-.0996-3.6967-.0996-3.6967,0-.2477.4009-1.311.4985-1.5386l1.3063-2.5077c.0158-.0369.0264-.0595.0389-.0795.5394-.8642.7885-1.8786.8851-2.8927,0,0,.9043-4.9747,1.1585-6.6406.005-.0325-.4996-5.5158-.4953-5.5485l1.5496-6.8172c.0386-.2315.0323-.4683-.0187-.6975l-1.5364-6.9139c-.2151-.9678.3801-1.9308,1.3419-2.1712l1.6416-.4104c.4075-.1019.7676-.341,1.0196-.677l2.2445-2.9927c.2375-.3166.3658-.7017.3658-1.0974v-.1102c0-1.1701-1.1152-1.1434-2.2575-.8896l-6.191.4868c-.3593.0798-.7343.0494-1.0761-.0873l-4.3602-1.7441c-.0767-.0307-.1555-.0562-.2357-.0762l-7.3628-1.8407c-.3375-.0844-.692-.0708-1.022.0393l-5.3714,1.7905c-.0821.0274-.1622.0605-.2396.0992l-3.4104,1.7052c-.3092.1546-.656.2184-1,.184l-7.4623-.7462c-1.0768-.1077-2.0111.7379-2.0111,1.82Z"/>
|
||||||
<path android:fillColor="#a57939" android:pathData="m65.1818,16.5746l-3.5658.2422c-.1304.0089-.2591.0347-.3828.077l-7.081,2.42c-.043.0147-.0854.0314-.1269.0499l-2.6409,1.1807c-.1171.0524-.2341.0883-.3607.1091-1.3737.2264-10.1731,1.8874-10.0194,7.2658,0,0-.4646,3.6003,1.951,6.1473,1.8803,1.9826,2.9889,4.6478,2.5192,7.3395-.3101,1.7768-1.2515,3.5306-3.4702,4.594h0l2.9104,1.0217c.4295.1508.767.4887.9174.9183l1.4763,4.2192,1.6616-1.7712c1.1136-1.1945,1.9689-5.8742,1.9689-5.8742.4855-3.6065.5032-7.264.0031-10.8685-.0148-.1064-.0287-.2031-.0417-.2889h0s1.2606-.965,1.2606-.965c.1608-.1231.3449-.2123.5411-.2622l5.5633-1.4149c.1716-.0436.3341-.1174.4799-.2177l4.3017-2.9608c.1811-.1247.3326-.2877.4436-.4775l2.6005-4.4465c.1016-.1737.1671-.3661.1927-.5658l.4849-3.7874c.1198-.9359-.6453-1.748-1.5867-1.6841Z"/>
|
<path android:fillColor="#a57939" android:pathData="m65.1818,16.5746l-3.5658.2422c-.1304.0089-.2591.0347-.3828.077l-7.081,2.42c-.043.0147-.0854.0314-.1269.0499l-2.6409,1.1807c-.1171.0524-.2341.0883-.3607.1091-1.3737.2264-10.1731,1.8874-10.0194,7.2658,0,0-.4646,3.6003,1.951,6.1473,1.8803,1.9826,2.9889,4.6478,2.5192,7.3395-.3101,1.7768-1.2515,3.5306-3.4702,4.594h0l2.9104,1.0217c.4295.1508.767.4887.9174.9183l1.4763,4.2192,1.6616-1.7712c1.1136-1.1945,1.9689-5.8742,1.9689-5.8742.4855-3.6065.5032-7.264.0031-10.8685-.0148-.1064-.0287-.2031-.0417-.2889h0s1.2606-.965,1.2606-.965c.1608-.1231.3449-.2123.5411-.2622l5.5633-1.4149c.1716-.0436.3341-.1174.4799-.2177l4.3017-2.9608c.1811-.1247.3326-.2877.4436-.4775l2.6005-4.4465c.1016-.1737.1671-.3661.1927-.5658l.4849-3.7874c.1198-.9359-.6453-1.748-1.5867-1.6841Z"/>
|
||||||
<path android:fillColor="#a57939" android:pathData="m6.867,16.5746l3.5658.2422c.1304.0089.2591.0347.3828.077l7.081,2.42c.043.0147.0854.0314.1269.0499l2.6409,1.1807c.1171.0524.2341.0883.3607.1091,1.3737.2264,10.1731,1.8874,10.0194,7.2658,0,0,.4645,3.6003-1.951,6.1473-1.8803,1.9826-2.9889,4.6478-2.5192,7.3395.3101,1.7768,1.2515,3.5306,3.4702,4.594h0l-2.9104,1.0217c-.4295.1508-.767.4887-.9174.9183l-1.4763,4.2192-1.6616-1.7712c-1.1136-1.1945-2.0412-6.9083-2.0412-6.9083-.3413-3.1673-.1698-6.3689.5077-9.4817h0c.1234-.5672-.092-1.1546-.553-1.5075l-.4665-.3571c-.1858-.1422-.4024-.2389-.6323-.2824l-6.0587-1.1441c-.2049-.0387-.3994-.1197-.5711-.2379l-4.2616-2.9332c-.1811-.1247-.3326-.2877-.4436-.4775l-2.6005-4.4465c-.1016-.1737-.1671-.3661-.1927-.5657l-.485-3.7875c-.1198-.9359.6453-1.7481,1.5867-1.6841Z"/>
|
<path android:fillColor="#a57939" android:pathData="m6.867,16.5746l3.5658.2422c.1304.0089.2591.0347.3828.077l7.081,2.42c.043.0147.0854.0314.1269.0499l2.6409,1.1807c.1171.0524.2341.0883.3607.1091,1.3737.2264,10.1731,1.8874,10.0194,7.2658,0,0,.4645,3.6003-1.951,6.1473-1.8803,1.9826-2.9889,4.6478-2.5192,7.3395.3101,1.7768,1.2515,3.5306,3.4702,4.594h0l-2.9104,1.0217c-.4295.1508-.767.4887-.9174.9183l-1.4763,4.2192-1.6616-1.7712c-1.1136-1.1945-2.0412-6.9083-2.0412-6.9083-.3413-3.1673-.1698-6.3689.5077-9.4817h0c.1234-.5672-.092-1.1546-.553-1.5075l-.4665-.3571c-.1858-.1422-.4024-.2389-.6323-.2824l-6.0587-1.1441c-.2049-.0387-.3994-.1197-.5711-.2379l-4.2616-2.9332c-.1811-.1247-.3326-.2877-.4436-.4775l-2.6005-4.4465c-.1016-.1737-.1671-.3661-.1927-.5657l-.485-3.7875c-.1198-.9359.6453-1.7481,1.5867-1.6841Z"/>
|
||||||
<path android:fillColor="#f4aa41" android:pathData="m27.3197,47.9945l-.6598,1.1318c-.7217,2.1691-.9617,4.4695-.7033,6.7408h0c.1194,1.0495.9592,1.871,2.0111,1.9674l.0811.0074,6.4963-.5146c1.249-.0989,2.5045-.0796,3.7499.0577l5.9262.6536c.7598.0838,1.5171-.1772,2.064-.7112h0c.4669-.4559.7436-1.0718.7746-1.7237l.0484-1.0203c.1133-2.3893-.4008-4.7667-1.4912-6.8958h0c-.4317-.843-1.2874-1.3848-2.234-1.4148l-8.6286-.2728h-3.9632c-1.4296,0-2.7515.7595-3.4715,1.9945Z"/>
|
<path android:fillColor="#f4aa41" android:pathData="m27.3197,47.9945l-.6598,1.1318c-.7217,2.1691-.9617,4.4695-.7033,6.7408h0c.1194,1.0495.9592,1.871,2.0111,1.9674l.0811.0074,6.4963-.5146c1.249-.0989,2.5045-.0796,3.7499.0577l5.9262.6536c.7598.0838,1.5171-.1772,2.064-.7112h0c.4669-.4559.7436-1.0718.7746-1.7237l.0484-1.0203c.1133-2.3893-.4008-4.7667-1.4912-6.8958h0c-.4317-.843-1.2874-1.3848-2.234-1.4148l-8.6286-.2728h-3.9632c-1.4296,0-2.7515.7595-3.4715,1.9945Z"/>
|
||||||
|
|
||||||
</group>
|
</group>
|
||||||
<group android:name="line">
|
<group android:name="line">
|
||||||
<path android:fillColor="#000" android:pathData="m26.1616,31.0595s-3.0474-.219-3.8284-1-.781-2.0474,0-2.8284,2.0474-.781,2.8284,0c.7811.781,1,3.8284,1,3.8284Z"/>
|
<path android:fillColor="#000" android:pathData="m26.1616,31.0595s-3.0474-.219-3.8284-1-.781-2.0474,0-2.8284,2.0474-.781,2.8284,0c.7811.781,1,3.8284,1,3.8284Z"/>
|
||||||
@@ -44,17 +29,6 @@
|
|||||||
</group>
|
</group>
|
||||||
|
|
||||||
|
|
||||||
<!-- Massive Background Coin Base -->
|
|
||||||
<path
|
|
||||||
android:pathData="M 256,76 A 180,180 0 1,1 255.9,76 Z"
|
|
||||||
android:fillColor="#FBBF24"/>
|
|
||||||
|
|
||||||
<!-- Coin Decorative Dotted Inner Rim -->
|
|
||||||
<path
|
|
||||||
android:pathData="M 256,101 A 155,155 0 1,1 255.9,101 Z"
|
|
||||||
android:strokeColor="#D97706"
|
|
||||||
android:strokeWidth="4"/>
|
|
||||||
|
|
||||||
</group>
|
</group>
|
||||||
</group>
|
</group>
|
||||||
</group>
|
</group>
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="72"
|
||||||
|
android:viewportHeight="72">
|
||||||
|
<group
|
||||||
|
android:scaleX="0.63461536"
|
||||||
|
android:scaleY="0.63461536"
|
||||||
|
android:translateX="13.153846"
|
||||||
|
android:translateY="13.153846">
|
||||||
|
|
||||||
|
<!-- Cow Outlines -->
|
||||||
|
<group android:name="color">
|
||||||
|
<path android:fillColor="#000" android:pathData="m15,12.0211h0c0,.6116.3056,1.1827.8145,1.5219l4.2449,2.83c.5764.3843,1.774.8721,1.688,1.5596l-1.7051,6.7284c-.028.2241-.0143.4514.0405.6705l1.8343,7.3371c.0548.2191.0685.4464.0405.6705l-.9152,7.322c-.028.2241-.0143.4514.0405.6705l1.7456,6.9825c.1104.4414.3813.8259.7599,1.0783l1.7312,1.1541c.433.2887.7225.7481.7961,1.2632l-.2598,3.7979s-.2916,3.1088,2.3012,6.051c1.6332,1.8533,5.7148,3.8846,8.2656,3.8847,3.3409,0,5.9547-1.9378,7.3949-3.6465.8914-1.0576,2.5398-2.4978,3.2821-5.8244.2014-.9023-.0996-3.6967-.0996-3.6967,0-.2477.4009-1.311.4985-1.5386l1.3063-2.5077c.0158-.0369.0264-.0595.0389-.0795.5394-.8642.7885-1.8786.8851-2.8927,0,0,.9043-4.9747,1.1585-6.6406.005-.0325-.4996-5.5158-.4953-5.5485l1.5496-6.8172c.0386-.2315.0323-.4683-.0187-.6975l-1.5364-6.9139c-.2151-.9678.3801-1.9308,1.3419-2.1712l1.6416-.4104c.4075-.1019.7676-.341,1.0196-.677l2.2445-2.9927c.2375-.3166.3658-.7017.3658-1.0974v-.1102c0-1.1701-1.1152-1.1434-2.2575-.8896l-6.191.4868c-.3593.0798-.7343.0494-1.0761-.0873l-4.3602-1.7441c-.0767-.0307-.1555-.0562-.2357-.0762l-7.3628-1.8407c-.3375-.0844-.692-.0708-1.022.0393l-5.3714,1.7905c-.0821.0274-.1622.0605-.2396.0992l-3.4104,1.7052c-.3092.1546-.656.2184-1,.184l-7.4623-.7462c-1.0768-.1077-2.0111.7379-2.0111,1.82Z"/>
|
||||||
|
<path android:fillColor="#000" android:pathData="m65.1818,16.5746l-3.5658.2422c-.1304.0089-.2591.0347-.3828.077l-7.081,2.42c-.043.0147-.0854.0314-.1269.0499l-2.6409,1.1807c-.1171.0524-.2341.0883-.3607.1091-1.3737.2264-10.1731,1.8874-10.0194,7.2658,0,0-.4646,3.6003,1.951,6.1473,1.8803,1.9826,2.9889,4.6478,2.5192,7.3395-.3101,1.7768-1.2515,3.5306-3.4702,4.594h0l2.9104,1.0217c.4295.1508.767.4887.9174.9183l1.4763,4.2192,1.6616-1.7712c1.1136-1.1945,1.9689-5.8742,1.9689-5.8742.4855-3.6065.5032-7.264.0031-10.8685-.0148-.1064-.0287-.2031-.0417-.2889h0s1.2606-.965,1.2606-.965c.1608-.1231.3449-.2123.5411-.2622l5.5633-1.4149c.1716-.0436.3341-.1174.4799-.2177l4.3017-2.9608c.1811-.1247.3326-.2877.4436-.4775l2.6005-4.4465c.1016-.1737.1671-.3661.1927-.5658l.4849-3.7874c.1198-.9359-.6453-1.748-1.5867-1.6841Z"/>
|
||||||
|
<path android:fillColor="#000" android:pathData="m6.867,16.5746l3.5658.2422c.1304.0089.2591.0347.3828.077l7.081,2.42c.043.0147.0854.0314.1269.0499l2.6409,1.1807c.1171.0524.2341.0883.3607.1091,1.3737.2264,10.1731,1.8874,10.0194,7.2658,0,0,.4645,3.6003-1.951,6.1473-1.8803,1.9826-2.9889,4.6478-2.5192,7.3395.3101,1.7768,1.2515,3.5306,3.4702,4.594h0l-2.9104,1.0217c-.4295.1508-.767.4887-.9174.9183l-1.4763,4.2192-1.6616-1.7712c-1.1136-1.1945-2.0412-6.9083-2.0412-6.9083-.3413-3.1673-.1698-6.3689.5077-9.4817h0c.1234-.5672-.092-1.1546-.553-1.5075l-.4665-.3571c-.1858-.1422-.4024-.2389-.6323-.2824l-6.0587-1.1441c-.2049-.0387-.3994-.1197-.5711-.2379l-4.2616-2.9332c-.1811-.1247-.3326-.2877-.4436-.4775l-2.6005-4.4465c-.1016-.1737-.1671-.3661-.1927-.5657l-.485-3.7875c-.1198-.9359.6453-1.7481,1.5867-1.6841Z"/>
|
||||||
|
<path android:fillColor="#000" android:pathData="m27.3197,47.9945l-.6598,1.1318c-.7217,2.1691-.9617,4.4695-.7033,6.7408h0c.1194,1.0495.9592,1.871,2.0111,1.9674l.0811.0074,6.4963-.5146c1.249-.0989,2.5045-.0796,3.7499.0577l5.9262.6536c.7598.0838,1.5171-.1772,2.064-.7112h0c.4669-.4559.7436-1.0718.7746-1.7237l.0484-1.0203c.1133-2.3893-.4008-4.7667-1.4912-6.8958h0c-.4317-.843-1.2874-1.3848-2.234-1.4148l-8.6286-.2728h-3.9632c-1.4296,0-2.7515.7595-3.4715,1.9945Z"/>
|
||||||
|
</group>
|
||||||
|
<group android:name="line">
|
||||||
|
<path android:fillColor="#000" android:pathData="m26.1616,31.0595s-3.0474-.219-3.8284-1-.781-2.0474,0-2.8284,2.0474-.781,2.8284,0c.7811.781,1,3.8284,1,3.8284Z"/>
|
||||||
|
<path android:strokeColor="#000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:pathData="m19.6191,26.2353l2.0377-9.248s-7.6806-.627-6.505-7.4454c0,0,9.9534,3.2917,12.7748.4702,0,0,6.2375-6.6642,16.1956-.0429,3.3226,2.2093,12.7748-.4702,12.7748-.4702,1.1756,6.8184-6.505,7.4454-6.505,7.4454l2.0377,9.248"/>
|
||||||
|
<path android:strokeColor="#000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:pathData="m28.4825,46h15.7265c.272,0,.5324.1138.7082.3213.8467.9992,3.1442,4.3471,2.0943,10.2966-.1836,1.0404-1.1647,1.7418-2.2037,1.5503-3.4759-.6406-11.324-1.7893-16.7592-.3263-1.1058.2976-2.2065-.454-2.3432-1.5909-.302-2.5115-.3157-6.6284,2.0103-9.8682.1751-.2438.4667-.3828.7668-.3828Z"/>
|
||||||
|
<path android:strokeColor="#000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:pathData="m29.8671,50.277s-.6865,2.855,3.1538,2.926"/>
|
||||||
|
<path android:strokeColor="#000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:pathData="m29.1276,61.4663s6.3482,7.9157,13.7936.3135"/>
|
||||||
|
<path android:strokeColor="#000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:pathData="m17.5177,20.1544c-3.0642-1.9576-7.3791-4.5873-12.4353-3.3426,0,0-1.4891,14.0288,15.3611,15.5179l1.2133.881s-2.2906,8.9177,1.2618,16.1018"/>
|
||||||
|
<path android:fillColor="#000" android:pathData="m45.8871,31.0139s3.0474-.219,3.8284-1c.7811-.781.7811-2.0474,0-2.8284-.781-.781-2.0474-.781-2.8284,0-.781.781-1,3.8284-1,3.8284Z"/>
|
||||||
|
<path android:strokeColor="#000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:pathData="m43.1817,50.2341s.6865,2.855-3.1538,2.926"/>
|
||||||
|
<path android:strokeColor="#000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:pathData="m54.7743,19.9585c3.0474-1.888,7.2683-4.4017,12.1921-3.1896,0,0,1.4891,14.0288-15.3611,15.5179l-1.2133.881s2.5382,7.9772-1.0141,15.1613"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
</vector>
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="128dp"
|
|
||||||
android:height="128dp"
|
|
||||||
android:viewportWidth="72"
|
|
||||||
android:viewportHeight="72">
|
|
||||||
<path
|
|
||||||
android:fillColor="#FFD700"
|
|
||||||
android:pathData="M36,2 C17.22,2 2,17.22 2,36 C2,54.78 17.22,70 36,70 C54.78,70 70,54.78 70,36 C70,17.22 54.78,2 36,2 Z" />
|
|
||||||
|
|
||||||
<!-- Inner Face (Expanded path) -->
|
|
||||||
<path
|
|
||||||
android:fillColor="#DAA520"
|
|
||||||
android:pathData="M36,8 C20.54,8 8,20.54 8,36 C8,51.46 20.54,64 36,64 C51.46,64 64,51.46 64,36 C64,20.54 51.46,8 36,8 Z" />
|
|
||||||
|
|
||||||
<!-- Center Detail (Pure path replacement for the center circle) -->
|
|
||||||
<path
|
|
||||||
android:fillColor="#FFD700"
|
|
||||||
android:pathData="M36,22 C28.27,22 22,28.27 22,36 C22,43.73 28.27,50 36,50 C43.73,50 50,43.73 50,36 C50,28.27 43.73,22 36,22 Z" />
|
|
||||||
|
|
||||||
|
|
||||||
<group android:name="color">
|
|
||||||
<path android:fillColor="#fff" android:pathData="m15,12.0211h0c0,.6116.3056,1.1827.8145,1.5219l4.2449,2.83c.5764.3843,1.774.8721,1.688,1.5596l-1.7051,6.7284c-.028.2241-.0143.4514.0405.6705l1.8343,7.3371c.0548.2191.0685.4464.0405.6705l-.9152,7.322c-.028.2241-.0143.4514.0405.6705l1.7456,6.9825c.1104.4414.3813.8259.7599,1.0783l1.7312,1.1541c.433.2887.7225.7481.7961,1.2632l-.2598,3.7979s-.2916,3.1088,2.3012,6.051c1.6332,1.8533,5.7148,3.8846,8.2656,3.8847,3.3409,0,5.9547-1.9378,7.3949-3.6465.8914-1.0576,2.5398-2.4978,3.2821-5.8244.2014-.9023-.0996-3.6967-.0996-3.6967,0-.2477.4009-1.311.4985-1.5386l1.3063-2.5077c.0158-.0369.0264-.0595.0389-.0795.5394-.8642.7885-1.8786.8851-2.8927,0,0,.9043-4.9747,1.1585-6.6406.005-.0325-.4996-5.5158-.4953-5.5485l1.5496-6.8172c.0386-.2315.0323-.4683-.0187-.6975l-1.5364-6.9139c-.2151-.9678.3801-1.9308,1.3419-2.1712l1.6416-.4104c.4075-.1019.7676-.341,1.0196-.677l2.2445-2.9927c.2375-.3166.3658-.7017.3658-1.0974v-.1102c0-1.1701-1.1152-1.1434-2.2575-.8896l-6.191.4868c-.3593.0798-.7343.0494-1.0761-.0873l-4.3602-1.7441c-.0767-.0307-.1555-.0562-.2357-.0762l-7.3628-1.8407c-.3375-.0844-.692-.0708-1.022.0393l-5.3714,1.7905c-.0821.0274-.1622.0605-.2396.0992l-3.4104,1.7052c-.3092.1546-.656.2184-1,.184l-7.4623-.7462c-1.0768-.1077-2.0111.7379-2.0111,1.82Z"/>
|
|
||||||
<path android:fillColor="#a57939" android:pathData="m65.1818,16.5746l-3.5658.2422c-.1304.0089-.2591.0347-.3828.077l-7.081,2.42c-.043.0147-.0854.0314-.1269.0499l-2.6409,1.1807c-.1171.0524-.2341.0883-.3607.1091-1.3737.2264-10.1731,1.8874-10.0194,7.2658,0,0-.4646,3.6003,1.951,6.1473,1.8803,1.9826,2.9889,4.6478,2.5192,7.3395-.3101,1.7768-1.2515,3.5306-3.4702,4.594h0l2.9104,1.0217c.4295.1508.767.4887.9174.9183l1.4763,4.2192,1.6616-1.7712c1.1136-1.1945,1.9689-5.8742,1.9689-5.8742.4855-3.6065.5032-7.264.0031-10.8685-.0148-.1064-.0287-.2031-.0417-.2889h0s1.2606-.965,1.2606-.965c.1608-.1231.3449-.2123.5411-.2622l5.5633-1.4149c.1716-.0436.3341-.1174.4799-.2177l4.3017-2.9608c.1811-.1247.3326-.2877.4436-.4775l2.6005-4.4465c.1016-.1737.1671-.3661.1927-.5658l.4849-3.7874c.1198-.9359-.6453-1.748-1.5867-1.6841Z"/>
|
|
||||||
<path android:fillColor="#a57939" android:pathData="m6.867,16.5746l3.5658.2422c.1304.0089.2591.0347.3828.077l7.081,2.42c.043.0147.0854.0314.1269.0499l2.6409,1.1807c.1171.0524.2341.0883.3607.1091,1.3737.2264,10.1731,1.8874,10.0194,7.2658,0,0,.4645,3.6003-1.951,6.1473-1.8803,1.9826-2.9889,4.6478-2.5192,7.3395.3101,1.7768,1.2515,3.5306,3.4702,4.594h0l-2.9104,1.0217c-.4295.1508-.767.4887-.9174.9183l-1.4763,4.2192-1.6616-1.7712c-1.1136-1.1945-2.0412-6.9083-2.0412-6.9083-.3413-3.1673-.1698-6.3689.5077-9.4817h0c.1234-.5672-.092-1.1546-.553-1.5075l-.4665-.3571c-.1858-.1422-.4024-.2389-.6323-.2824l-6.0587-1.1441c-.2049-.0387-.3994-.1197-.5711-.2379l-4.2616-2.9332c-.1811-.1247-.3326-.2877-.4436-.4775l-2.6005-4.4465c-.1016-.1737-.1671-.3661-.1927-.5657l-.485-3.7875c-.1198-.9359.6453-1.7481,1.5867-1.6841Z"/>
|
|
||||||
<path android:fillColor="#f4aa41" android:pathData="m27.3197,47.9945l-.6598,1.1318c-.7217,2.1691-.9617,4.4695-.7033,6.7408h0c.1194,1.0495.9592,1.871,2.0111,1.9674l.0811.0074,6.4963-.5146c1.249-.0989,2.5045-.0796,3.7499.0577l5.9262.6536c.7598.0838,1.5171-.1772,2.064-.7112h0c.4669-.4559.7436-1.0718.7746-1.7237l.0484-1.0203c.1133-2.3893-.4008-4.7667-1.4912-6.8958h0c-.4317-.843-1.2874-1.3848-2.234-1.4148l-8.6286-.2728h-3.9632c-1.4296,0-2.7515.7595-3.4715,1.9945Z"/>
|
|
||||||
|
|
||||||
</group>
|
|
||||||
<group android:name="line">
|
|
||||||
<path android:fillColor="#000" android:pathData="m26.1616,31.0595s-3.0474-.219-3.8284-1-.781-2.0474,0-2.8284,2.0474-.781,2.8284,0c.7811.781,1,3.8284,1,3.8284Z"/>
|
|
||||||
<path android:strokeColor="#000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:pathData="m19.6191,26.2353l2.0377-9.248s-7.6806-.627-6.505-7.4454c0,0,9.9534,3.2917,12.7748.4702,0,0,6.2375-6.6642,16.1956-.0429,3.3226,2.2093,12.7748-.4702,12.7748-.4702,1.1756,6.8184-6.505,7.4454-6.505,7.4454l2.0377,9.248"/>
|
|
||||||
<path android:strokeColor="#000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:pathData="m28.4825,46h15.7265c.272,0,.5324.1138.7082.3213.8467.9992,3.1442,4.3471,2.0943,10.2966-.1836,1.0404-1.1647,1.7418-2.2037,1.5503-3.4759-.6406-11.324-1.7893-16.7592-.3263-1.1058.2976-2.2065-.454-2.3432-1.5909-.302-2.5115-.3157-6.6284,2.0103-9.8682.1751-.2438.4667-.3828.7668-.3828Z"/>
|
|
||||||
<path android:strokeColor="#000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:pathData="m29.8671,50.277s-.6865,2.855,3.1538,2.926"/>
|
|
||||||
<path android:strokeColor="#000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:pathData="m29.1276,61.4663s6.3482,7.9157,13.7936.3135"/>
|
|
||||||
<path android:strokeColor="#000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:pathData="m17.5177,20.1544c-3.0642-1.9576-7.3791-4.5873-12.4353-3.3426,0,0-1.4891,14.0288,15.3611,15.5179l1.2133.881s-2.2906,8.9177,1.2618,16.1018"/>
|
|
||||||
<path android:fillColor="#000" android:pathData="m45.8871,31.0139s3.0474-.219,3.8284-1c.7811-.781.7811-2.0474,0-2.8284-.781-.781-2.0474-.781-2.8284,0-.781.781-1,3.8284-1,3.8284Z"/>
|
|
||||||
<path android:strokeColor="#000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:pathData="m43.1817,50.2341s.6865,2.855-3.1538,2.926"/>
|
|
||||||
<path android:strokeColor="#000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:pathData="m54.7743,19.9585c3.0474-1.888,7.2683-4.4017,12.1921-3.1896,0,0,1.4891,14.0288-15.3611,15.5179l-1.2133.881s2.5382,7.9772-1.0141,15.1613"/>
|
|
||||||
</group>
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Massive Background Coin Base -->
|
|
||||||
<path
|
|
||||||
android:pathData="M 256,76 A 180,180 0 1,1 255.9,76 Z"
|
|
||||||
android:fillColor="#FBBF24"/>
|
|
||||||
|
|
||||||
<!-- Coin Decorative Dotted Inner Rim -->
|
|
||||||
<path
|
|
||||||
android:pathData="M 256,101 A 155,155 0 1,1 255.9,101 Z"
|
|
||||||
android:strokeColor="#D97706"
|
|
||||||
android:strokeWidth="4"/>
|
|
||||||
|
|
||||||
</vector>
|
|
||||||
+1
-1
@@ -2,5 +2,5 @@
|
|||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@drawable/ic_launcher_background"/>
|
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
|
<monochrome android:drawable="@drawable/ic_launcher_monochrome"/>
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
||||||
+1
-1
@@ -2,5 +2,5 @@
|
|||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@drawable/ic_launcher_background"/>
|
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
|
<monochrome android:drawable="@drawable/ic_launcher_monochrome"/>
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
||||||
@@ -0,0 +1,241 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
<string name="app_name">Cowspent</string>
|
||||||
|
<!-- Actions -->
|
||||||
|
<string name="action_new_bill">New bill</string>
|
||||||
|
<string name="action_add_project">Add project</string>
|
||||||
|
<string name="action_save">Save</string>
|
||||||
|
<string name="action_edit">Edit</string>
|
||||||
|
<string name="action_share">Share</string>
|
||||||
|
<string name="action_search">Search</string>
|
||||||
|
<string name="action_delete">Delete</string>
|
||||||
|
<string name="action_archive">Archive</string>
|
||||||
|
<string name="action_unarchive">Unarchive</string>
|
||||||
|
<string name="action_export">Export</string>
|
||||||
|
<string name="action_stats">Stats</string>
|
||||||
|
<string name="action_settle">Settle</string>
|
||||||
|
<string name="action_scan_qrcode">Scan QR Code</string>
|
||||||
|
<string name="action_settings">Settings</string>
|
||||||
|
<string name="action_label_bills">Label missing categories</string>
|
||||||
|
<string name="action_logout">Logout</string>
|
||||||
|
<string name="action_connect">Connect</string>
|
||||||
|
<string name="action_discard">Discard</string>
|
||||||
|
<string name="action_members">Members</string>
|
||||||
|
<string name="action_labels">Labels</string>
|
||||||
|
<string name="action_currencies">Currencies</string>
|
||||||
|
<!-- Titles -->
|
||||||
|
<string name="title_stats">Statistics</string>
|
||||||
|
<string name="title_edit_project">Edit project</string>
|
||||||
|
<string name="title_label_bills">Label Bills</string>
|
||||||
|
<string name="title_labels">Manage Labels</string>
|
||||||
|
<string name="title_about">About</string>
|
||||||
|
<string name="title_settle">Settle Project</string>
|
||||||
|
<string name="title_share">Share Project</string>
|
||||||
|
<string name="title_add_project">Add Project</string>
|
||||||
|
<string name="title_add_category">Add Category</string>
|
||||||
|
<string name="title_add_payment_mode">Add Payment Mode</string>
|
||||||
|
<string name="title_account">Nextcloud Account</string>
|
||||||
|
<string name="title_share_web">Web link</string>
|
||||||
|
<string name="title_share_qr">Cowspent link</string>
|
||||||
|
<string name="title_confirm">Are you sure?</string>
|
||||||
|
<!-- Labels and Fields -->
|
||||||
|
<string name="label_all_bills">All bills</string>
|
||||||
|
<string name="label_categories">Categories</string>
|
||||||
|
<string name="label_payment_modes">Payment Modes</string>
|
||||||
|
<string name="label_name">Name</string>
|
||||||
|
<string name="label_icon">Icon / Emoji</string>
|
||||||
|
<string name="label_color">Color</string>
|
||||||
|
<string name="label_weight">Weight</string>
|
||||||
|
<string name="label_activated">Activated</string>
|
||||||
|
<string name="label_password">Password</string>
|
||||||
|
<string name="label_email">E-mail</string>
|
||||||
|
<string name="label_url">Server address</string>
|
||||||
|
<string name="label_username">Username</string>
|
||||||
|
<string name="label_comment">Comment</string>
|
||||||
|
<string name="label_what">What?</string>
|
||||||
|
<string name="label_payer">Who paid?</string>
|
||||||
|
<string name="label_owers">For whom?</string>
|
||||||
|
<string name="label_repeat">Repeat every</string>
|
||||||
|
<string name="label_mode">Mode</string>
|
||||||
|
<string name="label_category">Category</string>
|
||||||
|
<string name="label_project_id">Project ID/name</string>
|
||||||
|
<string name="label_project_title">Project title</string>
|
||||||
|
<string name="label_use_sso">Use Nextcloud App Account</string>
|
||||||
|
<!-- Dialogs and Messages -->
|
||||||
|
<string name="dialog_unsaved_changes_title">Unsaved changes</string>
|
||||||
|
<string name="dialog_unsaved_changes_msg">Save changes before leaving?</string>
|
||||||
|
<string name="dialog_confirm_remove_project_msg">The remote project will not be deleted.</string>
|
||||||
|
<string name="dialog_sync_error_title">Sync error</string>
|
||||||
|
<string name="dialog_sync_error_msg">Sync failed for %1$s.\n\n%2$s</string>
|
||||||
|
<string name="dialog_balanced_msg">Expenses are already balanced.</string>
|
||||||
|
<string name="msg_project_added">Project %1$s added</string>
|
||||||
|
<string name="msg_bill_labeled_done">All bills labeled</string>
|
||||||
|
<string name="msg_no_suggestions">No suggestions</string>
|
||||||
|
<string name="msg_auth_warning">Requires Cospend v0.3.4+.</string>
|
||||||
|
<string name="msg_link_copied">Link copied to clipboard</string>
|
||||||
|
<string name="msg_share_qr">Scan QR code or share the link to join.</string>
|
||||||
|
<string name="msg_share_web">Link for web browser access.</string>
|
||||||
|
<string name="msg_share_qr_warn">Share this link with a Cowspent user.</string>
|
||||||
|
<string name="msg_settle_intro">Settlement for %1$s:</string>
|
||||||
|
<string name="msg_settle_sentence">%1$s owes %3$.2f to %2$s</string>
|
||||||
|
<string name="msg_stats_intro">Stats for %1$s:</string>
|
||||||
|
<string name="msg_stats_header">Member (Paid | Spent | Balance)</string>
|
||||||
|
<string name="msg_logged_in_as">Logged in as %1$s</string>
|
||||||
|
<!-- Errors -->
|
||||||
|
<string name="error_generic">Error</string>
|
||||||
|
<string name="error_loading">Loading</string>
|
||||||
|
<string name="error_no_projects">No projects found</string>
|
||||||
|
<string name="error_no_members">No members found</string>
|
||||||
|
<string name="error_no_bills">No bills found</string>
|
||||||
|
<string name="error_no_member">At least one member required</string>
|
||||||
|
<string name="error_maintenance_mode">Server is in maintenance mode</string>
|
||||||
|
<string name="error_400">400 Bad request</string>
|
||||||
|
<string name="error_401">401 Unauthorized</string>
|
||||||
|
<string name="error_403">403 Forbidden</string>
|
||||||
|
<string name="error_404">404 Not Found</string>
|
||||||
|
<string name="error_sync">Sync failed: %1$s</string>
|
||||||
|
<string name="error_invalid_login">Invalid login: %1$s</string>
|
||||||
|
<string name="error_auth">Wrong username or password</string>
|
||||||
|
<string name="error_json">Invalid server response</string>
|
||||||
|
<string name="error_req_failed">Request failed</string>
|
||||||
|
<string name="error_invalid_email">Invalid e-mail</string>
|
||||||
|
<string name="error_invalid_project_id">Invalid project ID</string>
|
||||||
|
<string name="error_invalid_project_name">Invalid project title</string>
|
||||||
|
<string name="error_invalid_bill_name">Invalid bill name</string>
|
||||||
|
<string name="error_invalid_bill_date">Invalid bill date</string>
|
||||||
|
<string name="error_invalid_bill_payer">Payer required</string>
|
||||||
|
<string name="error_invalid_bill_owers">Owers required</string>
|
||||||
|
<string name="error_no_network">No network connection</string>
|
||||||
|
<string name="error_server">Server error</string>
|
||||||
|
<string name="error_io">Server connection broken</string>
|
||||||
|
<string name="error_share_impossible">Cannot share this project</string>
|
||||||
|
<!-- Drawer / Common UI -->
|
||||||
|
<string name="drawer_no_account">Connect to Nextcloud account</string>
|
||||||
|
<string name="drawer_last_sync">Last sync: %1$02d:%2$02d</string>
|
||||||
|
<string name="simple_cancel">Cancel</string>
|
||||||
|
<string name="simple_ok" tools:ignore="ButtonCase,Typos">Ok</string>
|
||||||
|
<string name="simple_yes">Yes</string>
|
||||||
|
<string name="simple_no">No</string>
|
||||||
|
<string name="simple_close">Close</string>
|
||||||
|
<!-- Settings -->
|
||||||
|
<string name="settings_appearance">Appearance</string>
|
||||||
|
<string name="settings_network">Network</string>
|
||||||
|
<string name="settings_other">Other</string>
|
||||||
|
<string name="settings_night_mode">Theme</string>
|
||||||
|
<string name="settings_offline_mode">Offline mode</string>
|
||||||
|
<string name="settings_offline_mode_summary">Only sync manually.</string>
|
||||||
|
<string name="settings_color_custom">Custom color</string>
|
||||||
|
<string name="settings_color_mode">Color Selection</string>
|
||||||
|
<string name="settings_show_archived">Show archived projects</string>
|
||||||
|
<string name="settings_beta_features">Beta Features</string>
|
||||||
|
<string name="settings_beta_features_summary">Enable experimental features. Use at your own risk.</string>
|
||||||
|
<string name="settings_url_warn_http">WARNING: \"http\" is unsafe. Use \"https\".</string>
|
||||||
|
<string name="settings_colorpicker_title">Choose Color</string>
|
||||||
|
<string name="pref_value_color_system">System</string>
|
||||||
|
<string name="pref_value_color_server">Nextcloud</string>
|
||||||
|
<string name="pref_value_color_manual">Manual</string>
|
||||||
|
<string name="pref_value_theme_light">Light</string>
|
||||||
|
<string name="pref_value_theme_dark">Dark</string>
|
||||||
|
<string name="pref_value_theme_system">Follow system</string>
|
||||||
|
<!-- Constants (Do not translate) -->
|
||||||
|
<!-- Enums and Lists -->
|
||||||
|
<string name="repeat_no">No repeat</string>
|
||||||
|
<string name="repeat_day">Daily</string>
|
||||||
|
<string name="repeat_week">Weekly</string>
|
||||||
|
<string name="repeat_fortnight">Fortnightly</string>
|
||||||
|
<string name="repeat_month">Monthly</string>
|
||||||
|
<string name="repeat_year">Yearly</string>
|
||||||
|
<string name="payment_mode_none">None</string>
|
||||||
|
<string name="payment_mode_all">All</string>
|
||||||
|
<string name="payment_mode_credit_card">Credit card</string>
|
||||||
|
<string name="payment_mode_cash">Cash</string>
|
||||||
|
<string name="payment_mode_check">Check</string>
|
||||||
|
<string name="payment_mode_online">Online</string>
|
||||||
|
<string name="payment_mode_transfer">Transfer</string>
|
||||||
|
<string name="category_none">None</string>
|
||||||
|
<string name="category_all">All</string>
|
||||||
|
<string name="category_all_except_reimbursement">All except reimbursement</string>
|
||||||
|
<string name="category_groceries">Grocery</string>
|
||||||
|
<string name="category_leisure">Bar/Party</string>
|
||||||
|
<string name="category_rent">Rent</string>
|
||||||
|
<string name="category_bills">Bill</string>
|
||||||
|
<string name="category_excursion">Excursion/Culture</string>
|
||||||
|
<string name="category_health">Health</string>
|
||||||
|
<string name="category_shopping">Shopping</string>
|
||||||
|
<string name="category_reimbursement">Reimbursement</string>
|
||||||
|
<string name="category_restaurant">Restaurant</string>
|
||||||
|
<string name="category_accomodation">Accommodation</string>
|
||||||
|
<string name="category_transport">Transport</string>
|
||||||
|
<string name="category_sport">Sport</string>
|
||||||
|
<!-- Project specific -->
|
||||||
|
<string name="new_project_action">What</string>
|
||||||
|
<string name="new_project_where">Where</string>
|
||||||
|
<string name="where_local">Local only</string>
|
||||||
|
<string name="where_cospend">Cospend</string>
|
||||||
|
<string name="where_ihatemoney">IHateMoney</string>
|
||||||
|
<string name="todo_join">Join existing project</string>
|
||||||
|
<string name="todo_create">Create new project</string>
|
||||||
|
<string name="import_tooltip">Import from file</string>
|
||||||
|
<string name="choose_account_project_dialog_title">Choose project</string>
|
||||||
|
<string name="choose_account_project_dialog_impossible">No projects found on this account.</string>
|
||||||
|
<string name="choose_project_management_action">Project</string>
|
||||||
|
<string name="project_added_success">Project added successfully.</string>
|
||||||
|
<string name="no_projects_text">You have no projects yet.</string>
|
||||||
|
<string name="configure_account_choice">Configure Nextcloud account</string>
|
||||||
|
<string name="add_project_choice">Add project manually</string>
|
||||||
|
<string name="no_members_text">No members in this project.</string>
|
||||||
|
<string name="no_bills_text">No bills found.</string>
|
||||||
|
<string name="member_already_exists">Member already exists.</string>
|
||||||
|
<string name="activity_dialog_title">Project: %1$s</string>
|
||||||
|
<string name="remove_project_confirmation">Project %1$s removed.</string>
|
||||||
|
<string name="file_saved_success">File saved: %1$s</string>
|
||||||
|
<string name="import_error_header">Import failed at row %d</string>
|
||||||
|
<string name="import_error_date">Invalid date format at row %d</string>
|
||||||
|
<string name="import_error_owers">Invalid owers at row %d</string>
|
||||||
|
<string name="add_member_dialog_title">Add Member</string>
|
||||||
|
<string name="edit_member_dialog_title">Edit Member</string>
|
||||||
|
<string name="member_edit_delete">Delete</string>
|
||||||
|
<string name="project_edition_no_change">No changes to save.</string>
|
||||||
|
<!-- Settlement -->
|
||||||
|
<string name="center_none">None (Optimal)</string>
|
||||||
|
<string name="settle_who">Who pays</string>
|
||||||
|
<string name="settle_to_whom">To whom</string>
|
||||||
|
<string name="settle_how_much">Amount</string>
|
||||||
|
<string name="simple_settle_share">Share</string>
|
||||||
|
<string name="simple_create_bills">Create bills</string>
|
||||||
|
<string name="settle_bill_what">Settlement</string>
|
||||||
|
<!-- Currencies -->
|
||||||
|
<string name="currency_dialog_title">Choose Currency (%s)</string>
|
||||||
|
<string name="setting_none">None</string>
|
||||||
|
<string name="setting_all">All</string>
|
||||||
|
<string name="currency_saved_success">Currency settings saved.</string>
|
||||||
|
<string name="main_currency">Main Currency</string>
|
||||||
|
<!-- Statistics -->
|
||||||
|
<string name="label_bills_suggested">Suggested Categories</string>
|
||||||
|
<string name="label_bills_skip">Skip</string>
|
||||||
|
<string name="stats_date_min">From</string>
|
||||||
|
<string name="stats_date_max">To</string>
|
||||||
|
<string name="stats_who">Member</string>
|
||||||
|
<string name="stats_paid">Paid</string>
|
||||||
|
<string name="stats_spent">Spent</string>
|
||||||
|
<string name="stats_balance">Balance</string>
|
||||||
|
<string name="total">Total: %1$s</string>
|
||||||
|
<!-- Errors Extra -->
|
||||||
|
<string name="error_project_connect_check">Connection failed: %1$s</string>
|
||||||
|
<string name="error_create_remote_project_helper">Creation failed: %1$s</string>
|
||||||
|
<string name="error_edit_remote_project_helper">Error updating remote project: %1$s</string>
|
||||||
|
<string name="remote_project_operation_no_network">Network unavailable for remote operation.</string>
|
||||||
|
<string name="error_scanning_bill_qr_code">Failed to parse QR code.</string>
|
||||||
|
<string name="error_token_mismatch">Authentication token mismatch. Please log in again.</string>
|
||||||
|
<string name="insufficient_access_level">You don\'t have permission to perform this action.</string>
|
||||||
|
<string name="delete_label_confirmation_title">Delete Label</string>
|
||||||
|
<string name="delete_label_confirmation_message">Are you sure you want to delete this label?</string>
|
||||||
|
<!-- About -->
|
||||||
|
<string name="about_version">Version %1$s</string>
|
||||||
|
<string name="about_maintainer_title">Maintainer</string>
|
||||||
|
<string name="about_license_title">License</string>
|
||||||
|
<string name="about_source_title">Source code</string>
|
||||||
|
<!-- New constants for backward compatibility or shared use -->
|
||||||
|
<string name="share_intent_title">Project %1$s</string>
|
||||||
|
<string name="share_chooser_title">Share %1$s</string>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,241 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
<string name="app_name">نفق</string>
|
||||||
|
<!-- Actions -->
|
||||||
|
<string name="action_new_bill">فاتورة جديدة</string>
|
||||||
|
<string name="action_add_project">إضافة مشروع</string>
|
||||||
|
<string name="action_save">حفظ</string>
|
||||||
|
<string name="action_edit">تحرير</string>
|
||||||
|
<string name="action_share">مشاركة</string>
|
||||||
|
<string name="action_search">البحث</string>
|
||||||
|
<string name="action_delete">حذف</string>
|
||||||
|
<string name="action_archive">أرشيف</string>
|
||||||
|
<string name="action_unarchive">إلغاء الأرشيف</string>
|
||||||
|
<string name="action_export">تصدير</string>
|
||||||
|
<string name="action_stats">إحصائيات</string>
|
||||||
|
<string name="action_settle">حسم</string>
|
||||||
|
<string name="action_scan_qrcode">Scan QR Code</string>
|
||||||
|
<string name="action_settings">الإعدادات</string>
|
||||||
|
<string name="action_label_bills">فئات التسمية المفقودة</string>
|
||||||
|
<string name="action_logout">تسجيل الخروج</string>
|
||||||
|
<string name="action_connect">الاتصال</string>
|
||||||
|
<string name="action_discard">تجاهل</string>
|
||||||
|
<string name="action_members">الأعضاء</string>
|
||||||
|
<string name="action_labels">التسميات</string>
|
||||||
|
<string name="action_currencies">العملات</string>
|
||||||
|
<!-- Titles -->
|
||||||
|
<string name="title_stats">الإحصائيات</string>
|
||||||
|
<string name="title_edit_project">تحرير المشروع</string>
|
||||||
|
<string name="title_label_bills">فواتير التسمية</string>
|
||||||
|
<string name="title_labels">إدارة التسميات</string>
|
||||||
|
<string name="title_about">حول</string>
|
||||||
|
<string name="title_settle">تسوية المشروع</string>
|
||||||
|
<string name="title_share">مشاركة المشروع</string>
|
||||||
|
<string name="title_add_project">إضافة مشروع</string>
|
||||||
|
<string name="title_add_category">إضافة فئة</string>
|
||||||
|
<string name="title_add_payment_mode">إضافة وضع الدفع</string>
|
||||||
|
<string name="title_account">حساب Nextcloud</string>
|
||||||
|
<string name="title_share_web">رابط ويب</string>
|
||||||
|
<string name="title_share_qr">Cowspent link</string>
|
||||||
|
<string name="title_confirm">هل أنت متأكد؟</string>
|
||||||
|
<!-- Labels and Fields -->
|
||||||
|
<string name="label_all_bills">جميع الفواتير</string>
|
||||||
|
<string name="label_categories">الفئات</string>
|
||||||
|
<string name="label_payment_modes">طرق الدفع</string>
|
||||||
|
<string name="label_name">الاسم</string>
|
||||||
|
<string name="label_icon">Icon / Emoji</string>
|
||||||
|
<string name="label_color">اللون</string>
|
||||||
|
<string name="label_weight">الوزن</string>
|
||||||
|
<string name="label_activated">مُفعّل</string>
|
||||||
|
<string name="label_password">كلمة المرور</string>
|
||||||
|
<string name="label_email">البريد الإلكتروني</string>
|
||||||
|
<string name="label_url">عنوان الخادم</string>
|
||||||
|
<string name="label_username">اسم المستخدم</string>
|
||||||
|
<string name="label_comment">تعليق</string>
|
||||||
|
<string name="label_what">ماذا؟</string>
|
||||||
|
<string name="label_payer">من المدفوع؟</string>
|
||||||
|
<string name="label_owers">لمن؟</string>
|
||||||
|
<string name="label_repeat">كرر كل</string>
|
||||||
|
<string name="label_mode">الوضع</string>
|
||||||
|
<string name="label_category">الفئة</string>
|
||||||
|
<string name="label_project_id">معرف/اسم المشروع</string>
|
||||||
|
<string name="label_project_title">عنوان المشروع</string>
|
||||||
|
<string name="label_use_sso">استخدام حساب تطبيق Nextcloud</string>
|
||||||
|
<!-- Dialogs and Messages -->
|
||||||
|
<string name="dialog_unsaved_changes_title">التغييرات غير المحفوظة</string>
|
||||||
|
<string name="dialog_unsaved_changes_msg">حفظ التغييرات قبل المغادرة؟</string>
|
||||||
|
<string name="dialog_confirm_remove_project_msg">لن يتم حذف المشروع البعيد.</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_balanced_msg">تم بالفعل موازنة المصروفات.</string>
|
||||||
|
<string name="msg_project_added">تم إضافة المشروع %1$s</string>
|
||||||
|
<string name="msg_bill_labeled_done">جميع الفواتير الموصوفة</string>
|
||||||
|
<string name="msg_no_suggestions">لا توجد اقتراحات</string>
|
||||||
|
<string name="msg_auth_warning">يتطلب انفاق التكلفة v0.3.4+.</string>
|
||||||
|
<string name="msg_link_copied">تم نسخ الرابط إلى الحافظة</string>
|
||||||
|
<string name="msg_share_qr">امسح رمز QR أو شارك الرابط للانضمام.</string>
|
||||||
|
<string name="msg_share_web">رابط للوصول إلى متصفح الويب.</string>
|
||||||
|
<string name="msg_share_qr_warn">شارك هذا الرابط مع مستخدم مستهلك.</string>
|
||||||
|
<string name="msg_settle_intro">التسوية ل %1$s:</string>
|
||||||
|
<string name="msg_settle_sentence">%1$s مدين %3$.2f ل %2$s</string>
|
||||||
|
<string name="msg_stats_intro">إحصائيات %1$s:</string>
|
||||||
|
<string name="msg_stats_header">عضو (رصيد مدفوع <unk> رصيد مدفوع)</string>
|
||||||
|
<string name="msg_logged_in_as">تم تسجيل الدخول كـ %1$s</string>
|
||||||
|
<!-- Errors -->
|
||||||
|
<string name="error_generic">خطأ</string>
|
||||||
|
<string name="error_loading">جاري التحميل</string>
|
||||||
|
<string name="error_no_projects">لم يتم العثور على مشاريع</string>
|
||||||
|
<string name="error_no_members">لم يتم العثور على أعضاء</string>
|
||||||
|
<string name="error_no_bills">لم يتم العثور على فواتير</string>
|
||||||
|
<string name="error_no_member">مطلوب عضو واحد على الأقل</string>
|
||||||
|
<string name="error_maintenance_mode">الخادم في وضع الصيانة</string>
|
||||||
|
<string name="error_400">400 طلب خاطئ</string>
|
||||||
|
<string name="error_401">401 غير مأذون</string>
|
||||||
|
<string name="error_403">403 محظور</string>
|
||||||
|
<string name="error_404">404 غير موجود</string>
|
||||||
|
<string name="error_sync">فشلت المزامنة: %1$s</string>
|
||||||
|
<string name="error_invalid_login">تسجيل الدخول غير صالح: %1$s</string>
|
||||||
|
<string name="error_auth">اسم المستخدم أو كلمة المرور خاطئة</string>
|
||||||
|
<string name="error_json">استجابة الخادم غير صالحة</string>
|
||||||
|
<string name="error_req_failed">فشل الطلب</string>
|
||||||
|
<string name="error_invalid_email">بريد إلكتروني غير صالح</string>
|
||||||
|
<string name="error_invalid_project_id">معرف المشروع غير صالح</string>
|
||||||
|
<string name="error_invalid_project_name">عنوان المشروع غير صالح</string>
|
||||||
|
<string name="error_invalid_bill_name">اسم الفاتورة غير صالح</string>
|
||||||
|
<string name="error_invalid_bill_date">تاريخ الفاتورة غير صالح</string>
|
||||||
|
<string name="error_invalid_bill_payer">الدفع مطلوب</string>
|
||||||
|
<string name="error_invalid_bill_owers">الحائزين مطلوبون</string>
|
||||||
|
<string name="error_no_network">لا يوجد اتصال بالشبكة</string>
|
||||||
|
<string name="error_server">خطأ في الخادم</string>
|
||||||
|
<string name="error_io">تم كسر اتصال الخادم</string>
|
||||||
|
<string name="error_share_impossible">لا يمكن مشاركة هذا المشروع</string>
|
||||||
|
<!-- Drawer / Common UI -->
|
||||||
|
<string name="drawer_no_account">الاتصال بحساب Nextcloud</string>
|
||||||
|
<string name="drawer_last_sync">آخر مزامنة: %1$02d:%2$02d</string>
|
||||||
|
<string name="simple_cancel">إلغاء</string>
|
||||||
|
<string name="simple_ok" tools:ignore="ButtonCase,Typos">حسناً</string>
|
||||||
|
<string name="simple_yes">نعم</string>
|
||||||
|
<string name="simple_no">لا</string>
|
||||||
|
<string name="simple_close">أغلق</string>
|
||||||
|
<!-- Settings -->
|
||||||
|
<string name="settings_appearance">المظهر</string>
|
||||||
|
<string name="settings_network">الشبكة</string>
|
||||||
|
<string name="settings_other">اخرى</string>
|
||||||
|
<string name="settings_night_mode">السمة</string>
|
||||||
|
<string name="settings_offline_mode">وضع غير متصل</string>
|
||||||
|
<string name="settings_offline_mode_summary">فقط المزامنة يدوياً.</string>
|
||||||
|
<string name="settings_color_custom">لون مخصص</string>
|
||||||
|
<string name="settings_color_mode">تحديد الألوان</string>
|
||||||
|
<string name="settings_show_archived">إظهار المشاريع المؤرشفة</string>
|
||||||
|
<string name="settings_beta_features">ميزات بيتا</string>
|
||||||
|
<string name="settings_beta_features_summary">تمكين الميزات التجريبية. استخدمها على مسؤوليتك الخاصة.</string>
|
||||||
|
<string name="settings_url_warn_http">تحذير: \"http\" غير آمن. استخدم \"https\".</string>
|
||||||
|
<string name="settings_colorpicker_title">اختر اللون</string>
|
||||||
|
<string name="pref_value_color_system">النظام</string>
|
||||||
|
<string name="pref_value_color_server">السحابة التالية</string>
|
||||||
|
<string name="pref_value_color_manual">دليل</string>
|
||||||
|
<string name="pref_value_theme_light">فاتح</string>
|
||||||
|
<string name="pref_value_theme_dark">داكن</string>
|
||||||
|
<string name="pref_value_theme_system">اتبع النظام</string>
|
||||||
|
<!-- Constants (Do not translate) -->
|
||||||
|
<!-- Enums and Lists -->
|
||||||
|
<string name="repeat_no">لا تكرّر</string>
|
||||||
|
<string name="repeat_day">يومياً</string>
|
||||||
|
<string name="repeat_week">أسبوعيا</string>
|
||||||
|
<string name="repeat_fortnight">نصف شهري</string>
|
||||||
|
<string name="repeat_month">شهريا</string>
|
||||||
|
<string name="repeat_year">سنوي</string>
|
||||||
|
<string name="payment_mode_none">لا</string>
|
||||||
|
<string name="payment_mode_all">الكل</string>
|
||||||
|
<string name="payment_mode_credit_card">بطاقة الائتمان</string>
|
||||||
|
<string name="payment_mode_cash">نقدا</string>
|
||||||
|
<string name="payment_mode_check">تحقق</string>
|
||||||
|
<string name="payment_mode_online">متصل</string>
|
||||||
|
<string name="payment_mode_transfer">نقل</string>
|
||||||
|
<string name="category_none">لا</string>
|
||||||
|
<string name="category_all">الكل</string>
|
||||||
|
<string name="category_all_except_reimbursement">جميع ما عدا رد التكاليف</string>
|
||||||
|
<string name="category_groceries">البقالة</string>
|
||||||
|
<string name="category_leisure">شريط/حفلة</string>
|
||||||
|
<string name="category_rent">إيجار</string>
|
||||||
|
<string name="category_bills">فاتورة</string>
|
||||||
|
<string name="category_excursion">الرحلة/الثقافة</string>
|
||||||
|
<string name="category_health">الصحة</string>
|
||||||
|
<string name="category_shopping">التسوق</string>
|
||||||
|
<string name="category_reimbursement">رد</string>
|
||||||
|
<string name="category_restaurant">مطعم</string>
|
||||||
|
<string name="category_accomodation">السكن</string>
|
||||||
|
<string name="category_transport">النقل</string>
|
||||||
|
<string name="category_sport">الرياضة</string>
|
||||||
|
<!-- Project specific -->
|
||||||
|
<string name="new_project_action">ماذا</string>
|
||||||
|
<string name="new_project_where">أين</string>
|
||||||
|
<string name="where_local">محلي فقط</string>
|
||||||
|
<string name="where_cospend">انفاق التكلفة</string>
|
||||||
|
<string name="where_ihatemoney">IHateMoney</string>
|
||||||
|
<string name="todo_join">الانضمام إلى المشروع الحالي</string>
|
||||||
|
<string name="todo_create">إنشاء مشروع جديد</string>
|
||||||
|
<string name="import_tooltip">استيراد من الملف</string>
|
||||||
|
<string name="choose_account_project_dialog_title">اختر المشروع</string>
|
||||||
|
<string name="choose_account_project_dialog_impossible">لا توجد مشاريع في هذا الحساب.</string>
|
||||||
|
<string name="choose_project_management_action">مشروع</string>
|
||||||
|
<string name="project_added_success">تمت إضافة المشروع بنجاح.</string>
|
||||||
|
<string name="no_projects_text">ليس لديك مشاريع حتى الآن.</string>
|
||||||
|
<string name="configure_account_choice">تكوين حساب Nextcloud</string>
|
||||||
|
<string name="add_project_choice">إضافة مشروع يدوياً</string>
|
||||||
|
<string name="no_members_text">لا يوجد أعضاء في هذا المشروع.</string>
|
||||||
|
<string name="no_bills_text">لا توجد فواتير.</string>
|
||||||
|
<string name="member_already_exists">العضو موجود مسبقا.</string>
|
||||||
|
<string name="activity_dialog_title">المشروع: %1$s</string>
|
||||||
|
<string name="remove_project_confirmation">تمت إزالة المشروع %1$s.</string>
|
||||||
|
<string name="file_saved_success">تم حفظ الملف: %1$s</string>
|
||||||
|
<string name="import_error_header">فشل الاستيراد في الصف %d</string>
|
||||||
|
<string name="import_error_date">تنسيق التاريخ غير صالح في الصف %d</string>
|
||||||
|
<string name="import_error_owers">غير صالح في الصف %d</string>
|
||||||
|
<string name="add_member_dialog_title">إضافة عضو</string>
|
||||||
|
<string name="edit_member_dialog_title">تعديل العضو</string>
|
||||||
|
<string name="member_edit_delete">حذف</string>
|
||||||
|
<string name="project_edition_no_change">لا توجد تغييرات للحفظ.</string>
|
||||||
|
<!-- Settlement -->
|
||||||
|
<string name="center_none">لا شيء (مثالي)</string>
|
||||||
|
<string name="settle_who">من يدفع</string>
|
||||||
|
<string name="settle_to_whom">مَن</string>
|
||||||
|
<string name="settle_how_much">المبلغ</string>
|
||||||
|
<string name="simple_settle_share">مشاركة</string>
|
||||||
|
<string name="simple_create_bills">إنشاء فواتير</string>
|
||||||
|
<string name="settle_bill_what">تسوية</string>
|
||||||
|
<!-- Currencies -->
|
||||||
|
<string name="currency_dialog_title">اختر العملة (%s)</string>
|
||||||
|
<string name="setting_none">لا</string>
|
||||||
|
<string name="setting_all">الكل</string>
|
||||||
|
<string name="currency_saved_success">تم حفظ إعدادات العملة.</string>
|
||||||
|
<string name="main_currency">العملة الرئيسية</string>
|
||||||
|
<!-- Statistics -->
|
||||||
|
<string name="label_bills_suggested">الفئات المقترحة</string>
|
||||||
|
<string name="label_bills_skip">تخطي</string>
|
||||||
|
<string name="stats_date_min">من</string>
|
||||||
|
<string name="stats_date_max">إلى</string>
|
||||||
|
<string name="stats_who">عضو</string>
|
||||||
|
<string name="stats_paid">مدفوع</string>
|
||||||
|
<string name="stats_spent">أنفق</string>
|
||||||
|
<string name="stats_balance">الرصيد</string>
|
||||||
|
<string name="total">المجموع: %1$s</string>
|
||||||
|
<!-- Errors Extra -->
|
||||||
|
<string name="error_project_connect_check">فشل الاتصال: %1$s</string>
|
||||||
|
<string name="error_create_remote_project_helper">فشل الإنشاء: %1$s</string>
|
||||||
|
<string name="error_edit_remote_project_helper">خطأ في تحديث المشروع البعيد: %1$s</string>
|
||||||
|
<string name="remote_project_operation_no_network">الشبكة غير متوفرة للتشغيل عن بعد.</string>
|
||||||
|
<string name="error_scanning_bill_qr_code">فشل تحليل رمز QR.</string>
|
||||||
|
<string name="error_token_mismatch">عدم تطابق رمز المصادقة. الرجاء تسجيل الدخول مرة أخرى.</string>
|
||||||
|
<string name="insufficient_access_level">ليس لديك الصلاحية للقيام بهذا الإجراء.</string>
|
||||||
|
<string name="delete_label_confirmation_title">حذف التسمية</string>
|
||||||
|
<string name="delete_label_confirmation_message">هل أنت متأكد من أنك تريد حذف هذه التسمية؟</string>
|
||||||
|
<!-- About -->
|
||||||
|
<string name="about_version">الإصدار %1$s</string>
|
||||||
|
<string name="about_maintainer_title">المشرف</string>
|
||||||
|
<string name="about_license_title">الترخيص</string>
|
||||||
|
<string name="about_source_title">الكود المصدري</string>
|
||||||
|
<!-- New constants for backward compatibility or shared use -->
|
||||||
|
<string name="share_intent_title">المشروع %1$s</string>
|
||||||
|
<string name="share_chooser_title">مشاركة %1$s</string>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,241 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
<string name="app_name">Cowspent</string>
|
||||||
|
<!-- Actions -->
|
||||||
|
<string name="action_new_bill">New bill</string>
|
||||||
|
<string name="action_add_project">Add project</string>
|
||||||
|
<string name="action_save">Save</string>
|
||||||
|
<string name="action_edit">Edit</string>
|
||||||
|
<string name="action_share">Share</string>
|
||||||
|
<string name="action_search">Search</string>
|
||||||
|
<string name="action_delete">Delete</string>
|
||||||
|
<string name="action_archive">Archive</string>
|
||||||
|
<string name="action_unarchive">Unarchive</string>
|
||||||
|
<string name="action_export">Export</string>
|
||||||
|
<string name="action_stats">Stats</string>
|
||||||
|
<string name="action_settle">Settle</string>
|
||||||
|
<string name="action_scan_qrcode">Scan QR Code</string>
|
||||||
|
<string name="action_settings">Settings</string>
|
||||||
|
<string name="action_label_bills">Label missing categories</string>
|
||||||
|
<string name="action_logout">Logout</string>
|
||||||
|
<string name="action_connect">Connect</string>
|
||||||
|
<string name="action_discard">Discard</string>
|
||||||
|
<string name="action_members">Members</string>
|
||||||
|
<string name="action_labels">Labels</string>
|
||||||
|
<string name="action_currencies">Currencies</string>
|
||||||
|
<!-- Titles -->
|
||||||
|
<string name="title_stats">Statistics</string>
|
||||||
|
<string name="title_edit_project">Edit project</string>
|
||||||
|
<string name="title_label_bills">Label Bills</string>
|
||||||
|
<string name="title_labels">Manage Labels</string>
|
||||||
|
<string name="title_about">About</string>
|
||||||
|
<string name="title_settle">Settle Project</string>
|
||||||
|
<string name="title_share">Share Project</string>
|
||||||
|
<string name="title_add_project">Add Project</string>
|
||||||
|
<string name="title_add_category">Add Category</string>
|
||||||
|
<string name="title_add_payment_mode">Add Payment Mode</string>
|
||||||
|
<string name="title_account">Nextcloud Account</string>
|
||||||
|
<string name="title_share_web">Web link</string>
|
||||||
|
<string name="title_share_qr">Cowspent link</string>
|
||||||
|
<string name="title_confirm">Are you sure?</string>
|
||||||
|
<!-- Labels and Fields -->
|
||||||
|
<string name="label_all_bills">All bills</string>
|
||||||
|
<string name="label_categories">Categories</string>
|
||||||
|
<string name="label_payment_modes">Payment Modes</string>
|
||||||
|
<string name="label_name">Name</string>
|
||||||
|
<string name="label_icon">Icon / Emoji</string>
|
||||||
|
<string name="label_color">Color</string>
|
||||||
|
<string name="label_weight">Weight</string>
|
||||||
|
<string name="label_activated">Activated</string>
|
||||||
|
<string name="label_password">Password</string>
|
||||||
|
<string name="label_email">E-mail</string>
|
||||||
|
<string name="label_url">Server address</string>
|
||||||
|
<string name="label_username">Username</string>
|
||||||
|
<string name="label_comment">Comment</string>
|
||||||
|
<string name="label_what">What?</string>
|
||||||
|
<string name="label_payer">Who paid?</string>
|
||||||
|
<string name="label_owers">For whom?</string>
|
||||||
|
<string name="label_repeat">Repeat every</string>
|
||||||
|
<string name="label_mode">Mode</string>
|
||||||
|
<string name="label_category">Category</string>
|
||||||
|
<string name="label_project_id">Project ID/name</string>
|
||||||
|
<string name="label_project_title">Project title</string>
|
||||||
|
<string name="label_use_sso">Use Nextcloud App Account</string>
|
||||||
|
<!-- Dialogs and Messages -->
|
||||||
|
<string name="dialog_unsaved_changes_title">Unsaved changes</string>
|
||||||
|
<string name="dialog_unsaved_changes_msg">Save changes before leaving?</string>
|
||||||
|
<string name="dialog_confirm_remove_project_msg">The remote project will not be deleted.</string>
|
||||||
|
<string name="dialog_sync_error_title">Sync error</string>
|
||||||
|
<string name="dialog_sync_error_msg">Sync failed for %1$s.\n\n%2$s</string>
|
||||||
|
<string name="dialog_balanced_msg">Expenses are already balanced.</string>
|
||||||
|
<string name="msg_project_added">Project %1$s added</string>
|
||||||
|
<string name="msg_bill_labeled_done">All bills labeled</string>
|
||||||
|
<string name="msg_no_suggestions">No suggestions</string>
|
||||||
|
<string name="msg_auth_warning">Requires Cospend v0.3.4+.</string>
|
||||||
|
<string name="msg_link_copied">Link copied to clipboard</string>
|
||||||
|
<string name="msg_share_qr">Scan QR code or share the link to join.</string>
|
||||||
|
<string name="msg_share_web">Link for web browser access.</string>
|
||||||
|
<string name="msg_share_qr_warn">Share this link with a Cowspent user.</string>
|
||||||
|
<string name="msg_settle_intro">Settlement for %1$s:</string>
|
||||||
|
<string name="msg_settle_sentence">%1$s owes %3$.2f to %2$s</string>
|
||||||
|
<string name="msg_stats_intro">Stats for %1$s:</string>
|
||||||
|
<string name="msg_stats_header">Member (Paid | Spent | Balance)</string>
|
||||||
|
<string name="msg_logged_in_as">Logged in as %1$s</string>
|
||||||
|
<!-- Errors -->
|
||||||
|
<string name="error_generic">Error</string>
|
||||||
|
<string name="error_loading">Loading</string>
|
||||||
|
<string name="error_no_projects">No projects found</string>
|
||||||
|
<string name="error_no_members">No members found</string>
|
||||||
|
<string name="error_no_bills">No bills found</string>
|
||||||
|
<string name="error_no_member">At least one member required</string>
|
||||||
|
<string name="error_maintenance_mode">Server is in maintenance mode</string>
|
||||||
|
<string name="error_400">400 Bad request</string>
|
||||||
|
<string name="error_401">401 Unauthorized</string>
|
||||||
|
<string name="error_403">403 Forbidden</string>
|
||||||
|
<string name="error_404">404 Not Found</string>
|
||||||
|
<string name="error_sync">Sync failed: %1$s</string>
|
||||||
|
<string name="error_invalid_login">Invalid login: %1$s</string>
|
||||||
|
<string name="error_auth">Wrong username or password</string>
|
||||||
|
<string name="error_json">Invalid server response</string>
|
||||||
|
<string name="error_req_failed">Request failed</string>
|
||||||
|
<string name="error_invalid_email">Invalid e-mail</string>
|
||||||
|
<string name="error_invalid_project_id">Invalid project ID</string>
|
||||||
|
<string name="error_invalid_project_name">Invalid project title</string>
|
||||||
|
<string name="error_invalid_bill_name">Invalid bill name</string>
|
||||||
|
<string name="error_invalid_bill_date">Invalid bill date</string>
|
||||||
|
<string name="error_invalid_bill_payer">Payer required</string>
|
||||||
|
<string name="error_invalid_bill_owers">Owers required</string>
|
||||||
|
<string name="error_no_network">No network connection</string>
|
||||||
|
<string name="error_server">Server error</string>
|
||||||
|
<string name="error_io">Server connection broken</string>
|
||||||
|
<string name="error_share_impossible">Cannot share this project</string>
|
||||||
|
<!-- Drawer / Common UI -->
|
||||||
|
<string name="drawer_no_account">Connect to Nextcloud account</string>
|
||||||
|
<string name="drawer_last_sync">Last sync: %1$02d:%2$02d</string>
|
||||||
|
<string name="simple_cancel">Cancel</string>
|
||||||
|
<string name="simple_ok" tools:ignore="ButtonCase,Typos">Ok</string>
|
||||||
|
<string name="simple_yes">Yes</string>
|
||||||
|
<string name="simple_no">No</string>
|
||||||
|
<string name="simple_close">Close</string>
|
||||||
|
<!-- Settings -->
|
||||||
|
<string name="settings_appearance">Appearance</string>
|
||||||
|
<string name="settings_network">Network</string>
|
||||||
|
<string name="settings_other">Other</string>
|
||||||
|
<string name="settings_night_mode">Theme</string>
|
||||||
|
<string name="settings_offline_mode">Offline mode</string>
|
||||||
|
<string name="settings_offline_mode_summary">Only sync manually.</string>
|
||||||
|
<string name="settings_color_custom">Custom color</string>
|
||||||
|
<string name="settings_color_mode">Color Selection</string>
|
||||||
|
<string name="settings_show_archived">Show archived projects</string>
|
||||||
|
<string name="settings_beta_features">Beta Features</string>
|
||||||
|
<string name="settings_beta_features_summary">Enable experimental features. Use at your own risk.</string>
|
||||||
|
<string name="settings_url_warn_http">WARNING: \"http\" is unsafe. Use \"https\".</string>
|
||||||
|
<string name="settings_colorpicker_title">Choose Color</string>
|
||||||
|
<string name="pref_value_color_system">System</string>
|
||||||
|
<string name="pref_value_color_server">Nextcloud</string>
|
||||||
|
<string name="pref_value_color_manual">Manual</string>
|
||||||
|
<string name="pref_value_theme_light">Light</string>
|
||||||
|
<string name="pref_value_theme_dark">Dark</string>
|
||||||
|
<string name="pref_value_theme_system">Follow system</string>
|
||||||
|
<!-- Constants (Do not translate) -->
|
||||||
|
<!-- Enums and Lists -->
|
||||||
|
<string name="repeat_no">No repeat</string>
|
||||||
|
<string name="repeat_day">Daily</string>
|
||||||
|
<string name="repeat_week">Weekly</string>
|
||||||
|
<string name="repeat_fortnight">Fortnightly</string>
|
||||||
|
<string name="repeat_month">Monthly</string>
|
||||||
|
<string name="repeat_year">Yearly</string>
|
||||||
|
<string name="payment_mode_none">None</string>
|
||||||
|
<string name="payment_mode_all">All</string>
|
||||||
|
<string name="payment_mode_credit_card">Credit card</string>
|
||||||
|
<string name="payment_mode_cash">Cash</string>
|
||||||
|
<string name="payment_mode_check">Check</string>
|
||||||
|
<string name="payment_mode_online">Online</string>
|
||||||
|
<string name="payment_mode_transfer">Transfer</string>
|
||||||
|
<string name="category_none">None</string>
|
||||||
|
<string name="category_all">All</string>
|
||||||
|
<string name="category_all_except_reimbursement">All except reimbursement</string>
|
||||||
|
<string name="category_groceries">Grocery</string>
|
||||||
|
<string name="category_leisure">Bar/Party</string>
|
||||||
|
<string name="category_rent">Rent</string>
|
||||||
|
<string name="category_bills">Bill</string>
|
||||||
|
<string name="category_excursion">Excursion/Culture</string>
|
||||||
|
<string name="category_health">Health</string>
|
||||||
|
<string name="category_shopping">Shopping</string>
|
||||||
|
<string name="category_reimbursement">Reimbursement</string>
|
||||||
|
<string name="category_restaurant">Restaurant</string>
|
||||||
|
<string name="category_accomodation">Accommodation</string>
|
||||||
|
<string name="category_transport">Transport</string>
|
||||||
|
<string name="category_sport">Sport</string>
|
||||||
|
<!-- Project specific -->
|
||||||
|
<string name="new_project_action">What</string>
|
||||||
|
<string name="new_project_where">Where</string>
|
||||||
|
<string name="where_local">Local only</string>
|
||||||
|
<string name="where_cospend">Cospend</string>
|
||||||
|
<string name="where_ihatemoney">IHateMoney</string>
|
||||||
|
<string name="todo_join">Join existing project</string>
|
||||||
|
<string name="todo_create">Create new project</string>
|
||||||
|
<string name="import_tooltip">Import from file</string>
|
||||||
|
<string name="choose_account_project_dialog_title">Choose project</string>
|
||||||
|
<string name="choose_account_project_dialog_impossible">No projects found on this account.</string>
|
||||||
|
<string name="choose_project_management_action">Project</string>
|
||||||
|
<string name="project_added_success">Project added successfully.</string>
|
||||||
|
<string name="no_projects_text">You have no projects yet.</string>
|
||||||
|
<string name="configure_account_choice">Configure Nextcloud account</string>
|
||||||
|
<string name="add_project_choice">Add project manually</string>
|
||||||
|
<string name="no_members_text">No members in this project.</string>
|
||||||
|
<string name="no_bills_text">No bills found.</string>
|
||||||
|
<string name="member_already_exists">Member already exists.</string>
|
||||||
|
<string name="activity_dialog_title">Project: %1$s</string>
|
||||||
|
<string name="remove_project_confirmation">Project %1$s removed.</string>
|
||||||
|
<string name="file_saved_success">File saved: %1$s</string>
|
||||||
|
<string name="import_error_header">Import failed at row %d</string>
|
||||||
|
<string name="import_error_date">Invalid date format at row %d</string>
|
||||||
|
<string name="import_error_owers">Invalid owers at row %d</string>
|
||||||
|
<string name="add_member_dialog_title">Add Member</string>
|
||||||
|
<string name="edit_member_dialog_title">Edit Member</string>
|
||||||
|
<string name="member_edit_delete">Delete</string>
|
||||||
|
<string name="project_edition_no_change">No changes to save.</string>
|
||||||
|
<!-- Settlement -->
|
||||||
|
<string name="center_none">None (Optimal)</string>
|
||||||
|
<string name="settle_who">Who pays</string>
|
||||||
|
<string name="settle_to_whom">To whom</string>
|
||||||
|
<string name="settle_how_much">Amount</string>
|
||||||
|
<string name="simple_settle_share">Share</string>
|
||||||
|
<string name="simple_create_bills">Create bills</string>
|
||||||
|
<string name="settle_bill_what">Settlement</string>
|
||||||
|
<!-- Currencies -->
|
||||||
|
<string name="currency_dialog_title">Choose Currency (%s)</string>
|
||||||
|
<string name="setting_none">None</string>
|
||||||
|
<string name="setting_all">All</string>
|
||||||
|
<string name="currency_saved_success">Currency settings saved.</string>
|
||||||
|
<string name="main_currency">Main Currency</string>
|
||||||
|
<!-- Statistics -->
|
||||||
|
<string name="label_bills_suggested">Suggested Categories</string>
|
||||||
|
<string name="label_bills_skip">Skip</string>
|
||||||
|
<string name="stats_date_min">From</string>
|
||||||
|
<string name="stats_date_max">To</string>
|
||||||
|
<string name="stats_who">Member</string>
|
||||||
|
<string name="stats_paid">Paid</string>
|
||||||
|
<string name="stats_spent">Spent</string>
|
||||||
|
<string name="stats_balance">Balance</string>
|
||||||
|
<string name="total">Total: %1$s</string>
|
||||||
|
<!-- Errors Extra -->
|
||||||
|
<string name="error_project_connect_check">Connection failed: %1$s</string>
|
||||||
|
<string name="error_create_remote_project_helper">Creation failed: %1$s</string>
|
||||||
|
<string name="error_edit_remote_project_helper">Error updating remote project: %1$s</string>
|
||||||
|
<string name="remote_project_operation_no_network">Network unavailable for remote operation.</string>
|
||||||
|
<string name="error_scanning_bill_qr_code">Failed to parse QR code.</string>
|
||||||
|
<string name="error_token_mismatch">Authentication token mismatch. Please log in again.</string>
|
||||||
|
<string name="insufficient_access_level">You don\'t have permission to perform this action.</string>
|
||||||
|
<string name="delete_label_confirmation_title">Delete Label</string>
|
||||||
|
<string name="delete_label_confirmation_message">Are you sure you want to delete this label?</string>
|
||||||
|
<!-- About -->
|
||||||
|
<string name="about_version">Version %1$s</string>
|
||||||
|
<string name="about_maintainer_title">Maintainer</string>
|
||||||
|
<string name="about_license_title">License</string>
|
||||||
|
<string name="about_source_title">Source code</string>
|
||||||
|
<!-- New constants for backward compatibility or shared use -->
|
||||||
|
<string name="share_intent_title">Project %1$s</string>
|
||||||
|
<string name="share_chooser_title">Share %1$s</string>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,241 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
<string name="app_name">Vyčerpané krávy</string>
|
||||||
|
<!-- Actions -->
|
||||||
|
<string name="action_new_bill">Nový účet</string>
|
||||||
|
<string name="action_add_project">Přidat projekt</string>
|
||||||
|
<string name="action_save">Uložit</string>
|
||||||
|
<string name="action_edit">Upravit</string>
|
||||||
|
<string name="action_share">Sdílet</string>
|
||||||
|
<string name="action_search">Hledat</string>
|
||||||
|
<string name="action_delete">Vymazat</string>
|
||||||
|
<string name="action_archive">Archiv</string>
|
||||||
|
<string name="action_unarchive">Zrušit archiv</string>
|
||||||
|
<string name="action_export">Exportovat</string>
|
||||||
|
<string name="action_stats">Statistiky</string>
|
||||||
|
<string name="action_settle">Sada</string>
|
||||||
|
<string name="action_scan_qrcode">Scan QR Code</string>
|
||||||
|
<string name="action_settings">Nastavení</string>
|
||||||
|
<string name="action_label_bills">Chybí kategorie</string>
|
||||||
|
<string name="action_logout">Odhlásit se</string>
|
||||||
|
<string name="action_connect">Připojit</string>
|
||||||
|
<string name="action_discard">Zahodit</string>
|
||||||
|
<string name="action_members">Členové</string>
|
||||||
|
<string name="action_labels">Popisky</string>
|
||||||
|
<string name="action_currencies">Měny</string>
|
||||||
|
<!-- Titles -->
|
||||||
|
<string name="title_stats">Statistiky</string>
|
||||||
|
<string name="title_edit_project">Upravit projekt</string>
|
||||||
|
<string name="title_label_bills">Etiketové účty</string>
|
||||||
|
<string name="title_labels">Spravovat štítky</string>
|
||||||
|
<string name="title_about">O aplikaci</string>
|
||||||
|
<string name="title_settle">Projekt settle</string>
|
||||||
|
<string name="title_share">Sdílet projekt</string>
|
||||||
|
<string name="title_add_project">Přidat projekt</string>
|
||||||
|
<string name="title_add_category">Přidat kategorii</string>
|
||||||
|
<string name="title_add_payment_mode">Přidat platební režim</string>
|
||||||
|
<string name="title_account">Nextcloud účet</string>
|
||||||
|
<string name="title_share_web">Webový odkaz</string>
|
||||||
|
<string name="title_share_qr">Cowspent link</string>
|
||||||
|
<string name="title_confirm">Jste si jisti?</string>
|
||||||
|
<!-- Labels and Fields -->
|
||||||
|
<string name="label_all_bills">Všechny účty</string>
|
||||||
|
<string name="label_categories">Kategorie</string>
|
||||||
|
<string name="label_payment_modes">Platební režimy</string>
|
||||||
|
<string name="label_name">Název</string>
|
||||||
|
<string name="label_icon">Icon / Emoji</string>
|
||||||
|
<string name="label_color">Barva</string>
|
||||||
|
<string name="label_weight">Hmotnost</string>
|
||||||
|
<string name="label_activated">Aktivováno</string>
|
||||||
|
<string name="label_password">Heslo</string>
|
||||||
|
<string name="label_email">E-mail</string>
|
||||||
|
<string name="label_url">Adresa serveru</string>
|
||||||
|
<string name="label_username">Uživatelské jméno</string>
|
||||||
|
<string name="label_comment">Komentář</string>
|
||||||
|
<string name="label_what">Co to znamená?</string>
|
||||||
|
<string name="label_payer">Kdo zaplatil?</string>
|
||||||
|
<string name="label_owers">Pro koho?</string>
|
||||||
|
<string name="label_repeat">Opakovat každý</string>
|
||||||
|
<string name="label_mode">Režim</string>
|
||||||
|
<string name="label_category">Kategorie</string>
|
||||||
|
<string name="label_project_id">ID / název projektu</string>
|
||||||
|
<string name="label_project_title">Název projektu</string>
|
||||||
|
<string name="label_use_sso">Použít Nextcloud účet aplikace</string>
|
||||||
|
<!-- Dialogs and Messages -->
|
||||||
|
<string name="dialog_unsaved_changes_title">Neuložené změny</string>
|
||||||
|
<string name="dialog_unsaved_changes_msg">Uložit změny před odchodem?</string>
|
||||||
|
<string name="dialog_confirm_remove_project_msg">Vzdálený projekt nebude odstraněn.</string>
|
||||||
|
<string name="dialog_sync_error_title">Chyba synchronizace</string>
|
||||||
|
<string name="dialog_sync_error_msg">Synchronizace se nezdařila pro %1$s.\n\n%2$s</string>
|
||||||
|
<string name="dialog_balanced_msg">Výdaje jsou již vyrovnané.</string>
|
||||||
|
<string name="msg_project_added">Projekt %1$s byl přidán</string>
|
||||||
|
<string name="msg_bill_labeled_done">Všechny účty jsou označeny</string>
|
||||||
|
<string name="msg_no_suggestions">Žádné návrhy</string>
|
||||||
|
<string name="msg_auth_warning">Vyžaduje Cospend v0.3.4+.</string>
|
||||||
|
<string name="msg_link_copied">Odkaz zkopírován do schránky</string>
|
||||||
|
<string name="msg_share_qr">Naskenujte QR kód nebo sdílejte odkaz pro připojení.</string>
|
||||||
|
<string name="msg_share_web">Odkaz pro přístup k webovému prohlížeči.</string>
|
||||||
|
<string name="msg_share_qr_warn">Sdílejte tento odkaz s Cowspent uživatelem.</string>
|
||||||
|
<string name="msg_settle_intro">Vyrovnání pro %1$s:</string>
|
||||||
|
<string name="msg_settle_sentence">%1$s dluží %3$.2f %2$s</string>
|
||||||
|
<string name="msg_stats_intro">Statistiky pro %1$s:</string>
|
||||||
|
<string name="msg_stats_header">Člen (Placené | utraceno | Zůstatek)</string>
|
||||||
|
<string name="msg_logged_in_as">Přihlášen jako %1$s</string>
|
||||||
|
<!-- Errors -->
|
||||||
|
<string name="error_generic">Chyba</string>
|
||||||
|
<string name="error_loading">Načítání</string>
|
||||||
|
<string name="error_no_projects">Nebyly nalezeny žádné projekty</string>
|
||||||
|
<string name="error_no_members">Nenalezeni žádní členové</string>
|
||||||
|
<string name="error_no_bills">Nebyly nalezeny žádné účty</string>
|
||||||
|
<string name="error_no_member">Požadován alespoň jeden člen</string>
|
||||||
|
<string name="error_maintenance_mode">Server je v režimu údržby</string>
|
||||||
|
<string name="error_400">400 špatný požadavek</string>
|
||||||
|
<string name="error_401">401 Neautorizováno</string>
|
||||||
|
<string name="error_403">403 zakázáno</string>
|
||||||
|
<string name="error_404">404 Nenalezeno</string>
|
||||||
|
<string name="error_sync">Synchronizace selhala: %1$s</string>
|
||||||
|
<string name="error_invalid_login">Neplatné přihlášení: %1$s</string>
|
||||||
|
<string name="error_auth">Nesprávné uživatelské jméno nebo heslo</string>
|
||||||
|
<string name="error_json">Neplatná odpověď serveru</string>
|
||||||
|
<string name="error_req_failed">Požadavek se nezdařil</string>
|
||||||
|
<string name="error_invalid_email">Neplatný e-mail</string>
|
||||||
|
<string name="error_invalid_project_id">Neplatné ID projektu</string>
|
||||||
|
<string name="error_invalid_project_name">Neplatný název projektu</string>
|
||||||
|
<string name="error_invalid_bill_name">Neplatné jméno účtu</string>
|
||||||
|
<string name="error_invalid_bill_date">Neplatné datum faktury</string>
|
||||||
|
<string name="error_invalid_bill_payer">Je vyžadován plátce</string>
|
||||||
|
<string name="error_invalid_bill_owers">Je vyžadován majitel</string>
|
||||||
|
<string name="error_no_network">Žádné připojení k síti</string>
|
||||||
|
<string name="error_server">Chyba serveru</string>
|
||||||
|
<string name="error_io">Připojení k serveru bylo přerušeno</string>
|
||||||
|
<string name="error_share_impossible">Nelze sdílet tento projekt</string>
|
||||||
|
<!-- Drawer / Common UI -->
|
||||||
|
<string name="drawer_no_account">Připojit k Nextcloud účtu</string>
|
||||||
|
<string name="drawer_last_sync">Poslední synchronizace: %1$02d:%2$02d</string>
|
||||||
|
<string name="simple_cancel">Zrušit</string>
|
||||||
|
<string name="simple_ok" tools:ignore="ButtonCase,Typos">Ok</string>
|
||||||
|
<string name="simple_yes">Ano</string>
|
||||||
|
<string name="simple_no">Ne</string>
|
||||||
|
<string name="simple_close">Zavřít</string>
|
||||||
|
<!-- Settings -->
|
||||||
|
<string name="settings_appearance">Vzhled</string>
|
||||||
|
<string name="settings_network">Síť</string>
|
||||||
|
<string name="settings_other">Ostatní</string>
|
||||||
|
<string name="settings_night_mode">Téma</string>
|
||||||
|
<string name="settings_offline_mode">Offline režim</string>
|
||||||
|
<string name="settings_offline_mode_summary">Synchronizovat pouze ručně.</string>
|
||||||
|
<string name="settings_color_custom">Vlastní barva</string>
|
||||||
|
<string name="settings_color_mode">Výběr barvy</string>
|
||||||
|
<string name="settings_show_archived">Zobrazit archivované projekty</string>
|
||||||
|
<string name="settings_beta_features">Beta funkce</string>
|
||||||
|
<string name="settings_beta_features_summary">Povolit experimentální funkce. Používejte na vlastní riziko.</string>
|
||||||
|
<string name="settings_url_warn_http">VAROVÁNÍ: \"http\" není bezpečné. Použijte \"https://\".</string>
|
||||||
|
<string name="settings_colorpicker_title">Vybrat barvu</string>
|
||||||
|
<string name="pref_value_color_system">Systém</string>
|
||||||
|
<string name="pref_value_color_server">Nextcloud</string>
|
||||||
|
<string name="pref_value_color_manual">Ruční</string>
|
||||||
|
<string name="pref_value_theme_light">Světlý</string>
|
||||||
|
<string name="pref_value_theme_dark">Tmavý</string>
|
||||||
|
<string name="pref_value_theme_system">Sledovat systém</string>
|
||||||
|
<!-- Constants (Do not translate) -->
|
||||||
|
<!-- Enums and Lists -->
|
||||||
|
<string name="repeat_no">Žádné opakování</string>
|
||||||
|
<string name="repeat_day">Denní</string>
|
||||||
|
<string name="repeat_week">Týdenní</string>
|
||||||
|
<string name="repeat_fortnight">Čtvrtletní</string>
|
||||||
|
<string name="repeat_month">Měsíčně</string>
|
||||||
|
<string name="repeat_year">Roční</string>
|
||||||
|
<string name="payment_mode_none">Nic</string>
|
||||||
|
<string name="payment_mode_all">Vše</string>
|
||||||
|
<string name="payment_mode_credit_card">Kreditní karta</string>
|
||||||
|
<string name="payment_mode_cash">Hotovost</string>
|
||||||
|
<string name="payment_mode_check">Zkontrolovat</string>
|
||||||
|
<string name="payment_mode_online">Online</string>
|
||||||
|
<string name="payment_mode_transfer">Převod</string>
|
||||||
|
<string name="category_none">Nic</string>
|
||||||
|
<string name="category_all">Vše</string>
|
||||||
|
<string name="category_all_except_reimbursement">Všechny kromě úhrady</string>
|
||||||
|
<string name="category_groceries">Slepičí</string>
|
||||||
|
<string name="category_leisure">Čárka/Party</string>
|
||||||
|
<string name="category_rent">Pronájem</string>
|
||||||
|
<string name="category_bills">Faktura</string>
|
||||||
|
<string name="category_excursion">Výstup/kultura</string>
|
||||||
|
<string name="category_health">Zdraví</string>
|
||||||
|
<string name="category_shopping">Nakupování</string>
|
||||||
|
<string name="category_reimbursement">Náhrada</string>
|
||||||
|
<string name="category_restaurant">Restaurace</string>
|
||||||
|
<string name="category_accomodation">Ubytování</string>
|
||||||
|
<string name="category_transport">Přeprava</string>
|
||||||
|
<string name="category_sport">Sport</string>
|
||||||
|
<!-- Project specific -->
|
||||||
|
<string name="new_project_action">Co</string>
|
||||||
|
<string name="new_project_where">Kde</string>
|
||||||
|
<string name="where_local">Pouze místní</string>
|
||||||
|
<string name="where_cospend">Koutracení</string>
|
||||||
|
<string name="where_ihatemoney">IhateMoney</string>
|
||||||
|
<string name="todo_join">Připojit se k existujícímu projektu</string>
|
||||||
|
<string name="todo_create">Vytvořit nový projekt</string>
|
||||||
|
<string name="import_tooltip">Importovat ze souboru</string>
|
||||||
|
<string name="choose_account_project_dialog_title">Vybrat projekt</string>
|
||||||
|
<string name="choose_account_project_dialog_impossible">Na tomto účtu nebyly nalezeny žádné projekty.</string>
|
||||||
|
<string name="choose_project_management_action">Projekt</string>
|
||||||
|
<string name="project_added_success">Projekt byl úspěšně přidán.</string>
|
||||||
|
<string name="no_projects_text">Zatím nemáte žádné projekty.</string>
|
||||||
|
<string name="configure_account_choice">Konfigurovat Nextcloud účet</string>
|
||||||
|
<string name="add_project_choice">Přidat projekt ručně</string>
|
||||||
|
<string name="no_members_text">Žádní členové tohoto projektu.</string>
|
||||||
|
<string name="no_bills_text">Nebyly nalezeny žádné účty.</string>
|
||||||
|
<string name="member_already_exists">Člen již existuje.</string>
|
||||||
|
<string name="activity_dialog_title">Projekt: %1$s</string>
|
||||||
|
<string name="remove_project_confirmation">Projekt %1$s byl odstraněn.</string>
|
||||||
|
<string name="file_saved_success">Soubor uložen: %1$s</string>
|
||||||
|
<string name="import_error_header">Import se nezdařil na řádku %d</string>
|
||||||
|
<string name="import_error_date">Neplatný formát data na řádku %d</string>
|
||||||
|
<string name="import_error_owers">Neplatné dlužníky na řádku %d</string>
|
||||||
|
<string name="add_member_dialog_title">Přidat člena</string>
|
||||||
|
<string name="edit_member_dialog_title">Upravit člena</string>
|
||||||
|
<string name="member_edit_delete">Vymazat</string>
|
||||||
|
<string name="project_edition_no_change">Žádné změny k uložení.</string>
|
||||||
|
<!-- Settlement -->
|
||||||
|
<string name="center_none">Žádný (optimální)</string>
|
||||||
|
<string name="settle_who">Kdo zaplatí</string>
|
||||||
|
<string name="settle_to_whom">komu</string>
|
||||||
|
<string name="settle_how_much">Částka, která má být získána</string>
|
||||||
|
<string name="simple_settle_share">Sdílet</string>
|
||||||
|
<string name="simple_create_bills">Vytvořit účty</string>
|
||||||
|
<string name="settle_bill_what">Vypořádání</string>
|
||||||
|
<!-- Currencies -->
|
||||||
|
<string name="currency_dialog_title">Vybrat měnu (%s)</string>
|
||||||
|
<string name="setting_none">Nic</string>
|
||||||
|
<string name="setting_all">Vše</string>
|
||||||
|
<string name="currency_saved_success">Nastavení měny uloženo.</string>
|
||||||
|
<string name="main_currency">Hlavní měna</string>
|
||||||
|
<!-- Statistics -->
|
||||||
|
<string name="label_bills_suggested">Navrhované kategorie</string>
|
||||||
|
<string name="label_bills_skip">Přeskočit</string>
|
||||||
|
<string name="stats_date_min">Od</string>
|
||||||
|
<string name="stats_date_max">Komu</string>
|
||||||
|
<string name="stats_who">Člen</string>
|
||||||
|
<string name="stats_paid">Zaplaceno</string>
|
||||||
|
<string name="stats_spent">Utraceno</string>
|
||||||
|
<string name="stats_balance">Zůstatek</string>
|
||||||
|
<string name="total">Celkem: %1$s</string>
|
||||||
|
<!-- Errors Extra -->
|
||||||
|
<string name="error_project_connect_check">Připojení se nezdařilo: %1$s</string>
|
||||||
|
<string name="error_create_remote_project_helper">Vytvoření selhalo: %1$s</string>
|
||||||
|
<string name="error_edit_remote_project_helper">Chyba při aktualizaci vzdáleného projektu: %1$s</string>
|
||||||
|
<string name="remote_project_operation_no_network">Síť není k dispozici pro vzdálenou operaci.</string>
|
||||||
|
<string name="error_scanning_bill_qr_code">Nepodařilo se zpracovat QR kód.</string>
|
||||||
|
<string name="error_token_mismatch">Ověřovací token se neshoduje. Přihlaste se znovu.</string>
|
||||||
|
<string name="insufficient_access_level">Nemáte oprávnění k provedení této akce.</string>
|
||||||
|
<string name="delete_label_confirmation_title">Odstranit štítek</string>
|
||||||
|
<string name="delete_label_confirmation_message">Jste si jisti, že chcete odstranit tento štítek?</string>
|
||||||
|
<!-- About -->
|
||||||
|
<string name="about_version">Verze %1$s</string>
|
||||||
|
<string name="about_maintainer_title">Správce</string>
|
||||||
|
<string name="about_license_title">Licence</string>
|
||||||
|
<string name="about_source_title">Zdrojový kód</string>
|
||||||
|
<!-- New constants for backward compatibility or shared use -->
|
||||||
|
<string name="share_intent_title">Projekt %1$s</string>
|
||||||
|
<string name="share_chooser_title">Sdílet %1$s</string>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,241 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
<string name="app_name">Kowbrugt</string>
|
||||||
|
<!-- Actions -->
|
||||||
|
<string name="action_new_bill">Ny regning</string>
|
||||||
|
<string name="action_add_project">Tilføj projekt</string>
|
||||||
|
<string name="action_save">Gem</string>
|
||||||
|
<string name="action_edit">Rediger</string>
|
||||||
|
<string name="action_share">Del</string>
|
||||||
|
<string name="action_search">Søg</string>
|
||||||
|
<string name="action_delete">Slet</string>
|
||||||
|
<string name="action_archive">Arkiv</string>
|
||||||
|
<string name="action_unarchive">Fjern Arkiv</string>
|
||||||
|
<string name="action_export">Eksporter</string>
|
||||||
|
<string name="action_stats">Statistik</string>
|
||||||
|
<string name="action_settle">Afregn</string>
|
||||||
|
<string name="action_scan_qrcode">Scan QR Code</string>
|
||||||
|
<string name="action_settings">Indstillinger</string>
|
||||||
|
<string name="action_label_bills">Etiket mangler kategorier</string>
|
||||||
|
<string name="action_logout">Log Ud</string>
|
||||||
|
<string name="action_connect">Forbind</string>
|
||||||
|
<string name="action_discard">Kassér</string>
|
||||||
|
<string name="action_members">Medlemmer</string>
|
||||||
|
<string name="action_labels">Etiketter</string>
|
||||||
|
<string name="action_currencies">Valutaer</string>
|
||||||
|
<!-- Titles -->
|
||||||
|
<string name="title_stats">Statistik</string>
|
||||||
|
<string name="title_edit_project">Rediger projekt</string>
|
||||||
|
<string name="title_label_bills">Etiketregninger</string>
|
||||||
|
<string name="title_labels">Administrer Etiketter</string>
|
||||||
|
<string name="title_about">Om</string>
|
||||||
|
<string name="title_settle">Afregn Projekt</string>
|
||||||
|
<string name="title_share">Del Projekt</string>
|
||||||
|
<string name="title_add_project">Tilføj Projekt</string>
|
||||||
|
<string name="title_add_category">Tilføj Kategori</string>
|
||||||
|
<string name="title_add_payment_mode">Tilføj Betalingstilstand</string>
|
||||||
|
<string name="title_account">Nextcloud Konto</string>
|
||||||
|
<string name="title_share_web">Weblink</string>
|
||||||
|
<string name="title_share_qr">Cowspent link</string>
|
||||||
|
<string name="title_confirm">Er du sikker?</string>
|
||||||
|
<!-- Labels and Fields -->
|
||||||
|
<string name="label_all_bills">Alle regninger</string>
|
||||||
|
<string name="label_categories">Kategorier</string>
|
||||||
|
<string name="label_payment_modes">Betalingstilstande</string>
|
||||||
|
<string name="label_name">Navn</string>
|
||||||
|
<string name="label_icon">Icon / Emoji</string>
|
||||||
|
<string name="label_color">Farve</string>
|
||||||
|
<string name="label_weight">Vægt</string>
|
||||||
|
<string name="label_activated">Aktiveret</string>
|
||||||
|
<string name="label_password">Adgangskode</string>
|
||||||
|
<string name="label_email">E-mail</string>
|
||||||
|
<string name="label_url">Server adresse</string>
|
||||||
|
<string name="label_username">Brugernavn</string>
|
||||||
|
<string name="label_comment">Kommentar</string>
|
||||||
|
<string name="label_what">Hvad?</string>
|
||||||
|
<string name="label_payer">Hvem betalte?</string>
|
||||||
|
<string name="label_owers">For hvem?</string>
|
||||||
|
<string name="label_repeat">Gentag hver</string>
|
||||||
|
<string name="label_mode">Tilstand</string>
|
||||||
|
<string name="label_category">Kategori</string>
|
||||||
|
<string name="label_project_id">Projekt ID/navn</string>
|
||||||
|
<string name="label_project_title">Projektets titel</string>
|
||||||
|
<string name="label_use_sso">Brug Nextcloud App-konto</string>
|
||||||
|
<!-- Dialogs and Messages -->
|
||||||
|
<string name="dialog_unsaved_changes_title">Ikke-gemte ændringer</string>
|
||||||
|
<string name="dialog_unsaved_changes_msg">Gem ændringer før afrejsen?</string>
|
||||||
|
<string name="dialog_confirm_remove_project_msg">Fjernprojektet vil ikke blive slettet.</string>
|
||||||
|
<string name="dialog_sync_error_title">Synkronisering fejl</string>
|
||||||
|
<string name="dialog_sync_error_msg">Synkronisering mislykkedes for %1$s.\n\n%2$s</string>
|
||||||
|
<string name="dialog_balanced_msg">Udgifterne er allerede afbalancerede.</string>
|
||||||
|
<string name="msg_project_added">Projekt %1$s tilføjet</string>
|
||||||
|
<string name="msg_bill_labeled_done">Alle regninger mærket</string>
|
||||||
|
<string name="msg_no_suggestions">Ingen forslag</string>
|
||||||
|
<string name="msg_auth_warning">Kræver Cospend v0.3.4+.</string>
|
||||||
|
<string name="msg_link_copied">Link kopieret til udklipsholder</string>
|
||||||
|
<string name="msg_share_qr">Scan QR-kode eller del linket for at deltage.</string>
|
||||||
|
<string name="msg_share_web">Link til webbrowser-adgang.</string>
|
||||||
|
<string name="msg_share_qr_warn">Del dette link med en Cowspent bruger.</string>
|
||||||
|
<string name="msg_settle_intro">Udligning for %1$s:</string>
|
||||||
|
<string name="msg_settle_sentence">%1$s skylder %3$.2f til %2$s</string>
|
||||||
|
<string name="msg_stats_intro">Statistik for %1$s:</string>
|
||||||
|
<string name="msg_stats_header">Medlem (Betalt - Spent - Balance)</string>
|
||||||
|
<string name="msg_logged_in_as">Logget ind som %1$s</string>
|
||||||
|
<!-- Errors -->
|
||||||
|
<string name="error_generic">Fejl</string>
|
||||||
|
<string name="error_loading">Indlæser</string>
|
||||||
|
<string name="error_no_projects">Ingen projekter fundet</string>
|
||||||
|
<string name="error_no_members">Ingen medlemmer fundet</string>
|
||||||
|
<string name="error_no_bills">Ingen regninger fundet</string>
|
||||||
|
<string name="error_no_member">Mindst et medlem kræves</string>
|
||||||
|
<string name="error_maintenance_mode">Serveren er i vedligeholdelsestilstand</string>
|
||||||
|
<string name="error_400">400 Dårlig forespørgsel</string>
|
||||||
|
<string name="error_401">401 Uautoriseret</string>
|
||||||
|
<string name="error_403">403 Forbudt</string>
|
||||||
|
<string name="error_404">404 Ikke Fundet</string>
|
||||||
|
<string name="error_sync">Synkronisering mislykkedes: %1$s</string>
|
||||||
|
<string name="error_invalid_login">Ugyldigt login: %1$s</string>
|
||||||
|
<string name="error_auth">Forkert brugernavn eller adgangskode</string>
|
||||||
|
<string name="error_json">Ugyldigt serversvar</string>
|
||||||
|
<string name="error_req_failed">Anmodning mislykkedes</string>
|
||||||
|
<string name="error_invalid_email">Ugyldig e-mail</string>
|
||||||
|
<string name="error_invalid_project_id">Ugyldigt projekt ID</string>
|
||||||
|
<string name="error_invalid_project_name">Ugyldig projekttitel</string>
|
||||||
|
<string name="error_invalid_bill_name">Ugyldigt regnings navn</string>
|
||||||
|
<string name="error_invalid_bill_date">Ugyldig faktureringsdato</string>
|
||||||
|
<string name="error_invalid_bill_payer">Betaler påkrævet</string>
|
||||||
|
<string name="error_invalid_bill_owers">Ejere påkrævet</string>
|
||||||
|
<string name="error_no_network">Ingen netværksforbindelse</string>
|
||||||
|
<string name="error_server">Server fejl</string>
|
||||||
|
<string name="error_io">Server forbindelse ødelagt</string>
|
||||||
|
<string name="error_share_impossible">Kan ikke dele dette projekt</string>
|
||||||
|
<!-- Drawer / Common UI -->
|
||||||
|
<string name="drawer_no_account">Opret forbindelse til Nextcloud konto</string>
|
||||||
|
<string name="drawer_last_sync">Seneste synkronisering: %1$02d:%2$02d</string>
|
||||||
|
<string name="simple_cancel">Annuller</string>
|
||||||
|
<string name="simple_ok" tools:ignore="ButtonCase,Typos">OK</string>
|
||||||
|
<string name="simple_yes">Ja</string>
|
||||||
|
<string name="simple_no">Nej</string>
|
||||||
|
<string name="simple_close">Luk</string>
|
||||||
|
<!-- Settings -->
|
||||||
|
<string name="settings_appearance">Udseende</string>
|
||||||
|
<string name="settings_network">Netværk</string>
|
||||||
|
<string name="settings_other">Andet</string>
|
||||||
|
<string name="settings_night_mode">Tema</string>
|
||||||
|
<string name="settings_offline_mode">Offline tilstand</string>
|
||||||
|
<string name="settings_offline_mode_summary">Synkroniser kun manuelt.</string>
|
||||||
|
<string name="settings_color_custom">Brugerdefineret farve</string>
|
||||||
|
<string name="settings_color_mode">Valg Af Farver</string>
|
||||||
|
<string name="settings_show_archived">Vis arkiverede projekter</string>
|
||||||
|
<string name="settings_beta_features">Beta Funktioner</string>
|
||||||
|
<string name="settings_beta_features_summary">Aktiver eksperimentelle funktioner. Brug på egen risiko.</string>
|
||||||
|
<string name="settings_url_warn_http">ADVARSEL: \"http\" er usikkert. Brug \"https\".</string>
|
||||||
|
<string name="settings_colorpicker_title">Vælg Farve</string>
|
||||||
|
<string name="pref_value_color_system">System</string>
|
||||||
|
<string name="pref_value_color_server">Nextcloud</string>
|
||||||
|
<string name="pref_value_color_manual">Manuelt</string>
|
||||||
|
<string name="pref_value_theme_light">Lys</string>
|
||||||
|
<string name="pref_value_theme_dark">Mørk</string>
|
||||||
|
<string name="pref_value_theme_system">Følg system</string>
|
||||||
|
<!-- Constants (Do not translate) -->
|
||||||
|
<!-- Enums and Lists -->
|
||||||
|
<string name="repeat_no">Ingen gentagelse</string>
|
||||||
|
<string name="repeat_day">Dagligt</string>
|
||||||
|
<string name="repeat_week">Ugentlig</string>
|
||||||
|
<string name="repeat_fortnight">Fortnightly</string>
|
||||||
|
<string name="repeat_month">Månedligt</string>
|
||||||
|
<string name="repeat_year">Årligt</string>
|
||||||
|
<string name="payment_mode_none">Ingen</string>
|
||||||
|
<string name="payment_mode_all">Alle</string>
|
||||||
|
<string name="payment_mode_credit_card">Kreditkort</string>
|
||||||
|
<string name="payment_mode_cash">Kontant</string>
|
||||||
|
<string name="payment_mode_check">Tjek</string>
|
||||||
|
<string name="payment_mode_online">Online</string>
|
||||||
|
<string name="payment_mode_transfer">Overfør</string>
|
||||||
|
<string name="category_none">Ingen</string>
|
||||||
|
<string name="category_all">Alle</string>
|
||||||
|
<string name="category_all_except_reimbursement">Alle undtagen refusion</string>
|
||||||
|
<string name="category_groceries">Køller</string>
|
||||||
|
<string name="category_leisure">Bar/Party</string>
|
||||||
|
<string name="category_rent">Leje</string>
|
||||||
|
<string name="category_bills">Regning</string>
|
||||||
|
<string name="category_excursion">Udflugter/Kultur</string>
|
||||||
|
<string name="category_health">Sundhed</string>
|
||||||
|
<string name="category_shopping">Shopping</string>
|
||||||
|
<string name="category_reimbursement">Tilbagebetaling</string>
|
||||||
|
<string name="category_restaurant">Restaurant</string>
|
||||||
|
<string name="category_accomodation">Overnatning</string>
|
||||||
|
<string name="category_transport">Transport</string>
|
||||||
|
<string name="category_sport">Sport</string>
|
||||||
|
<!-- Project specific -->
|
||||||
|
<string name="new_project_action">Hvad</string>
|
||||||
|
<string name="new_project_where">Hvor</string>
|
||||||
|
<string name="where_local">Kun lokalt</string>
|
||||||
|
<string name="where_cospend">Cospendér</string>
|
||||||
|
<string name="where_ihatemoney">IHateMoney</string>
|
||||||
|
<string name="todo_join">Deltag i eksisterende projekt</string>
|
||||||
|
<string name="todo_create">Opret nyt projekt</string>
|
||||||
|
<string name="import_tooltip">Importer fra fil</string>
|
||||||
|
<string name="choose_account_project_dialog_title">Vælg projekt</string>
|
||||||
|
<string name="choose_account_project_dialog_impossible">Ingen projekter fundet på denne konto.</string>
|
||||||
|
<string name="choose_project_management_action">Projekt</string>
|
||||||
|
<string name="project_added_success">Projekt tilføjet.</string>
|
||||||
|
<string name="no_projects_text">Du har endnu ingen projekter.</string>
|
||||||
|
<string name="configure_account_choice">Konfigurer Nextcloud konto</string>
|
||||||
|
<string name="add_project_choice">Tilføj projekt manuelt</string>
|
||||||
|
<string name="no_members_text">Ingen medlemmer i dette projekt.</string>
|
||||||
|
<string name="no_bills_text">Ingen regninger fundet.</string>
|
||||||
|
<string name="member_already_exists">Medlemmet findes allerede.</string>
|
||||||
|
<string name="activity_dialog_title">Projekt: %1$s</string>
|
||||||
|
<string name="remove_project_confirmation">Projekt %1$s fjernet.</string>
|
||||||
|
<string name="file_saved_success">Fil gemt: %1$s</string>
|
||||||
|
<string name="import_error_header">Import mislykkedes ved række %d</string>
|
||||||
|
<string name="import_error_date">Ugyldigt datoformat ved række %d</string>
|
||||||
|
<string name="import_error_owers">Ugyldige ejere ved række %d</string>
|
||||||
|
<string name="add_member_dialog_title">Tilføj Medlem</string>
|
||||||
|
<string name="edit_member_dialog_title">Rediger Medlem</string>
|
||||||
|
<string name="member_edit_delete">Slet</string>
|
||||||
|
<string name="project_edition_no_change">Ingen ændringer at gemme.</string>
|
||||||
|
<!-- Settlement -->
|
||||||
|
<string name="center_none">Ingen (Optimal)</string>
|
||||||
|
<string name="settle_who">Hvem betaler</string>
|
||||||
|
<string name="settle_to_whom">Til hvem</string>
|
||||||
|
<string name="settle_how_much">Beløb</string>
|
||||||
|
<string name="simple_settle_share">Del</string>
|
||||||
|
<string name="simple_create_bills">Opret regninger</string>
|
||||||
|
<string name="settle_bill_what">Afregning</string>
|
||||||
|
<!-- Currencies -->
|
||||||
|
<string name="currency_dialog_title">Vælg Valuta (%s)</string>
|
||||||
|
<string name="setting_none">Ingen</string>
|
||||||
|
<string name="setting_all">Alle</string>
|
||||||
|
<string name="currency_saved_success">Valuta indstillinger gemt.</string>
|
||||||
|
<string name="main_currency">Primær Valuta</string>
|
||||||
|
<!-- Statistics -->
|
||||||
|
<string name="label_bills_suggested">Foreslåede Kategorier</string>
|
||||||
|
<string name="label_bills_skip">Overspring</string>
|
||||||
|
<string name="stats_date_min">Fra</string>
|
||||||
|
<string name="stats_date_max">Til</string>
|
||||||
|
<string name="stats_who">Medlem</string>
|
||||||
|
<string name="stats_paid">Betalt</string>
|
||||||
|
<string name="stats_spent">Brugt</string>
|
||||||
|
<string name="stats_balance">Saldo</string>
|
||||||
|
<string name="total">Total: %1$s</string>
|
||||||
|
<!-- Errors Extra -->
|
||||||
|
<string name="error_project_connect_check">Forbindelse fejlede: %1$s</string>
|
||||||
|
<string name="error_create_remote_project_helper">Oprettelse fejlede: %1$s</string>
|
||||||
|
<string name="error_edit_remote_project_helper">Fejl under opdatering af fjernprojekt: %1$s</string>
|
||||||
|
<string name="remote_project_operation_no_network">Netværk utilgængeligt for fjernbetjening.</string>
|
||||||
|
<string name="error_scanning_bill_qr_code">Kunne ikke fortolke QR-kode.</string>
|
||||||
|
<string name="error_token_mismatch">Godkendelse token mismatch. Log venligst ind igen.</string>
|
||||||
|
<string name="insufficient_access_level">Du har ikke tilladelse til at udføre denne handling.</string>
|
||||||
|
<string name="delete_label_confirmation_title">Slet Etiket</string>
|
||||||
|
<string name="delete_label_confirmation_message">Er du sikker på, at du vil slette denne etiket?</string>
|
||||||
|
<!-- About -->
|
||||||
|
<string name="about_version">Version %1$s</string>
|
||||||
|
<string name="about_maintainer_title">Vedligeholder</string>
|
||||||
|
<string name="about_license_title">Licens</string>
|
||||||
|
<string name="about_source_title">Kilde kode</string>
|
||||||
|
<!-- New constants for backward compatibility or shared use -->
|
||||||
|
<string name="share_intent_title">Projekt %1$s</string>
|
||||||
|
<string name="share_chooser_title">Del %1$s</string>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,241 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
<string name="app_name">Kuh ausgegeben</string>
|
||||||
|
<!-- Actions -->
|
||||||
|
<string name="action_new_bill">Neue Rechnung</string>
|
||||||
|
<string name="action_add_project">Projekt hinzufügen</string>
|
||||||
|
<string name="action_save">Speichern</string>
|
||||||
|
<string name="action_edit">Bearbeiten</string>
|
||||||
|
<string name="action_share">Teilen</string>
|
||||||
|
<string name="action_search">Suchen</string>
|
||||||
|
<string name="action_delete">Löschen</string>
|
||||||
|
<string name="action_archive">Archivieren</string>
|
||||||
|
<string name="action_unarchive">Archivieren</string>
|
||||||
|
<string name="action_export">Exportieren</string>
|
||||||
|
<string name="action_stats">Statistik</string>
|
||||||
|
<string name="action_settle">Abrechnen</string>
|
||||||
|
<string name="action_scan_qrcode">Scan QR Code</string>
|
||||||
|
<string name="action_settings">Einstellungen</string>
|
||||||
|
<string name="action_label_bills">Fehlende Kategorien</string>
|
||||||
|
<string name="action_logout">Abmelden</string>
|
||||||
|
<string name="action_connect">Verbinden</string>
|
||||||
|
<string name="action_discard">Verwerfen</string>
|
||||||
|
<string name="action_members">Mitglieder</string>
|
||||||
|
<string name="action_labels">Etiketten</string>
|
||||||
|
<string name="action_currencies">Währungen</string>
|
||||||
|
<!-- Titles -->
|
||||||
|
<string name="title_stats">Statistiken</string>
|
||||||
|
<string name="title_edit_project">Projekt bearbeiten</string>
|
||||||
|
<string name="title_label_bills">Etikett Rechnungen</string>
|
||||||
|
<string name="title_labels">Labels verwalten</string>
|
||||||
|
<string name="title_about">Über</string>
|
||||||
|
<string name="title_settle">Projekt regeln</string>
|
||||||
|
<string name="title_share">Projekt teilen</string>
|
||||||
|
<string name="title_add_project">Projekt hinzufügen</string>
|
||||||
|
<string name="title_add_category">Neue Kategorie</string>
|
||||||
|
<string name="title_add_payment_mode">Zahlungsmodus hinzufügen</string>
|
||||||
|
<string name="title_account">Nächstes Cloud-Konto</string>
|
||||||
|
<string name="title_share_web">Weblink</string>
|
||||||
|
<string name="title_share_qr">Cowspent link</string>
|
||||||
|
<string name="title_confirm">Bist du sicher?</string>
|
||||||
|
<!-- Labels and Fields -->
|
||||||
|
<string name="label_all_bills">Alle Rechnungen</string>
|
||||||
|
<string name="label_categories">Kategorien</string>
|
||||||
|
<string name="label_payment_modes">Zahlungsarten</string>
|
||||||
|
<string name="label_name">Name</string>
|
||||||
|
<string name="label_icon">Icon / Emoji</string>
|
||||||
|
<string name="label_color">Farbe</string>
|
||||||
|
<string name="label_weight">Gewicht</string>
|
||||||
|
<string name="label_activated">Aktiviert</string>
|
||||||
|
<string name="label_password">Passwort</string>
|
||||||
|
<string name="label_email">E-Mail</string>
|
||||||
|
<string name="label_url">Serveradresse</string>
|
||||||
|
<string name="label_username">Benutzername</string>
|
||||||
|
<string name="label_comment">Kommentar</string>
|
||||||
|
<string name="label_what">Was?</string>
|
||||||
|
<string name="label_payer">Wer hat bezahlt?</string>
|
||||||
|
<string name="label_owers">Für wen?</string>
|
||||||
|
<string name="label_repeat">Wiederholen alle</string>
|
||||||
|
<string name="label_mode">Modus</string>
|
||||||
|
<string name="label_category">Kategorie</string>
|
||||||
|
<string name="label_project_id">Projekt-ID/Name</string>
|
||||||
|
<string name="label_project_title">Projekttitel</string>
|
||||||
|
<string name="label_use_sso">Nextcloud App-Konto verwenden</string>
|
||||||
|
<!-- Dialogs and Messages -->
|
||||||
|
<string name="dialog_unsaved_changes_title">Ungespeicherte Änderungen</string>
|
||||||
|
<string name="dialog_unsaved_changes_msg">Änderungen vor dem Verlassen speichern?</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_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="msg_project_added">Projekt %1$s hinzugefügt</string>
|
||||||
|
<string name="msg_bill_labeled_done">Alle Rechnungen markiert</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_link_copied">Link in Zwischenablage kopiert</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 auf Web-Browser.</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_sentence">%1$s schuldet %3$.2f %2$s</string>
|
||||||
|
<string name="msg_stats_intro">Statistiken für %1$s:</string>
|
||||||
|
<string name="msg_stats_header">Mitglied (Paid | Ausgaben | Guthaben)</string>
|
||||||
|
<string name="msg_logged_in_as">Angemeldet als %1$s</string>
|
||||||
|
<!-- Errors -->
|
||||||
|
<string name="error_generic">Fehler</string>
|
||||||
|
<string name="error_loading">Lädt</string>
|
||||||
|
<string name="error_no_projects">Keine Projekte gefunden</string>
|
||||||
|
<string name="error_no_members">Keine Mitglieder gefunden</string>
|
||||||
|
<string name="error_no_bills">Keine Rechnungen gefunden</string>
|
||||||
|
<string name="error_no_member">Mindestens ein Mitglied erforderlich</string>
|
||||||
|
<string name="error_maintenance_mode">Server ist im Wartungsmodus</string>
|
||||||
|
<string name="error_400">400 Falsche Anfrage</string>
|
||||||
|
<string name="error_401">401 Unautorisiert</string>
|
||||||
|
<string name="error_403">403 Verboten</string>
|
||||||
|
<string name="error_404">404 Nicht gefunden</string>
|
||||||
|
<string name="error_sync">Sync fehlgeschlagen: %1$s</string>
|
||||||
|
<string name="error_invalid_login">Ungültiger Login: %1$s</string>
|
||||||
|
<string name="error_auth">Benutzername oder Passwort falsch</string>
|
||||||
|
<string name="error_json">Ungültige Server-Antwort</string>
|
||||||
|
<string name="error_req_failed">Anfrage fehlgeschlagen</string>
|
||||||
|
<string name="error_invalid_email">Ungültige E-Mail</string>
|
||||||
|
<string name="error_invalid_project_id">Ungültige Projekt-ID</string>
|
||||||
|
<string name="error_invalid_project_name">Ungültiger Projekttitel</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_payer">Zahler erforderlich</string>
|
||||||
|
<string name="error_invalid_bill_owers">Eigene erforderlich</string>
|
||||||
|
<string name="error_no_network">Keine Netzwerkverbindung</string>
|
||||||
|
<string name="error_server">Serverfehler</string>
|
||||||
|
<string name="error_io">Serververbindung defekt</string>
|
||||||
|
<string name="error_share_impossible">Dieses Projekt kann nicht geteilt werden</string>
|
||||||
|
<!-- Drawer / Common UI -->
|
||||||
|
<string name="drawer_no_account">Mit Nextcloud Konto verbinden</string>
|
||||||
|
<string name="drawer_last_sync">Letzter Sync: %1$02d:%2$02d</string>
|
||||||
|
<string name="simple_cancel">Abbrechen</string>
|
||||||
|
<string name="simple_ok" tools:ignore="ButtonCase,Typos">Ok</string>
|
||||||
|
<string name="simple_yes">Ja</string>
|
||||||
|
<string name="simple_no">Nein</string>
|
||||||
|
<string name="simple_close">Schließen</string>
|
||||||
|
<!-- Settings -->
|
||||||
|
<string name="settings_appearance">Erscheinung</string>
|
||||||
|
<string name="settings_network">Netzwerk</string>
|
||||||
|
<string name="settings_other">Andere</string>
|
||||||
|
<string name="settings_night_mode">Thema</string>
|
||||||
|
<string name="settings_offline_mode">Offline-Modus</string>
|
||||||
|
<string name="settings_offline_mode_summary">Nur manuell synchronisieren.</string>
|
||||||
|
<string name="settings_color_custom">Eigene Farbe</string>
|
||||||
|
<string name="settings_color_mode">Farbauswahl</string>
|
||||||
|
<string name="settings_show_archived">Archivierte Projekte anzeigen</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_url_warn_http">WARNUNG: \"http\" ist unsicher. Verwenden Sie \"https\".</string>
|
||||||
|
<string name="settings_colorpicker_title">Farbe wählen</string>
|
||||||
|
<string name="pref_value_color_system">System</string>
|
||||||
|
<string name="pref_value_color_server">Nächste Cloud</string>
|
||||||
|
<string name="pref_value_color_manual">Manuell</string>
|
||||||
|
<string name="pref_value_theme_light">Hell</string>
|
||||||
|
<string name="pref_value_theme_dark">Dunkel</string>
|
||||||
|
<string name="pref_value_theme_system">System folgen</string>
|
||||||
|
<!-- Constants (Do not translate) -->
|
||||||
|
<!-- Enums and Lists -->
|
||||||
|
<string name="repeat_no">Keine Wiederholung</string>
|
||||||
|
<string name="repeat_day">Täglich</string>
|
||||||
|
<string name="repeat_week">Wöchentlich</string>
|
||||||
|
<string name="repeat_fortnight">Vierzehn</string>
|
||||||
|
<string name="repeat_month">Monatlich</string>
|
||||||
|
<string name="repeat_year">Jährlich</string>
|
||||||
|
<string name="payment_mode_none">Keine</string>
|
||||||
|
<string name="payment_mode_all">Alle</string>
|
||||||
|
<string name="payment_mode_credit_card">Kreditkarte</string>
|
||||||
|
<string name="payment_mode_cash">Bargeld</string>
|
||||||
|
<string name="payment_mode_check">Prüfen</string>
|
||||||
|
<string name="payment_mode_online">Online</string>
|
||||||
|
<string name="payment_mode_transfer">Überweisung</string>
|
||||||
|
<string name="category_none">Keine</string>
|
||||||
|
<string name="category_all">Alle</string>
|
||||||
|
<string name="category_all_except_reimbursement">Alle außer Rückerstattung</string>
|
||||||
|
<string name="category_groceries">Fleischgerichte</string>
|
||||||
|
<string name="category_leisure">Bar/Party</string>
|
||||||
|
<string name="category_rent">Miete</string>
|
||||||
|
<string name="category_bills">Abrechnung</string>
|
||||||
|
<string name="category_excursion">Ausflug/Kultur</string>
|
||||||
|
<string name="category_health">Gesundheit</string>
|
||||||
|
<string name="category_shopping">Einkaufen</string>
|
||||||
|
<string name="category_reimbursement">Rückzahlung</string>
|
||||||
|
<string name="category_restaurant">Restaurant</string>
|
||||||
|
<string name="category_accomodation">Unterkunft</string>
|
||||||
|
<string name="category_transport">Transport</string>
|
||||||
|
<string name="category_sport">Sport</string>
|
||||||
|
<!-- Project specific -->
|
||||||
|
<string name="new_project_action">Was</string>
|
||||||
|
<string name="new_project_where">Wo</string>
|
||||||
|
<string name="where_local">Nur lokal</string>
|
||||||
|
<string name="where_cospend">Ko-Ausgaben</string>
|
||||||
|
<string name="where_ihatemoney">IHateMoney</string>
|
||||||
|
<string name="todo_join">Bestehendes Projekt beitreten</string>
|
||||||
|
<string name="todo_create">Neues Projekt erstellen</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_impossible">Keine Projekte auf diesem Konto gefunden.</string>
|
||||||
|
<string name="choose_project_management_action">Projekt</string>
|
||||||
|
<string name="project_added_success">Projekt erfolgreich hinzugefügt.</string>
|
||||||
|
<string name="no_projects_text">Sie haben noch keine Projekte.</string>
|
||||||
|
<string name="configure_account_choice">Nextcloud-Konto konfigurieren</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_bills_text">Keine Rechnungen gefunden.</string>
|
||||||
|
<string name="member_already_exists">Mitglied existiert bereits.</string>
|
||||||
|
<string name="activity_dialog_title">Projekt: %1$s</string>
|
||||||
|
<string name="remove_project_confirmation">Projekt %1$s entfernt.</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_date">Ungültiges Datumsformat 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="edit_member_dialog_title">Mitglied bearbeiten</string>
|
||||||
|
<string name="member_edit_delete">Löschen</string>
|
||||||
|
<string name="project_edition_no_change">Keine zu speichernden Änderungen.</string>
|
||||||
|
<!-- Settlement -->
|
||||||
|
<string name="center_none">Keine (optimal)</string>
|
||||||
|
<string name="settle_who">Wer zahlt</string>
|
||||||
|
<string name="settle_to_whom">An wen</string>
|
||||||
|
<string name="settle_how_much">Betrag</string>
|
||||||
|
<string name="simple_settle_share">Teilen</string>
|
||||||
|
<string name="simple_create_bills">Rechnungen erstellen</string>
|
||||||
|
<string name="settle_bill_what">Abrechnung</string>
|
||||||
|
<!-- Currencies -->
|
||||||
|
<string name="currency_dialog_title">Währung wählen (%s)</string>
|
||||||
|
<string name="setting_none">Keine</string>
|
||||||
|
<string name="setting_all">Alle</string>
|
||||||
|
<string name="currency_saved_success">Währungseinstellungen gespeichert.</string>
|
||||||
|
<string name="main_currency">Hauptwährung</string>
|
||||||
|
<!-- Statistics -->
|
||||||
|
<string name="label_bills_suggested">Empfohlene Kategorien</string>
|
||||||
|
<string name="label_bills_skip">Überspringen</string>
|
||||||
|
<string name="stats_date_min">Von</string>
|
||||||
|
<string name="stats_date_max">An</string>
|
||||||
|
<string name="stats_who">Mitglied</string>
|
||||||
|
<string name="stats_paid">Bezahlt</string>
|
||||||
|
<string name="stats_spent">Ausgegeben</string>
|
||||||
|
<string name="stats_balance">Saldo</string>
|
||||||
|
<string name="total">Gesamt: %1$s</string>
|
||||||
|
<!-- Errors Extra -->
|
||||||
|
<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_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="error_scanning_bill_qr_code">Fehler beim Analysieren des QR-Codes.</string>
|
||||||
|
<string name="error_token_mismatch">Authentifizierungstoken stimmen nicht überein. Bitte melden Sie sich erneut an.</string>
|
||||||
|
<string name="insufficient_access_level">Sie haben keine Berechtigung, um diese Aktion durchzuführen.</string>
|
||||||
|
<string name="delete_label_confirmation_title">Label löschen</string>
|
||||||
|
<string name="delete_label_confirmation_message">Sind Sie sicher, dass Sie diese Bezeichnung löschen möchten?</string>
|
||||||
|
<!-- About -->
|
||||||
|
<string name="about_version">Version %1$s</string>
|
||||||
|
<string name="about_maintainer_title">Betreuer</string>
|
||||||
|
<string name="about_license_title">Lizenz</string>
|
||||||
|
<string name="about_source_title">Quellcode</string>
|
||||||
|
<!-- New constants for backward compatibility or shared use -->
|
||||||
|
<string name="share_intent_title">Projekt %1$s</string>
|
||||||
|
<string name="share_chooser_title">Teilen %1$s</string>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,241 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
<string name="app_name">Έλαιο</string>
|
||||||
|
<!-- Actions -->
|
||||||
|
<string name="action_new_bill">Νέος λογαριασμός</string>
|
||||||
|
<string name="action_add_project">Προσθήκη έργου</string>
|
||||||
|
<string name="action_save">Αποθήκευση</string>
|
||||||
|
<string name="action_edit">Επεξεργασία</string>
|
||||||
|
<string name="action_share">Κοινοποίηση</string>
|
||||||
|
<string name="action_search">Αναζήτηση</string>
|
||||||
|
<string name="action_delete">Διαγραφή</string>
|
||||||
|
<string name="action_archive">Αρχειοθέτηση</string>
|
||||||
|
<string name="action_unarchive">Αποσυμπιεσμένο</string>
|
||||||
|
<string name="action_export">Εξαγωγή</string>
|
||||||
|
<string name="action_stats">Στατιστικά</string>
|
||||||
|
<string name="action_settle">Διευθέτηση</string>
|
||||||
|
<string name="action_scan_qrcode">Scan QR Code</string>
|
||||||
|
<string name="action_settings">Ρυθμίσεις</string>
|
||||||
|
<string name="action_label_bills">Λείπει η ετικέτα κατηγορίες</string>
|
||||||
|
<string name="action_logout">Αποσύνδεση</string>
|
||||||
|
<string name="action_connect">Σύνδεση</string>
|
||||||
|
<string name="action_discard">Απορρίψτε</string>
|
||||||
|
<string name="action_members">Μέλη</string>
|
||||||
|
<string name="action_labels">Ετικέτες</string>
|
||||||
|
<string name="action_currencies">Νομίσματα</string>
|
||||||
|
<!-- Titles -->
|
||||||
|
<string name="title_stats">Στατιστικά</string>
|
||||||
|
<string name="title_edit_project">Επεξεργασία έργου</string>
|
||||||
|
<string name="title_label_bills">Λογαριασμοί Ετικέτας</string>
|
||||||
|
<string name="title_labels">Διαχείριση Ετικετών</string>
|
||||||
|
<string name="title_about">Σχετικά</string>
|
||||||
|
<string name="title_settle">Διευθέτηση Έργου</string>
|
||||||
|
<string name="title_share">Μοιραστείτε Το Έργο</string>
|
||||||
|
<string name="title_add_project">Προσθήκη Έργου</string>
|
||||||
|
<string name="title_add_category">Προσθήκη Κατηγορίας</string>
|
||||||
|
<string name="title_add_payment_mode">Προσθήκη Λειτουργίας Πληρωμής</string>
|
||||||
|
<string name="title_account">Λογαριασμός Nextcloud</string>
|
||||||
|
<string name="title_share_web">Σύνδεσμος ιστού</string>
|
||||||
|
<string name="title_share_qr">Cowspent link</string>
|
||||||
|
<string name="title_confirm">Είσαι σίγουρος?</string>
|
||||||
|
<!-- Labels and Fields -->
|
||||||
|
<string name="label_all_bills">Όλοι οι λογαριασμοί</string>
|
||||||
|
<string name="label_categories">Κατηγορίες</string>
|
||||||
|
<string name="label_payment_modes">Τρόποι Πληρωμής</string>
|
||||||
|
<string name="label_name">Όνομα</string>
|
||||||
|
<string name="label_icon">Icon / Emoji</string>
|
||||||
|
<string name="label_color">Χρώμα</string>
|
||||||
|
<string name="label_weight">Βάρος</string>
|
||||||
|
<string name="label_activated">Ενεργοποιήθηκε</string>
|
||||||
|
<string name="label_password">Κωδικός</string>
|
||||||
|
<string name="label_email">E-mail</string>
|
||||||
|
<string name="label_url">Διεύθυνση διακομιστή</string>
|
||||||
|
<string name="label_username">Όνομα Χρήστη</string>
|
||||||
|
<string name="label_comment">Σχόλιο</string>
|
||||||
|
<string name="label_what">Τι Είναι?</string>
|
||||||
|
<string name="label_payer">Ποιος πλήρωσε?</string>
|
||||||
|
<string name="label_owers">Για ποιον?</string>
|
||||||
|
<string name="label_repeat">Επανάληψη κάθε</string>
|
||||||
|
<string name="label_mode">Λειτουργία</string>
|
||||||
|
<string name="label_category">Κατηγορία</string>
|
||||||
|
<string name="label_project_id">Ταυτότητα/όνομα Έργου</string>
|
||||||
|
<string name="label_project_title">Τίτλος έργου</string>
|
||||||
|
<string name="label_use_sso">Χρήση Λογαριασμού Εφαρμογής Nextcloud</string>
|
||||||
|
<!-- Dialogs and Messages -->
|
||||||
|
<string name="dialog_unsaved_changes_title">Μη αποθηκευμένες αλλαγές</string>
|
||||||
|
<string name="dialog_unsaved_changes_msg">Αποθήκευση αλλαγών πριν την αναχώρηση?</string>
|
||||||
|
<string name="dialog_confirm_remove_project_msg">Το απομακρυσμένο έργο δεν θα διαγραφεί.</string>
|
||||||
|
<string name="dialog_sync_error_title">Σφάλμα συγχρονισμού</string>
|
||||||
|
<string name="dialog_sync_error_msg">Ο Συγχρονισμός απέτυχε για %1$s.\n\n%2$s</string>
|
||||||
|
<string name="dialog_balanced_msg">Οι δαπάνες είναι ήδη ισορροπημένες.</string>
|
||||||
|
<string name="msg_project_added">Το έργο %1$s προστέθηκε</string>
|
||||||
|
<string name="msg_bill_labeled_done">Όλοι οι λογαριασμοί με επισήμανση</string>
|
||||||
|
<string name="msg_no_suggestions">Δεν υπάρχουν προτάσεις</string>
|
||||||
|
<string name="msg_auth_warning">Απαιτεί Cospend v0.3.4+.</string>
|
||||||
|
<string name="msg_link_copied">Ο σύνδεσμος αντιγράφηκε στο πρόχειρο</string>
|
||||||
|
<string name="msg_share_qr">Σαρώστε τον κωδικό QR ή μοιραστείτε το σύνδεσμο για να ενταχθείτε.</string>
|
||||||
|
<string name="msg_share_web">Σύνδεσμος για πρόσβαση στο πρόγραμμα περιήγησης ιστού.</string>
|
||||||
|
<string name="msg_share_qr_warn">Μοιραστείτε αυτό το σύνδεσμο με έναν χρήστη Cowspent .</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_stats_intro">Στατιστικά για %1$s:</string>
|
||||||
|
<string name="msg_stats_header">Μέλος (Πάροχος (Ισορροπία)</string>
|
||||||
|
<string name="msg_logged_in_as">Συνδεδεμένος ως %1$s</string>
|
||||||
|
<!-- Errors -->
|
||||||
|
<string name="error_generic">Σφάλμα</string>
|
||||||
|
<string name="error_loading">Φόρτωση</string>
|
||||||
|
<string name="error_no_projects">Δεν βρέθηκαν πρότζεκτ</string>
|
||||||
|
<string name="error_no_members">Δεν βρέθηκαν μέλη</string>
|
||||||
|
<string name="error_no_bills">Δεν βρέθηκαν λογαριασμοί</string>
|
||||||
|
<string name="error_no_member">Απαιτείται τουλάχιστον ένα μέλος</string>
|
||||||
|
<string name="error_maintenance_mode">Ο διακομιστής είναι σε λειτουργία συντήρησης</string>
|
||||||
|
<string name="error_400">400 Εσφαλμένο αίτημα</string>
|
||||||
|
<string name="error_401">401 Μη Εξουσιοδοτημένο</string>
|
||||||
|
<string name="error_403">403 Απαγορευμένο</string>
|
||||||
|
<string name="error_404">404 Δεν Βρέθηκε</string>
|
||||||
|
<string name="error_sync">Συγχρονισμός απέτυχε: %1$s</string>
|
||||||
|
<string name="error_invalid_login">Μη έγκυρη σύνδεση: %1$s</string>
|
||||||
|
<string name="error_auth">Λάθος όνομα χρήστη ή κωδικός πρόσβασης</string>
|
||||||
|
<string name="error_json">Μη έγκυρη απάντηση διακομιστή</string>
|
||||||
|
<string name="error_req_failed">Το αίτημα απέτυχε</string>
|
||||||
|
<string name="error_invalid_email">Μη έγκυρο e-mail</string>
|
||||||
|
<string name="error_invalid_project_id">Μη έγκυρο ID έργου</string>
|
||||||
|
<string name="error_invalid_project_name">Μη έγκυρος τίτλος έργου</string>
|
||||||
|
<string name="error_invalid_bill_name">Μη έγκυρο όνομα λογαριασμού</string>
|
||||||
|
<string name="error_invalid_bill_date">Μη έγκυρη ημερομηνία λογαριασμού</string>
|
||||||
|
<string name="error_invalid_bill_payer">Απαιτείται πληρωτής</string>
|
||||||
|
<string name="error_invalid_bill_owers">Απαιτούνται ιδιοκτήτες</string>
|
||||||
|
<string name="error_no_network">Χωρίς σύνδεση δικτύου</string>
|
||||||
|
<string name="error_server">Σφάλμα διακομιστή</string>
|
||||||
|
<string name="error_io">Η σύνδεση διακομιστή έσπασε</string>
|
||||||
|
<string name="error_share_impossible">Αδύνατη η κοινοποίηση αυτού του έργου</string>
|
||||||
|
<!-- Drawer / Common UI -->
|
||||||
|
<string name="drawer_no_account">Συνδεθείτε στο λογαριασμό Nextcloud</string>
|
||||||
|
<string name="drawer_last_sync">Τελευταίος συγχρονισμός: %1$02d:%2$02d</string>
|
||||||
|
<string name="simple_cancel">Ακύρωση</string>
|
||||||
|
<string name="simple_ok" tools:ignore="ButtonCase,Typos">Εντάξει</string>
|
||||||
|
<string name="simple_yes">Ναι</string>
|
||||||
|
<string name="simple_no">Όχι</string>
|
||||||
|
<string name="simple_close">Κλείσιμο</string>
|
||||||
|
<!-- Settings -->
|
||||||
|
<string name="settings_appearance">Εμφάνιση</string>
|
||||||
|
<string name="settings_network">Δίκτυο</string>
|
||||||
|
<string name="settings_other">Άλλο</string>
|
||||||
|
<string name="settings_night_mode">Θέμα</string>
|
||||||
|
<string name="settings_offline_mode">Λειτουργία χωρίς σύνδεση</string>
|
||||||
|
<string name="settings_offline_mode_summary">Μόνο χειροκίνητος συγχρονισμός.</string>
|
||||||
|
<string name="settings_color_custom">Προσαρμοσμένο χρώμα</string>
|
||||||
|
<string name="settings_color_mode">Επιλογή Χρώματος</string>
|
||||||
|
<string name="settings_show_archived">Εμφάνιση αρχειοθετημένων έργων</string>
|
||||||
|
<string name="settings_beta_features">Χαρακτηριστικά Beta</string>
|
||||||
|
<string name="settings_beta_features_summary">Ενεργοποιήσετε πειραματικά χαρακτηριστικά. Χρησιμοποιήστε το με δική σας ευθύνη.</string>
|
||||||
|
<string name="settings_url_warn_http">ΠΡΟΕΙΔΟΠΟΙΗΣΗ: Το \"http\" δεν είναι ασφαλές. Χρησιμοποιήστε το \"Ř\".</string>
|
||||||
|
<string name="settings_colorpicker_title">Επιλογή Χρώματος</string>
|
||||||
|
<string name="pref_value_color_system">Σύστημα</string>
|
||||||
|
<string name="pref_value_color_server">Nextcloud</string>
|
||||||
|
<string name="pref_value_color_manual">Χειροκίνητα</string>
|
||||||
|
<string name="pref_value_theme_light">Φωτεινό</string>
|
||||||
|
<string name="pref_value_theme_dark">Σκοτεινό</string>
|
||||||
|
<string name="pref_value_theme_system">Ακολουθήστε το σύστημα</string>
|
||||||
|
<!-- Constants (Do not translate) -->
|
||||||
|
<!-- Enums and Lists -->
|
||||||
|
<string name="repeat_no">Χωρίς επανάληψη</string>
|
||||||
|
<string name="repeat_day">Καθημερινά</string>
|
||||||
|
<string name="repeat_week">Εβδομαδιαία</string>
|
||||||
|
<string name="repeat_fortnight">Δεκαπενθήμερα</string>
|
||||||
|
<string name="repeat_month">Μηνιαία</string>
|
||||||
|
<string name="repeat_year">Ετήσια</string>
|
||||||
|
<string name="payment_mode_none">Κανένα</string>
|
||||||
|
<string name="payment_mode_all">Όλα</string>
|
||||||
|
<string name="payment_mode_credit_card">Πιστωτική κάρτα</string>
|
||||||
|
<string name="payment_mode_cash">Μετρητά</string>
|
||||||
|
<string name="payment_mode_check">Έλεγχος</string>
|
||||||
|
<string name="payment_mode_online">Συνδεδεμένος</string>
|
||||||
|
<string name="payment_mode_transfer">Μεταφορά</string>
|
||||||
|
<string name="category_none">Κανένα</string>
|
||||||
|
<string name="category_all">Όλα</string>
|
||||||
|
<string name="category_all_except_reimbursement">Όλα εκτός από την επιστροφή</string>
|
||||||
|
<string name="category_groceries">Παντοπωλείο</string>
|
||||||
|
<string name="category_leisure">Μπαρ/Πάρτι</string>
|
||||||
|
<string name="category_rent">Ενοίκιο</string>
|
||||||
|
<string name="category_bills">Λογαριασμός</string>
|
||||||
|
<string name="category_excursion">Εκδρομή/Πολιτισμός</string>
|
||||||
|
<string name="category_health">Υγεία</string>
|
||||||
|
<string name="category_shopping">Αγορές</string>
|
||||||
|
<string name="category_reimbursement">Επιστροφή</string>
|
||||||
|
<string name="category_restaurant">Εστιατόριο</string>
|
||||||
|
<string name="category_accomodation">Διαμονή</string>
|
||||||
|
<string name="category_transport">Μεταφορές</string>
|
||||||
|
<string name="category_sport">Αθλητισμός</string>
|
||||||
|
<!-- Project specific -->
|
||||||
|
<string name="new_project_action">Τι</string>
|
||||||
|
<string name="new_project_where">Όπου</string>
|
||||||
|
<string name="where_local">Τοπικό μόνο</string>
|
||||||
|
<string name="where_cospend">Κόστος</string>
|
||||||
|
<string name="where_ihatemoney">IHateMoney</string>
|
||||||
|
<string name="todo_join">Συμμετοχή σε υπάρχον έργο</string>
|
||||||
|
<string name="todo_create">Δημιουργία νέου έργου</string>
|
||||||
|
<string name="import_tooltip">Εισαγωγή από αρχείο</string>
|
||||||
|
<string name="choose_account_project_dialog_title">Επιλογή έργου</string>
|
||||||
|
<string name="choose_account_project_dialog_impossible">Δεν βρέθηκαν έργα σε αυτόν τον λογαριασμό.</string>
|
||||||
|
<string name="choose_project_management_action">Έργο</string>
|
||||||
|
<string name="project_added_success">Το έργο προστέθηκε επιτυχώς.</string>
|
||||||
|
<string name="no_projects_text">Δεν έχετε ακόμα πρότζεκτ.</string>
|
||||||
|
<string name="configure_account_choice">Ρύθμιση λογαριασμού Nextcloud</string>
|
||||||
|
<string name="add_project_choice">Χειροκίνητη προσθήκη έργου</string>
|
||||||
|
<string name="no_members_text">Κανένα μέλος σε αυτό το έργο.</string>
|
||||||
|
<string name="no_bills_text">Δεν βρέθηκαν λογαριασμοί.</string>
|
||||||
|
<string name="member_already_exists">Το μέλος υπάρχει ήδη.</string>
|
||||||
|
<string name="activity_dialog_title">Έργο: %1$s</string>
|
||||||
|
<string name="remove_project_confirmation">Το έργο %1$s αφαιρέθηκε.</string>
|
||||||
|
<string name="file_saved_success">Το αρχείο αποθηκεύτηκε: %1$s</string>
|
||||||
|
<string name="import_error_header">Η εισαγωγή απέτυχε στη γραμμή %d</string>
|
||||||
|
<string name="import_error_date">Μη έγκυρη μορφή ημερομηνίας στη γραμμή %d</string>
|
||||||
|
<string name="import_error_owers">Μη έγκυρες owers στη σειρά %d</string>
|
||||||
|
<string name="add_member_dialog_title">Προσθήκη Μελών</string>
|
||||||
|
<string name="edit_member_dialog_title">Επεξεργασία Μέλους</string>
|
||||||
|
<string name="member_edit_delete">Διαγραφή</string>
|
||||||
|
<string name="project_edition_no_change">Δεν υπάρχουν αλλαγές για αποθήκευση.</string>
|
||||||
|
<!-- Settlement -->
|
||||||
|
<string name="center_none">Κανένα (Βέλτιστο Μέρος)</string>
|
||||||
|
<string name="settle_who">Ποιος πληρώνει</string>
|
||||||
|
<string name="settle_to_whom">Σε ποιους</string>
|
||||||
|
<string name="settle_how_much">Ποσό</string>
|
||||||
|
<string name="simple_settle_share">Κοινοποίηση</string>
|
||||||
|
<string name="simple_create_bills">Δημιουργία λογαριασμών</string>
|
||||||
|
<string name="settle_bill_what">Διευθέτηση</string>
|
||||||
|
<!-- Currencies -->
|
||||||
|
<string name="currency_dialog_title">Επιλέξτε Νόμισμα (%s)</string>
|
||||||
|
<string name="setting_none">Κανένα</string>
|
||||||
|
<string name="setting_all">Όλα</string>
|
||||||
|
<string name="currency_saved_success">Οι ρυθμίσεις νομίσματος αποθηκεύτηκαν.</string>
|
||||||
|
<string name="main_currency">Κύριο Νόμισμα</string>
|
||||||
|
<!-- Statistics -->
|
||||||
|
<string name="label_bills_suggested">Προτεινόμενες Κατηγορίες</string>
|
||||||
|
<string name="label_bills_skip">Παράλειψη</string>
|
||||||
|
<string name="stats_date_min">Από</string>
|
||||||
|
<string name="stats_date_max">Προς</string>
|
||||||
|
<string name="stats_who">Κράτος</string>
|
||||||
|
<string name="stats_paid">Πληρωμένο</string>
|
||||||
|
<string name="stats_spent">Δαπανήθηκε</string>
|
||||||
|
<string name="stats_balance">Υπόλοιπο</string>
|
||||||
|
<string name="total">Σύνολο: %1$s</string>
|
||||||
|
<!-- Errors Extra -->
|
||||||
|
<string name="error_project_connect_check">Αποτυχία σύνδεσης: %1$s</string>
|
||||||
|
<string name="error_create_remote_project_helper">Η δημιουργία απέτυχε: %1$s</string>
|
||||||
|
<string name="error_edit_remote_project_helper">Σφάλμα ενημέρωσης απομακρυσμένου έργου: %1$s</string>
|
||||||
|
<string name="remote_project_operation_no_network">Το δίκτυο δεν είναι διαθέσιμο για απομακρυσμένη λειτουργία.</string>
|
||||||
|
<string name="error_scanning_bill_qr_code">Αποτυχία ανάλυσης QR code.</string>
|
||||||
|
<string name="error_token_mismatch">Ασυμφωνία διακριτικού ταυτοποίησης. Παρακαλώ συνδεθείτε ξανά.</string>
|
||||||
|
<string name="insufficient_access_level">Δεν έχετε άδεια για να εκτελέσετε αυτήν την ενέργεια.</string>
|
||||||
|
<string name="delete_label_confirmation_title">Διαγραφή Ετικέτας</string>
|
||||||
|
<string name="delete_label_confirmation_message">Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτή την ετικέτα?</string>
|
||||||
|
<!-- About -->
|
||||||
|
<string name="about_version">Έκδοση %1$s</string>
|
||||||
|
<string name="about_maintainer_title">Συντηρητής</string>
|
||||||
|
<string name="about_license_title">Άδεια</string>
|
||||||
|
<string name="about_source_title">Πηγαίος κώδικας</string>
|
||||||
|
<!-- New constants for backward compatibility or shared use -->
|
||||||
|
<string name="share_intent_title">Έργο %1$s</string>
|
||||||
|
<string name="share_chooser_title">Κοινοποίηση %1$s</string>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,241 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
<string name="app_name">Cowspent</string>
|
||||||
|
<!-- Actions -->
|
||||||
|
<string name="action_new_bill">New bill</string>
|
||||||
|
<string name="action_add_project">Add project</string>
|
||||||
|
<string name="action_save">Save</string>
|
||||||
|
<string name="action_edit">Edit</string>
|
||||||
|
<string name="action_share">Share</string>
|
||||||
|
<string name="action_search">Search</string>
|
||||||
|
<string name="action_delete">Delete</string>
|
||||||
|
<string name="action_archive">Archive</string>
|
||||||
|
<string name="action_unarchive">Unarchive</string>
|
||||||
|
<string name="action_export">Export</string>
|
||||||
|
<string name="action_stats">Stats</string>
|
||||||
|
<string name="action_settle">Settle</string>
|
||||||
|
<string name="action_scan_qrcode">Scan QR Code</string>
|
||||||
|
<string name="action_settings">Settings</string>
|
||||||
|
<string name="action_label_bills">Label missing categories</string>
|
||||||
|
<string name="action_logout">Logout</string>
|
||||||
|
<string name="action_connect">Connect</string>
|
||||||
|
<string name="action_discard">Discard</string>
|
||||||
|
<string name="action_members">Members</string>
|
||||||
|
<string name="action_labels">Labels</string>
|
||||||
|
<string name="action_currencies">Currencies</string>
|
||||||
|
<!-- Titles -->
|
||||||
|
<string name="title_stats">Statistics</string>
|
||||||
|
<string name="title_edit_project">Edit project</string>
|
||||||
|
<string name="title_label_bills">Label Bills</string>
|
||||||
|
<string name="title_labels">Manage Labels</string>
|
||||||
|
<string name="title_about">About</string>
|
||||||
|
<string name="title_settle">Settle Project</string>
|
||||||
|
<string name="title_share">Share Project</string>
|
||||||
|
<string name="title_add_project">Add Project</string>
|
||||||
|
<string name="title_add_category">Add Category</string>
|
||||||
|
<string name="title_add_payment_mode">Add Payment Mode</string>
|
||||||
|
<string name="title_account">Nextcloud Account</string>
|
||||||
|
<string name="title_share_web">Web link</string>
|
||||||
|
<string name="title_share_qr">Cowspent link</string>
|
||||||
|
<string name="title_confirm">Are you sure?</string>
|
||||||
|
<!-- Labels and Fields -->
|
||||||
|
<string name="label_all_bills">All bills</string>
|
||||||
|
<string name="label_categories">Categories</string>
|
||||||
|
<string name="label_payment_modes">Payment Modes</string>
|
||||||
|
<string name="label_name">Name</string>
|
||||||
|
<string name="label_icon">Icon / Emoji</string>
|
||||||
|
<string name="label_color">Color</string>
|
||||||
|
<string name="label_weight">Weight</string>
|
||||||
|
<string name="label_activated">Activated</string>
|
||||||
|
<string name="label_password">Password</string>
|
||||||
|
<string name="label_email">E-mail</string>
|
||||||
|
<string name="label_url">Server address</string>
|
||||||
|
<string name="label_username">Username</string>
|
||||||
|
<string name="label_comment">Comment</string>
|
||||||
|
<string name="label_what">What?</string>
|
||||||
|
<string name="label_payer">Who paid?</string>
|
||||||
|
<string name="label_owers">For whom?</string>
|
||||||
|
<string name="label_repeat">Repeat every</string>
|
||||||
|
<string name="label_mode">Mode</string>
|
||||||
|
<string name="label_category">Category</string>
|
||||||
|
<string name="label_project_id">Project ID/name</string>
|
||||||
|
<string name="label_project_title">Project title</string>
|
||||||
|
<string name="label_use_sso">Use Nextcloud App Account</string>
|
||||||
|
<!-- Dialogs and Messages -->
|
||||||
|
<string name="dialog_unsaved_changes_title">Unsaved changes</string>
|
||||||
|
<string name="dialog_unsaved_changes_msg">Save changes before leaving?</string>
|
||||||
|
<string name="dialog_confirm_remove_project_msg">The remote project will not be deleted.</string>
|
||||||
|
<string name="dialog_sync_error_title">Sync error</string>
|
||||||
|
<string name="dialog_sync_error_msg">Sync failed for %1$s.\n\n%2$s</string>
|
||||||
|
<string name="dialog_balanced_msg">Expenses are already balanced.</string>
|
||||||
|
<string name="msg_project_added">Project %1$s added</string>
|
||||||
|
<string name="msg_bill_labeled_done">All bills labeled</string>
|
||||||
|
<string name="msg_no_suggestions">No suggestions</string>
|
||||||
|
<string name="msg_auth_warning">Requires Cospend v0.3.4+.</string>
|
||||||
|
<string name="msg_link_copied">Link copied to clipboard</string>
|
||||||
|
<string name="msg_share_qr">Scan QR code or share the link to join.</string>
|
||||||
|
<string name="msg_share_web">Link for web browser access.</string>
|
||||||
|
<string name="msg_share_qr_warn">Share this link with a Cowspent user.</string>
|
||||||
|
<string name="msg_settle_intro">Settlement for %1$s:</string>
|
||||||
|
<string name="msg_settle_sentence">%1$s owes %3$.2f to %2$s</string>
|
||||||
|
<string name="msg_stats_intro">Stats for %1$s:</string>
|
||||||
|
<string name="msg_stats_header">Member (Paid | Spent | Balance)</string>
|
||||||
|
<string name="msg_logged_in_as">Logged in as %1$s</string>
|
||||||
|
<!-- Errors -->
|
||||||
|
<string name="error_generic">Error</string>
|
||||||
|
<string name="error_loading">Loading</string>
|
||||||
|
<string name="error_no_projects">No projects found</string>
|
||||||
|
<string name="error_no_members">No members found</string>
|
||||||
|
<string name="error_no_bills">No bills found</string>
|
||||||
|
<string name="error_no_member">At least one member required</string>
|
||||||
|
<string name="error_maintenance_mode">Server is in maintenance mode</string>
|
||||||
|
<string name="error_400">400 Bad request</string>
|
||||||
|
<string name="error_401">401 Unauthorized</string>
|
||||||
|
<string name="error_403">403 Forbidden</string>
|
||||||
|
<string name="error_404">404 Not Found</string>
|
||||||
|
<string name="error_sync">Sync failed: %1$s</string>
|
||||||
|
<string name="error_invalid_login">Invalid login: %1$s</string>
|
||||||
|
<string name="error_auth">Wrong username or password</string>
|
||||||
|
<string name="error_json">Invalid server response</string>
|
||||||
|
<string name="error_req_failed">Request failed</string>
|
||||||
|
<string name="error_invalid_email">Invalid e-mail</string>
|
||||||
|
<string name="error_invalid_project_id">Invalid project ID</string>
|
||||||
|
<string name="error_invalid_project_name">Invalid project title</string>
|
||||||
|
<string name="error_invalid_bill_name">Invalid bill name</string>
|
||||||
|
<string name="error_invalid_bill_date">Invalid bill date</string>
|
||||||
|
<string name="error_invalid_bill_payer">Payer required</string>
|
||||||
|
<string name="error_invalid_bill_owers">Owers required</string>
|
||||||
|
<string name="error_no_network">No network connection</string>
|
||||||
|
<string name="error_server">Server error</string>
|
||||||
|
<string name="error_io">Server connection broken</string>
|
||||||
|
<string name="error_share_impossible">Cannot share this project</string>
|
||||||
|
<!-- Drawer / Common UI -->
|
||||||
|
<string name="drawer_no_account">Connect to Nextcloud account</string>
|
||||||
|
<string name="drawer_last_sync">Last sync: %1$02d:%2$02d</string>
|
||||||
|
<string name="simple_cancel">Cancel</string>
|
||||||
|
<string name="simple_ok" tools:ignore="ButtonCase,Typos">Ok</string>
|
||||||
|
<string name="simple_yes">Yes</string>
|
||||||
|
<string name="simple_no">No</string>
|
||||||
|
<string name="simple_close">Close</string>
|
||||||
|
<!-- Settings -->
|
||||||
|
<string name="settings_appearance">Appearance</string>
|
||||||
|
<string name="settings_network">Network</string>
|
||||||
|
<string name="settings_other">Other</string>
|
||||||
|
<string name="settings_night_mode">Theme</string>
|
||||||
|
<string name="settings_offline_mode">Offline mode</string>
|
||||||
|
<string name="settings_offline_mode_summary">Only sync manually.</string>
|
||||||
|
<string name="settings_color_custom">Custom color</string>
|
||||||
|
<string name="settings_color_mode">Color Selection</string>
|
||||||
|
<string name="settings_show_archived">Show archived projects</string>
|
||||||
|
<string name="settings_beta_features">Beta Features</string>
|
||||||
|
<string name="settings_beta_features_summary">Enable experimental features. Use at your own risk.</string>
|
||||||
|
<string name="settings_url_warn_http">WARNING: \"http\" is unsafe. Use \"https\".</string>
|
||||||
|
<string name="settings_colorpicker_title">Choose Color</string>
|
||||||
|
<string name="pref_value_color_system">System</string>
|
||||||
|
<string name="pref_value_color_server">Nextcloud</string>
|
||||||
|
<string name="pref_value_color_manual">Manual</string>
|
||||||
|
<string name="pref_value_theme_light">Light</string>
|
||||||
|
<string name="pref_value_theme_dark">Dark</string>
|
||||||
|
<string name="pref_value_theme_system">Follow system</string>
|
||||||
|
<!-- Constants (Do not translate) -->
|
||||||
|
<!-- Enums and Lists -->
|
||||||
|
<string name="repeat_no">No repeat</string>
|
||||||
|
<string name="repeat_day">Daily</string>
|
||||||
|
<string name="repeat_week">Weekly</string>
|
||||||
|
<string name="repeat_fortnight">Fortnightly</string>
|
||||||
|
<string name="repeat_month">Monthly</string>
|
||||||
|
<string name="repeat_year">Yearly</string>
|
||||||
|
<string name="payment_mode_none">None</string>
|
||||||
|
<string name="payment_mode_all">All</string>
|
||||||
|
<string name="payment_mode_credit_card">Credit card</string>
|
||||||
|
<string name="payment_mode_cash">Cash</string>
|
||||||
|
<string name="payment_mode_check">Check</string>
|
||||||
|
<string name="payment_mode_online">Online</string>
|
||||||
|
<string name="payment_mode_transfer">Transfer</string>
|
||||||
|
<string name="category_none">None</string>
|
||||||
|
<string name="category_all">All</string>
|
||||||
|
<string name="category_all_except_reimbursement">All except reimbursement</string>
|
||||||
|
<string name="category_groceries">Grocery</string>
|
||||||
|
<string name="category_leisure">Bar/Party</string>
|
||||||
|
<string name="category_rent">Rent</string>
|
||||||
|
<string name="category_bills">Bill</string>
|
||||||
|
<string name="category_excursion">Excursion/Culture</string>
|
||||||
|
<string name="category_health">Health</string>
|
||||||
|
<string name="category_shopping">Shopping</string>
|
||||||
|
<string name="category_reimbursement">Reimbursement</string>
|
||||||
|
<string name="category_restaurant">Restaurant</string>
|
||||||
|
<string name="category_accomodation">Accommodation</string>
|
||||||
|
<string name="category_transport">Transport</string>
|
||||||
|
<string name="category_sport">Sport</string>
|
||||||
|
<!-- Project specific -->
|
||||||
|
<string name="new_project_action">What</string>
|
||||||
|
<string name="new_project_where">Where</string>
|
||||||
|
<string name="where_local">Local only</string>
|
||||||
|
<string name="where_cospend">Cospend</string>
|
||||||
|
<string name="where_ihatemoney">IHateMoney</string>
|
||||||
|
<string name="todo_join">Join existing project</string>
|
||||||
|
<string name="todo_create">Create new project</string>
|
||||||
|
<string name="import_tooltip">Import from file</string>
|
||||||
|
<string name="choose_account_project_dialog_title">Choose project</string>
|
||||||
|
<string name="choose_account_project_dialog_impossible">No projects found on this account.</string>
|
||||||
|
<string name="choose_project_management_action">Project</string>
|
||||||
|
<string name="project_added_success">Project added successfully.</string>
|
||||||
|
<string name="no_projects_text">You have no projects yet.</string>
|
||||||
|
<string name="configure_account_choice">Configure Nextcloud account</string>
|
||||||
|
<string name="add_project_choice">Add project manually</string>
|
||||||
|
<string name="no_members_text">No members in this project.</string>
|
||||||
|
<string name="no_bills_text">No bills found.</string>
|
||||||
|
<string name="member_already_exists">Member already exists.</string>
|
||||||
|
<string name="activity_dialog_title">Project: %1$s</string>
|
||||||
|
<string name="remove_project_confirmation">Project %1$s removed.</string>
|
||||||
|
<string name="file_saved_success">File saved: %1$s</string>
|
||||||
|
<string name="import_error_header">Import failed at row %d</string>
|
||||||
|
<string name="import_error_date">Invalid date format at row %d</string>
|
||||||
|
<string name="import_error_owers">Invalid owers at row %d</string>
|
||||||
|
<string name="add_member_dialog_title">Add Member</string>
|
||||||
|
<string name="edit_member_dialog_title">Edit Member</string>
|
||||||
|
<string name="member_edit_delete">Delete</string>
|
||||||
|
<string name="project_edition_no_change">No changes to save.</string>
|
||||||
|
<!-- Settlement -->
|
||||||
|
<string name="center_none">None (Optimal)</string>
|
||||||
|
<string name="settle_who">Who pays</string>
|
||||||
|
<string name="settle_to_whom">To whom</string>
|
||||||
|
<string name="settle_how_much">Amount</string>
|
||||||
|
<string name="simple_settle_share">Share</string>
|
||||||
|
<string name="simple_create_bills">Create bills</string>
|
||||||
|
<string name="settle_bill_what">Settlement</string>
|
||||||
|
<!-- Currencies -->
|
||||||
|
<string name="currency_dialog_title">Choose Currency (%s)</string>
|
||||||
|
<string name="setting_none">None</string>
|
||||||
|
<string name="setting_all">All</string>
|
||||||
|
<string name="currency_saved_success">Currency settings saved.</string>
|
||||||
|
<string name="main_currency">Main Currency</string>
|
||||||
|
<!-- Statistics -->
|
||||||
|
<string name="label_bills_suggested">Suggested Categories</string>
|
||||||
|
<string name="label_bills_skip">Skip</string>
|
||||||
|
<string name="stats_date_min">From</string>
|
||||||
|
<string name="stats_date_max">To</string>
|
||||||
|
<string name="stats_who">Member</string>
|
||||||
|
<string name="stats_paid">Paid</string>
|
||||||
|
<string name="stats_spent">Spent</string>
|
||||||
|
<string name="stats_balance">Balance</string>
|
||||||
|
<string name="total">Total: %1$s</string>
|
||||||
|
<!-- Errors Extra -->
|
||||||
|
<string name="error_project_connect_check">Connection failed: %1$s</string>
|
||||||
|
<string name="error_create_remote_project_helper">Creation failed: %1$s</string>
|
||||||
|
<string name="error_edit_remote_project_helper">Error updating remote project: %1$s</string>
|
||||||
|
<string name="remote_project_operation_no_network">Network unavailable for remote operation.</string>
|
||||||
|
<string name="error_scanning_bill_qr_code">Failed to parse QR code.</string>
|
||||||
|
<string name="error_token_mismatch">Authentication token mismatch. Please log in again.</string>
|
||||||
|
<string name="insufficient_access_level">You don\'t have permission to perform this action.</string>
|
||||||
|
<string name="delete_label_confirmation_title">Delete Label</string>
|
||||||
|
<string name="delete_label_confirmation_message">Are you sure you want to delete this label?</string>
|
||||||
|
<!-- About -->
|
||||||
|
<string name="about_version">Version %1$s</string>
|
||||||
|
<string name="about_maintainer_title">Maintainer</string>
|
||||||
|
<string name="about_license_title">License</string>
|
||||||
|
<string name="about_source_title">Source code</string>
|
||||||
|
<!-- New constants for backward compatibility or shared use -->
|
||||||
|
<string name="share_intent_title">Project %1$s</string>
|
||||||
|
<string name="share_chooser_title">Share %1$s</string>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,241 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
<string name="app_name">Capucha gastada</string>
|
||||||
|
<!-- Actions -->
|
||||||
|
<string name="action_new_bill">Nueva factura</string>
|
||||||
|
<string name="action_add_project">Añadir proyecto</string>
|
||||||
|
<string name="action_save">Guardar</string>
|
||||||
|
<string name="action_edit">Editar</string>
|
||||||
|
<string name="action_share">Compartir</string>
|
||||||
|
<string name="action_search">Buscar</string>
|
||||||
|
<string name="action_delete">Eliminar</string>
|
||||||
|
<string name="action_archive">Archivar</string>
|
||||||
|
<string name="action_unarchive">Desarchivar</string>
|
||||||
|
<string name="action_export">Exportar</string>
|
||||||
|
<string name="action_stats">Estadísticas</string>
|
||||||
|
<string name="action_settle">Salir</string>
|
||||||
|
<string name="action_scan_qrcode">Scan QR Code</string>
|
||||||
|
<string name="action_settings">Ajustes</string>
|
||||||
|
<string name="action_label_bills">Categorías faltantes de etiqueta</string>
|
||||||
|
<string name="action_logout">Cerrar sesión</string>
|
||||||
|
<string name="action_connect">Conectar</string>
|
||||||
|
<string name="action_discard">Descartar</string>
|
||||||
|
<string name="action_members">Miembros</string>
|
||||||
|
<string name="action_labels">Etiquetas</string>
|
||||||
|
<string name="action_currencies">Monedas</string>
|
||||||
|
<!-- Titles -->
|
||||||
|
<string name="title_stats">Estadísticas</string>
|
||||||
|
<string name="title_edit_project">Editar proyecto</string>
|
||||||
|
<string name="title_label_bills">Facturas de etiqueta</string>
|
||||||
|
<string name="title_labels">Administrar etiquetas</string>
|
||||||
|
<string name="title_about">Acerca de</string>
|
||||||
|
<string name="title_settle">Liquidar Proyecto</string>
|
||||||
|
<string name="title_share">Compartir proyecto</string>
|
||||||
|
<string name="title_add_project">Añadir proyecto</string>
|
||||||
|
<string name="title_add_category">Añadir categoría</string>
|
||||||
|
<string name="title_add_payment_mode">Añadir modo de pago</string>
|
||||||
|
<string name="title_account">Cuenta Nextcloud</string>
|
||||||
|
<string name="title_share_web">Enlace web</string>
|
||||||
|
<string name="title_share_qr">Cowspent link</string>
|
||||||
|
<string name="title_confirm">¿Estás seguro?</string>
|
||||||
|
<!-- Labels and Fields -->
|
||||||
|
<string name="label_all_bills">Todas las facturas</string>
|
||||||
|
<string name="label_categories">Categorías</string>
|
||||||
|
<string name="label_payment_modes">Modos de pago</string>
|
||||||
|
<string name="label_name">Nombre</string>
|
||||||
|
<string name="label_icon">Icon / Emoji</string>
|
||||||
|
<string name="label_color">Color</string>
|
||||||
|
<string name="label_weight">Peso</string>
|
||||||
|
<string name="label_activated">Activado</string>
|
||||||
|
<string name="label_password">Contraseña</string>
|
||||||
|
<string name="label_email">E-mail</string>
|
||||||
|
<string name="label_url">Dirección del servidor</string>
|
||||||
|
<string name="label_username">Usuario</string>
|
||||||
|
<string name="label_comment">Comentario</string>
|
||||||
|
<string name="label_what">¿Qué?</string>
|
||||||
|
<string name="label_payer">¿Quién pagó?</string>
|
||||||
|
<string name="label_owers">¿Para quién?</string>
|
||||||
|
<string name="label_repeat">Repetir cada</string>
|
||||||
|
<string name="label_mode">Modo</string>
|
||||||
|
<string name="label_category">Categoría</string>
|
||||||
|
<string name="label_project_id">ID del proyecto/nombre</string>
|
||||||
|
<string name="label_project_title">Título del proyecto</string>
|
||||||
|
<string name="label_use_sso">Usar cuenta de la aplicación Nextcloud</string>
|
||||||
|
<!-- Dialogs and Messages -->
|
||||||
|
<string name="dialog_unsaved_changes_title">Cambios sin guardar</string>
|
||||||
|
<string name="dialog_unsaved_changes_msg">¿Guardar cambios antes de salir?</string>
|
||||||
|
<string name="dialog_confirm_remove_project_msg">El proyecto remoto no se eliminará.</string>
|
||||||
|
<string name="dialog_sync_error_title">Error de sincronización</string>
|
||||||
|
<string name="dialog_sync_error_msg">Sincronización fallida para %1$s.\n\n%2$s</string>
|
||||||
|
<string name="dialog_balanced_msg">Los gastos ya están equilibrados.</string>
|
||||||
|
<string name="msg_project_added">Proyecto %1$s añadido</string>
|
||||||
|
<string name="msg_bill_labeled_done">Todas las facturas etiquetadas</string>
|
||||||
|
<string name="msg_no_suggestions">No hay sugerencias</string>
|
||||||
|
<string name="msg_auth_warning">Requiere Gasto v0.3.4+.</string>
|
||||||
|
<string name="msg_link_copied">Enlace copiado al portapapeles</string>
|
||||||
|
<string name="msg_share_qr">Escanea el código QR o comparte el enlace para unirte.</string>
|
||||||
|
<string name="msg_share_web">Enlace para acceso al navegador web.</string>
|
||||||
|
<string name="msg_share_qr_warn">Comparte este enlace con un usuario de Cowged.</string>
|
||||||
|
<string name="msg_settle_intro">Acuerdo para %1$s:</string>
|
||||||
|
<string name="msg_settle_sentence">%1$s debe %3$.2f a %2$s</string>
|
||||||
|
<string name="msg_stats_intro">Estadísticas de %1$s:</string>
|
||||||
|
<string name="msg_stats_header">Miembro Pagado | Gasto | Saldo)</string>
|
||||||
|
<string name="msg_logged_in_as">Logged in as %1$s</string>
|
||||||
|
<!-- Errors -->
|
||||||
|
<string name="error_generic">Error</string>
|
||||||
|
<string name="error_loading">Cargando</string>
|
||||||
|
<string name="error_no_projects">No se encontraron proyectos</string>
|
||||||
|
<string name="error_no_members">Ningún miembro encontrado</string>
|
||||||
|
<string name="error_no_bills">No se encontraron facturas</string>
|
||||||
|
<string name="error_no_member">Se requiere al menos un miembro</string>
|
||||||
|
<string name="error_maintenance_mode">El servidor está en modo mantenimiento</string>
|
||||||
|
<string name="error_400">400 Solicitud errónea</string>
|
||||||
|
<string name="error_401">401 no autorizado</string>
|
||||||
|
<string name="error_403">403 Prohibida</string>
|
||||||
|
<string name="error_404">404 no encontrado</string>
|
||||||
|
<string name="error_sync">Sincronización fallida: %1$s</string>
|
||||||
|
<string name="error_invalid_login">Inicio de sesión no válido: %1$s</string>
|
||||||
|
<string name="error_auth">Nombre de usuario o contraseña incorrectos</string>
|
||||||
|
<string name="error_json">Respuesta del servidor inválida</string>
|
||||||
|
<string name="error_req_failed">Petición fallida</string>
|
||||||
|
<string name="error_invalid_email">E-mail inválido</string>
|
||||||
|
<string name="error_invalid_project_id">ID de proyecto inválido</string>
|
||||||
|
<string name="error_invalid_project_name">Título del proyecto inválido</string>
|
||||||
|
<string name="error_invalid_bill_name">Nombre de factura no válido</string>
|
||||||
|
<string name="error_invalid_bill_date">Fecha de factura inválida</string>
|
||||||
|
<string name="error_invalid_bill_payer">Pagador requerido</string>
|
||||||
|
<string name="error_invalid_bill_owers">Propietarios requeridos</string>
|
||||||
|
<string name="error_no_network">No hay conexión de red</string>
|
||||||
|
<string name="error_server">Error del servidor</string>
|
||||||
|
<string name="error_io">Conexión con el servidor dañada</string>
|
||||||
|
<string name="error_share_impossible">No se puede compartir este proyecto</string>
|
||||||
|
<!-- Drawer / Common UI -->
|
||||||
|
<string name="drawer_no_account">Conectar a la cuenta de Nextcloud</string>
|
||||||
|
<string name="drawer_last_sync">Última sincronización: %1$02d:%2$02d</string>
|
||||||
|
<string name="simple_cancel">Cancelar</string>
|
||||||
|
<string name="simple_ok" tools:ignore="ButtonCase,Typos">Ok</string>
|
||||||
|
<string name="simple_yes">Sí</string>
|
||||||
|
<string name="simple_no">Nu</string>
|
||||||
|
<string name="simple_close">Cerrar</string>
|
||||||
|
<!-- Settings -->
|
||||||
|
<string name="settings_appearance">Apariencia</string>
|
||||||
|
<string name="settings_network">Red</string>
|
||||||
|
<string name="settings_other">Otro</string>
|
||||||
|
<string name="settings_night_mode">Tema</string>
|
||||||
|
<string name="settings_offline_mode">Modo sin conexión</string>
|
||||||
|
<string name="settings_offline_mode_summary">Solo sincronizar manualmente.</string>
|
||||||
|
<string name="settings_color_custom">Color personalizado</string>
|
||||||
|
<string name="settings_color_mode">Selección de color</string>
|
||||||
|
<string name="settings_show_archived">Mostrar proyectos archivados</string>
|
||||||
|
<string name="settings_beta_features">Características beta</string>
|
||||||
|
<string name="settings_beta_features_summary">Activar características experimentales. Úsalo bajo tu propio riesgo.</string>
|
||||||
|
<string name="settings_url_warn_http">ADVERTENCIA: \"http\" no es seguro. Use \"https\".</string>
|
||||||
|
<string name="settings_colorpicker_title">Elegir color</string>
|
||||||
|
<string name="pref_value_color_system">Sistema</string>
|
||||||
|
<string name="pref_value_color_server">Nextcloud</string>
|
||||||
|
<string name="pref_value_color_manual">Manual</string>
|
||||||
|
<string name="pref_value_theme_light">Claro</string>
|
||||||
|
<string name="pref_value_theme_dark">Oscuro</string>
|
||||||
|
<string name="pref_value_theme_system">Seguir sistema</string>
|
||||||
|
<!-- Constants (Do not translate) -->
|
||||||
|
<!-- Enums and Lists -->
|
||||||
|
<string name="repeat_no">No repetir</string>
|
||||||
|
<string name="repeat_day">Diario</string>
|
||||||
|
<string name="repeat_week">Semanal</string>
|
||||||
|
<string name="repeat_fortnight">Fortnocturno</string>
|
||||||
|
<string name="repeat_month">Mensual</string>
|
||||||
|
<string name="repeat_year">Anual</string>
|
||||||
|
<string name="payment_mode_none">Ninguna</string>
|
||||||
|
<string name="payment_mode_all">Todos</string>
|
||||||
|
<string name="payment_mode_credit_card">Tarjeta de crédito</string>
|
||||||
|
<string name="payment_mode_cash">Dinero</string>
|
||||||
|
<string name="payment_mode_check">Comprobar</string>
|
||||||
|
<string name="payment_mode_online">En línea</string>
|
||||||
|
<string name="payment_mode_transfer">Transferir</string>
|
||||||
|
<string name="category_none">Ninguna</string>
|
||||||
|
<string name="category_all">Todos</string>
|
||||||
|
<string name="category_all_except_reimbursement">Todos excepto reembolso</string>
|
||||||
|
<string name="category_groceries">Comestible</string>
|
||||||
|
<string name="category_leisure">Bar/Fiesta</string>
|
||||||
|
<string name="category_rent">Alquiler</string>
|
||||||
|
<string name="category_bills">Factura</string>
|
||||||
|
<string name="category_excursion">Excursión/Cultura</string>
|
||||||
|
<string name="category_health">Salud</string>
|
||||||
|
<string name="category_shopping">Compras</string>
|
||||||
|
<string name="category_reimbursement">Reembolso</string>
|
||||||
|
<string name="category_restaurant">Restaurante</string>
|
||||||
|
<string name="category_accomodation">Alojamiento</string>
|
||||||
|
<string name="category_transport">Transporte</string>
|
||||||
|
<string name="category_sport">Deporte</string>
|
||||||
|
<!-- Project specific -->
|
||||||
|
<string name="new_project_action">Qué</string>
|
||||||
|
<string name="new_project_where">Donde</string>
|
||||||
|
<string name="where_local">Solo local</string>
|
||||||
|
<string name="where_cospend">Gastar</string>
|
||||||
|
<string name="where_ihatemoney">Dinero IHate</string>
|
||||||
|
<string name="todo_join">Unirse al proyecto existente</string>
|
||||||
|
<string name="todo_create">Crear nuevo proyecto</string>
|
||||||
|
<string name="import_tooltip">Importar desde archivo</string>
|
||||||
|
<string name="choose_account_project_dialog_title">Elegir proyecto</string>
|
||||||
|
<string name="choose_account_project_dialog_impossible">No se encontraron proyectos en esta cuenta.</string>
|
||||||
|
<string name="choose_project_management_action">Projekt</string>
|
||||||
|
<string name="project_added_success">Proyecto añadido correctamente.</string>
|
||||||
|
<string name="no_projects_text">Aún no tienes proyectos.</string>
|
||||||
|
<string name="configure_account_choice">Configurar cuenta Nextcloud</string>
|
||||||
|
<string name="add_project_choice">Añadir proyecto manualmente</string>
|
||||||
|
<string name="no_members_text">No hay miembros en este proyecto.</string>
|
||||||
|
<string name="no_bills_text">No se encontraron facturas.</string>
|
||||||
|
<string name="member_already_exists">El miembro ya existe.</string>
|
||||||
|
<string name="activity_dialog_title">Proyecto: %1$s</string>
|
||||||
|
<string name="remove_project_confirmation">Proyecto %1$s eliminado.</string>
|
||||||
|
<string name="file_saved_success">Archivo guardado: %1$s</string>
|
||||||
|
<string name="import_error_header">Error al importar en la fila %d</string>
|
||||||
|
<string name="import_error_date">Formato de fecha no válido en la fila %d</string>
|
||||||
|
<string name="import_error_owers">Dueños no válidos en la fila %d</string>
|
||||||
|
<string name="add_member_dialog_title">Añadir miembro</string>
|
||||||
|
<string name="edit_member_dialog_title">Editar miembro</string>
|
||||||
|
<string name="member_edit_delete">Eliminar</string>
|
||||||
|
<string name="project_edition_no_change">No hay cambios para guardar.</string>
|
||||||
|
<!-- Settlement -->
|
||||||
|
<string name="center_none">Ninguno (óptimo)</string>
|
||||||
|
<string name="settle_who">Quién paga</string>
|
||||||
|
<string name="settle_to_whom">A quien</string>
|
||||||
|
<string name="settle_how_much">Cantidad</string>
|
||||||
|
<string name="simple_settle_share">Compartir</string>
|
||||||
|
<string name="simple_create_bills">Crear facturas</string>
|
||||||
|
<string name="settle_bill_what">Acuerdo</string>
|
||||||
|
<!-- Currencies -->
|
||||||
|
<string name="currency_dialog_title">Elija la moneda (%s)</string>
|
||||||
|
<string name="setting_none">Ninguna</string>
|
||||||
|
<string name="setting_all">Todos</string>
|
||||||
|
<string name="currency_saved_success">Ajustes de moneda guardados.</string>
|
||||||
|
<string name="main_currency">Moneda principal</string>
|
||||||
|
<!-- Statistics -->
|
||||||
|
<string name="label_bills_suggested">Categorías sugeridas</string>
|
||||||
|
<string name="label_bills_skip">Saltar</string>
|
||||||
|
<string name="stats_date_min">De</string>
|
||||||
|
<string name="stats_date_max">A</string>
|
||||||
|
<string name="stats_who">Miembro</string>
|
||||||
|
<string name="stats_paid">Pagado</string>
|
||||||
|
<string name="stats_spent">Escrito</string>
|
||||||
|
<string name="stats_balance">Saldo</string>
|
||||||
|
<string name="total">Total: %1$s</string>
|
||||||
|
<!-- Errors Extra -->
|
||||||
|
<string name="error_project_connect_check">Conexión fallida: %1$s</string>
|
||||||
|
<string name="error_create_remote_project_helper">Creación fallida: %1$s</string>
|
||||||
|
<string name="error_edit_remote_project_helper">Error al actualizar proyecto remoto: %1$s</string>
|
||||||
|
<string name="remote_project_operation_no_network">Red no disponible para operaciones remotas.</string>
|
||||||
|
<string name="error_scanning_bill_qr_code">Error al analizar el código QR.</string>
|
||||||
|
<string name="error_token_mismatch">El token de autenticación no coincide. Por favor, inicia sesión de nuevo.</string>
|
||||||
|
<string name="insufficient_access_level">No tienes permiso para realizar esta acción.</string>
|
||||||
|
<string name="delete_label_confirmation_title">Eliminar Etiqueta</string>
|
||||||
|
<string name="delete_label_confirmation_message">¿Está seguro que desea eliminar esta etiqueta?</string>
|
||||||
|
<!-- About -->
|
||||||
|
<string name="about_version">Versión %1$s</string>
|
||||||
|
<string name="about_maintainer_title">Mantenedor</string>
|
||||||
|
<string name="about_license_title">Licencia</string>
|
||||||
|
<string name="about_source_title">Código fuente</string>
|
||||||
|
<!-- New constants for backward compatibility or shared use -->
|
||||||
|
<string name="share_intent_title">Proyecto %1$s</string>
|
||||||
|
<string name="share_chooser_title">Compartir %1$s</string>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,241 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
<string name="app_name">Lehmä</string>
|
||||||
|
<!-- Actions -->
|
||||||
|
<string name="action_new_bill">Uusi lasku</string>
|
||||||
|
<string name="action_add_project">Lisää projekti</string>
|
||||||
|
<string name="action_save">Tallenna</string>
|
||||||
|
<string name="action_edit">Muokkaa</string>
|
||||||
|
<string name="action_share">Jaa</string>
|
||||||
|
<string name="action_search">Etsi</string>
|
||||||
|
<string name="action_delete">Poista</string>
|
||||||
|
<string name="action_archive">Arkistoi</string>
|
||||||
|
<string name="action_unarchive">Poista</string>
|
||||||
|
<string name="action_export">Vie</string>
|
||||||
|
<string name="action_stats">Tilastot</string>
|
||||||
|
<string name="action_settle">Ratkaise</string>
|
||||||
|
<string name="action_scan_qrcode">Scan QR Code</string>
|
||||||
|
<string name="action_settings">Asetukset</string>
|
||||||
|
<string name="action_label_bills">Tunnisteet puuttuvat kategoriat</string>
|
||||||
|
<string name="action_logout">Kirjaudu Ulos</string>
|
||||||
|
<string name="action_connect">Yhdistä</string>
|
||||||
|
<string name="action_discard">Hylkää</string>
|
||||||
|
<string name="action_members">Jäsenet</string>
|
||||||
|
<string name="action_labels">Tunnisteet</string>
|
||||||
|
<string name="action_currencies">Valuutat</string>
|
||||||
|
<!-- Titles -->
|
||||||
|
<string name="title_stats">Tilastot</string>
|
||||||
|
<string name="title_edit_project">Muokkaa projektia</string>
|
||||||
|
<string name="title_label_bills">Merkitse Laskut</string>
|
||||||
|
<string name="title_labels">Hallitse Tunnisteita</string>
|
||||||
|
<string name="title_about">Tietoja</string>
|
||||||
|
<string name="title_settle">Ratkaise Projekti</string>
|
||||||
|
<string name="title_share">Jaa Projekti</string>
|
||||||
|
<string name="title_add_project">Lisää Projekti</string>
|
||||||
|
<string name="title_add_category">Lisää Kategoria</string>
|
||||||
|
<string name="title_add_payment_mode">Lisää Maksutila</string>
|
||||||
|
<string name="title_account">Nextcloud Tili</string>
|
||||||
|
<string name="title_share_web">Linkki</string>
|
||||||
|
<string name="title_share_qr">Cowspent link</string>
|
||||||
|
<string name="title_confirm">Oletko varma?</string>
|
||||||
|
<!-- Labels and Fields -->
|
||||||
|
<string name="label_all_bills">Kaikki laskut</string>
|
||||||
|
<string name="label_categories">Kategoriat</string>
|
||||||
|
<string name="label_payment_modes">Maksutavat</string>
|
||||||
|
<string name="label_name">Nimi</string>
|
||||||
|
<string name="label_icon">Icon / Emoji</string>
|
||||||
|
<string name="label_color">Väri</string>
|
||||||
|
<string name="label_weight">Paino</string>
|
||||||
|
<string name="label_activated">Aktivoitu</string>
|
||||||
|
<string name="label_password">Salasana</string>
|
||||||
|
<string name="label_email">Sähköposti</string>
|
||||||
|
<string name="label_url">Palvelimen osoite</string>
|
||||||
|
<string name="label_username">Käyttäjätunnus</string>
|
||||||
|
<string name="label_comment">Kommentti</string>
|
||||||
|
<string name="label_what">Mitä?</string>
|
||||||
|
<string name="label_payer">Kuka maksoi?</string>
|
||||||
|
<string name="label_owers">Kenen puolesta?</string>
|
||||||
|
<string name="label_repeat">Toista joka</string>
|
||||||
|
<string name="label_mode">Tila</string>
|
||||||
|
<string name="label_category">Kategoria</string>
|
||||||
|
<string name="label_project_id">Projektin ID/nimi</string>
|
||||||
|
<string name="label_project_title">Projektin otsikko</string>
|
||||||
|
<string name="label_use_sso">Käytä Nextcloud -sovellustiliä</string>
|
||||||
|
<!-- Dialogs and Messages -->
|
||||||
|
<string name="dialog_unsaved_changes_title">Tallentamattomat muutokset</string>
|
||||||
|
<string name="dialog_unsaved_changes_msg">Tallennetaanko muutokset ennen lähtöä?</string>
|
||||||
|
<string name="dialog_confirm_remove_project_msg">Etäprojektia ei poisteta.</string>
|
||||||
|
<string name="dialog_sync_error_title">Virhe synkronoinnissa</string>
|
||||||
|
<string name="dialog_sync_error_msg">%1$ssynkronointi epäonnistui.\n\n%2$s</string>
|
||||||
|
<string name="dialog_balanced_msg">Menot ovat jo tasapainossa.</string>
|
||||||
|
<string name="msg_project_added">Projekti %1$s lisätty</string>
|
||||||
|
<string name="msg_bill_labeled_done">Kaikki laskut merkitty</string>
|
||||||
|
<string name="msg_no_suggestions">Ei ehdotuksia</string>
|
||||||
|
<string name="msg_auth_warning">Tarvitsee Kustannukset v0.3.4+.</string>
|
||||||
|
<string name="msg_link_copied">Linkki kopioitu leikepöydälle</string>
|
||||||
|
<string name="msg_share_qr">Skannaa QR-koodi tai jaa linkki johon liittyä.</string>
|
||||||
|
<string name="msg_share_web">Linkki selaimen käyttöön.</string>
|
||||||
|
<string name="msg_share_qr_warn">Jaa tämä linkki Cowkäytetyn käyttäjän kanssa.</string>
|
||||||
|
<string name="msg_settle_intro">%1$s:n toimitus:</string>
|
||||||
|
<string name="msg_settle_sentence">%1$s owes %3$.2f to %2$s</string>
|
||||||
|
<string name="msg_stats_intro">Tilastot %1$s:</string>
|
||||||
|
<string name="msg_stats_header">Jäsenen (Käytetty Saldo)</string>
|
||||||
|
<string name="msg_logged_in_as">Kirjautunut sisään nimellä %1$s</string>
|
||||||
|
<!-- Errors -->
|
||||||
|
<string name="error_generic">Virhe</string>
|
||||||
|
<string name="error_loading">Ladataan</string>
|
||||||
|
<string name="error_no_projects">Projekteja ei löytynyt</string>
|
||||||
|
<string name="error_no_members">Jäseniä ei löytynyt</string>
|
||||||
|
<string name="error_no_bills">Laskuja ei löytynyt</string>
|
||||||
|
<string name="error_no_member">Vaadittu vähintään yksi jäsen</string>
|
||||||
|
<string name="error_maintenance_mode">Palvelin on huoltotilassa</string>
|
||||||
|
<string name="error_400">400 Virheellinen pyyntö</string>
|
||||||
|
<string name="error_401">401 Luvaton</string>
|
||||||
|
<string name="error_403">403 Kielletty</string>
|
||||||
|
<string name="error_404">404 Ei Löytynyt</string>
|
||||||
|
<string name="error_sync">Synkronointi epäonnistui: %1$s</string>
|
||||||
|
<string name="error_invalid_login">Virheellinen kirjautuminen: %1$s</string>
|
||||||
|
<string name="error_auth">Väärä käyttäjätunnus tai salasana</string>
|
||||||
|
<string name="error_json">Virheellinen palvelimen vastaus</string>
|
||||||
|
<string name="error_req_failed">Pyyntö epäonnistui</string>
|
||||||
|
<string name="error_invalid_email">Virheellinen sähköpostiosoite</string>
|
||||||
|
<string name="error_invalid_project_id">Virheellinen projektin tunnus</string>
|
||||||
|
<string name="error_invalid_project_name">Virheellinen projektin otsikko</string>
|
||||||
|
<string name="error_invalid_bill_name">Virheellinen laskun nimi</string>
|
||||||
|
<string name="error_invalid_bill_date">Virheellinen laskun päivämäärä</string>
|
||||||
|
<string name="error_invalid_bill_payer">Maksaja vaaditaan</string>
|
||||||
|
<string name="error_invalid_bill_owers">Uutiskirjeet vaaditaan</string>
|
||||||
|
<string name="error_no_network">Ei verkkoyhteyttä</string>
|
||||||
|
<string name="error_server">Palvelin virhe</string>
|
||||||
|
<string name="error_io">Palvelinyhteys rikki</string>
|
||||||
|
<string name="error_share_impossible">Projektia ei voi jakaa</string>
|
||||||
|
<!-- Drawer / Common UI -->
|
||||||
|
<string name="drawer_no_account">Yhdistä Nextcloud tiliin</string>
|
||||||
|
<string name="drawer_last_sync">Last sync: %1$02d:%2$02d</string>
|
||||||
|
<string name="simple_cancel">Peruuta</string>
|
||||||
|
<string name="simple_ok" tools:ignore="ButtonCase,Typos">OK</string>
|
||||||
|
<string name="simple_yes">Kyllä</string>
|
||||||
|
<string name="simple_no">Ei</string>
|
||||||
|
<string name="simple_close">Sulje</string>
|
||||||
|
<!-- Settings -->
|
||||||
|
<string name="settings_appearance">Ulkoasu</string>
|
||||||
|
<string name="settings_network">Verkko</string>
|
||||||
|
<string name="settings_other">Muu</string>
|
||||||
|
<string name="settings_night_mode">Teema</string>
|
||||||
|
<string name="settings_offline_mode">Offline-tila</string>
|
||||||
|
<string name="settings_offline_mode_summary">Synkronoi vain käsin.</string>
|
||||||
|
<string name="settings_color_custom">Mukautettu väri</string>
|
||||||
|
<string name="settings_color_mode">Värin Valinta</string>
|
||||||
|
<string name="settings_show_archived">Näytä arkistoidut projektit</string>
|
||||||
|
<string name="settings_beta_features">Beta Ominaisuudet</string>
|
||||||
|
<string name="settings_beta_features_summary">Ota käyttöön kokeelliset ominaisuudet. Käytä omalla vastuullasi.</string>
|
||||||
|
<string name="settings_url_warn_http">VAROITUS: \"http\" ei ole turvallinen. Käytä \"℃\".</string>
|
||||||
|
<string name="settings_colorpicker_title">Valitse Väri</string>
|
||||||
|
<string name="pref_value_color_system">Järjestelmä</string>
|
||||||
|
<string name="pref_value_color_server">Nextcloud</string>
|
||||||
|
<string name="pref_value_color_manual">Manuaalinen</string>
|
||||||
|
<string name="pref_value_theme_light">Vaalea</string>
|
||||||
|
<string name="pref_value_theme_dark">Tumma</string>
|
||||||
|
<string name="pref_value_theme_system">Seuraa järjestelmää</string>
|
||||||
|
<!-- Constants (Do not translate) -->
|
||||||
|
<!-- Enums and Lists -->
|
||||||
|
<string name="repeat_no">Ei toistumista</string>
|
||||||
|
<string name="repeat_day">Päivittäin</string>
|
||||||
|
<string name="repeat_week">Viikoittain</string>
|
||||||
|
<string name="repeat_fortnight">Onneksi</string>
|
||||||
|
<string name="repeat_month">Kuukausittain</string>
|
||||||
|
<string name="repeat_year">Vuosittain</string>
|
||||||
|
<string name="payment_mode_none">Ei Mitään</string>
|
||||||
|
<string name="payment_mode_all">Kaikki</string>
|
||||||
|
<string name="payment_mode_credit_card">Luottokortti</string>
|
||||||
|
<string name="payment_mode_cash">Käteinen</string>
|
||||||
|
<string name="payment_mode_check">Tarkista</string>
|
||||||
|
<string name="payment_mode_online">Paikalla</string>
|
||||||
|
<string name="payment_mode_transfer">Siirto</string>
|
||||||
|
<string name="category_none">Ei Mitään</string>
|
||||||
|
<string name="category_all">Kaikki</string>
|
||||||
|
<string name="category_all_except_reimbursement">Kaikki paitsi korvaukset</string>
|
||||||
|
<string name="category_groceries">Ruokakauppa</string>
|
||||||
|
<string name="category_leisure">Baari/Juhla</string>
|
||||||
|
<string name="category_rent">Vuokra</string>
|
||||||
|
<string name="category_bills">Lasku</string>
|
||||||
|
<string name="category_excursion">Retki/Kulttuuri</string>
|
||||||
|
<string name="category_health">Terveys</string>
|
||||||
|
<string name="category_shopping">Ostokset</string>
|
||||||
|
<string name="category_reimbursement">Korvaus</string>
|
||||||
|
<string name="category_restaurant">Ravintola</string>
|
||||||
|
<string name="category_accomodation">Majoitus</string>
|
||||||
|
<string name="category_transport">Kuljetus</string>
|
||||||
|
<string name="category_sport">Urheilu</string>
|
||||||
|
<!-- Project specific -->
|
||||||
|
<string name="new_project_action">Mitä</string>
|
||||||
|
<string name="new_project_where">Missä</string>
|
||||||
|
<string name="where_local">Vain paikallinen</string>
|
||||||
|
<string name="where_cospend">Kulut</string>
|
||||||
|
<string name="where_ihatemoney">IHateMoney</string>
|
||||||
|
<string name="todo_join">Liity olemassa olevaan projektiin</string>
|
||||||
|
<string name="todo_create">Luo uusi projekti</string>
|
||||||
|
<string name="import_tooltip">Tuo tiedostosta</string>
|
||||||
|
<string name="choose_account_project_dialog_title">Valitse projekti</string>
|
||||||
|
<string name="choose_account_project_dialog_impossible">Tällä tilillä ei löytynyt projekteja.</string>
|
||||||
|
<string name="choose_project_management_action">Projekti</string>
|
||||||
|
<string name="project_added_success">Projekti lisätty onnistuneesti.</string>
|
||||||
|
<string name="no_projects_text">Sinulla ei ole vielä projekteja.</string>
|
||||||
|
<string name="configure_account_choice">Määritä Nextcloud tili</string>
|
||||||
|
<string name="add_project_choice">Lisää projekti käsin</string>
|
||||||
|
<string name="no_members_text">Ei jäseniä tässä projektissa.</string>
|
||||||
|
<string name="no_bills_text">Laskuja ei löytynyt.</string>
|
||||||
|
<string name="member_already_exists">Jäsentä on jo olemassa.</string>
|
||||||
|
<string name="activity_dialog_title">Projekti: %1$s</string>
|
||||||
|
<string name="remove_project_confirmation">Projekti %1$s poistettu.</string>
|
||||||
|
<string name="file_saved_success">Tiedosto tallennettu: %1$s</string>
|
||||||
|
<string name="import_error_header">Tuonti epäonnistui rivillä %d</string>
|
||||||
|
<string name="import_error_date">Päivämäärän muoto ei kelpaa rivillä %d</string>
|
||||||
|
<string name="import_error_owers">Virheelliset ruohonleikkurit rivillä %d</string>
|
||||||
|
<string name="add_member_dialog_title">Lisää Jäsen</string>
|
||||||
|
<string name="edit_member_dialog_title">Muokkaa Jäsen</string>
|
||||||
|
<string name="member_edit_delete">Poista</string>
|
||||||
|
<string name="project_edition_no_change">Tallennettavia muutoksia ei ole.</string>
|
||||||
|
<!-- Settlement -->
|
||||||
|
<string name="center_none">Ei Mitään (valinnainen)</string>
|
||||||
|
<string name="settle_who">Kuka maksaa</string>
|
||||||
|
<string name="settle_to_whom">Kenelle</string>
|
||||||
|
<string name="settle_how_much">Määrä</string>
|
||||||
|
<string name="simple_settle_share">Jaa</string>
|
||||||
|
<string name="simple_create_bills">Luo laskuja</string>
|
||||||
|
<string name="settle_bill_what">Selvitys</string>
|
||||||
|
<!-- Currencies -->
|
||||||
|
<string name="currency_dialog_title">Valitse Valuutta (%s)</string>
|
||||||
|
<string name="setting_none">Ei Mitään</string>
|
||||||
|
<string name="setting_all">Kaikki</string>
|
||||||
|
<string name="currency_saved_success">Valuutan asetukset tallennettu.</string>
|
||||||
|
<string name="main_currency">Päävaluutta</string>
|
||||||
|
<!-- Statistics -->
|
||||||
|
<string name="label_bills_suggested">Ehdotetut Kategoriat</string>
|
||||||
|
<string name="label_bills_skip">Ohita</string>
|
||||||
|
<string name="stats_date_min">Alkaen</string>
|
||||||
|
<string name="stats_date_max">Vastaanottaja</string>
|
||||||
|
<string name="stats_who">Jäsen</string>
|
||||||
|
<string name="stats_paid">Maksettu</string>
|
||||||
|
<string name="stats_spent">Käytetty</string>
|
||||||
|
<string name="stats_balance">Saldo</string>
|
||||||
|
<string name="total">Yhteensä: %1$s</string>
|
||||||
|
<!-- Errors Extra -->
|
||||||
|
<string name="error_project_connect_check">Yhteys epäonnistui: %1$s</string>
|
||||||
|
<string name="error_create_remote_project_helper">Luonti epäonnistui: %1$s</string>
|
||||||
|
<string name="error_edit_remote_project_helper">Virhe päivitettäessä etäporjektia: %1$s</string>
|
||||||
|
<string name="remote_project_operation_no_network">Verkko ei ole käytettävissä etäkäyttöön.</string>
|
||||||
|
<string name="error_scanning_bill_qr_code">QR-koodin jäsentäminen epäonnistui.</string>
|
||||||
|
<string name="error_token_mismatch">Todennustunnus ei täsmää. Kirjaudu sisään uudelleen.</string>
|
||||||
|
<string name="insufficient_access_level">Sinulla ei ole oikeuksia suorittaa tätä toimintoa.</string>
|
||||||
|
<string name="delete_label_confirmation_title">Poista Tunniste</string>
|
||||||
|
<string name="delete_label_confirmation_message">Oletko varma, että haluat poistaa tämän tunnisteen?</string>
|
||||||
|
<!-- About -->
|
||||||
|
<string name="about_version">Versio %1$s</string>
|
||||||
|
<string name="about_maintainer_title">Ylläpitäjä</string>
|
||||||
|
<string name="about_license_title">Lisenssi</string>
|
||||||
|
<string name="about_source_title">Lähdekoodi</string>
|
||||||
|
<!-- New constants for backward compatibility or shared use -->
|
||||||
|
<string name="share_intent_title">Projekti %1$s</string>
|
||||||
|
<string name="share_chooser_title">Jaa %1$s</string>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,241 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
<string name="app_name">Vache dépensée</string>
|
||||||
|
<!-- Actions -->
|
||||||
|
<string name="action_new_bill">Nouvelle facture</string>
|
||||||
|
<string name="action_add_project">Ajouter un projet</string>
|
||||||
|
<string name="action_save">Enregistrer</string>
|
||||||
|
<string name="action_edit">Editer</string>
|
||||||
|
<string name="action_share">Partager</string>
|
||||||
|
<string name="action_search">Chercher</string>
|
||||||
|
<string name="action_delete">Supprimez</string>
|
||||||
|
<string name="action_archive">Archive</string>
|
||||||
|
<string name="action_unarchive">Désarchiver</string>
|
||||||
|
<string name="action_export">Exportation</string>
|
||||||
|
<string name="action_stats">Stats</string>
|
||||||
|
<string name="action_settle">Régler</string>
|
||||||
|
<string name="action_scan_qrcode">Scan QR Code</string>
|
||||||
|
<string name="action_settings">Réglages</string>
|
||||||
|
<string name="action_label_bills">Catégories manquantes</string>
|
||||||
|
<string name="action_logout">Déconnexion</string>
|
||||||
|
<string name="action_connect">Connecter</string>
|
||||||
|
<string name="action_discard">Abandonner</string>
|
||||||
|
<string name="action_members">Membres</string>
|
||||||
|
<string name="action_labels">Étiquettes</string>
|
||||||
|
<string name="action_currencies">Devises</string>
|
||||||
|
<!-- Titles -->
|
||||||
|
<string name="title_stats">Statistiques</string>
|
||||||
|
<string name="title_edit_project">Modifier le projet</string>
|
||||||
|
<string name="title_label_bills">Étiquettes de factures</string>
|
||||||
|
<string name="title_labels">Gérer les étiquettes</string>
|
||||||
|
<string name="title_about">À propos de</string>
|
||||||
|
<string name="title_settle">Régler le projet</string>
|
||||||
|
<string name="title_share">Partager le projet</string>
|
||||||
|
<string name="title_add_project">Ajouter un projet</string>
|
||||||
|
<string name="title_add_category">Ajouter une catégorie</string>
|
||||||
|
<string name="title_add_payment_mode">Ajouter un mode de paiement</string>
|
||||||
|
<string name="title_account">Compte Nextcloud</string>
|
||||||
|
<string name="title_share_web">Lien web</string>
|
||||||
|
<string name="title_share_qr">Cowspent link</string>
|
||||||
|
<string name="title_confirm">Êtes-vous sûr(e) ?</string>
|
||||||
|
<!-- Labels and Fields -->
|
||||||
|
<string name="label_all_bills">Toutes les factures</string>
|
||||||
|
<string name="label_categories">Catégories</string>
|
||||||
|
<string name="label_payment_modes">Modes de paiement</string>
|
||||||
|
<string name="label_name">Nom</string>
|
||||||
|
<string name="label_icon">Icon / Emoji</string>
|
||||||
|
<string name="label_color">Couleur</string>
|
||||||
|
<string name="label_weight">Poids</string>
|
||||||
|
<string name="label_activated">Activé</string>
|
||||||
|
<string name="label_password">Mot de passe</string>
|
||||||
|
<string name="label_email">Courriel</string>
|
||||||
|
<string name="label_url">Adresse du serveur</string>
|
||||||
|
<string name="label_username">Nom d\'utilisateur</string>
|
||||||
|
<string name="label_comment">Commenter</string>
|
||||||
|
<string name="label_what">Quoi?</string>
|
||||||
|
<string name="label_payer">Qui a payé ?</string>
|
||||||
|
<string name="label_owers">Pour qui?</string>
|
||||||
|
<string name="label_repeat">Répéter toutes les</string>
|
||||||
|
<string name="label_mode">Mode</string>
|
||||||
|
<string name="label_category">Catégorie</string>
|
||||||
|
<string name="label_project_id">ID/nom du projet</string>
|
||||||
|
<string name="label_project_title">Titre du projet</string>
|
||||||
|
<string name="label_use_sso">Utiliser le compte Nextcloud App</string>
|
||||||
|
<!-- Dialogs and Messages -->
|
||||||
|
<string name="dialog_unsaved_changes_title">Modifications non enregistrées</string>
|
||||||
|
<string name="dialog_unsaved_changes_msg">Enregistrer les modifications avant de partir?</string>
|
||||||
|
<string name="dialog_confirm_remove_project_msg">Le projet distant ne sera pas supprimé.</string>
|
||||||
|
<string name="dialog_sync_error_title">Erreur de synchronisation</string>
|
||||||
|
<string name="dialog_sync_error_msg">Échec de la synchronisation pour %1$s.\n\n%2$s</string>
|
||||||
|
<string name="dialog_balanced_msg">Les dépenses sont déjà équilibrées.</string>
|
||||||
|
<string name="msg_project_added">Projet %1$s ajouté</string>
|
||||||
|
<string name="msg_bill_labeled_done">Toutes les factures étiquetées</string>
|
||||||
|
<string name="msg_no_suggestions">Aucune suggestion</string>
|
||||||
|
<string name="msg_auth_warning">Nécessite Cospend v0.3.4+.</string>
|
||||||
|
<string name="msg_link_copied">Lien copié dans le presse-papiers</string>
|
||||||
|
<string name="msg_share_qr">Scannez le code QR ou partagez le lien pour vous inscrire.</string>
|
||||||
|
<string name="msg_share_web">Lien pour accéder au navigateur Web.</string>
|
||||||
|
<string name="msg_share_qr_warn">Partagez ce lien avec un utilisateur Cowspent</string>
|
||||||
|
<string name="msg_settle_intro">Réglage pour %1$s:</string>
|
||||||
|
<string name="msg_settle_sentence">%1$s doit %3$.2f à %2$s</string>
|
||||||
|
<string name="msg_stats_intro">Statistiques pour %1$s:</string>
|
||||||
|
<string name="msg_stats_header">Membre (payé | Dépensé | Solde)</string>
|
||||||
|
<string name="msg_logged_in_as">Connecté en tant que %1$s</string>
|
||||||
|
<!-- Errors -->
|
||||||
|
<string name="error_generic">Erreur</string>
|
||||||
|
<string name="error_loading">En cours de chargement</string>
|
||||||
|
<string name="error_no_projects">Aucun projet trouvé</string>
|
||||||
|
<string name="error_no_members">Aucun membre trouvé</string>
|
||||||
|
<string name="error_no_bills">Aucune facture trouvée</string>
|
||||||
|
<string name="error_no_member">Au moins un membre est requis</string>
|
||||||
|
<string name="error_maintenance_mode">Le serveur est en mode maintenance</string>
|
||||||
|
<string name="error_400">400 Mauvaise requête</string>
|
||||||
|
<string name="error_401">401 Non autorisé</string>
|
||||||
|
<string name="error_403">403 Interdit</string>
|
||||||
|
<string name="error_404">404 introuvable</string>
|
||||||
|
<string name="error_sync">Échec de la synchronisation : %1$s</string>
|
||||||
|
<string name="error_invalid_login">Identifiant invalide : %1$s</string>
|
||||||
|
<string name="error_auth">Mauvais nom d\'utilisateur ou mot de passe</string>
|
||||||
|
<string name="error_json">Réponse du serveur invalide</string>
|
||||||
|
<string name="error_req_failed">La requête a échoué</string>
|
||||||
|
<string name="error_invalid_email">Adresse e-mail invalide</string>
|
||||||
|
<string name="error_invalid_project_id">ID de projet invalide</string>
|
||||||
|
<string name="error_invalid_project_name">Titre du projet non valide</string>
|
||||||
|
<string name="error_invalid_bill_name">Nom de facture invalide</string>
|
||||||
|
<string name="error_invalid_bill_date">Date de facture invalide</string>
|
||||||
|
<string name="error_invalid_bill_payer">Payeur requis</string>
|
||||||
|
<string name="error_invalid_bill_owers">Propriétaires requis</string>
|
||||||
|
<string name="error_no_network">Aucune connexion réseau</string>
|
||||||
|
<string name="error_server">Erreur serveur</string>
|
||||||
|
<string name="error_io">La connexion au serveur a échoué</string>
|
||||||
|
<string name="error_share_impossible">Impossible de partager ce projet</string>
|
||||||
|
<!-- Drawer / Common UI -->
|
||||||
|
<string name="drawer_no_account">Se connecter au compte Nextcloud</string>
|
||||||
|
<string name="drawer_last_sync">Dernière synchronisation : %1$02d:%2$02d</string>
|
||||||
|
<string name="simple_cancel">Abandonner</string>
|
||||||
|
<string name="simple_ok" tools:ignore="ButtonCase,Typos">Ok</string>
|
||||||
|
<string name="simple_yes">Oui</string>
|
||||||
|
<string name="simple_no">Non</string>
|
||||||
|
<string name="simple_close">Fermer</string>
|
||||||
|
<!-- Settings -->
|
||||||
|
<string name="settings_appearance">Apparence</string>
|
||||||
|
<string name="settings_network">Réseau</string>
|
||||||
|
<string name="settings_other">Autres</string>
|
||||||
|
<string name="settings_night_mode">Thème</string>
|
||||||
|
<string name="settings_offline_mode">Mode hors-ligne</string>
|
||||||
|
<string name="settings_offline_mode_summary">Synchroniser uniquement manuellement.</string>
|
||||||
|
<string name="settings_color_custom">Couleur personnalisée</string>
|
||||||
|
<string name="settings_color_mode">Sélection de la couleur</string>
|
||||||
|
<string name="settings_show_archived">Afficher les projets archivés</string>
|
||||||
|
<string name="settings_beta_features">Fonctionnalités bêta</string>
|
||||||
|
<string name="settings_beta_features_summary">Activer les fonctionnalités expérimentales. Utiliser à vos propres risques.</string>
|
||||||
|
<string name="settings_url_warn_http">AVERTISSEMENT : \"http\" n\'est pas sûr. Utilisez \"https\".</string>
|
||||||
|
<string name="settings_colorpicker_title">Choisir une couleur</string>
|
||||||
|
<string name="pref_value_color_system">Système</string>
|
||||||
|
<string name="pref_value_color_server">Nuage suivant</string>
|
||||||
|
<string name="pref_value_color_manual">Manuelle</string>
|
||||||
|
<string name="pref_value_theme_light">Lumière</string>
|
||||||
|
<string name="pref_value_theme_dark">Sombre</string>
|
||||||
|
<string name="pref_value_theme_system">Suivre le système</string>
|
||||||
|
<!-- Constants (Do not translate) -->
|
||||||
|
<!-- Enums and Lists -->
|
||||||
|
<string name="repeat_no">Pas de répétition</string>
|
||||||
|
<string name="repeat_day">Tous les jours</string>
|
||||||
|
<string name="repeat_week">Hebdomadaire</string>
|
||||||
|
<string name="repeat_fortnight">Tous les quinze jours</string>
|
||||||
|
<string name="repeat_month">Mensuel</string>
|
||||||
|
<string name="repeat_year">Annuel</string>
|
||||||
|
<string name="payment_mode_none">Aucun</string>
|
||||||
|
<string name="payment_mode_all">Tous</string>
|
||||||
|
<string name="payment_mode_credit_card">Carte de crédit</string>
|
||||||
|
<string name="payment_mode_cash">Espèces</string>
|
||||||
|
<string name="payment_mode_check">Contrôler</string>
|
||||||
|
<string name="payment_mode_online">En ligne</string>
|
||||||
|
<string name="payment_mode_transfer">Transférer</string>
|
||||||
|
<string name="category_none">Aucun</string>
|
||||||
|
<string name="category_all">Tous</string>
|
||||||
|
<string name="category_all_except_reimbursement">Tout sauf remboursement</string>
|
||||||
|
<string name="category_groceries">Épicerie</string>
|
||||||
|
<string name="category_leisure">Barre/Fête</string>
|
||||||
|
<string name="category_rent">Louer</string>
|
||||||
|
<string name="category_bills">Facture</string>
|
||||||
|
<string name="category_excursion">Excursion/Culture</string>
|
||||||
|
<string name="category_health">Santé</string>
|
||||||
|
<string name="category_shopping">Achats</string>
|
||||||
|
<string name="category_reimbursement">Remboursement</string>
|
||||||
|
<string name="category_restaurant">Restaurant</string>
|
||||||
|
<string name="category_accomodation">Hébergement</string>
|
||||||
|
<string name="category_transport">Transport</string>
|
||||||
|
<string name="category_sport">Sport</string>
|
||||||
|
<!-- Project specific -->
|
||||||
|
<string name="new_project_action">Qu\'est-ce que</string>
|
||||||
|
<string name="new_project_where">Où se trouve</string>
|
||||||
|
<string name="where_local">Seulement en local</string>
|
||||||
|
<string name="where_cospend">Codépense</string>
|
||||||
|
<string name="where_ihatemoney">format@@0 IHateMoney</string>
|
||||||
|
<string name="todo_join">Rejoindre un projet existant</string>
|
||||||
|
<string name="todo_create">Créer un nouveau projet</string>
|
||||||
|
<string name="import_tooltip">Importer depuis un fichier</string>
|
||||||
|
<string name="choose_account_project_dialog_title">Choisir un projet</string>
|
||||||
|
<string name="choose_account_project_dialog_impossible">Aucun projet trouvé sur ce compte.</string>
|
||||||
|
<string name="choose_project_management_action">Projet</string>
|
||||||
|
<string name="project_added_success">Le projet a été ajouté avec succès.</string>
|
||||||
|
<string name="no_projects_text">Vous n\'avez pas encore de projets.</string>
|
||||||
|
<string name="configure_account_choice">Configurer le compte Nextcloud</string>
|
||||||
|
<string name="add_project_choice">Ajouter un projet manuellement</string>
|
||||||
|
<string name="no_members_text">Aucun membre dans ce projet.</string>
|
||||||
|
<string name="no_bills_text">Aucune facture trouvée.</string>
|
||||||
|
<string name="member_already_exists">Ce membre existe déjà.</string>
|
||||||
|
<string name="activity_dialog_title">Projet: %1$s</string>
|
||||||
|
<string name="remove_project_confirmation">Projet %1$s supprimé.</string>
|
||||||
|
<string name="file_saved_success">Fichier enregistré : %1$s</string>
|
||||||
|
<string name="import_error_header">Échec de l\'importation à la ligne %d</string>
|
||||||
|
<string name="import_error_date">Format de date invalide à la ligne %d</string>
|
||||||
|
<string name="import_error_owers">Propriétés non valides à la ligne %d</string>
|
||||||
|
<string name="add_member_dialog_title">Ajouter un membre</string>
|
||||||
|
<string name="edit_member_dialog_title">Modifier le membre</string>
|
||||||
|
<string name="member_edit_delete">Supprimez</string>
|
||||||
|
<string name="project_edition_no_change">Aucune modification à enregistrer.</string>
|
||||||
|
<!-- Settlement -->
|
||||||
|
<string name="center_none">Aucun (optimiste)</string>
|
||||||
|
<string name="settle_who">Qui paie</string>
|
||||||
|
<string name="settle_to_whom">À qui</string>
|
||||||
|
<string name="settle_how_much">Montant</string>
|
||||||
|
<string name="simple_settle_share">Partager</string>
|
||||||
|
<string name="simple_create_bills">Créer des factures</string>
|
||||||
|
<string name="settle_bill_what">Réglement</string>
|
||||||
|
<!-- Currencies -->
|
||||||
|
<string name="currency_dialog_title">Choisir la devise (%s)</string>
|
||||||
|
<string name="setting_none">Aucun</string>
|
||||||
|
<string name="setting_all">Tous</string>
|
||||||
|
<string name="currency_saved_success">Paramètres des devises enregistrés.</string>
|
||||||
|
<string name="main_currency">Devise principale</string>
|
||||||
|
<!-- Statistics -->
|
||||||
|
<string name="label_bills_suggested">Catégories suggérées</string>
|
||||||
|
<string name="label_bills_skip">Ignorer</string>
|
||||||
|
<string name="stats_date_min">A partir de</string>
|
||||||
|
<string name="stats_date_max">À</string>
|
||||||
|
<string name="stats_who">Membre</string>
|
||||||
|
<string name="stats_paid">Payé</string>
|
||||||
|
<string name="stats_spent">Dépensé</string>
|
||||||
|
<string name="stats_balance">Solde</string>
|
||||||
|
<string name="total">Total : %1$s</string>
|
||||||
|
<!-- Errors Extra -->
|
||||||
|
<string name="error_project_connect_check">Échec de la connexion : %1$s</string>
|
||||||
|
<string name="error_create_remote_project_helper">Échec de la création : %1$s</string>
|
||||||
|
<string name="error_edit_remote_project_helper">Erreur lors de la mise à jour du projet distant : %1$s</string>
|
||||||
|
<string name="remote_project_operation_no_network">Réseau indisponible pour le fonctionnement à distance.</string>
|
||||||
|
<string name="error_scanning_bill_qr_code">Impossible d\'analyser le code QR.</string>
|
||||||
|
<string name="error_token_mismatch">Incompatibilité du jeton d\'authentification. Veuillez vous reconnecter.</string>
|
||||||
|
<string name="insufficient_access_level">Vous n\'avez pas la permission d\'effectuer cette action.</string>
|
||||||
|
<string name="delete_label_confirmation_title">Supprimer l\'étiquette</string>
|
||||||
|
<string name="delete_label_confirmation_message">Êtes-vous sûr de vouloir supprimer ce label ?</string>
|
||||||
|
<!-- About -->
|
||||||
|
<string name="about_version">Version %1$s</string>
|
||||||
|
<string name="about_maintainer_title">Mainteneur</string>
|
||||||
|
<string name="about_license_title">Licence</string>
|
||||||
|
<string name="about_source_title">Code source</string>
|
||||||
|
<!-- New constants for backward compatibility or shared use -->
|
||||||
|
<string name="share_intent_title">Projet %1$s</string>
|
||||||
|
<string name="share_chooser_title">Partager %1$s</string>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,241 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
<string name="app_name">Cowspent</string>
|
||||||
|
<!-- Actions -->
|
||||||
|
<string name="action_new_bill">New bill</string>
|
||||||
|
<string name="action_add_project">Add project</string>
|
||||||
|
<string name="action_save">Save</string>
|
||||||
|
<string name="action_edit">Edit</string>
|
||||||
|
<string name="action_share">Share</string>
|
||||||
|
<string name="action_search">Search</string>
|
||||||
|
<string name="action_delete">Delete</string>
|
||||||
|
<string name="action_archive">Archive</string>
|
||||||
|
<string name="action_unarchive">Unarchive</string>
|
||||||
|
<string name="action_export">Export</string>
|
||||||
|
<string name="action_stats">Stats</string>
|
||||||
|
<string name="action_settle">Settle</string>
|
||||||
|
<string name="action_scan_qrcode">Scan QR Code</string>
|
||||||
|
<string name="action_settings">Settings</string>
|
||||||
|
<string name="action_label_bills">Label missing categories</string>
|
||||||
|
<string name="action_logout">Logout</string>
|
||||||
|
<string name="action_connect">Connect</string>
|
||||||
|
<string name="action_discard">Discard</string>
|
||||||
|
<string name="action_members">Members</string>
|
||||||
|
<string name="action_labels">Labels</string>
|
||||||
|
<string name="action_currencies">Currencies</string>
|
||||||
|
<!-- Titles -->
|
||||||
|
<string name="title_stats">Statistics</string>
|
||||||
|
<string name="title_edit_project">Edit project</string>
|
||||||
|
<string name="title_label_bills">Label Bills</string>
|
||||||
|
<string name="title_labels">Manage Labels</string>
|
||||||
|
<string name="title_about">About</string>
|
||||||
|
<string name="title_settle">Settle Project</string>
|
||||||
|
<string name="title_share">Share Project</string>
|
||||||
|
<string name="title_add_project">Add Project</string>
|
||||||
|
<string name="title_add_category">Add Category</string>
|
||||||
|
<string name="title_add_payment_mode">Add Payment Mode</string>
|
||||||
|
<string name="title_account">Nextcloud Account</string>
|
||||||
|
<string name="title_share_web">Web link</string>
|
||||||
|
<string name="title_share_qr">Cowspent link</string>
|
||||||
|
<string name="title_confirm">Are you sure?</string>
|
||||||
|
<!-- Labels and Fields -->
|
||||||
|
<string name="label_all_bills">All bills</string>
|
||||||
|
<string name="label_categories">Categories</string>
|
||||||
|
<string name="label_payment_modes">Payment Modes</string>
|
||||||
|
<string name="label_name">Name</string>
|
||||||
|
<string name="label_icon">Icon / Emoji</string>
|
||||||
|
<string name="label_color">Color</string>
|
||||||
|
<string name="label_weight">Weight</string>
|
||||||
|
<string name="label_activated">Activated</string>
|
||||||
|
<string name="label_password">Password</string>
|
||||||
|
<string name="label_email">E-mail</string>
|
||||||
|
<string name="label_url">Server address</string>
|
||||||
|
<string name="label_username">Username</string>
|
||||||
|
<string name="label_comment">Comment</string>
|
||||||
|
<string name="label_what">What?</string>
|
||||||
|
<string name="label_payer">Who paid?</string>
|
||||||
|
<string name="label_owers">For whom?</string>
|
||||||
|
<string name="label_repeat">Repeat every</string>
|
||||||
|
<string name="label_mode">Mode</string>
|
||||||
|
<string name="label_category">Category</string>
|
||||||
|
<string name="label_project_id">Project ID/name</string>
|
||||||
|
<string name="label_project_title">Project title</string>
|
||||||
|
<string name="label_use_sso">Use Nextcloud App Account</string>
|
||||||
|
<!-- Dialogs and Messages -->
|
||||||
|
<string name="dialog_unsaved_changes_title">Unsaved changes</string>
|
||||||
|
<string name="dialog_unsaved_changes_msg">Save changes before leaving?</string>
|
||||||
|
<string name="dialog_confirm_remove_project_msg">The remote project will not be deleted.</string>
|
||||||
|
<string name="dialog_sync_error_title">Sync error</string>
|
||||||
|
<string name="dialog_sync_error_msg">Sync failed for %1$s.\n\n%2$s</string>
|
||||||
|
<string name="dialog_balanced_msg">Expenses are already balanced.</string>
|
||||||
|
<string name="msg_project_added">Project %1$s added</string>
|
||||||
|
<string name="msg_bill_labeled_done">All bills labeled</string>
|
||||||
|
<string name="msg_no_suggestions">No suggestions</string>
|
||||||
|
<string name="msg_auth_warning">Requires Cospend v0.3.4+.</string>
|
||||||
|
<string name="msg_link_copied">Link copied to clipboard</string>
|
||||||
|
<string name="msg_share_qr">Scan QR code or share the link to join.</string>
|
||||||
|
<string name="msg_share_web">Link for web browser access.</string>
|
||||||
|
<string name="msg_share_qr_warn">Share this link with a Cowspent user.</string>
|
||||||
|
<string name="msg_settle_intro">Settlement for %1$s:</string>
|
||||||
|
<string name="msg_settle_sentence">%1$s owes %3$.2f to %2$s</string>
|
||||||
|
<string name="msg_stats_intro">Stats for %1$s:</string>
|
||||||
|
<string name="msg_stats_header">Member (Paid | Spent | Balance)</string>
|
||||||
|
<string name="msg_logged_in_as">Logged in as %1$s</string>
|
||||||
|
<!-- Errors -->
|
||||||
|
<string name="error_generic">Error</string>
|
||||||
|
<string name="error_loading">Loading</string>
|
||||||
|
<string name="error_no_projects">No projects found</string>
|
||||||
|
<string name="error_no_members">No members found</string>
|
||||||
|
<string name="error_no_bills">No bills found</string>
|
||||||
|
<string name="error_no_member">At least one member required</string>
|
||||||
|
<string name="error_maintenance_mode">Server is in maintenance mode</string>
|
||||||
|
<string name="error_400">400 Bad request</string>
|
||||||
|
<string name="error_401">401 Unauthorized</string>
|
||||||
|
<string name="error_403">403 Forbidden</string>
|
||||||
|
<string name="error_404">404 Not Found</string>
|
||||||
|
<string name="error_sync">Sync failed: %1$s</string>
|
||||||
|
<string name="error_invalid_login">Invalid login: %1$s</string>
|
||||||
|
<string name="error_auth">Wrong username or password</string>
|
||||||
|
<string name="error_json">Invalid server response</string>
|
||||||
|
<string name="error_req_failed">Request failed</string>
|
||||||
|
<string name="error_invalid_email">Invalid e-mail</string>
|
||||||
|
<string name="error_invalid_project_id">Invalid project ID</string>
|
||||||
|
<string name="error_invalid_project_name">Invalid project title</string>
|
||||||
|
<string name="error_invalid_bill_name">Invalid bill name</string>
|
||||||
|
<string name="error_invalid_bill_date">Invalid bill date</string>
|
||||||
|
<string name="error_invalid_bill_payer">Payer required</string>
|
||||||
|
<string name="error_invalid_bill_owers">Owers required</string>
|
||||||
|
<string name="error_no_network">No network connection</string>
|
||||||
|
<string name="error_server">Server error</string>
|
||||||
|
<string name="error_io">Server connection broken</string>
|
||||||
|
<string name="error_share_impossible">Cannot share this project</string>
|
||||||
|
<!-- Drawer / Common UI -->
|
||||||
|
<string name="drawer_no_account">Connect to Nextcloud account</string>
|
||||||
|
<string name="drawer_last_sync">Last sync: %1$02d:%2$02d</string>
|
||||||
|
<string name="simple_cancel">Cancel</string>
|
||||||
|
<string name="simple_ok" tools:ignore="ButtonCase,Typos">Ok</string>
|
||||||
|
<string name="simple_yes">Yes</string>
|
||||||
|
<string name="simple_no">No</string>
|
||||||
|
<string name="simple_close">Close</string>
|
||||||
|
<!-- Settings -->
|
||||||
|
<string name="settings_appearance">Appearance</string>
|
||||||
|
<string name="settings_network">Network</string>
|
||||||
|
<string name="settings_other">Other</string>
|
||||||
|
<string name="settings_night_mode">Theme</string>
|
||||||
|
<string name="settings_offline_mode">Offline mode</string>
|
||||||
|
<string name="settings_offline_mode_summary">Only sync manually.</string>
|
||||||
|
<string name="settings_color_custom">Custom color</string>
|
||||||
|
<string name="settings_color_mode">Color Selection</string>
|
||||||
|
<string name="settings_show_archived">Show archived projects</string>
|
||||||
|
<string name="settings_beta_features">Beta Features</string>
|
||||||
|
<string name="settings_beta_features_summary">Enable experimental features. Use at your own risk.</string>
|
||||||
|
<string name="settings_url_warn_http">WARNING: \"http\" is unsafe. Use \"https\".</string>
|
||||||
|
<string name="settings_colorpicker_title">Choose Color</string>
|
||||||
|
<string name="pref_value_color_system">System</string>
|
||||||
|
<string name="pref_value_color_server">Nextcloud</string>
|
||||||
|
<string name="pref_value_color_manual">Manual</string>
|
||||||
|
<string name="pref_value_theme_light">Light</string>
|
||||||
|
<string name="pref_value_theme_dark">Dark</string>
|
||||||
|
<string name="pref_value_theme_system">Follow system</string>
|
||||||
|
<!-- Constants (Do not translate) -->
|
||||||
|
<!-- Enums and Lists -->
|
||||||
|
<string name="repeat_no">No repeat</string>
|
||||||
|
<string name="repeat_day">Daily</string>
|
||||||
|
<string name="repeat_week">Weekly</string>
|
||||||
|
<string name="repeat_fortnight">Fortnightly</string>
|
||||||
|
<string name="repeat_month">Monthly</string>
|
||||||
|
<string name="repeat_year">Yearly</string>
|
||||||
|
<string name="payment_mode_none">None</string>
|
||||||
|
<string name="payment_mode_all">All</string>
|
||||||
|
<string name="payment_mode_credit_card">Credit card</string>
|
||||||
|
<string name="payment_mode_cash">Cash</string>
|
||||||
|
<string name="payment_mode_check">Check</string>
|
||||||
|
<string name="payment_mode_online">Online</string>
|
||||||
|
<string name="payment_mode_transfer">Transfer</string>
|
||||||
|
<string name="category_none">None</string>
|
||||||
|
<string name="category_all">All</string>
|
||||||
|
<string name="category_all_except_reimbursement">All except reimbursement</string>
|
||||||
|
<string name="category_groceries">Grocery</string>
|
||||||
|
<string name="category_leisure">Bar/Party</string>
|
||||||
|
<string name="category_rent">Rent</string>
|
||||||
|
<string name="category_bills">Bill</string>
|
||||||
|
<string name="category_excursion">Excursion/Culture</string>
|
||||||
|
<string name="category_health">Health</string>
|
||||||
|
<string name="category_shopping">Shopping</string>
|
||||||
|
<string name="category_reimbursement">Reimbursement</string>
|
||||||
|
<string name="category_restaurant">Restaurant</string>
|
||||||
|
<string name="category_accomodation">Accommodation</string>
|
||||||
|
<string name="category_transport">Transport</string>
|
||||||
|
<string name="category_sport">Sport</string>
|
||||||
|
<!-- Project specific -->
|
||||||
|
<string name="new_project_action">What</string>
|
||||||
|
<string name="new_project_where">Where</string>
|
||||||
|
<string name="where_local">Local only</string>
|
||||||
|
<string name="where_cospend">Cospend</string>
|
||||||
|
<string name="where_ihatemoney">IHateMoney</string>
|
||||||
|
<string name="todo_join">Join existing project</string>
|
||||||
|
<string name="todo_create">Create new project</string>
|
||||||
|
<string name="import_tooltip">Import from file</string>
|
||||||
|
<string name="choose_account_project_dialog_title">Choose project</string>
|
||||||
|
<string name="choose_account_project_dialog_impossible">No projects found on this account.</string>
|
||||||
|
<string name="choose_project_management_action">Project</string>
|
||||||
|
<string name="project_added_success">Project added successfully.</string>
|
||||||
|
<string name="no_projects_text">You have no projects yet.</string>
|
||||||
|
<string name="configure_account_choice">Configure Nextcloud account</string>
|
||||||
|
<string name="add_project_choice">Add project manually</string>
|
||||||
|
<string name="no_members_text">No members in this project.</string>
|
||||||
|
<string name="no_bills_text">No bills found.</string>
|
||||||
|
<string name="member_already_exists">Member already exists.</string>
|
||||||
|
<string name="activity_dialog_title">Project: %1$s</string>
|
||||||
|
<string name="remove_project_confirmation">Project %1$s removed.</string>
|
||||||
|
<string name="file_saved_success">File saved: %1$s</string>
|
||||||
|
<string name="import_error_header">Import failed at row %d</string>
|
||||||
|
<string name="import_error_date">Invalid date format at row %d</string>
|
||||||
|
<string name="import_error_owers">Invalid owers at row %d</string>
|
||||||
|
<string name="add_member_dialog_title">Add Member</string>
|
||||||
|
<string name="edit_member_dialog_title">Edit Member</string>
|
||||||
|
<string name="member_edit_delete">Delete</string>
|
||||||
|
<string name="project_edition_no_change">No changes to save.</string>
|
||||||
|
<!-- Settlement -->
|
||||||
|
<string name="center_none">None (Optimal)</string>
|
||||||
|
<string name="settle_who">Who pays</string>
|
||||||
|
<string name="settle_to_whom">To whom</string>
|
||||||
|
<string name="settle_how_much">Amount</string>
|
||||||
|
<string name="simple_settle_share">Share</string>
|
||||||
|
<string name="simple_create_bills">Create bills</string>
|
||||||
|
<string name="settle_bill_what">Settlement</string>
|
||||||
|
<!-- Currencies -->
|
||||||
|
<string name="currency_dialog_title">Choose Currency (%s)</string>
|
||||||
|
<string name="setting_none">None</string>
|
||||||
|
<string name="setting_all">All</string>
|
||||||
|
<string name="currency_saved_success">Currency settings saved.</string>
|
||||||
|
<string name="main_currency">Main Currency</string>
|
||||||
|
<!-- Statistics -->
|
||||||
|
<string name="label_bills_suggested">Suggested Categories</string>
|
||||||
|
<string name="label_bills_skip">Skip</string>
|
||||||
|
<string name="stats_date_min">From</string>
|
||||||
|
<string name="stats_date_max">To</string>
|
||||||
|
<string name="stats_who">Member</string>
|
||||||
|
<string name="stats_paid">Paid</string>
|
||||||
|
<string name="stats_spent">Spent</string>
|
||||||
|
<string name="stats_balance">Balance</string>
|
||||||
|
<string name="total">Total: %1$s</string>
|
||||||
|
<!-- Errors Extra -->
|
||||||
|
<string name="error_project_connect_check">Connection failed: %1$s</string>
|
||||||
|
<string name="error_create_remote_project_helper">Creation failed: %1$s</string>
|
||||||
|
<string name="error_edit_remote_project_helper">Error updating remote project: %1$s</string>
|
||||||
|
<string name="remote_project_operation_no_network">Network unavailable for remote operation.</string>
|
||||||
|
<string name="error_scanning_bill_qr_code">Failed to parse QR code.</string>
|
||||||
|
<string name="error_token_mismatch">Authentication token mismatch. Please log in again.</string>
|
||||||
|
<string name="insufficient_access_level">You don\'t have permission to perform this action.</string>
|
||||||
|
<string name="delete_label_confirmation_title">Delete Label</string>
|
||||||
|
<string name="delete_label_confirmation_message">Are you sure you want to delete this label?</string>
|
||||||
|
<!-- About -->
|
||||||
|
<string name="about_version">Version %1$s</string>
|
||||||
|
<string name="about_maintainer_title">Maintainer</string>
|
||||||
|
<string name="about_license_title">License</string>
|
||||||
|
<string name="about_source_title">Source code</string>
|
||||||
|
<!-- New constants for backward compatibility or shared use -->
|
||||||
|
<string name="share_intent_title">Project %1$s</string>
|
||||||
|
<string name="share_chooser_title">Share %1$s</string>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,241 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
<string name="app_name">Cowspent</string>
|
||||||
|
<!-- Actions -->
|
||||||
|
<string name="action_new_bill">Nuova bolletta</string>
|
||||||
|
<string name="action_add_project">Aggiungi progetto</string>
|
||||||
|
<string name="action_save">Salva</string>
|
||||||
|
<string name="action_edit">Modifica</string>
|
||||||
|
<string name="action_share">Condividi</string>
|
||||||
|
<string name="action_search">Cerca</string>
|
||||||
|
<string name="action_delete">Elimina</string>
|
||||||
|
<string name="action_archive">Archivio</string>
|
||||||
|
<string name="action_unarchive">Disarchivia</string>
|
||||||
|
<string name="action_export">Esporta</string>
|
||||||
|
<string name="action_stats">Statistiche</string>
|
||||||
|
<string name="action_settle">Salta</string>
|
||||||
|
<string name="action_scan_qrcode">Scan QR Code</string>
|
||||||
|
<string name="action_settings">Impostazioni</string>
|
||||||
|
<string name="action_label_bills">Etichetta categorie mancanti</string>
|
||||||
|
<string name="action_logout">Esci</string>
|
||||||
|
<string name="action_connect">Connetti</string>
|
||||||
|
<string name="action_discard">Scarta</string>
|
||||||
|
<string name="action_members">Membri</string>
|
||||||
|
<string name="action_labels">Etichette</string>
|
||||||
|
<string name="action_currencies">Valute</string>
|
||||||
|
<!-- Titles -->
|
||||||
|
<string name="title_stats">Statistiche</string>
|
||||||
|
<string name="title_edit_project">Modifica progetto</string>
|
||||||
|
<string name="title_label_bills">Etichetta Di Bollette</string>
|
||||||
|
<string name="title_labels">Gestisci Etichette</string>
|
||||||
|
<string name="title_about">Informazioni</string>
|
||||||
|
<string name="title_settle">Settle Project</string>
|
||||||
|
<string name="title_share">Condividi Progetto</string>
|
||||||
|
<string name="title_add_project">Aggiungi Progetto</string>
|
||||||
|
<string name="title_add_category">Aggiungi Categoria</string>
|
||||||
|
<string name="title_add_payment_mode">Aggiungi Modalità Di Pagamento</string>
|
||||||
|
<string name="title_account">Account Nextcloud</string>
|
||||||
|
<string name="title_share_web">Collegamento web</string>
|
||||||
|
<string name="title_share_qr">Cowspent link</string>
|
||||||
|
<string name="title_confirm">Sei sicuro?</string>
|
||||||
|
<!-- Labels and Fields -->
|
||||||
|
<string name="label_all_bills">Tutte le bollette</string>
|
||||||
|
<string name="label_categories">Categorie</string>
|
||||||
|
<string name="label_payment_modes">Modalità Di Pagamento</string>
|
||||||
|
<string name="label_name">Nome</string>
|
||||||
|
<string name="label_icon">Icon / Emoji</string>
|
||||||
|
<string name="label_color">Colore</string>
|
||||||
|
<string name="label_weight">Peso</string>
|
||||||
|
<string name="label_activated">Attivato</string>
|
||||||
|
<string name="label_password">Password</string>
|
||||||
|
<string name="label_email">E-mail</string>
|
||||||
|
<string name="label_url">Indirizzo del server</string>
|
||||||
|
<string name="label_username">Username</string>
|
||||||
|
<string name="label_comment">Commento</string>
|
||||||
|
<string name="label_what">Cosa?</string>
|
||||||
|
<string name="label_payer">Chi ha pagato?</string>
|
||||||
|
<string name="label_owers">Per chi?</string>
|
||||||
|
<string name="label_repeat">Ripeti ogni</string>
|
||||||
|
<string name="label_mode">Modalità</string>
|
||||||
|
<string name="label_category">Categoria</string>
|
||||||
|
<string name="label_project_id">Id Del Progetto/Nome</string>
|
||||||
|
<string name="label_project_title">Titolo del progetto</string>
|
||||||
|
<string name="label_use_sso">Usa Account App Nextcloud</string>
|
||||||
|
<!-- Dialogs and Messages -->
|
||||||
|
<string name="dialog_unsaved_changes_title">Modifiche non salvate</string>
|
||||||
|
<string name="dialog_unsaved_changes_msg">Salvare le modifiche prima di partire?</string>
|
||||||
|
<string name="dialog_confirm_remove_project_msg">Il progetto remoto non verrà eliminato.</string>
|
||||||
|
<string name="dialog_sync_error_title">Errore di sincronizzazione</string>
|
||||||
|
<string name="dialog_sync_error_msg">Sincronizzazione fallita per %1$s.\n\n%2$s</string>
|
||||||
|
<string name="dialog_balanced_msg">Le spese sono già bilanciate.</string>
|
||||||
|
<string name="msg_project_added">Project %1$s added</string>
|
||||||
|
<string name="msg_bill_labeled_done">Tutte le fatture etichettate</string>
|
||||||
|
<string name="msg_no_suggestions">Nessun suggerimento</string>
|
||||||
|
<string name="msg_auth_warning">Richiede Cospend v0.3.4+.</string>
|
||||||
|
<string name="msg_link_copied">Link copiato negli appunti</string>
|
||||||
|
<string name="msg_share_qr">Scansiona il codice QR o condividi il link per unirsi.</string>
|
||||||
|
<string name="msg_share_web">Link per l\'accesso al browser web.</string>
|
||||||
|
<string name="msg_share_qr_warn">Condividi questo link con un utente Cowspent</string>
|
||||||
|
<string name="msg_settle_intro">Regolamento per %1$s:</string>
|
||||||
|
<string name="msg_settle_sentence">%1$s deve %3$.2f a %2$s</string>
|
||||||
|
<string name="msg_stats_intro">Statistiche per %1$s:</string>
|
||||||
|
<string name="msg_stats_header">Membro (Saldo <unk> Speso <unk> Balance)</string>
|
||||||
|
<string name="msg_logged_in_as">Accesso effettuato come %1$s</string>
|
||||||
|
<!-- Errors -->
|
||||||
|
<string name="error_generic">Errore</string>
|
||||||
|
<string name="error_loading">Caricamento</string>
|
||||||
|
<string name="error_no_projects">Nessun progetto trovato</string>
|
||||||
|
<string name="error_no_members">Nessun membro trovato</string>
|
||||||
|
<string name="error_no_bills">Nessuna bolletta trovata</string>
|
||||||
|
<string name="error_no_member">Almeno un membro richiesto</string>
|
||||||
|
<string name="error_maintenance_mode">Il server è in modalità manutenzione</string>
|
||||||
|
<string name="error_400">400 Richiesta errata</string>
|
||||||
|
<string name="error_401">401 Non Autorizzato</string>
|
||||||
|
<string name="error_403">403 Vietato</string>
|
||||||
|
<string name="error_404">404 Non Trovato</string>
|
||||||
|
<string name="error_sync">Sincronizzazione non riuscita: %1$s</string>
|
||||||
|
<string name="error_invalid_login">Login non valido: %1$s</string>
|
||||||
|
<string name="error_auth">Nome utente o password errati</string>
|
||||||
|
<string name="error_json">Risposta server non valida</string>
|
||||||
|
<string name="error_req_failed">Richiesta fallita</string>
|
||||||
|
<string name="error_invalid_email">E-mail non valida</string>
|
||||||
|
<string name="error_invalid_project_id">ID progetto non valido</string>
|
||||||
|
<string name="error_invalid_project_name">Titolo progetto non valido</string>
|
||||||
|
<string name="error_invalid_bill_name">Nome fattura non valido</string>
|
||||||
|
<string name="error_invalid_bill_date">Data fattura non valida</string>
|
||||||
|
<string name="error_invalid_bill_payer">Pagatore richiesto</string>
|
||||||
|
<string name="error_invalid_bill_owers">Proprietari richiesti</string>
|
||||||
|
<string name="error_no_network">Nessuna connessione di rete</string>
|
||||||
|
<string name="error_server">Errore del server</string>
|
||||||
|
<string name="error_io">Connessione al server interrotta</string>
|
||||||
|
<string name="error_share_impossible">Impossibile condividere questo progetto</string>
|
||||||
|
<!-- Drawer / Common UI -->
|
||||||
|
<string name="drawer_no_account">Connetti all\'account Nextcloud</string>
|
||||||
|
<string name="drawer_last_sync">Ultima sincronizzazione: %1$02d:%2$02d</string>
|
||||||
|
<string name="simple_cancel">Annulla</string>
|
||||||
|
<string name="simple_ok" tools:ignore="ButtonCase,Typos">Ok</string>
|
||||||
|
<string name="simple_yes">Sì</string>
|
||||||
|
<string name="simple_no">No</string>
|
||||||
|
<string name="simple_close">Chiudi</string>
|
||||||
|
<!-- Settings -->
|
||||||
|
<string name="settings_appearance">Aspetto</string>
|
||||||
|
<string name="settings_network">Rete</string>
|
||||||
|
<string name="settings_other">Altro</string>
|
||||||
|
<string name="settings_night_mode">Tema</string>
|
||||||
|
<string name="settings_offline_mode">Modalità offline</string>
|
||||||
|
<string name="settings_offline_mode_summary">Sincronizza solo manualmente.</string>
|
||||||
|
<string name="settings_color_custom">Colore personalizzato</string>
|
||||||
|
<string name="settings_color_mode">Selezione Colore</string>
|
||||||
|
<string name="settings_show_archived">Mostra progetti archiviati</string>
|
||||||
|
<string name="settings_beta_features">Caratteristiche Beta</string>
|
||||||
|
<string name="settings_beta_features_summary">Abilita le funzioni sperimentali. Usa a tuo rischio.</string>
|
||||||
|
<string name="settings_url_warn_http">ATTENZIONE: \"http\" non è sicuro. Usa \"국\".</string>
|
||||||
|
<string name="settings_colorpicker_title">Scegli Colore</string>
|
||||||
|
<string name="pref_value_color_system">Sistema</string>
|
||||||
|
<string name="pref_value_color_server">Nextcloud</string>
|
||||||
|
<string name="pref_value_color_manual">Manuale</string>
|
||||||
|
<string name="pref_value_theme_light">Chiaro</string>
|
||||||
|
<string name="pref_value_theme_dark">Scuro</string>
|
||||||
|
<string name="pref_value_theme_system">Segui sistema</string>
|
||||||
|
<!-- Constants (Do not translate) -->
|
||||||
|
<!-- Enums and Lists -->
|
||||||
|
<string name="repeat_no">Nessuna ripetizione</string>
|
||||||
|
<string name="repeat_day">Giornaliero</string>
|
||||||
|
<string name="repeat_week">Settimanale</string>
|
||||||
|
<string name="repeat_fortnight">Quindici</string>
|
||||||
|
<string name="repeat_month">Mensile</string>
|
||||||
|
<string name="repeat_year">Annuale</string>
|
||||||
|
<string name="payment_mode_none">Nessuno</string>
|
||||||
|
<string name="payment_mode_all">Tutti</string>
|
||||||
|
<string name="payment_mode_credit_card">Carta di credito</string>
|
||||||
|
<string name="payment_mode_cash">Contanti</string>
|
||||||
|
<string name="payment_mode_check">Controlla</string>
|
||||||
|
<string name="payment_mode_online">Online</string>
|
||||||
|
<string name="payment_mode_transfer">Trasferisci</string>
|
||||||
|
<string name="category_none">Nessuno</string>
|
||||||
|
<string name="category_all">Tutti</string>
|
||||||
|
<string name="category_all_except_reimbursement">Tutti tranne il rimborso</string>
|
||||||
|
<string name="category_groceries">Alimentari</string>
|
||||||
|
<string name="category_leisure">Bar/Party</string>
|
||||||
|
<string name="category_rent">Affitto</string>
|
||||||
|
<string name="category_bills">Fattura</string>
|
||||||
|
<string name="category_excursion">Escursione/Cultura</string>
|
||||||
|
<string name="category_health">Salute</string>
|
||||||
|
<string name="category_shopping">Shopping</string>
|
||||||
|
<string name="category_reimbursement">Rimborso</string>
|
||||||
|
<string name="category_restaurant">Ristorante</string>
|
||||||
|
<string name="category_accomodation">Alloggio</string>
|
||||||
|
<string name="category_transport">Trasporti</string>
|
||||||
|
<string name="category_sport">Sport</string>
|
||||||
|
<!-- Project specific -->
|
||||||
|
<string name="new_project_action">Cosa</string>
|
||||||
|
<string name="new_project_where">Dove</string>
|
||||||
|
<string name="where_local">Solo locale</string>
|
||||||
|
<string name="where_cospend">Cospend</string>
|
||||||
|
<string name="where_ihatemoney">IHateMoney</string>
|
||||||
|
<string name="todo_join">Unisciti al progetto esistente</string>
|
||||||
|
<string name="todo_create">Crea nuovo progetto</string>
|
||||||
|
<string name="import_tooltip">Importa da file</string>
|
||||||
|
<string name="choose_account_project_dialog_title">Scegli progetto</string>
|
||||||
|
<string name="choose_account_project_dialog_impossible">Nessun progetto trovato su questo account.</string>
|
||||||
|
<string name="choose_project_management_action">Progetto</string>
|
||||||
|
<string name="project_added_success">Progetto aggiunto con successo.</string>
|
||||||
|
<string name="no_projects_text">Non hai ancora alcun progetto.</string>
|
||||||
|
<string name="configure_account_choice">Configura account Nextcloud</string>
|
||||||
|
<string name="add_project_choice">Aggiunge manualmente il progetto</string>
|
||||||
|
<string name="no_members_text">Nessun membro in questo progetto.</string>
|
||||||
|
<string name="no_bills_text">Nessuna bolletta trovata.</string>
|
||||||
|
<string name="member_already_exists">Membro già esistente.</string>
|
||||||
|
<string name="activity_dialog_title">Progetto: %1$s</string>
|
||||||
|
<string name="remove_project_confirmation">Project %1$s removed.</string>
|
||||||
|
<string name="file_saved_success">File salvato: %1$s</string>
|
||||||
|
<string name="import_error_header">Importazione fallita alla riga %d</string>
|
||||||
|
<string name="import_error_date">Formato data non valido alla riga %d</string>
|
||||||
|
<string name="import_error_owers">Guanti non validi alla riga %d</string>
|
||||||
|
<string name="add_member_dialog_title">Aggiungi Membro</string>
|
||||||
|
<string name="edit_member_dialog_title">Modifica Membro</string>
|
||||||
|
<string name="member_edit_delete">Elimina</string>
|
||||||
|
<string name="project_edition_no_change">Nessuna modifica da salvare.</string>
|
||||||
|
<!-- Settlement -->
|
||||||
|
<string name="center_none">Nessuno (ottimale)</string>
|
||||||
|
<string name="settle_who">Chi paga</string>
|
||||||
|
<string name="settle_to_whom">A chi</string>
|
||||||
|
<string name="settle_how_much">Importo</string>
|
||||||
|
<string name="simple_settle_share">Condividi</string>
|
||||||
|
<string name="simple_create_bills">Crea bollette</string>
|
||||||
|
<string name="settle_bill_what">Regolamento</string>
|
||||||
|
<!-- Currencies -->
|
||||||
|
<string name="currency_dialog_title">Scegli Valuta (%s)</string>
|
||||||
|
<string name="setting_none">Nessuno</string>
|
||||||
|
<string name="setting_all">Tutti</string>
|
||||||
|
<string name="currency_saved_success">Impostazioni valuta salvate.</string>
|
||||||
|
<string name="main_currency">Valuta Principale</string>
|
||||||
|
<!-- Statistics -->
|
||||||
|
<string name="label_bills_suggested">Categorie Suggerite</string>
|
||||||
|
<string name="label_bills_skip">Salta</string>
|
||||||
|
<string name="stats_date_min">Da</string>
|
||||||
|
<string name="stats_date_max">A</string>
|
||||||
|
<string name="stats_who">Membro</string>
|
||||||
|
<string name="stats_paid">Pagato</string>
|
||||||
|
<string name="stats_spent">Speso</string>
|
||||||
|
<string name="stats_balance">Saldo</string>
|
||||||
|
<string name="total">Totale: %1$s</string>
|
||||||
|
<!-- Errors Extra -->
|
||||||
|
<string name="error_project_connect_check">Connessione non riuscita: %1$s</string>
|
||||||
|
<string name="error_create_remote_project_helper">Creazione fallita: %1$s</string>
|
||||||
|
<string name="error_edit_remote_project_helper">Errore nell\'aggiornare il progetto remoto: %1$s</string>
|
||||||
|
<string name="remote_project_operation_no_network">Rete non disponibile per operazioni remote.</string>
|
||||||
|
<string name="error_scanning_bill_qr_code">Analisi del codice QR non riuscita.</string>
|
||||||
|
<string name="error_token_mismatch">Token di autenticazione non corrisponde. Effettua nuovamente il login.</string>
|
||||||
|
<string name="insufficient_access_level">Non hai i permessi per eseguire questa azione.</string>
|
||||||
|
<string name="delete_label_confirmation_title">Elimina Etichetta</string>
|
||||||
|
<string name="delete_label_confirmation_message">Sei sicuro di voler eliminare questa etichetta?</string>
|
||||||
|
<!-- About -->
|
||||||
|
<string name="about_version">Versione %1$s</string>
|
||||||
|
<string name="about_maintainer_title">Responsabile</string>
|
||||||
|
<string name="about_license_title">Licenza</string>
|
||||||
|
<string name="about_source_title">Codice sorgente</string>
|
||||||
|
<!-- New constants for backward compatibility or shared use -->
|
||||||
|
<string name="share_intent_title">Project %1$s</string>
|
||||||
|
<string name="share_chooser_title">Condividi %1$s</string>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,241 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
<string name="app_name">Cowspent</string>
|
||||||
|
<!-- Actions -->
|
||||||
|
<string name="action_new_bill">New bill</string>
|
||||||
|
<string name="action_add_project">Add project</string>
|
||||||
|
<string name="action_save">Save</string>
|
||||||
|
<string name="action_edit">Edit</string>
|
||||||
|
<string name="action_share">Share</string>
|
||||||
|
<string name="action_search">Search</string>
|
||||||
|
<string name="action_delete">Delete</string>
|
||||||
|
<string name="action_archive">Archive</string>
|
||||||
|
<string name="action_unarchive">Unarchive</string>
|
||||||
|
<string name="action_export">Export</string>
|
||||||
|
<string name="action_stats">Stats</string>
|
||||||
|
<string name="action_settle">Settle</string>
|
||||||
|
<string name="action_scan_qrcode">Scan QR Code</string>
|
||||||
|
<string name="action_settings">Settings</string>
|
||||||
|
<string name="action_label_bills">Label missing categories</string>
|
||||||
|
<string name="action_logout">Logout</string>
|
||||||
|
<string name="action_connect">Connect</string>
|
||||||
|
<string name="action_discard">Discard</string>
|
||||||
|
<string name="action_members">Members</string>
|
||||||
|
<string name="action_labels">Labels</string>
|
||||||
|
<string name="action_currencies">Currencies</string>
|
||||||
|
<!-- Titles -->
|
||||||
|
<string name="title_stats">Statistics</string>
|
||||||
|
<string name="title_edit_project">Edit project</string>
|
||||||
|
<string name="title_label_bills">Label Bills</string>
|
||||||
|
<string name="title_labels">Manage Labels</string>
|
||||||
|
<string name="title_about">About</string>
|
||||||
|
<string name="title_settle">Settle Project</string>
|
||||||
|
<string name="title_share">Share Project</string>
|
||||||
|
<string name="title_add_project">Add Project</string>
|
||||||
|
<string name="title_add_category">Add Category</string>
|
||||||
|
<string name="title_add_payment_mode">Add Payment Mode</string>
|
||||||
|
<string name="title_account">Nextcloud Account</string>
|
||||||
|
<string name="title_share_web">Web link</string>
|
||||||
|
<string name="title_share_qr">Cowspent link</string>
|
||||||
|
<string name="title_confirm">Are you sure?</string>
|
||||||
|
<!-- Labels and Fields -->
|
||||||
|
<string name="label_all_bills">All bills</string>
|
||||||
|
<string name="label_categories">Categories</string>
|
||||||
|
<string name="label_payment_modes">Payment Modes</string>
|
||||||
|
<string name="label_name">Name</string>
|
||||||
|
<string name="label_icon">Icon / Emoji</string>
|
||||||
|
<string name="label_color">Color</string>
|
||||||
|
<string name="label_weight">Weight</string>
|
||||||
|
<string name="label_activated">Activated</string>
|
||||||
|
<string name="label_password">Password</string>
|
||||||
|
<string name="label_email">E-mail</string>
|
||||||
|
<string name="label_url">Server address</string>
|
||||||
|
<string name="label_username">Username</string>
|
||||||
|
<string name="label_comment">Comment</string>
|
||||||
|
<string name="label_what">What?</string>
|
||||||
|
<string name="label_payer">Who paid?</string>
|
||||||
|
<string name="label_owers">For whom?</string>
|
||||||
|
<string name="label_repeat">Repeat every</string>
|
||||||
|
<string name="label_mode">Mode</string>
|
||||||
|
<string name="label_category">Category</string>
|
||||||
|
<string name="label_project_id">Project ID/name</string>
|
||||||
|
<string name="label_project_title">Project title</string>
|
||||||
|
<string name="label_use_sso">Use Nextcloud App Account</string>
|
||||||
|
<!-- Dialogs and Messages -->
|
||||||
|
<string name="dialog_unsaved_changes_title">Unsaved changes</string>
|
||||||
|
<string name="dialog_unsaved_changes_msg">Save changes before leaving?</string>
|
||||||
|
<string name="dialog_confirm_remove_project_msg">The remote project will not be deleted.</string>
|
||||||
|
<string name="dialog_sync_error_title">Sync error</string>
|
||||||
|
<string name="dialog_sync_error_msg">Sync failed for %1$s.\n\n%2$s</string>
|
||||||
|
<string name="dialog_balanced_msg">Expenses are already balanced.</string>
|
||||||
|
<string name="msg_project_added">Project %1$s added</string>
|
||||||
|
<string name="msg_bill_labeled_done">All bills labeled</string>
|
||||||
|
<string name="msg_no_suggestions">No suggestions</string>
|
||||||
|
<string name="msg_auth_warning">Requires Cospend v0.3.4+.</string>
|
||||||
|
<string name="msg_link_copied">Link copied to clipboard</string>
|
||||||
|
<string name="msg_share_qr">Scan QR code or share the link to join.</string>
|
||||||
|
<string name="msg_share_web">Link for web browser access.</string>
|
||||||
|
<string name="msg_share_qr_warn">Share this link with a Cowspent user.</string>
|
||||||
|
<string name="msg_settle_intro">Settlement for %1$s:</string>
|
||||||
|
<string name="msg_settle_sentence">%1$s owes %3$.2f to %2$s</string>
|
||||||
|
<string name="msg_stats_intro">Stats for %1$s:</string>
|
||||||
|
<string name="msg_stats_header">Member (Paid | Spent | Balance)</string>
|
||||||
|
<string name="msg_logged_in_as">Logged in as %1$s</string>
|
||||||
|
<!-- Errors -->
|
||||||
|
<string name="error_generic">Error</string>
|
||||||
|
<string name="error_loading">Loading</string>
|
||||||
|
<string name="error_no_projects">No projects found</string>
|
||||||
|
<string name="error_no_members">No members found</string>
|
||||||
|
<string name="error_no_bills">No bills found</string>
|
||||||
|
<string name="error_no_member">At least one member required</string>
|
||||||
|
<string name="error_maintenance_mode">Server is in maintenance mode</string>
|
||||||
|
<string name="error_400">400 Bad request</string>
|
||||||
|
<string name="error_401">401 Unauthorized</string>
|
||||||
|
<string name="error_403">403 Forbidden</string>
|
||||||
|
<string name="error_404">404 Not Found</string>
|
||||||
|
<string name="error_sync">Sync failed: %1$s</string>
|
||||||
|
<string name="error_invalid_login">Invalid login: %1$s</string>
|
||||||
|
<string name="error_auth">Wrong username or password</string>
|
||||||
|
<string name="error_json">Invalid server response</string>
|
||||||
|
<string name="error_req_failed">Request failed</string>
|
||||||
|
<string name="error_invalid_email">Invalid e-mail</string>
|
||||||
|
<string name="error_invalid_project_id">Invalid project ID</string>
|
||||||
|
<string name="error_invalid_project_name">Invalid project title</string>
|
||||||
|
<string name="error_invalid_bill_name">Invalid bill name</string>
|
||||||
|
<string name="error_invalid_bill_date">Invalid bill date</string>
|
||||||
|
<string name="error_invalid_bill_payer">Payer required</string>
|
||||||
|
<string name="error_invalid_bill_owers">Owers required</string>
|
||||||
|
<string name="error_no_network">No network connection</string>
|
||||||
|
<string name="error_server">Server error</string>
|
||||||
|
<string name="error_io">Server connection broken</string>
|
||||||
|
<string name="error_share_impossible">Cannot share this project</string>
|
||||||
|
<!-- Drawer / Common UI -->
|
||||||
|
<string name="drawer_no_account">Connect to Nextcloud account</string>
|
||||||
|
<string name="drawer_last_sync">Last sync: %1$02d:%2$02d</string>
|
||||||
|
<string name="simple_cancel">Cancel</string>
|
||||||
|
<string name="simple_ok" tools:ignore="ButtonCase,Typos">Ok</string>
|
||||||
|
<string name="simple_yes">Yes</string>
|
||||||
|
<string name="simple_no">No</string>
|
||||||
|
<string name="simple_close">Close</string>
|
||||||
|
<!-- Settings -->
|
||||||
|
<string name="settings_appearance">Appearance</string>
|
||||||
|
<string name="settings_network">Network</string>
|
||||||
|
<string name="settings_other">Other</string>
|
||||||
|
<string name="settings_night_mode">Theme</string>
|
||||||
|
<string name="settings_offline_mode">Offline mode</string>
|
||||||
|
<string name="settings_offline_mode_summary">Only sync manually.</string>
|
||||||
|
<string name="settings_color_custom">Custom color</string>
|
||||||
|
<string name="settings_color_mode">Color Selection</string>
|
||||||
|
<string name="settings_show_archived">Show archived projects</string>
|
||||||
|
<string name="settings_beta_features">Beta Features</string>
|
||||||
|
<string name="settings_beta_features_summary">Enable experimental features. Use at your own risk.</string>
|
||||||
|
<string name="settings_url_warn_http">WARNING: \"http\" is unsafe. Use \"https\".</string>
|
||||||
|
<string name="settings_colorpicker_title">Choose Color</string>
|
||||||
|
<string name="pref_value_color_system">System</string>
|
||||||
|
<string name="pref_value_color_server">Nextcloud</string>
|
||||||
|
<string name="pref_value_color_manual">Manual</string>
|
||||||
|
<string name="pref_value_theme_light">Light</string>
|
||||||
|
<string name="pref_value_theme_dark">Dark</string>
|
||||||
|
<string name="pref_value_theme_system">Follow system</string>
|
||||||
|
<!-- Constants (Do not translate) -->
|
||||||
|
<!-- Enums and Lists -->
|
||||||
|
<string name="repeat_no">No repeat</string>
|
||||||
|
<string name="repeat_day">Daily</string>
|
||||||
|
<string name="repeat_week">Weekly</string>
|
||||||
|
<string name="repeat_fortnight">Fortnightly</string>
|
||||||
|
<string name="repeat_month">Monthly</string>
|
||||||
|
<string name="repeat_year">Yearly</string>
|
||||||
|
<string name="payment_mode_none">None</string>
|
||||||
|
<string name="payment_mode_all">All</string>
|
||||||
|
<string name="payment_mode_credit_card">Credit card</string>
|
||||||
|
<string name="payment_mode_cash">Cash</string>
|
||||||
|
<string name="payment_mode_check">Check</string>
|
||||||
|
<string name="payment_mode_online">Online</string>
|
||||||
|
<string name="payment_mode_transfer">Transfer</string>
|
||||||
|
<string name="category_none">None</string>
|
||||||
|
<string name="category_all">All</string>
|
||||||
|
<string name="category_all_except_reimbursement">All except reimbursement</string>
|
||||||
|
<string name="category_groceries">Grocery</string>
|
||||||
|
<string name="category_leisure">Bar/Party</string>
|
||||||
|
<string name="category_rent">Rent</string>
|
||||||
|
<string name="category_bills">Bill</string>
|
||||||
|
<string name="category_excursion">Excursion/Culture</string>
|
||||||
|
<string name="category_health">Health</string>
|
||||||
|
<string name="category_shopping">Shopping</string>
|
||||||
|
<string name="category_reimbursement">Reimbursement</string>
|
||||||
|
<string name="category_restaurant">Restaurant</string>
|
||||||
|
<string name="category_accomodation">Accommodation</string>
|
||||||
|
<string name="category_transport">Transport</string>
|
||||||
|
<string name="category_sport">Sport</string>
|
||||||
|
<!-- Project specific -->
|
||||||
|
<string name="new_project_action">What</string>
|
||||||
|
<string name="new_project_where">Where</string>
|
||||||
|
<string name="where_local">Local only</string>
|
||||||
|
<string name="where_cospend">Cospend</string>
|
||||||
|
<string name="where_ihatemoney">IHateMoney</string>
|
||||||
|
<string name="todo_join">Join existing project</string>
|
||||||
|
<string name="todo_create">Create new project</string>
|
||||||
|
<string name="import_tooltip">Import from file</string>
|
||||||
|
<string name="choose_account_project_dialog_title">Choose project</string>
|
||||||
|
<string name="choose_account_project_dialog_impossible">No projects found on this account.</string>
|
||||||
|
<string name="choose_project_management_action">Project</string>
|
||||||
|
<string name="project_added_success">Project added successfully.</string>
|
||||||
|
<string name="no_projects_text">You have no projects yet.</string>
|
||||||
|
<string name="configure_account_choice">Configure Nextcloud account</string>
|
||||||
|
<string name="add_project_choice">Add project manually</string>
|
||||||
|
<string name="no_members_text">No members in this project.</string>
|
||||||
|
<string name="no_bills_text">No bills found.</string>
|
||||||
|
<string name="member_already_exists">Member already exists.</string>
|
||||||
|
<string name="activity_dialog_title">Project: %1$s</string>
|
||||||
|
<string name="remove_project_confirmation">Project %1$s removed.</string>
|
||||||
|
<string name="file_saved_success">File saved: %1$s</string>
|
||||||
|
<string name="import_error_header">Import failed at row %d</string>
|
||||||
|
<string name="import_error_date">Invalid date format at row %d</string>
|
||||||
|
<string name="import_error_owers">Invalid owers at row %d</string>
|
||||||
|
<string name="add_member_dialog_title">Add Member</string>
|
||||||
|
<string name="edit_member_dialog_title">Edit Member</string>
|
||||||
|
<string name="member_edit_delete">Delete</string>
|
||||||
|
<string name="project_edition_no_change">No changes to save.</string>
|
||||||
|
<!-- Settlement -->
|
||||||
|
<string name="center_none">None (Optimal)</string>
|
||||||
|
<string name="settle_who">Who pays</string>
|
||||||
|
<string name="settle_to_whom">To whom</string>
|
||||||
|
<string name="settle_how_much">Amount</string>
|
||||||
|
<string name="simple_settle_share">Share</string>
|
||||||
|
<string name="simple_create_bills">Create bills</string>
|
||||||
|
<string name="settle_bill_what">Settlement</string>
|
||||||
|
<!-- Currencies -->
|
||||||
|
<string name="currency_dialog_title">Choose Currency (%s)</string>
|
||||||
|
<string name="setting_none">None</string>
|
||||||
|
<string name="setting_all">All</string>
|
||||||
|
<string name="currency_saved_success">Currency settings saved.</string>
|
||||||
|
<string name="main_currency">Main Currency</string>
|
||||||
|
<!-- Statistics -->
|
||||||
|
<string name="label_bills_suggested">Suggested Categories</string>
|
||||||
|
<string name="label_bills_skip">Skip</string>
|
||||||
|
<string name="stats_date_min">From</string>
|
||||||
|
<string name="stats_date_max">To</string>
|
||||||
|
<string name="stats_who">Member</string>
|
||||||
|
<string name="stats_paid">Paid</string>
|
||||||
|
<string name="stats_spent">Spent</string>
|
||||||
|
<string name="stats_balance">Balance</string>
|
||||||
|
<string name="total">Total: %1$s</string>
|
||||||
|
<!-- Errors Extra -->
|
||||||
|
<string name="error_project_connect_check">Connection failed: %1$s</string>
|
||||||
|
<string name="error_create_remote_project_helper">Creation failed: %1$s</string>
|
||||||
|
<string name="error_edit_remote_project_helper">Error updating remote project: %1$s</string>
|
||||||
|
<string name="remote_project_operation_no_network">Network unavailable for remote operation.</string>
|
||||||
|
<string name="error_scanning_bill_qr_code">Failed to parse QR code.</string>
|
||||||
|
<string name="error_token_mismatch">Authentication token mismatch. Please log in again.</string>
|
||||||
|
<string name="insufficient_access_level">You don\'t have permission to perform this action.</string>
|
||||||
|
<string name="delete_label_confirmation_title">Delete Label</string>
|
||||||
|
<string name="delete_label_confirmation_message">Are you sure you want to delete this label?</string>
|
||||||
|
<!-- About -->
|
||||||
|
<string name="about_version">Version %1$s</string>
|
||||||
|
<string name="about_maintainer_title">Maintainer</string>
|
||||||
|
<string name="about_license_title">License</string>
|
||||||
|
<string name="about_source_title">Source code</string>
|
||||||
|
<!-- New constants for backward compatibility or shared use -->
|
||||||
|
<string name="share_intent_title">Project %1$s</string>
|
||||||
|
<string name="share_chooser_title">Share %1$s</string>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,241 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
<string name="app_name">Cowspent</string>
|
||||||
|
<!-- Actions -->
|
||||||
|
<string name="action_new_bill">新しい請求書</string>
|
||||||
|
<string name="action_add_project">プロジェクトを追加</string>
|
||||||
|
<string name="action_save">保存</string>
|
||||||
|
<string name="action_edit">編集</string>
|
||||||
|
<string name="action_share">共有</string>
|
||||||
|
<string name="action_search">検索</string>
|
||||||
|
<string name="action_delete">削除</string>
|
||||||
|
<string name="action_archive">アーカイブ</string>
|
||||||
|
<string name="action_unarchive">アーカイブ解除</string>
|
||||||
|
<string name="action_export">エクスポート</string>
|
||||||
|
<string name="action_stats">統計</string>
|
||||||
|
<string name="action_settle">決済する</string>
|
||||||
|
<string name="action_scan_qrcode">Scan QR Code</string>
|
||||||
|
<string name="action_settings">設定</string>
|
||||||
|
<string name="action_label_bills">ラベル不足しているカテゴリ</string>
|
||||||
|
<string name="action_logout">ログアウト</string>
|
||||||
|
<string name="action_connect">接続する</string>
|
||||||
|
<string name="action_discard">破棄する</string>
|
||||||
|
<string name="action_members">メンバー</string>
|
||||||
|
<string name="action_labels">ラベル</string>
|
||||||
|
<string name="action_currencies">通貨</string>
|
||||||
|
<!-- Titles -->
|
||||||
|
<string name="title_stats">統計情報</string>
|
||||||
|
<string name="title_edit_project">プロジェクトを編集</string>
|
||||||
|
<string name="title_label_bills">請求書のラベル</string>
|
||||||
|
<string name="title_labels">ラベルの管理</string>
|
||||||
|
<string name="title_about">About</string>
|
||||||
|
<string name="title_settle">プロジェクトを決済する</string>
|
||||||
|
<string name="title_share">プロジェクトを共有</string>
|
||||||
|
<string name="title_add_project">プロジェクトを追加</string>
|
||||||
|
<string name="title_add_category">カテゴリを追加</string>
|
||||||
|
<string name="title_add_payment_mode">支払いモードを追加</string>
|
||||||
|
<string name="title_account">Nextcloudアカウント</string>
|
||||||
|
<string name="title_share_web">ウェブリンク</string>
|
||||||
|
<string name="title_share_qr">Cowspent link</string>
|
||||||
|
<string name="title_confirm">よろしいですか?</string>
|
||||||
|
<!-- Labels and Fields -->
|
||||||
|
<string name="label_all_bills">すべての請求書</string>
|
||||||
|
<string name="label_categories">カテゴリ</string>
|
||||||
|
<string name="label_payment_modes">支払いモード</string>
|
||||||
|
<string name="label_name">名前</string>
|
||||||
|
<string name="label_icon">Icon / Emoji</string>
|
||||||
|
<string name="label_color">色</string>
|
||||||
|
<string name="label_weight">重量</string>
|
||||||
|
<string name="label_activated">アクティベート済み</string>
|
||||||
|
<string name="label_password">パスワード</string>
|
||||||
|
<string name="label_email">電子メール</string>
|
||||||
|
<string name="label_url">サーバーアドレス</string>
|
||||||
|
<string name="label_username">ユーザー名</string>
|
||||||
|
<string name="label_comment">コメント</string>
|
||||||
|
<string name="label_what">何?</string>
|
||||||
|
<string name="label_payer">誰が支払ったのですか?</string>
|
||||||
|
<string name="label_owers">誰のために?</string>
|
||||||
|
<string name="label_repeat">繰り返します</string>
|
||||||
|
<string name="label_mode">モード</string>
|
||||||
|
<string name="label_category">カテゴリ</string>
|
||||||
|
<string name="label_project_id">プロジェクト ID/名前</string>
|
||||||
|
<string name="label_project_title">プロジェクトのタイトル</string>
|
||||||
|
<string name="label_use_sso">Nextcloudアプリアカウントを使用する</string>
|
||||||
|
<!-- Dialogs and Messages -->
|
||||||
|
<string name="dialog_unsaved_changes_title">保存されていない変更</string>
|
||||||
|
<string name="dialog_unsaved_changes_msg">脱退する前に変更を保存しますか?</string>
|
||||||
|
<string name="dialog_confirm_remove_project_msg">リモートプロジェクトは削除されません。</string>
|
||||||
|
<string name="dialog_sync_error_title">同期エラー</string>
|
||||||
|
<string name="dialog_sync_error_msg">%1$sの同期に失敗しました。\n\n%2$s</string>
|
||||||
|
<string name="dialog_balanced_msg">費用はすでに均衡しています。</string>
|
||||||
|
<string name="msg_project_added">プロジェクト %1$s が追加されました</string>
|
||||||
|
<string name="msg_bill_labeled_done">すべての請求書がラベル付けされました</string>
|
||||||
|
<string name="msg_no_suggestions">候補なし</string>
|
||||||
|
<string name="msg_auth_warning">Cospend v0.3.4以上が必要です。</string>
|
||||||
|
<string name="msg_link_copied">リンクをクリップボードにコピーしました</string>
|
||||||
|
<string name="msg_share_qr">QRコードをスキャンするか、リンクを共有して参加してください。</string>
|
||||||
|
<string name="msg_share_web">ウェブブラウザへのアクセスのためのリンク。</string>
|
||||||
|
<string name="msg_share_qr_warn">Cowspent ユーザーとこのリンクを共有します。</string>
|
||||||
|
<string name="msg_settle_intro">%1$sの決済:</string>
|
||||||
|
<string name="msg_settle_sentence">%1$s は %3$.2f を %2$sに借りています</string>
|
||||||
|
<string name="msg_stats_intro">%1$s の統計:</string>
|
||||||
|
<string name="msg_stats_header">メンバー (ペイド | 使用済み)</string>
|
||||||
|
<string name="msg_logged_in_as">%1$s としてログイン中</string>
|
||||||
|
<!-- Errors -->
|
||||||
|
<string name="error_generic">エラー</string>
|
||||||
|
<string name="error_loading">読み込み中</string>
|
||||||
|
<string name="error_no_projects">プロジェクトが見つかりません</string>
|
||||||
|
<string name="error_no_members">メンバーが見つかりませんでした</string>
|
||||||
|
<string name="error_no_bills">請求書が見つかりません</string>
|
||||||
|
<string name="error_no_member">少なくとも1人のメンバーが必要です</string>
|
||||||
|
<string name="error_maintenance_mode">サーバーはメンテナンスモードです</string>
|
||||||
|
<string name="error_400">400件の不正なリクエスト</string>
|
||||||
|
<string name="error_401">401 Unauthorized</string>
|
||||||
|
<string name="error_403">403 禁止されました</string>
|
||||||
|
<string name="error_404">404 が見つかりません</string>
|
||||||
|
<string name="error_sync">同期失敗: %1$s</string>
|
||||||
|
<string name="error_invalid_login">無効なログイン: %1$s</string>
|
||||||
|
<string name="error_auth">ユーザー名またはパスワードが間違っています</string>
|
||||||
|
<string name="error_json">無効なサーバー応答</string>
|
||||||
|
<string name="error_req_failed">リクエストに失敗しました</string>
|
||||||
|
<string name="error_invalid_email">無効なメールアドレス</string>
|
||||||
|
<string name="error_invalid_project_id">無効なプロジェクト ID</string>
|
||||||
|
<string name="error_invalid_project_name">無効なプロジェクトのタイトル</string>
|
||||||
|
<string name="error_invalid_bill_name">無効な請求書名</string>
|
||||||
|
<string name="error_invalid_bill_date">請求書の日付が無効</string>
|
||||||
|
<string name="error_invalid_bill_payer">支払者が必要です</string>
|
||||||
|
<string name="error_invalid_bill_owers">所有者が必要</string>
|
||||||
|
<string name="error_no_network">ネットワーク接続がありません</string>
|
||||||
|
<string name="error_server">サーバーエラー</string>
|
||||||
|
<string name="error_io">サーバー接続が切断されました</string>
|
||||||
|
<string name="error_share_impossible">このプロジェクトを共有できません</string>
|
||||||
|
<!-- Drawer / Common UI -->
|
||||||
|
<string name="drawer_no_account">Nextcloudアカウントに接続</string>
|
||||||
|
<string name="drawer_last_sync">最終同期: %1$02d:%2$02d</string>
|
||||||
|
<string name="simple_cancel">キャンセル</string>
|
||||||
|
<string name="simple_ok" tools:ignore="ButtonCase,Typos">OK</string>
|
||||||
|
<string name="simple_yes">はい</string>
|
||||||
|
<string name="simple_no">いいえ</string>
|
||||||
|
<string name="simple_close">閉じる</string>
|
||||||
|
<!-- Settings -->
|
||||||
|
<string name="settings_appearance">外観</string>
|
||||||
|
<string name="settings_network">ネットワーク</string>
|
||||||
|
<string name="settings_other">その他</string>
|
||||||
|
<string name="settings_night_mode">テーマ</string>
|
||||||
|
<string name="settings_offline_mode">オフラインモード</string>
|
||||||
|
<string name="settings_offline_mode_summary">手動同期のみ。</string>
|
||||||
|
<string name="settings_color_custom">カスタムカラー</string>
|
||||||
|
<string name="settings_color_mode">色の選択</string>
|
||||||
|
<string name="settings_show_archived">アーカイブされたプロジェクトを表示</string>
|
||||||
|
<string name="settings_beta_features">ベータ版の機能</string>
|
||||||
|
<string name="settings_beta_features_summary">実験的な機能を有効にします。自己責任で使用してください。</string>
|
||||||
|
<string name="settings_url_warn_http">警告: \"http\"は安全ではありません。\"https\"を使用してください。</string>
|
||||||
|
<string name="settings_colorpicker_title">色を選択</string>
|
||||||
|
<string name="pref_value_color_system">システム</string>
|
||||||
|
<string name="pref_value_color_server">Nextcloud:</string>
|
||||||
|
<string name="pref_value_color_manual">マニュアル</string>
|
||||||
|
<string name="pref_value_theme_light">ライト</string>
|
||||||
|
<string name="pref_value_theme_dark">ダーク</string>
|
||||||
|
<string name="pref_value_theme_system">システムをフォロー</string>
|
||||||
|
<!-- Constants (Do not translate) -->
|
||||||
|
<!-- Enums and Lists -->
|
||||||
|
<string name="repeat_no">繰り返しなし</string>
|
||||||
|
<string name="repeat_day">毎日</string>
|
||||||
|
<string name="repeat_week">Weekly</string>
|
||||||
|
<string name="repeat_fortnight">四十夜</string>
|
||||||
|
<string name="repeat_month">月ごと</string>
|
||||||
|
<string name="repeat_year">年ごと</string>
|
||||||
|
<string name="payment_mode_none">なし</string>
|
||||||
|
<string name="payment_mode_all">すべて</string>
|
||||||
|
<string name="payment_mode_credit_card">クレジットカード</string>
|
||||||
|
<string name="payment_mode_cash">現金</string>
|
||||||
|
<string name="payment_mode_check">チェック</string>
|
||||||
|
<string name="payment_mode_online">オンライン</string>
|
||||||
|
<string name="payment_mode_transfer">送金</string>
|
||||||
|
<string name="category_none">なし</string>
|
||||||
|
<string name="category_all">すべて</string>
|
||||||
|
<string name="category_all_except_reimbursement">払い戻し以外のすべて</string>
|
||||||
|
<string name="category_groceries">食料品店</string>
|
||||||
|
<string name="category_leisure">バー/パーティー</string>
|
||||||
|
<string name="category_rent">レンタル</string>
|
||||||
|
<string name="category_bills">請求書:</string>
|
||||||
|
<string name="category_excursion">エクスカーション/文化</string>
|
||||||
|
<string name="category_health">健康</string>
|
||||||
|
<string name="category_shopping">ショッピング</string>
|
||||||
|
<string name="category_reimbursement">払い戻し額</string>
|
||||||
|
<string name="category_restaurant">レストラン</string>
|
||||||
|
<string name="category_accomodation">宿泊施設</string>
|
||||||
|
<string name="category_transport">交通</string>
|
||||||
|
<string name="category_sport">スポーツ</string>
|
||||||
|
<!-- Project specific -->
|
||||||
|
<string name="new_project_action">What</string>
|
||||||
|
<string name="new_project_where">場所</string>
|
||||||
|
<string name="where_local">ローカルのみ</string>
|
||||||
|
<string name="where_cospend">Cospend</string>
|
||||||
|
<string name="where_ihatemoney">IHateMoney</string>
|
||||||
|
<string name="todo_join">既存のプロジェクトに参加</string>
|
||||||
|
<string name="todo_create">新しいプロジェクトを作成</string>
|
||||||
|
<string name="import_tooltip">ファイルからインポート</string>
|
||||||
|
<string name="choose_account_project_dialog_title">プロジェクトを選択</string>
|
||||||
|
<string name="choose_account_project_dialog_impossible">このアカウントにはプロジェクトが見つかりません。</string>
|
||||||
|
<string name="choose_project_management_action">プロジェクト</string>
|
||||||
|
<string name="project_added_success">プロジェクトが正常に追加されました。</string>
|
||||||
|
<string name="no_projects_text">プロジェクトはまだありません。</string>
|
||||||
|
<string name="configure_account_choice">Nextcloudアカウントの設定</string>
|
||||||
|
<string name="add_project_choice">プロジェクトを手動で追加</string>
|
||||||
|
<string name="no_members_text">このプロジェクトにはメンバーがいません。</string>
|
||||||
|
<string name="no_bills_text">請求書が見つかりません。</string>
|
||||||
|
<string name="member_already_exists">メンバーは既に存在します。</string>
|
||||||
|
<string name="activity_dialog_title">プロジェクト: %1$s</string>
|
||||||
|
<string name="remove_project_confirmation">プロジェクト %1$s が削除されました。</string>
|
||||||
|
<string name="file_saved_success">ファイルを保存しました: %1$s</string>
|
||||||
|
<string name="import_error_header">%d 行目のインポートに失敗しました</string>
|
||||||
|
<string name="import_error_date">%d 行目に不正な日付形式があります</string>
|
||||||
|
<string name="import_error_owers">行 %d に無効な所有者がいます</string>
|
||||||
|
<string name="add_member_dialog_title">メンバーを追加</string>
|
||||||
|
<string name="edit_member_dialog_title">メンバーを編集</string>
|
||||||
|
<string name="member_edit_delete">削除</string>
|
||||||
|
<string name="project_edition_no_change">保存する変更はありません。</string>
|
||||||
|
<!-- Settlement -->
|
||||||
|
<string name="center_none">なし (最適)</string>
|
||||||
|
<string name="settle_who">誰が支払うか</string>
|
||||||
|
<string name="settle_to_whom">誰に</string>
|
||||||
|
<string name="settle_how_much">金額</string>
|
||||||
|
<string name="simple_settle_share">共有</string>
|
||||||
|
<string name="simple_create_bills">請求書を作成</string>
|
||||||
|
<string name="settle_bill_what">受領日</string>
|
||||||
|
<!-- Currencies -->
|
||||||
|
<string name="currency_dialog_title">通貨を選択 (%s)</string>
|
||||||
|
<string name="setting_none">なし</string>
|
||||||
|
<string name="setting_all">すべて</string>
|
||||||
|
<string name="currency_saved_success">通貨設定を保存しました。</string>
|
||||||
|
<string name="main_currency">主な通貨</string>
|
||||||
|
<!-- Statistics -->
|
||||||
|
<string name="label_bills_suggested">おすすめのカテゴリ</string>
|
||||||
|
<string name="label_bills_skip">スキップ</string>
|
||||||
|
<string name="stats_date_min">差出人:</string>
|
||||||
|
<string name="stats_date_max">終了日</string>
|
||||||
|
<string name="stats_who">メンバー</string>
|
||||||
|
<string name="stats_paid">支払い済み</string>
|
||||||
|
<string name="stats_spent">使用済み:</string>
|
||||||
|
<string name="stats_balance">残高</string>
|
||||||
|
<string name="total">合計: %1$s</string>
|
||||||
|
<!-- Errors Extra -->
|
||||||
|
<string name="error_project_connect_check">接続に失敗しました: %1$s</string>
|
||||||
|
<string name="error_create_remote_project_helper">作成失敗: %1$s</string>
|
||||||
|
<string name="error_edit_remote_project_helper">リモートプロジェクトの更新中にエラーが発生しました: %1$s</string>
|
||||||
|
<string name="remote_project_operation_no_network">リモート操作でネットワークを利用できません。</string>
|
||||||
|
<string name="error_scanning_bill_qr_code">QRコードの解析に失敗しました。</string>
|
||||||
|
<string name="error_token_mismatch">認証トークンが一致しません。もう一度ログインしてください。</string>
|
||||||
|
<string name="insufficient_access_level">このアクションを実行する権限がありません。</string>
|
||||||
|
<string name="delete_label_confirmation_title">ラベルを削除</string>
|
||||||
|
<string name="delete_label_confirmation_message">このラベルを削除してもよろしいですか?</string>
|
||||||
|
<!-- About -->
|
||||||
|
<string name="about_version">バージョン %1$s</string>
|
||||||
|
<string name="about_maintainer_title">メンテナー</string>
|
||||||
|
<string name="about_license_title">ライセンス</string>
|
||||||
|
<string name="about_source_title">ソース コード</string>
|
||||||
|
<!-- New constants for backward compatibility or shared use -->
|
||||||
|
<string name="share_intent_title">プロジェクト %1$s</string>
|
||||||
|
<string name="share_chooser_title">%1$sを共有</string>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,241 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
<string name="app_name">Cowspent</string>
|
||||||
|
<!-- Actions -->
|
||||||
|
<string name="action_new_bill">New bill</string>
|
||||||
|
<string name="action_add_project">Add project</string>
|
||||||
|
<string name="action_save">Save</string>
|
||||||
|
<string name="action_edit">Edit</string>
|
||||||
|
<string name="action_share">Share</string>
|
||||||
|
<string name="action_search">Search</string>
|
||||||
|
<string name="action_delete">Delete</string>
|
||||||
|
<string name="action_archive">Archive</string>
|
||||||
|
<string name="action_unarchive">Unarchive</string>
|
||||||
|
<string name="action_export">Export</string>
|
||||||
|
<string name="action_stats">Stats</string>
|
||||||
|
<string name="action_settle">Settle</string>
|
||||||
|
<string name="action_scan_qrcode">Scan QR Code</string>
|
||||||
|
<string name="action_settings">Settings</string>
|
||||||
|
<string name="action_label_bills">Label missing categories</string>
|
||||||
|
<string name="action_logout">Logout</string>
|
||||||
|
<string name="action_connect">Connect</string>
|
||||||
|
<string name="action_discard">Discard</string>
|
||||||
|
<string name="action_members">Members</string>
|
||||||
|
<string name="action_labels">Labels</string>
|
||||||
|
<string name="action_currencies">Currencies</string>
|
||||||
|
<!-- Titles -->
|
||||||
|
<string name="title_stats">Statistics</string>
|
||||||
|
<string name="title_edit_project">Edit project</string>
|
||||||
|
<string name="title_label_bills">Label Bills</string>
|
||||||
|
<string name="title_labels">Manage Labels</string>
|
||||||
|
<string name="title_about">About</string>
|
||||||
|
<string name="title_settle">Settle Project</string>
|
||||||
|
<string name="title_share">Share Project</string>
|
||||||
|
<string name="title_add_project">Add Project</string>
|
||||||
|
<string name="title_add_category">Add Category</string>
|
||||||
|
<string name="title_add_payment_mode">Add Payment Mode</string>
|
||||||
|
<string name="title_account">Nextcloud Account</string>
|
||||||
|
<string name="title_share_web">Web link</string>
|
||||||
|
<string name="title_share_qr">Cowspent link</string>
|
||||||
|
<string name="title_confirm">Are you sure?</string>
|
||||||
|
<!-- Labels and Fields -->
|
||||||
|
<string name="label_all_bills">All bills</string>
|
||||||
|
<string name="label_categories">Categories</string>
|
||||||
|
<string name="label_payment_modes">Payment Modes</string>
|
||||||
|
<string name="label_name">Name</string>
|
||||||
|
<string name="label_icon">Icon / Emoji</string>
|
||||||
|
<string name="label_color">Color</string>
|
||||||
|
<string name="label_weight">Weight</string>
|
||||||
|
<string name="label_activated">Activated</string>
|
||||||
|
<string name="label_password">Password</string>
|
||||||
|
<string name="label_email">E-mail</string>
|
||||||
|
<string name="label_url">Server address</string>
|
||||||
|
<string name="label_username">Username</string>
|
||||||
|
<string name="label_comment">Comment</string>
|
||||||
|
<string name="label_what">What?</string>
|
||||||
|
<string name="label_payer">Who paid?</string>
|
||||||
|
<string name="label_owers">For whom?</string>
|
||||||
|
<string name="label_repeat">Repeat every</string>
|
||||||
|
<string name="label_mode">Mode</string>
|
||||||
|
<string name="label_category">Category</string>
|
||||||
|
<string name="label_project_id">Project ID/name</string>
|
||||||
|
<string name="label_project_title">Project title</string>
|
||||||
|
<string name="label_use_sso">Use Nextcloud App Account</string>
|
||||||
|
<!-- Dialogs and Messages -->
|
||||||
|
<string name="dialog_unsaved_changes_title">Unsaved changes</string>
|
||||||
|
<string name="dialog_unsaved_changes_msg">Save changes before leaving?</string>
|
||||||
|
<string name="dialog_confirm_remove_project_msg">The remote project will not be deleted.</string>
|
||||||
|
<string name="dialog_sync_error_title">Sync error</string>
|
||||||
|
<string name="dialog_sync_error_msg">Sync failed for %1$s.\n\n%2$s</string>
|
||||||
|
<string name="dialog_balanced_msg">Expenses are already balanced.</string>
|
||||||
|
<string name="msg_project_added">Project %1$s added</string>
|
||||||
|
<string name="msg_bill_labeled_done">All bills labeled</string>
|
||||||
|
<string name="msg_no_suggestions">No suggestions</string>
|
||||||
|
<string name="msg_auth_warning">Requires Cospend v0.3.4+.</string>
|
||||||
|
<string name="msg_link_copied">Link copied to clipboard</string>
|
||||||
|
<string name="msg_share_qr">Scan QR code or share the link to join.</string>
|
||||||
|
<string name="msg_share_web">Link for web browser access.</string>
|
||||||
|
<string name="msg_share_qr_warn">Share this link with a Cowspent user.</string>
|
||||||
|
<string name="msg_settle_intro">Settlement for %1$s:</string>
|
||||||
|
<string name="msg_settle_sentence">%1$s owes %3$.2f to %2$s</string>
|
||||||
|
<string name="msg_stats_intro">Stats for %1$s:</string>
|
||||||
|
<string name="msg_stats_header">Member (Paid | Spent | Balance)</string>
|
||||||
|
<string name="msg_logged_in_as">Logged in as %1$s</string>
|
||||||
|
<!-- Errors -->
|
||||||
|
<string name="error_generic">Error</string>
|
||||||
|
<string name="error_loading">Loading</string>
|
||||||
|
<string name="error_no_projects">No projects found</string>
|
||||||
|
<string name="error_no_members">No members found</string>
|
||||||
|
<string name="error_no_bills">No bills found</string>
|
||||||
|
<string name="error_no_member">At least one member required</string>
|
||||||
|
<string name="error_maintenance_mode">Server is in maintenance mode</string>
|
||||||
|
<string name="error_400">400 Bad request</string>
|
||||||
|
<string name="error_401">401 Unauthorized</string>
|
||||||
|
<string name="error_403">403 Forbidden</string>
|
||||||
|
<string name="error_404">404 Not Found</string>
|
||||||
|
<string name="error_sync">Sync failed: %1$s</string>
|
||||||
|
<string name="error_invalid_login">Invalid login: %1$s</string>
|
||||||
|
<string name="error_auth">Wrong username or password</string>
|
||||||
|
<string name="error_json">Invalid server response</string>
|
||||||
|
<string name="error_req_failed">Request failed</string>
|
||||||
|
<string name="error_invalid_email">Invalid e-mail</string>
|
||||||
|
<string name="error_invalid_project_id">Invalid project ID</string>
|
||||||
|
<string name="error_invalid_project_name">Invalid project title</string>
|
||||||
|
<string name="error_invalid_bill_name">Invalid bill name</string>
|
||||||
|
<string name="error_invalid_bill_date">Invalid bill date</string>
|
||||||
|
<string name="error_invalid_bill_payer">Payer required</string>
|
||||||
|
<string name="error_invalid_bill_owers">Owers required</string>
|
||||||
|
<string name="error_no_network">No network connection</string>
|
||||||
|
<string name="error_server">Server error</string>
|
||||||
|
<string name="error_io">Server connection broken</string>
|
||||||
|
<string name="error_share_impossible">Cannot share this project</string>
|
||||||
|
<!-- Drawer / Common UI -->
|
||||||
|
<string name="drawer_no_account">Connect to Nextcloud account</string>
|
||||||
|
<string name="drawer_last_sync">Last sync: %1$02d:%2$02d</string>
|
||||||
|
<string name="simple_cancel">Cancel</string>
|
||||||
|
<string name="simple_ok" tools:ignore="ButtonCase,Typos">Ok</string>
|
||||||
|
<string name="simple_yes">Yes</string>
|
||||||
|
<string name="simple_no">No</string>
|
||||||
|
<string name="simple_close">Close</string>
|
||||||
|
<!-- Settings -->
|
||||||
|
<string name="settings_appearance">Appearance</string>
|
||||||
|
<string name="settings_network">Network</string>
|
||||||
|
<string name="settings_other">Other</string>
|
||||||
|
<string name="settings_night_mode">Theme</string>
|
||||||
|
<string name="settings_offline_mode">Offline mode</string>
|
||||||
|
<string name="settings_offline_mode_summary">Only sync manually.</string>
|
||||||
|
<string name="settings_color_custom">Custom color</string>
|
||||||
|
<string name="settings_color_mode">Color Selection</string>
|
||||||
|
<string name="settings_show_archived">Show archived projects</string>
|
||||||
|
<string name="settings_beta_features">Beta Features</string>
|
||||||
|
<string name="settings_beta_features_summary">Enable experimental features. Use at your own risk.</string>
|
||||||
|
<string name="settings_url_warn_http">WARNING: \"http\" is unsafe. Use \"https\".</string>
|
||||||
|
<string name="settings_colorpicker_title">Choose Color</string>
|
||||||
|
<string name="pref_value_color_system">System</string>
|
||||||
|
<string name="pref_value_color_server">Nextcloud</string>
|
||||||
|
<string name="pref_value_color_manual">Manual</string>
|
||||||
|
<string name="pref_value_theme_light">Light</string>
|
||||||
|
<string name="pref_value_theme_dark">Dark</string>
|
||||||
|
<string name="pref_value_theme_system">Follow system</string>
|
||||||
|
<!-- Constants (Do not translate) -->
|
||||||
|
<!-- Enums and Lists -->
|
||||||
|
<string name="repeat_no">No repeat</string>
|
||||||
|
<string name="repeat_day">Daily</string>
|
||||||
|
<string name="repeat_week">Weekly</string>
|
||||||
|
<string name="repeat_fortnight">Fortnightly</string>
|
||||||
|
<string name="repeat_month">Monthly</string>
|
||||||
|
<string name="repeat_year">Yearly</string>
|
||||||
|
<string name="payment_mode_none">None</string>
|
||||||
|
<string name="payment_mode_all">All</string>
|
||||||
|
<string name="payment_mode_credit_card">Credit card</string>
|
||||||
|
<string name="payment_mode_cash">Cash</string>
|
||||||
|
<string name="payment_mode_check">Check</string>
|
||||||
|
<string name="payment_mode_online">Online</string>
|
||||||
|
<string name="payment_mode_transfer">Transfer</string>
|
||||||
|
<string name="category_none">None</string>
|
||||||
|
<string name="category_all">All</string>
|
||||||
|
<string name="category_all_except_reimbursement">All except reimbursement</string>
|
||||||
|
<string name="category_groceries">Grocery</string>
|
||||||
|
<string name="category_leisure">Bar/Party</string>
|
||||||
|
<string name="category_rent">Rent</string>
|
||||||
|
<string name="category_bills">Bill</string>
|
||||||
|
<string name="category_excursion">Excursion/Culture</string>
|
||||||
|
<string name="category_health">Health</string>
|
||||||
|
<string name="category_shopping">Shopping</string>
|
||||||
|
<string name="category_reimbursement">Reimbursement</string>
|
||||||
|
<string name="category_restaurant">Restaurant</string>
|
||||||
|
<string name="category_accomodation">Accommodation</string>
|
||||||
|
<string name="category_transport">Transport</string>
|
||||||
|
<string name="category_sport">Sport</string>
|
||||||
|
<!-- Project specific -->
|
||||||
|
<string name="new_project_action">What</string>
|
||||||
|
<string name="new_project_where">Where</string>
|
||||||
|
<string name="where_local">Local only</string>
|
||||||
|
<string name="where_cospend">Cospend</string>
|
||||||
|
<string name="where_ihatemoney">IHateMoney</string>
|
||||||
|
<string name="todo_join">Join existing project</string>
|
||||||
|
<string name="todo_create">Create new project</string>
|
||||||
|
<string name="import_tooltip">Import from file</string>
|
||||||
|
<string name="choose_account_project_dialog_title">Choose project</string>
|
||||||
|
<string name="choose_account_project_dialog_impossible">No projects found on this account.</string>
|
||||||
|
<string name="choose_project_management_action">Project</string>
|
||||||
|
<string name="project_added_success">Project added successfully.</string>
|
||||||
|
<string name="no_projects_text">You have no projects yet.</string>
|
||||||
|
<string name="configure_account_choice">Configure Nextcloud account</string>
|
||||||
|
<string name="add_project_choice">Add project manually</string>
|
||||||
|
<string name="no_members_text">No members in this project.</string>
|
||||||
|
<string name="no_bills_text">No bills found.</string>
|
||||||
|
<string name="member_already_exists">Member already exists.</string>
|
||||||
|
<string name="activity_dialog_title">Project: %1$s</string>
|
||||||
|
<string name="remove_project_confirmation">Project %1$s removed.</string>
|
||||||
|
<string name="file_saved_success">File saved: %1$s</string>
|
||||||
|
<string name="import_error_header">Import failed at row %d</string>
|
||||||
|
<string name="import_error_date">Invalid date format at row %d</string>
|
||||||
|
<string name="import_error_owers">Invalid owers at row %d</string>
|
||||||
|
<string name="add_member_dialog_title">Add Member</string>
|
||||||
|
<string name="edit_member_dialog_title">Edit Member</string>
|
||||||
|
<string name="member_edit_delete">Delete</string>
|
||||||
|
<string name="project_edition_no_change">No changes to save.</string>
|
||||||
|
<!-- Settlement -->
|
||||||
|
<string name="center_none">None (Optimal)</string>
|
||||||
|
<string name="settle_who">Who pays</string>
|
||||||
|
<string name="settle_to_whom">To whom</string>
|
||||||
|
<string name="settle_how_much">Amount</string>
|
||||||
|
<string name="simple_settle_share">Share</string>
|
||||||
|
<string name="simple_create_bills">Create bills</string>
|
||||||
|
<string name="settle_bill_what">Settlement</string>
|
||||||
|
<!-- Currencies -->
|
||||||
|
<string name="currency_dialog_title">Choose Currency (%s)</string>
|
||||||
|
<string name="setting_none">None</string>
|
||||||
|
<string name="setting_all">All</string>
|
||||||
|
<string name="currency_saved_success">Currency settings saved.</string>
|
||||||
|
<string name="main_currency">Main Currency</string>
|
||||||
|
<!-- Statistics -->
|
||||||
|
<string name="label_bills_suggested">Suggested Categories</string>
|
||||||
|
<string name="label_bills_skip">Skip</string>
|
||||||
|
<string name="stats_date_min">From</string>
|
||||||
|
<string name="stats_date_max">To</string>
|
||||||
|
<string name="stats_who">Member</string>
|
||||||
|
<string name="stats_paid">Paid</string>
|
||||||
|
<string name="stats_spent">Spent</string>
|
||||||
|
<string name="stats_balance">Balance</string>
|
||||||
|
<string name="total">Total: %1$s</string>
|
||||||
|
<!-- Errors Extra -->
|
||||||
|
<string name="error_project_connect_check">Connection failed: %1$s</string>
|
||||||
|
<string name="error_create_remote_project_helper">Creation failed: %1$s</string>
|
||||||
|
<string name="error_edit_remote_project_helper">Error updating remote project: %1$s</string>
|
||||||
|
<string name="remote_project_operation_no_network">Network unavailable for remote operation.</string>
|
||||||
|
<string name="error_scanning_bill_qr_code">Failed to parse QR code.</string>
|
||||||
|
<string name="error_token_mismatch">Authentication token mismatch. Please log in again.</string>
|
||||||
|
<string name="insufficient_access_level">You don\'t have permission to perform this action.</string>
|
||||||
|
<string name="delete_label_confirmation_title">Delete Label</string>
|
||||||
|
<string name="delete_label_confirmation_message">Are you sure you want to delete this label?</string>
|
||||||
|
<!-- About -->
|
||||||
|
<string name="about_version">Version %1$s</string>
|
||||||
|
<string name="about_maintainer_title">Maintainer</string>
|
||||||
|
<string name="about_license_title">License</string>
|
||||||
|
<string name="about_source_title">Source code</string>
|
||||||
|
<!-- New constants for backward compatibility or shared use -->
|
||||||
|
<string name="share_intent_title">Project %1$s</string>
|
||||||
|
<string name="share_chooser_title">Share %1$s</string>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,241 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
<string name="app_name">Gekocht</string>
|
||||||
|
<!-- Actions -->
|
||||||
|
<string name="action_new_bill">Nieuwe rekening</string>
|
||||||
|
<string name="action_add_project">Project toevoegen</string>
|
||||||
|
<string name="action_save">Opslaan</string>
|
||||||
|
<string name="action_edit">Bewerken</string>
|
||||||
|
<string name="action_share">Delen</string>
|
||||||
|
<string name="action_search">Zoeken</string>
|
||||||
|
<string name="action_delete">Verwijderen</string>
|
||||||
|
<string name="action_archive">Archief</string>
|
||||||
|
<string name="action_unarchive">Dearchiveren</string>
|
||||||
|
<string name="action_export">Exporteren</string>
|
||||||
|
<string name="action_stats">Statistieken</string>
|
||||||
|
<string name="action_settle">Schikken</string>
|
||||||
|
<string name="action_scan_qrcode">Scan QR Code</string>
|
||||||
|
<string name="action_settings">Instellingen</string>
|
||||||
|
<string name="action_label_bills">Label ontbrekende categorieën</string>
|
||||||
|
<string name="action_logout">Afmelden</string>
|
||||||
|
<string name="action_connect">Verbinden</string>
|
||||||
|
<string name="action_discard">Weggooien</string>
|
||||||
|
<string name="action_members">leden</string>
|
||||||
|
<string name="action_labels">Labels</string>
|
||||||
|
<string name="action_currencies">Valuta\'s</string>
|
||||||
|
<!-- Titles -->
|
||||||
|
<string name="title_stats">Statistieken</string>
|
||||||
|
<string name="title_edit_project">Bewerk project</string>
|
||||||
|
<string name="title_label_bills">Label rekeningen</string>
|
||||||
|
<string name="title_labels">Labels beheren</string>
|
||||||
|
<string name="title_about">Informatie</string>
|
||||||
|
<string name="title_settle">Project schikken</string>
|
||||||
|
<string name="title_share">Project delen</string>
|
||||||
|
<string name="title_add_project">Project toevoegen</string>
|
||||||
|
<string name="title_add_category">Categorie toevoegen</string>
|
||||||
|
<string name="title_add_payment_mode">Betalingsmodus toevoegen</string>
|
||||||
|
<string name="title_account">Nextcloud account</string>
|
||||||
|
<string name="title_share_web">Weblink</string>
|
||||||
|
<string name="title_share_qr">Cowspent link</string>
|
||||||
|
<string name="title_confirm">Weet je het zeker?</string>
|
||||||
|
<!-- Labels and Fields -->
|
||||||
|
<string name="label_all_bills">Alle rekeningen</string>
|
||||||
|
<string name="label_categories">Categoriechar@@0n</string>
|
||||||
|
<string name="label_payment_modes">Betalings modi</string>
|
||||||
|
<string name="label_name">naam</string>
|
||||||
|
<string name="label_icon">Icon / Emoji</string>
|
||||||
|
<string name="label_color">Kleur</string>
|
||||||
|
<string name="label_weight">Gewicht</string>
|
||||||
|
<string name="label_activated">Geactiveerd</string>
|
||||||
|
<string name="label_password">Wachtwoord</string>
|
||||||
|
<string name="label_email">E-mailadres</string>
|
||||||
|
<string name="label_url">Server adres</string>
|
||||||
|
<string name="label_username">Gebruikersnaam</string>
|
||||||
|
<string name="label_comment">Opmerking</string>
|
||||||
|
<string name="label_what">Wat?</string>
|
||||||
|
<string name="label_payer">Wie heeft betaald?</string>
|
||||||
|
<string name="label_owers">Voor wie?</string>
|
||||||
|
<string name="label_repeat">Herhaal elke</string>
|
||||||
|
<string name="label_mode">Modus</string>
|
||||||
|
<string name="label_category">categorie</string>
|
||||||
|
<string name="label_project_id">Project ID/naam</string>
|
||||||
|
<string name="label_project_title">Project titel</string>
|
||||||
|
<string name="label_use_sso">Gebruik Nextcloud account</string>
|
||||||
|
<!-- Dialogs and Messages -->
|
||||||
|
<string name="dialog_unsaved_changes_title">Niet-opgeslagen wijzigingen</string>
|
||||||
|
<string name="dialog_unsaved_changes_msg">Wijzigingen opslaan voor verlaten?</string>
|
||||||
|
<string name="dialog_confirm_remove_project_msg">Het externe project wordt niet verwijderd.</string>
|
||||||
|
<string name="dialog_sync_error_title">Synchronisatie fout</string>
|
||||||
|
<string name="dialog_sync_error_msg">Synchronisatie mislukt voor %1$s.\n\n%2$s</string>
|
||||||
|
<string name="dialog_balanced_msg">Uitgaven zijn al in evenwicht.</string>
|
||||||
|
<string name="msg_project_added">Project %1$s toegevoegd</string>
|
||||||
|
<string name="msg_bill_labeled_done">Alle contracten gelabeld</string>
|
||||||
|
<string name="msg_no_suggestions">Geen suggesties</string>
|
||||||
|
<string name="msg_auth_warning">Vereist Cospend-v0.3.4+.</string>
|
||||||
|
<string name="msg_link_copied">Link gekopieerd naar klembord</string>
|
||||||
|
<string name="msg_share_qr">Scan QR-code of deel de link om je aan te sluiten.</string>
|
||||||
|
<string name="msg_share_web">Link voor toegang tot de webbrowser.</string>
|
||||||
|
<string name="msg_share_qr_warn">Deel deze link met een Cowspent gebruiker.</string>
|
||||||
|
<string name="msg_settle_intro">Afwikkeling voor %1$s:</string>
|
||||||
|
<string name="msg_settle_sentence">%1$s is %3$.2f verschuldigd aan %2$s</string>
|
||||||
|
<string name="msg_stats_intro">Statistieken voor %1$s:</string>
|
||||||
|
<string name="msg_stats_header">Lid ((Paid →Besteed Balance)</string>
|
||||||
|
<string name="msg_logged_in_as">Ingelogd als %1$s</string>
|
||||||
|
<!-- Errors -->
|
||||||
|
<string name="error_generic">Foutmelding</string>
|
||||||
|
<string name="error_loading">Laden...</string>
|
||||||
|
<string name="error_no_projects">Geen projecten gevonden</string>
|
||||||
|
<string name="error_no_members">Geen deelnemers gevonden</string>
|
||||||
|
<string name="error_no_bills">Geen contracten gevonden</string>
|
||||||
|
<string name="error_no_member">Ten minste één lid vereist</string>
|
||||||
|
<string name="error_maintenance_mode">Server is in onderhoudsmodus</string>
|
||||||
|
<string name="error_400">400 Slechte aanvraag</string>
|
||||||
|
<string name="error_401">401 Ongeautoriseerd</string>
|
||||||
|
<string name="error_403">403 Verboden</string>
|
||||||
|
<string name="error_404">404 niet gevonden</string>
|
||||||
|
<string name="error_sync">Synchronisatie mislukt: %1$s</string>
|
||||||
|
<string name="error_invalid_login">Ongeldige login: %1$s</string>
|
||||||
|
<string name="error_auth">Verkeerde gebruikersnaam of wachtwoord</string>
|
||||||
|
<string name="error_json">Ongeldige serverreactie</string>
|
||||||
|
<string name="error_req_failed">Verzoek mislukt</string>
|
||||||
|
<string name="error_invalid_email">Ongeldig e-mailadres</string>
|
||||||
|
<string name="error_invalid_project_id">Ongeldig project ID</string>
|
||||||
|
<string name="error_invalid_project_name">Ongeldige projecttitel</string>
|
||||||
|
<string name="error_invalid_bill_name">Ongeldige rekeningnaam</string>
|
||||||
|
<string name="error_invalid_bill_date">Ongeldige rekeningdatum</string>
|
||||||
|
<string name="error_invalid_bill_payer">Betaler vereist</string>
|
||||||
|
<string name="error_invalid_bill_owers">Owers vereist</string>
|
||||||
|
<string name="error_no_network">Geen netwerkverbinding</string>
|
||||||
|
<string name="error_server">Server fout</string>
|
||||||
|
<string name="error_io">Verbinding met server verbroken</string>
|
||||||
|
<string name="error_share_impossible">Kan dit project niet delen</string>
|
||||||
|
<!-- Drawer / Common UI -->
|
||||||
|
<string name="drawer_no_account">Verbinding maken met Nextcloud account</string>
|
||||||
|
<string name="drawer_last_sync">Laatste synchronisatie: %1$02d:%2$02d</string>
|
||||||
|
<string name="simple_cancel">annuleren</string>
|
||||||
|
<string name="simple_ok" tools:ignore="ButtonCase,Typos">OK</string>
|
||||||
|
<string name="simple_yes">ja</string>
|
||||||
|
<string name="simple_no">Neen</string>
|
||||||
|
<string name="simple_close">Afsluiten</string>
|
||||||
|
<!-- Settings -->
|
||||||
|
<string name="settings_appearance">Uiterlijk</string>
|
||||||
|
<string name="settings_network">Netwerk</string>
|
||||||
|
<string name="settings_other">anders</string>
|
||||||
|
<string name="settings_night_mode">Thema</string>
|
||||||
|
<string name="settings_offline_mode">Offline modus</string>
|
||||||
|
<string name="settings_offline_mode_summary">Alleen handmatig synchroniseren.</string>
|
||||||
|
<string name="settings_color_custom">Aangepaste kleur</string>
|
||||||
|
<string name="settings_color_mode">Kleur Selectie</string>
|
||||||
|
<string name="settings_show_archived">Gearchiveerde projecten weergeven</string>
|
||||||
|
<string name="settings_beta_features">Beta Functies</string>
|
||||||
|
<string name="settings_beta_features_summary">Experimentele functies inschakelen. Gebruik op eigen risico.</string>
|
||||||
|
<string name="settings_url_warn_http">WAARSCHUWING: \"http\" is onveilig. Gebruik \"https\".</string>
|
||||||
|
<string name="settings_colorpicker_title">Kies kleur</string>
|
||||||
|
<string name="pref_value_color_system">Systeem</string>
|
||||||
|
<string name="pref_value_color_server">Nextcloud</string>
|
||||||
|
<string name="pref_value_color_manual">Handleiding</string>
|
||||||
|
<string name="pref_value_theme_light">Licht</string>
|
||||||
|
<string name="pref_value_theme_dark">Donker</string>
|
||||||
|
<string name="pref_value_theme_system">Volg systeem</string>
|
||||||
|
<!-- Constants (Do not translate) -->
|
||||||
|
<!-- Enums and Lists -->
|
||||||
|
<string name="repeat_no">Geen herhaling</string>
|
||||||
|
<string name="repeat_day">Dagelijks</string>
|
||||||
|
<string name="repeat_week">wekelijks</string>
|
||||||
|
<string name="repeat_fortnight">Tweewekelijks</string>
|
||||||
|
<string name="repeat_month">maandelijks</string>
|
||||||
|
<string name="repeat_year">Jaarlijks</string>
|
||||||
|
<string name="payment_mode_none">geen</string>
|
||||||
|
<string name="payment_mode_all">Allemaal</string>
|
||||||
|
<string name="payment_mode_credit_card">Creditcard bestellen</string>
|
||||||
|
<string name="payment_mode_cash">Contant</string>
|
||||||
|
<string name="payment_mode_check">Controleer</string>
|
||||||
|
<string name="payment_mode_online">Online</string>
|
||||||
|
<string name="payment_mode_transfer">Overschrijving</string>
|
||||||
|
<string name="category_none">geen</string>
|
||||||
|
<string name="category_all">Allemaal</string>
|
||||||
|
<string name="category_all_except_reimbursement">Alles behalve terugbetaling</string>
|
||||||
|
<string name="category_groceries">Kruidenier</string>
|
||||||
|
<string name="category_leisure">Bar/Feest</string>
|
||||||
|
<string name="category_rent">Huur</string>
|
||||||
|
<string name="category_bills">Rekening</string>
|
||||||
|
<string name="category_excursion">Excursie/cultuur</string>
|
||||||
|
<string name="category_health">Gezondheid</string>
|
||||||
|
<string name="category_shopping">Winkelen</string>
|
||||||
|
<string name="category_reimbursement">Vergoeding</string>
|
||||||
|
<string name="category_restaurant">Restaurant</string>
|
||||||
|
<string name="category_accomodation">Accommodatie</string>
|
||||||
|
<string name="category_transport">Vervoer</string>
|
||||||
|
<string name="category_sport">Sport</string>
|
||||||
|
<!-- Project specific -->
|
||||||
|
<string name="new_project_action">Wat</string>
|
||||||
|
<string name="new_project_where">Waar</string>
|
||||||
|
<string name="where_local">Alleen lokaal</string>
|
||||||
|
<string name="where_cospend">Coderen</string>
|
||||||
|
<string name="where_ihatemoney">IHateMoney</string>
|
||||||
|
<string name="todo_join">Deelnemen aan bestaand project</string>
|
||||||
|
<string name="todo_create">Nieuw project aanmaken</string>
|
||||||
|
<string name="import_tooltip">Importeren uit bestand</string>
|
||||||
|
<string name="choose_account_project_dialog_title">Kies project</string>
|
||||||
|
<string name="choose_account_project_dialog_impossible">Geen projecten gevonden op dit account.</string>
|
||||||
|
<string name="choose_project_management_action">Project</string>
|
||||||
|
<string name="project_added_success">Project succesvol toegevoegd.</string>
|
||||||
|
<string name="no_projects_text">U heeft nog geen projecten.</string>
|
||||||
|
<string name="configure_account_choice">Configureer Nextcloud account</string>
|
||||||
|
<string name="add_project_choice">Project handmatig toevoegen</string>
|
||||||
|
<string name="no_members_text">Geen deelnemers in dit project.</string>
|
||||||
|
<string name="no_bills_text">Geen contracten gevonden.</string>
|
||||||
|
<string name="member_already_exists">Lid bestaat al.</string>
|
||||||
|
<string name="activity_dialog_title">Project: %1$s</string>
|
||||||
|
<string name="remove_project_confirmation">Project %1$s verwijderd.</string>
|
||||||
|
<string name="file_saved_success">Bestand opgeslagen: %1$s</string>
|
||||||
|
<string name="import_error_header">Importeren mislukt op rij %d</string>
|
||||||
|
<string name="import_error_date">Ongeldige datumnotatie op rij %d</string>
|
||||||
|
<string name="import_error_owers">Ongeldige owers op rij %d</string>
|
||||||
|
<string name="add_member_dialog_title">Lid toevoegen</string>
|
||||||
|
<string name="edit_member_dialog_title">Lid bewerken</string>
|
||||||
|
<string name="member_edit_delete">Verwijderen</string>
|
||||||
|
<string name="project_edition_no_change">Geen wijzigingen om op te slaan.</string>
|
||||||
|
<!-- Settlement -->
|
||||||
|
<string name="center_none">Geen (Optimaal)</string>
|
||||||
|
<string name="settle_who">Wie betaalt</string>
|
||||||
|
<string name="settle_to_whom">Naar wie</string>
|
||||||
|
<string name="settle_how_much">Hoeveelheid</string>
|
||||||
|
<string name="simple_settle_share">Delen</string>
|
||||||
|
<string name="simple_create_bills">Rekeningen aanmaken</string>
|
||||||
|
<string name="settle_bill_what">Afwikkeling</string>
|
||||||
|
<!-- Currencies -->
|
||||||
|
<string name="currency_dialog_title">Kies valuta (%s)</string>
|
||||||
|
<string name="setting_none">geen</string>
|
||||||
|
<string name="setting_all">Allemaal</string>
|
||||||
|
<string name="currency_saved_success">Valuta-instellingen opgeslagen.</string>
|
||||||
|
<string name="main_currency">Belangrijkste valuta</string>
|
||||||
|
<!-- Statistics -->
|
||||||
|
<string name="label_bills_suggested">Voorgestelde categorieën</string>
|
||||||
|
<string name="label_bills_skip">Overslaan</string>
|
||||||
|
<string name="stats_date_min">van</string>
|
||||||
|
<string name="stats_date_max">tot</string>
|
||||||
|
<string name="stats_who">Lid</string>
|
||||||
|
<string name="stats_paid">Betaald</string>
|
||||||
|
<string name="stats_spent">Uitgegeven</string>
|
||||||
|
<string name="stats_balance">Saldo</string>
|
||||||
|
<string name="total">Totaal: %1$s</string>
|
||||||
|
<!-- Errors Extra -->
|
||||||
|
<string name="error_project_connect_check">Verbinding mislukt: %1$s</string>
|
||||||
|
<string name="error_create_remote_project_helper">Aanmaken mislukt: %1$s</string>
|
||||||
|
<string name="error_edit_remote_project_helper">Fout bij het bijwerken van extern project: %1$s</string>
|
||||||
|
<string name="remote_project_operation_no_network">Netwerk niet beschikbaar voor operatie op afstand.</string>
|
||||||
|
<string name="error_scanning_bill_qr_code">Fout bij het verwerken van QR-code.</string>
|
||||||
|
<string name="error_token_mismatch">Authenticatie token komt niet overeen. Log opnieuw in.</string>
|
||||||
|
<string name="insufficient_access_level">Je hebt geen toestemming om deze actie uit te voeren.</string>
|
||||||
|
<string name="delete_label_confirmation_title">Label verwijderen</string>
|
||||||
|
<string name="delete_label_confirmation_message">Weet u zeker dat u dit label wilt verwijderen?</string>
|
||||||
|
<!-- About -->
|
||||||
|
<string name="about_version">Versie %1$s</string>
|
||||||
|
<string name="about_maintainer_title">Beheerder</string>
|
||||||
|
<string name="about_license_title">Licentie</string>
|
||||||
|
<string name="about_source_title">Bron code</string>
|
||||||
|
<!-- New constants for backward compatibility or shared use -->
|
||||||
|
<string name="share_intent_title">Project %1$s</string>
|
||||||
|
<string name="share_chooser_title">Delen %1$s</string>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,241 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
<string name="app_name">Kowspent</string>
|
||||||
|
<!-- Actions -->
|
||||||
|
<string name="action_new_bill">Ny regning</string>
|
||||||
|
<string name="action_add_project">Legg til prosjekt</string>
|
||||||
|
<string name="action_save">Lagre</string>
|
||||||
|
<string name="action_edit">Rediger</string>
|
||||||
|
<string name="action_share">Del</string>
|
||||||
|
<string name="action_search">Søk</string>
|
||||||
|
<string name="action_delete">Slett</string>
|
||||||
|
<string name="action_archive">Arkiv</string>
|
||||||
|
<string name="action_unarchive">Avarkiver</string>
|
||||||
|
<string name="action_export">Eksporter</string>
|
||||||
|
<string name="action_stats">Statistikk</string>
|
||||||
|
<string name="action_settle">Betal</string>
|
||||||
|
<string name="action_scan_qrcode">Scan QR Code</string>
|
||||||
|
<string name="action_settings">Innstillinger</string>
|
||||||
|
<string name="action_label_bills">Etiketten mangler kategorier</string>
|
||||||
|
<string name="action_logout">Logg</string>
|
||||||
|
<string name="action_connect">Nettverk</string>
|
||||||
|
<string name="action_discard">Forkast</string>
|
||||||
|
<string name="action_members">Medlemmer</string>
|
||||||
|
<string name="action_labels">Etiketter</string>
|
||||||
|
<string name="action_currencies">Valutaer</string>
|
||||||
|
<!-- Titles -->
|
||||||
|
<string name="title_stats">Statistikk</string>
|
||||||
|
<string name="title_edit_project">Endre prosjekt</string>
|
||||||
|
<string name="title_label_bills">Etikett regninger</string>
|
||||||
|
<string name="title_labels">Administrer etiketter</string>
|
||||||
|
<string name="title_about">Om</string>
|
||||||
|
<string name="title_settle">Oppgjør Prosjekt</string>
|
||||||
|
<string name="title_share">Del prosjeket</string>
|
||||||
|
<string name="title_add_project">Legg til prosjekt</string>
|
||||||
|
<string name="title_add_category">Legg til kategori</string>
|
||||||
|
<string name="title_add_payment_mode">Legg til betalingsmodus</string>
|
||||||
|
<string name="title_account">Nextcloud konto</string>
|
||||||
|
<string name="title_share_web">Nettlenke</string>
|
||||||
|
<string name="title_share_qr">Cowspent link</string>
|
||||||
|
<string name="title_confirm">Er du sikker?</string>
|
||||||
|
<!-- Labels and Fields -->
|
||||||
|
<string name="label_all_bills">Alle regninger</string>
|
||||||
|
<string name="label_categories">Kategorier</string>
|
||||||
|
<string name="label_payment_modes">Betalings modus</string>
|
||||||
|
<string name="label_name">Navn</string>
|
||||||
|
<string name="label_icon">Icon / Emoji</string>
|
||||||
|
<string name="label_color">Farge</string>
|
||||||
|
<string name="label_weight">Vekt</string>
|
||||||
|
<string name="label_activated">Aktivert</string>
|
||||||
|
<string name="label_password">Passord</string>
|
||||||
|
<string name="label_email">E-post</string>
|
||||||
|
<string name="label_url">Server adresse</string>
|
||||||
|
<string name="label_username">Brukernavn</string>
|
||||||
|
<string name="label_comment">Kommentar</string>
|
||||||
|
<string name="label_what">Hva?</string>
|
||||||
|
<string name="label_payer">Hvem betalt?</string>
|
||||||
|
<string name="label_owers">For hvem?</string>
|
||||||
|
<string name="label_repeat">Gjenta hver</string>
|
||||||
|
<string name="label_mode">Modus</string>
|
||||||
|
<string name="label_category">Kategori</string>
|
||||||
|
<string name="label_project_id">Prosjektets ID/navn</string>
|
||||||
|
<string name="label_project_title">Tittel på prosjektet</string>
|
||||||
|
<string name="label_use_sso">Bruk Nextcloud App-konto</string>
|
||||||
|
<!-- Dialogs and Messages -->
|
||||||
|
<string name="dialog_unsaved_changes_title">Ulagrede endringer</string>
|
||||||
|
<string name="dialog_unsaved_changes_msg">Lagre endringer før du drar?</string>
|
||||||
|
<string name="dialog_confirm_remove_project_msg">Det eksterne prosjektet vil ikke bli slettet.</string>
|
||||||
|
<string name="dialog_sync_error_title">Feil med synkronisering</string>
|
||||||
|
<string name="dialog_sync_error_msg">Synkronisering feilet for %1$s.\n\n%2$s</string>
|
||||||
|
<string name="dialog_balanced_msg">Utgifter er allerede balanserte.</string>
|
||||||
|
<string name="msg_project_added">Prosjekt %1$s lagt til</string>
|
||||||
|
<string name="msg_bill_labeled_done">Alle regninger merket</string>
|
||||||
|
<string name="msg_no_suggestions">Ingen forslag</string>
|
||||||
|
<string name="msg_auth_warning">Krever Cospend v0.3.4+.</string>
|
||||||
|
<string name="msg_link_copied">Kobling kopiert til utklippstavlen</string>
|
||||||
|
<string name="msg_share_qr">Skann QR-kode eller del lenken for å bli med.</string>
|
||||||
|
<string name="msg_share_web">Lenke for tilgang til nettleser.</string>
|
||||||
|
<string name="msg_share_qr_warn">Del denne lenken med en bruker i nærområdet.</string>
|
||||||
|
<string name="msg_settle_intro">Avgjøring for %1$s:</string>
|
||||||
|
<string name="msg_settle_sentence">%1$s owes %3$.2f to %2$s</string>
|
||||||
|
<string name="msg_stats_intro">Statistikk for %1$s:</string>
|
||||||
|
<string name="msg_stats_header">Medlem (hjelpemiddel for sprøytestatistikk Balance)</string>
|
||||||
|
<string name="msg_logged_in_as">Logget inn som %1$s</string>
|
||||||
|
<!-- Errors -->
|
||||||
|
<string name="error_generic">Feil</string>
|
||||||
|
<string name="error_loading">Laster</string>
|
||||||
|
<string name="error_no_projects">Ingen prosjekter funnet</string>
|
||||||
|
<string name="error_no_members">Ingen medlemmer funnet</string>
|
||||||
|
<string name="error_no_bills">Ingen regninger funnet</string>
|
||||||
|
<string name="error_no_member">Minst ett medlem som kreves</string>
|
||||||
|
<string name="error_maintenance_mode">Serveren er i vedlikeholdsmodus</string>
|
||||||
|
<string name="error_400">400 ugyldig forespørsel</string>
|
||||||
|
<string name="error_401">401 Uautorisert</string>
|
||||||
|
<string name="error_403">403 forbudt</string>
|
||||||
|
<string name="error_404">404 ikke funnet</string>
|
||||||
|
<string name="error_sync">Synkronisering feilet: %1$s</string>
|
||||||
|
<string name="error_invalid_login">Ugyldig innlogging: %1$s</string>
|
||||||
|
<string name="error_auth">Feil brukernavn eller passord</string>
|
||||||
|
<string name="error_json">Ugyldig svar fra tjeneren</string>
|
||||||
|
<string name="error_req_failed">Forespørselen mislyktes</string>
|
||||||
|
<string name="error_invalid_email">Ugyldig e-post</string>
|
||||||
|
<string name="error_invalid_project_id">Ugyldig prosjekt-ID</string>
|
||||||
|
<string name="error_invalid_project_name">Ugyldig prosjekttittel</string>
|
||||||
|
<string name="error_invalid_bill_name">Ugyldig faktureringsnavn</string>
|
||||||
|
<string name="error_invalid_bill_date">Ugyldig faktureringsdato</string>
|
||||||
|
<string name="error_invalid_bill_payer">Betalingsgiver kreves</string>
|
||||||
|
<string name="error_invalid_bill_owers">Eier kreves</string>
|
||||||
|
<string name="error_no_network">Ingen nettverkstilkobling</string>
|
||||||
|
<string name="error_server">Server feil</string>
|
||||||
|
<string name="error_io">Server-tilkobling ødelagt</string>
|
||||||
|
<string name="error_share_impossible">Kan ikke dele dette prosjektet</string>
|
||||||
|
<!-- Drawer / Common UI -->
|
||||||
|
<string name="drawer_no_account">Koble til Nextcloud konto</string>
|
||||||
|
<string name="drawer_last_sync">Siste synkronisering: %1$02d:%2$02d</string>
|
||||||
|
<string name="simple_cancel">Avbryt</string>
|
||||||
|
<string name="simple_ok" tools:ignore="ButtonCase,Typos">OK</string>
|
||||||
|
<string name="simple_yes">Ja</string>
|
||||||
|
<string name="simple_no">Nei</string>
|
||||||
|
<string name="simple_close">Lukk</string>
|
||||||
|
<!-- Settings -->
|
||||||
|
<string name="settings_appearance">Utseende</string>
|
||||||
|
<string name="settings_network">Nettverk</string>
|
||||||
|
<string name="settings_other">Annet</string>
|
||||||
|
<string name="settings_night_mode">Tema</string>
|
||||||
|
<string name="settings_offline_mode">Frakoblet modus</string>
|
||||||
|
<string name="settings_offline_mode_summary">Bare synkroniser manuelt.</string>
|
||||||
|
<string name="settings_color_custom">Tilpasset farge</string>
|
||||||
|
<string name="settings_color_mode">Farge Valg</string>
|
||||||
|
<string name="settings_show_archived">Vis arkiverte prosjekter</string>
|
||||||
|
<string name="settings_beta_features">Beta-funksjoner</string>
|
||||||
|
<string name="settings_beta_features_summary">Aktiver eksperimentelle funksjoner. Bruk på eget ansvar.</string>
|
||||||
|
<string name="settings_url_warn_http">ADVARSEL: «http» er usikkert. Bruk «https».</string>
|
||||||
|
<string name="settings_colorpicker_title">Velg farge</string>
|
||||||
|
<string name="pref_value_color_system">Systemadministrasjon</string>
|
||||||
|
<string name="pref_value_color_server">Nextcloud</string>
|
||||||
|
<string name="pref_value_color_manual">Manuell</string>
|
||||||
|
<string name="pref_value_theme_light">Lys</string>
|
||||||
|
<string name="pref_value_theme_dark">Mørk</string>
|
||||||
|
<string name="pref_value_theme_system">Følg system</string>
|
||||||
|
<!-- Constants (Do not translate) -->
|
||||||
|
<!-- Enums and Lists -->
|
||||||
|
<string name="repeat_no">Ingen gjentakelse</string>
|
||||||
|
<string name="repeat_day">Daglig</string>
|
||||||
|
<string name="repeat_week">Ukentlig</string>
|
||||||
|
<string name="repeat_fortnight">Nattlig</string>
|
||||||
|
<string name="repeat_month">Månedlig</string>
|
||||||
|
<string name="repeat_year">Årlig</string>
|
||||||
|
<string name="payment_mode_none">Ingen</string>
|
||||||
|
<string name="payment_mode_all">Alle</string>
|
||||||
|
<string name="payment_mode_credit_card">Kredittkort</string>
|
||||||
|
<string name="payment_mode_cash">Kontant</string>
|
||||||
|
<string name="payment_mode_check">Sjekk</string>
|
||||||
|
<string name="payment_mode_online">Pålogget</string>
|
||||||
|
<string name="payment_mode_transfer">Overfør</string>
|
||||||
|
<string name="category_none">Ingen</string>
|
||||||
|
<string name="category_all">Alle</string>
|
||||||
|
<string name="category_all_except_reimbursement">Alt unntatt refusjon</string>
|
||||||
|
<string name="category_groceries">Dagligvare</string>
|
||||||
|
<string name="category_leisure">Bar/Party</string>
|
||||||
|
<string name="category_rent">Lei</string>
|
||||||
|
<string name="category_bills">Regning</string>
|
||||||
|
<string name="category_excursion">Eksurering/Kultur</string>
|
||||||
|
<string name="category_health">Helse</string>
|
||||||
|
<string name="category_shopping">Handling</string>
|
||||||
|
<string name="category_reimbursement">Refusjon</string>
|
||||||
|
<string name="category_restaurant">Resturant</string>
|
||||||
|
<string name="category_accomodation">Overnatting</string>
|
||||||
|
<string name="category_transport">Transporter</string>
|
||||||
|
<string name="category_sport">Sport</string>
|
||||||
|
<!-- Project specific -->
|
||||||
|
<string name="new_project_action">Hva</string>
|
||||||
|
<string name="new_project_where">Hvor</string>
|
||||||
|
<string name="where_local">Kun lokalt</string>
|
||||||
|
<string name="where_cospend">evangeliet</string>
|
||||||
|
<string name="where_ihatemoney">IHatePenger</string>
|
||||||
|
<string name="todo_join">Delta i eksisterende prosjekt</string>
|
||||||
|
<string name="todo_create">Opprett nytt prosjekt</string>
|
||||||
|
<string name="import_tooltip">Importer fra fil</string>
|
||||||
|
<string name="choose_account_project_dialog_title">Velg prosjekt</string>
|
||||||
|
<string name="choose_account_project_dialog_impossible">Ingen prosjekter ble funnet på denne kontoen.</string>
|
||||||
|
<string name="choose_project_management_action">Prosjekt</string>
|
||||||
|
<string name="project_added_success">Prosjektet ble lagt til.</string>
|
||||||
|
<string name="no_projects_text">Du har ingen prosjekter ennå.</string>
|
||||||
|
<string name="configure_account_choice">Konfigurer Nextcloud konto</string>
|
||||||
|
<string name="add_project_choice">Legg til prosjekt manuelt</string>
|
||||||
|
<string name="no_members_text">Ingen medlemmer i dette prosjektet.</string>
|
||||||
|
<string name="no_bills_text">Ingen regninger funnet.</string>
|
||||||
|
<string name="member_already_exists">Medlem finnes allerede.</string>
|
||||||
|
<string name="activity_dialog_title">Prosjekt: %1$s</string>
|
||||||
|
<string name="remove_project_confirmation">Prosjektet %1$s fjernet.</string>
|
||||||
|
<string name="file_saved_success">Fil lagret: %1$s</string>
|
||||||
|
<string name="import_error_header">Import failed at row %d</string>
|
||||||
|
<string name="import_error_date">Ugyldig dato format ved rad %d</string>
|
||||||
|
<string name="import_error_owers">Ugyldige doser på rad %d</string>
|
||||||
|
<string name="add_member_dialog_title">Legg til medlem</string>
|
||||||
|
<string name="edit_member_dialog_title">Rediger medlem</string>
|
||||||
|
<string name="member_edit_delete">Slett</string>
|
||||||
|
<string name="project_edition_no_change">Ingen endringer å lagre.</string>
|
||||||
|
<!-- Settlement -->
|
||||||
|
<string name="center_none">Ingen (optimalt)</string>
|
||||||
|
<string name="settle_who">Hvem betaler</string>
|
||||||
|
<string name="settle_to_whom">Til hvem</string>
|
||||||
|
<string name="settle_how_much">Beløp</string>
|
||||||
|
<string name="simple_settle_share">Del</string>
|
||||||
|
<string name="simple_create_bills">Opprett regninger</string>
|
||||||
|
<string name="settle_bill_what">Oppgjør</string>
|
||||||
|
<!-- Currencies -->
|
||||||
|
<string name="currency_dialog_title">Velg valuta (%s)</string>
|
||||||
|
<string name="setting_none">Ingen</string>
|
||||||
|
<string name="setting_all">Alle</string>
|
||||||
|
<string name="currency_saved_success">Valuta innstillinger lagret.</string>
|
||||||
|
<string name="main_currency">Hoved valuta</string>
|
||||||
|
<!-- Statistics -->
|
||||||
|
<string name="label_bills_suggested">Foreslåtte kategorier</string>
|
||||||
|
<string name="label_bills_skip">Hopp</string>
|
||||||
|
<string name="stats_date_min">Fra</string>
|
||||||
|
<string name="stats_date_max">Til</string>
|
||||||
|
<string name="stats_who">Medlem</string>
|
||||||
|
<string name="stats_paid">Betalt</string>
|
||||||
|
<string name="stats_spent">Brukt</string>
|
||||||
|
<string name="stats_balance">Balanse</string>
|
||||||
|
<string name="total">Totalt: %1$s</string>
|
||||||
|
<!-- Errors Extra -->
|
||||||
|
<string name="error_project_connect_check">Tilkobling mislyktes: %1$s</string>
|
||||||
|
<string name="error_create_remote_project_helper">Creation failed: %1$s</string>
|
||||||
|
<string name="error_edit_remote_project_helper">Feil ved oppdatering av fjernprosjektet: %1$s</string>
|
||||||
|
<string name="remote_project_operation_no_network">Nettverk er ikke tilgjengelig for ekstern operasjon.</string>
|
||||||
|
<string name="error_scanning_bill_qr_code">Kunne ikke analysere QR-kode.</string>
|
||||||
|
<string name="error_token_mismatch">Feil ved godkjenningstoken. Vennligst logg inn på nytt.</string>
|
||||||
|
<string name="insufficient_access_level">Du har ikke tillatelse til å gjøre denne handlingen.</string>
|
||||||
|
<string name="delete_label_confirmation_title">Slett etikett</string>
|
||||||
|
<string name="delete_label_confirmation_message">Er du sikker på at du vil slette dette merket?</string>
|
||||||
|
<!-- About -->
|
||||||
|
<string name="about_version">Versjon %1$s</string>
|
||||||
|
<string name="about_maintainer_title">Vedlikeholdere</string>
|
||||||
|
<string name="about_license_title">Lisens</string>
|
||||||
|
<string name="about_source_title">Kildekode</string>
|
||||||
|
<!-- New constants for backward compatibility or shared use -->
|
||||||
|
<string name="share_intent_title">Prosjekt %1$s</string>
|
||||||
|
<string name="share_chooser_title">Del %1$s</string>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,241 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
<string name="app_name">Krowy wydane</string>
|
||||||
|
<!-- Actions -->
|
||||||
|
<string name="action_new_bill">Nowy rachunek</string>
|
||||||
|
<string name="action_add_project">Dodaj projekt</string>
|
||||||
|
<string name="action_save">Zapisz</string>
|
||||||
|
<string name="action_edit">Edytuj</string>
|
||||||
|
<string name="action_share">Udostępnij</string>
|
||||||
|
<string name="action_search">Szukaj</string>
|
||||||
|
<string name="action_delete">Usuń</string>
|
||||||
|
<string name="action_archive">Archiwum</string>
|
||||||
|
<string name="action_unarchive">Usuń z archiwum</string>
|
||||||
|
<string name="action_export">Eksportuj</string>
|
||||||
|
<string name="action_stats">Statystyki</string>
|
||||||
|
<string name="action_settle">Utrata</string>
|
||||||
|
<string name="action_scan_qrcode">Scan QR Code</string>
|
||||||
|
<string name="action_settings">Ustawienia</string>
|
||||||
|
<string name="action_label_bills">Brakujące kategorie etykiet</string>
|
||||||
|
<string name="action_logout">Wyloguj się</string>
|
||||||
|
<string name="action_connect">Połącz</string>
|
||||||
|
<string name="action_discard">Odrzuć</string>
|
||||||
|
<string name="action_members">Członkowie</string>
|
||||||
|
<string name="action_labels">Etykiety</string>
|
||||||
|
<string name="action_currencies">Waluty</string>
|
||||||
|
<!-- Titles -->
|
||||||
|
<string name="title_stats">Statystyki</string>
|
||||||
|
<string name="title_edit_project">Edytuj projekt</string>
|
||||||
|
<string name="title_label_bills">Etykiety rachunków</string>
|
||||||
|
<string name="title_labels">Zarządzaj etykietami</string>
|
||||||
|
<string name="title_about">O programie</string>
|
||||||
|
<string name="title_settle">Ustawiony projekt</string>
|
||||||
|
<string name="title_share">Udostępnij projekt</string>
|
||||||
|
<string name="title_add_project">Dodaj projekt</string>
|
||||||
|
<string name="title_add_category">Dodaj kategorię</string>
|
||||||
|
<string name="title_add_payment_mode">Dodaj tryb płatności</string>
|
||||||
|
<string name="title_account">Konto Nextcloud</string>
|
||||||
|
<string name="title_share_web">Link internetowy</string>
|
||||||
|
<string name="title_share_qr">Cowspent link</string>
|
||||||
|
<string name="title_confirm">Jesteś pewien?</string>
|
||||||
|
<!-- Labels and Fields -->
|
||||||
|
<string name="label_all_bills">Wszystkie rachunki</string>
|
||||||
|
<string name="label_categories">Kategorie</string>
|
||||||
|
<string name="label_payment_modes">Tryby płatności</string>
|
||||||
|
<string name="label_name">Nazwisko</string>
|
||||||
|
<string name="label_icon">Icon / Emoji</string>
|
||||||
|
<string name="label_color">Kolor</string>
|
||||||
|
<string name="label_weight">Waga</string>
|
||||||
|
<string name="label_activated">Aktywowano</string>
|
||||||
|
<string name="label_password">Hasło</string>
|
||||||
|
<string name="label_email">E-mail</string>
|
||||||
|
<string name="label_url">Adres serwera</string>
|
||||||
|
<string name="label_username">Nazwa użytkownika</string>
|
||||||
|
<string name="label_comment">Komentarz</string>
|
||||||
|
<string name="label_what">Co?</string>
|
||||||
|
<string name="label_payer">Kto zapłacił?</string>
|
||||||
|
<string name="label_owers">Dla kogo?</string>
|
||||||
|
<string name="label_repeat">Powtarzaj co</string>
|
||||||
|
<string name="label_mode">Tryb</string>
|
||||||
|
<string name="label_category">Kategoria</string>
|
||||||
|
<string name="label_project_id">Identyfikator/nazwa projektu</string>
|
||||||
|
<string name="label_project_title">Tytuł projektu</string>
|
||||||
|
<string name="label_use_sso">Użyj konta aplikacji Nextcloud</string>
|
||||||
|
<!-- Dialogs and Messages -->
|
||||||
|
<string name="dialog_unsaved_changes_title">Niezapisane zmiany</string>
|
||||||
|
<string name="dialog_unsaved_changes_msg">Zapisać zmiany przed wyjściem?</string>
|
||||||
|
<string name="dialog_confirm_remove_project_msg">Zdalny projekt nie zostanie usunięty.</string>
|
||||||
|
<string name="dialog_sync_error_title">Błąd synchronizacji</string>
|
||||||
|
<string name="dialog_sync_error_msg">Synchronizacja %1$snie powiodła się.\n\n%2$s</string>
|
||||||
|
<string name="dialog_balanced_msg">Wydatki są już zrównoważone.</string>
|
||||||
|
<string name="msg_project_added">Projekt %1$s dodany</string>
|
||||||
|
<string name="msg_bill_labeled_done">Wszystkie rachunki oznaczone</string>
|
||||||
|
<string name="msg_no_suggestions">Brak sugestii</string>
|
||||||
|
<string name="msg_auth_warning">Wymaga Dołączyć v0.3.4+.</string>
|
||||||
|
<string name="msg_link_copied">Link skopiowany do schowka</string>
|
||||||
|
<string name="msg_share_qr">Zeskanuj kod QR lub udostępnij link, aby dołączyć.</string>
|
||||||
|
<string name="msg_share_web">Link dostępu do przeglądarki internetowej.</string>
|
||||||
|
<string name="msg_share_qr_warn">Udostępnij ten link użytkownikowi Cowspent</string>
|
||||||
|
<string name="msg_settle_intro">Rozliczenie dla %1$s:</string>
|
||||||
|
<string name="msg_settle_sentence">%1$s jest winny %3$.2f do %2$s</string>
|
||||||
|
<string name="msg_stats_intro">Statystyki dla %1$s:</string>
|
||||||
|
<string name="msg_stats_header">Członek (Pomoc | Wydano | Saldo)</string>
|
||||||
|
<string name="msg_logged_in_as">Zalogowano jako %1$s</string>
|
||||||
|
<!-- Errors -->
|
||||||
|
<string name="error_generic">Błąd</string>
|
||||||
|
<string name="error_loading">Ładowanie</string>
|
||||||
|
<string name="error_no_projects">Nie znaleziono projektów</string>
|
||||||
|
<string name="error_no_members">Nie znaleziono członków</string>
|
||||||
|
<string name="error_no_bills">Nie znaleziono rachunków</string>
|
||||||
|
<string name="error_no_member">Wymagany co najmniej jeden członek</string>
|
||||||
|
<string name="error_maintenance_mode">Serwer jest w trybie konserwacji</string>
|
||||||
|
<string name="error_400">400 błędnych żądań</string>
|
||||||
|
<string name="error_401">401 Nieautoryzowany</string>
|
||||||
|
<string name="error_403">403 Zabronione</string>
|
||||||
|
<string name="error_404">404 Nie znaleziono</string>
|
||||||
|
<string name="error_sync">Synchronizacja nie powiodła się: %1$s</string>
|
||||||
|
<string name="error_invalid_login">Nieprawidłowy login: %1$s</string>
|
||||||
|
<string name="error_auth">Nieprawidłowa nazwa użytkownika lub hasło</string>
|
||||||
|
<string name="error_json">Nieprawidłowa odpowiedź serwera</string>
|
||||||
|
<string name="error_req_failed">Żądanie nie powiodło się</string>
|
||||||
|
<string name="error_invalid_email">Nieprawidłowy adres e-mail</string>
|
||||||
|
<string name="error_invalid_project_id">Nieprawidłowy identyfikator projektu</string>
|
||||||
|
<string name="error_invalid_project_name">Nieprawidłowy tytuł projektu</string>
|
||||||
|
<string name="error_invalid_bill_name">Nieprawidłowa nazwa rachunku</string>
|
||||||
|
<string name="error_invalid_bill_date">Nieprawidłowa data rozliczenia</string>
|
||||||
|
<string name="error_invalid_bill_payer">Wymagany płatnik</string>
|
||||||
|
<string name="error_invalid_bill_owers">Wymagane jest posiadanie</string>
|
||||||
|
<string name="error_no_network">Brak połączenia sieciowego</string>
|
||||||
|
<string name="error_server">Błąd serwera</string>
|
||||||
|
<string name="error_io">Połączenie z serwerem przerwane</string>
|
||||||
|
<string name="error_share_impossible">Nie można udostępnić tego projektu</string>
|
||||||
|
<!-- Drawer / Common UI -->
|
||||||
|
<string name="drawer_no_account">Połącz z kontem Nextcloud</string>
|
||||||
|
<string name="drawer_last_sync">Ostatnia synchronizacja: %1$02d:%2$02d</string>
|
||||||
|
<string name="simple_cancel">Anuluj</string>
|
||||||
|
<string name="simple_ok" tools:ignore="ButtonCase,Typos">Ok</string>
|
||||||
|
<string name="simple_yes">Tak</string>
|
||||||
|
<string name="simple_no">Nie</string>
|
||||||
|
<string name="simple_close">Zamknij</string>
|
||||||
|
<!-- Settings -->
|
||||||
|
<string name="settings_appearance">Wygląd</string>
|
||||||
|
<string name="settings_network">Sieć</string>
|
||||||
|
<string name="settings_other">Inne</string>
|
||||||
|
<string name="settings_night_mode">Motyw</string>
|
||||||
|
<string name="settings_offline_mode">Tryb offline</string>
|
||||||
|
<string name="settings_offline_mode_summary">Synchronizuj tylko ręcznie.</string>
|
||||||
|
<string name="settings_color_custom">Własny kolor</string>
|
||||||
|
<string name="settings_color_mode">Wybór koloru</string>
|
||||||
|
<string name="settings_show_archived">Pokaż zarchiwizowane projekty</string>
|
||||||
|
<string name="settings_beta_features">Funkcje Beta</string>
|
||||||
|
<string name="settings_beta_features_summary">Włącz funkcje eksperymentalne. Używaj na własne ryzyko.</string>
|
||||||
|
<string name="settings_url_warn_http">OSTRZEŻENIE: \"http\" jest niebezpieczny. Użyj \"https\".</string>
|
||||||
|
<string name="settings_colorpicker_title">Wybierz kolor</string>
|
||||||
|
<string name="pref_value_color_system">System</string>
|
||||||
|
<string name="pref_value_color_server">Nextcloud</string>
|
||||||
|
<string name="pref_value_color_manual">Ręcznie</string>
|
||||||
|
<string name="pref_value_theme_light">Światło</string>
|
||||||
|
<string name="pref_value_theme_dark">Ciemny</string>
|
||||||
|
<string name="pref_value_theme_system">Śledź system</string>
|
||||||
|
<!-- Constants (Do not translate) -->
|
||||||
|
<!-- Enums and Lists -->
|
||||||
|
<string name="repeat_no">Brak powtórzeń</string>
|
||||||
|
<string name="repeat_day">Codziennie</string>
|
||||||
|
<string name="repeat_week">Tygodniowo</string>
|
||||||
|
<string name="repeat_fortnight">Co dwa tygodnie</string>
|
||||||
|
<string name="repeat_month">Miesięczny</string>
|
||||||
|
<string name="repeat_year">Rocznie</string>
|
||||||
|
<string name="payment_mode_none">Brak</string>
|
||||||
|
<string name="payment_mode_all">Wszystkie</string>
|
||||||
|
<string name="payment_mode_credit_card">Karta kredytowa</string>
|
||||||
|
<string name="payment_mode_cash">Gotówka</string>
|
||||||
|
<string name="payment_mode_check">Sprawdzanie</string>
|
||||||
|
<string name="payment_mode_online">Dostępny</string>
|
||||||
|
<string name="payment_mode_transfer">Przelew</string>
|
||||||
|
<string name="category_none">Brak</string>
|
||||||
|
<string name="category_all">Wszystkie</string>
|
||||||
|
<string name="category_all_except_reimbursement">Wszystkie z wyjątkiem zwrotu</string>
|
||||||
|
<string name="category_groceries">Żywność</string>
|
||||||
|
<string name="category_leisure">Pasek/Impreza</string>
|
||||||
|
<string name="category_rent">Wynajem</string>
|
||||||
|
<string name="category_bills">Rachunek</string>
|
||||||
|
<string name="category_excursion">Eksploatacja/kultura</string>
|
||||||
|
<string name="category_health">Zdrowie</string>
|
||||||
|
<string name="category_shopping">Zakupy</string>
|
||||||
|
<string name="category_reimbursement">Zwrot kosztów</string>
|
||||||
|
<string name="category_restaurant">Restauracja</string>
|
||||||
|
<string name="category_accomodation">Zakwaterowanie</string>
|
||||||
|
<string name="category_transport">Transport</string>
|
||||||
|
<string name="category_sport">Sport</string>
|
||||||
|
<!-- Project specific -->
|
||||||
|
<string name="new_project_action">Co</string>
|
||||||
|
<string name="new_project_where">Gdzie</string>
|
||||||
|
<string name="where_local">Tylko lokalne</string>
|
||||||
|
<string name="where_cospend">Dołącz</string>
|
||||||
|
<string name="where_ihatemoney">IHateMoney</string>
|
||||||
|
<string name="todo_join">Dołącz do istniejącego projektu</string>
|
||||||
|
<string name="todo_create">Utwórz nowy projekt</string>
|
||||||
|
<string name="import_tooltip">Importuj z pliku</string>
|
||||||
|
<string name="choose_account_project_dialog_title">Wybierz projekt</string>
|
||||||
|
<string name="choose_account_project_dialog_impossible">Nie znaleziono projektów na tym koncie.</string>
|
||||||
|
<string name="choose_project_management_action">Projekt</string>
|
||||||
|
<string name="project_added_success">Projekt dodany pomyślnie.</string>
|
||||||
|
<string name="no_projects_text">Nie masz jeszcze żadnych projektów.</string>
|
||||||
|
<string name="configure_account_choice">Konfiguruj konto Nextcloud</string>
|
||||||
|
<string name="add_project_choice">Dodaj projekt ręcznie</string>
|
||||||
|
<string name="no_members_text">Brak członków w tym projekcie.</string>
|
||||||
|
<string name="no_bills_text">Nie znaleziono rachunków.</string>
|
||||||
|
<string name="member_already_exists">Użytkownik już istnieje.</string>
|
||||||
|
<string name="activity_dialog_title">Projekt: %1$s</string>
|
||||||
|
<string name="remove_project_confirmation">Projekt %1$s usunięty.</string>
|
||||||
|
<string name="file_saved_success">Plik zapisany: %1$s</string>
|
||||||
|
<string name="import_error_header">Importowanie nie powiodło się w wierszu %d</string>
|
||||||
|
<string name="import_error_date">Nieprawidłowy format daty w wierszu %d</string>
|
||||||
|
<string name="import_error_owers">Niepoprawne owce w wierszu %d</string>
|
||||||
|
<string name="add_member_dialog_title">Dodaj członka</string>
|
||||||
|
<string name="edit_member_dialog_title">Edytuj członka</string>
|
||||||
|
<string name="member_edit_delete">Usuń</string>
|
||||||
|
<string name="project_edition_no_change">Brak zmian do zapisu.</string>
|
||||||
|
<!-- Settlement -->
|
||||||
|
<string name="center_none">Brak (Optymalny)</string>
|
||||||
|
<string name="settle_who">Kto płaci</string>
|
||||||
|
<string name="settle_to_whom">Do kogo</string>
|
||||||
|
<string name="settle_how_much">Kwota</string>
|
||||||
|
<string name="simple_settle_share">Udostępnij</string>
|
||||||
|
<string name="simple_create_bills">Utwórz rachunki</string>
|
||||||
|
<string name="settle_bill_what">Rozliczenie</string>
|
||||||
|
<!-- Currencies -->
|
||||||
|
<string name="currency_dialog_title">Wybierz walutę (%s)</string>
|
||||||
|
<string name="setting_none">Brak</string>
|
||||||
|
<string name="setting_all">Wszystkie</string>
|
||||||
|
<string name="currency_saved_success">Zapisano ustawienia waluty.</string>
|
||||||
|
<string name="main_currency">Waluta główna</string>
|
||||||
|
<!-- Statistics -->
|
||||||
|
<string name="label_bills_suggested">Sugerowane kategorie</string>
|
||||||
|
<string name="label_bills_skip">Pomiń</string>
|
||||||
|
<string name="stats_date_min">Od</string>
|
||||||
|
<string name="stats_date_max">Do</string>
|
||||||
|
<string name="stats_who">Członek</string>
|
||||||
|
<string name="stats_paid">Zapłacone</string>
|
||||||
|
<string name="stats_spent">Wydano</string>
|
||||||
|
<string name="stats_balance">Saldo</string>
|
||||||
|
<string name="total">Razem: %1$s</string>
|
||||||
|
<!-- Errors Extra -->
|
||||||
|
<string name="error_project_connect_check">Połączenie nie powiodło się: %1$s</string>
|
||||||
|
<string name="error_create_remote_project_helper">Utworzenie nie powiodło się: %1$s</string>
|
||||||
|
<string name="error_edit_remote_project_helper">Błąd podczas aktualizacji zdalnego projektu: %1$s</string>
|
||||||
|
<string name="remote_project_operation_no_network">Sieć niedostępna dla zdalnej operacji.</string>
|
||||||
|
<string name="error_scanning_bill_qr_code">Nie udało się przetworzyć kodu QR.</string>
|
||||||
|
<string name="error_token_mismatch">Nieprawidłowy token uwierzytelniania. Zaloguj się ponownie.</string>
|
||||||
|
<string name="insufficient_access_level">Nie masz uprawnień do wykonania tej czynności.</string>
|
||||||
|
<string name="delete_label_confirmation_title">Usuń etykietę</string>
|
||||||
|
<string name="delete_label_confirmation_message">Czy na pewno chcesz usunąć tę etykietę?</string>
|
||||||
|
<!-- About -->
|
||||||
|
<string name="about_version">Wersja %1$s</string>
|
||||||
|
<string name="about_maintainer_title">Opiekun</string>
|
||||||
|
<string name="about_license_title">Licencja</string>
|
||||||
|
<string name="about_source_title">Kod źródłowy</string>
|
||||||
|
<!-- New constants for backward compatibility or shared use -->
|
||||||
|
<string name="share_intent_title">Projekt %1$s</string>
|
||||||
|
<string name="share_chooser_title">Udostępnij %1$s</string>
|
||||||
|
</resources>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user