Compare commits
17
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d0ab7f6286 | ||
|
|
c2da40ba4d | ||
|
|
6e45507b22 | ||
|
|
99d05680c5 | ||
|
|
d89d99ceff | ||
|
|
40be1b79f2 | ||
|
|
e807d6fa66 | ||
|
|
8ae8df6ef8 | ||
|
|
791f3572b6 | ||
|
|
ce8053ff2c | ||
|
|
f5f4c083f0 | ||
|
|
6800fc6c41
|
||
|
|
1f56757d30 | ||
|
|
73dc64a688 | ||
|
|
3b22037496 | ||
|
|
206c756ad7 | ||
|
|
a66d046b3f |
@@ -93,11 +93,11 @@ jobs:
|
||||
|
||||
- name: Upload to Google Play
|
||||
uses: r0adkll/upload-google-play@v1
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
# if: startsWith(github.ref, 'refs/tags/')
|
||||
with:
|
||||
serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }}
|
||||
packageName: net.helcel.cowspent
|
||||
releaseFiles: app/build/outputs/bundle/signedRelease/app-signedRelease.aab
|
||||
tracks: ${{ startsWith(github.ref, 'refs/tags/') && 'production' || 'alpha' }}
|
||||
tracks: ${{ startsWith(github.ref, 'refs/tags/') && 'production' || 'internal' }}
|
||||
status: completed
|
||||
whatsNewDirectory: tmp/whatsnew
|
||||
+8
-8
@@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'com.android.application' version '9.3.2'
|
||||
id 'org.jetbrains.kotlin.plugin.serialization' version '2.4.10'
|
||||
id 'org.jetbrains.kotlin.plugin.compose' version '2.4.10'
|
||||
id 'org.jetbrains.kotlin.plugin.serialization' version '2.4.20'
|
||||
id 'org.jetbrains.kotlin.plugin.compose' version '2.4.21'
|
||||
id 'jacoco'
|
||||
}
|
||||
|
||||
@@ -118,12 +118,12 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'androidx.compose.foundation:foundation:1.12.0'
|
||||
implementation 'androidx.compose.runtime:runtime:1.12.0'
|
||||
implementation 'androidx.compose.foundation:foundation:1.12.1'
|
||||
implementation 'androidx.compose.runtime:runtime:1.12.1'
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.5'
|
||||
|
||||
implementation 'androidx.preference:preference-ktx:1.2.1'
|
||||
implementation 'androidx.work:work-runtime-ktx:2.11.2'
|
||||
implementation 'androidx.work:work-runtime-ktx:2.12.0'
|
||||
|
||||
implementation 'com.google.zxing:core:3.5.4'
|
||||
implementation 'androidx.camera:camera-camera2:1.6.2'
|
||||
@@ -132,7 +132,7 @@ dependencies {
|
||||
implementation 'com.github.nextcloud:Android-SingleSignOn:1.3.4'
|
||||
implementation 'com.opencsv:opencsv:5.12.0'
|
||||
|
||||
implementation platform('androidx.compose:compose-bom:2026.08.00')
|
||||
implementation platform('androidx.compose:compose-bom:2026.09.00')
|
||||
implementation 'androidx.compose.ui:ui'
|
||||
implementation 'androidx.compose.material:material'
|
||||
implementation 'androidx.compose.material:material-icons-extended'
|
||||
@@ -147,12 +147,12 @@ dependencies {
|
||||
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
testImplementation 'io.mockk:mockk:1.14.11'
|
||||
testImplementation 'org.robolectric:robolectric:4.16.1'
|
||||
testImplementation 'org.robolectric:robolectric:4.17'
|
||||
testImplementation 'androidx.test:core:1.7.0'
|
||||
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.11.0'
|
||||
testImplementation 'androidx.compose.ui:ui-test-junit4'
|
||||
|
||||
androidTestImplementation platform('androidx.compose:compose-bom:2026.08.00')
|
||||
androidTestImplementation platform('androidx.compose:compose-bom:2026.09.00')
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.3.0'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.7.0'
|
||||
androidTestImplementation 'androidx.compose.ui:ui-test-junit4'
|
||||
|
||||
+10
-2
@@ -48,8 +48,10 @@ import net.helcel.cowspent.model.Transaction
|
||||
import net.helcel.cowspent.model.UserItem
|
||||
import net.helcel.cowspent.persistence.CowspentSQLiteOpenHelper
|
||||
import net.helcel.cowspent.util.SupportUtil
|
||||
import net.helcel.cowspent.util.SupportUtil.SETTLE_DIRECT
|
||||
import net.helcel.cowspent.util.SupportUtil.SETTLE_OPTIMAL
|
||||
import net.helcel.cowspent.util.SupportUtil.settleBills
|
||||
import net.helcel.cowspent.util.SupportUtil.settleBillsDirect
|
||||
|
||||
@Composable
|
||||
fun ProjectSettlementDialogContent(
|
||||
@@ -60,10 +62,12 @@ fun ProjectSettlementDialogContent(
|
||||
onDismiss: () -> Unit
|
||||
) {
|
||||
val centerNoneStr = stringResource(R.string.center_none)
|
||||
val centerDirectStr = stringResource(R.string.center_none_direct)
|
||||
val memberList = remember(proj.id) { db.getMembersOfProject(proj.id, null) }
|
||||
val userList = remember(memberList, centerNoneStr) {
|
||||
val userList = remember(memberList, centerNoneStr, centerDirectStr) {
|
||||
buildList {
|
||||
add(UserItem(SETTLE_OPTIMAL, centerNoneStr))
|
||||
add(UserItem(SETTLE_DIRECT, centerDirectStr))
|
||||
addAll(memberList.map { UserItem(it.id, it.name) })
|
||||
}
|
||||
}
|
||||
@@ -91,7 +95,11 @@ fun ProjectSettlementDialogContent(
|
||||
}
|
||||
|
||||
val transactions = remember(selectedMemberId, membersBalance, membersSortedByName) {
|
||||
settleBills(membersSortedByName, membersBalance, selectedMemberId)
|
||||
if (selectedMemberId == SETTLE_DIRECT) {
|
||||
settleBillsDirect(membersSortedByName, db.getBillsOfProject(proj.id))
|
||||
} else {
|
||||
settleBills(membersSortedByName, membersBalance, selectedMemberId)
|
||||
}
|
||||
}
|
||||
|
||||
ProjectSettlementUI(
|
||||
|
||||
@@ -141,7 +141,7 @@ object SupportUtil {
|
||||
membersBalance[b.payerId] = balPayer + amount
|
||||
val paid = membersPaid[b.payerId] ?: 0.0
|
||||
membersPaid[b.payerId] = paid + amount
|
||||
|
||||
|
||||
var nbOwerShares = 0.0
|
||||
for (bo in b.billOwers) {
|
||||
nbOwerShares += membersWeight[bo.memberId] ?: 0.0
|
||||
@@ -167,6 +167,39 @@ object SupportUtil {
|
||||
}
|
||||
|
||||
const val SETTLE_OPTIMAL: Long = 0
|
||||
const val SETTLE_DIRECT: Long = -1
|
||||
|
||||
@JvmStatic
|
||||
fun settleBillsDirect(members: List<DBMember>, bills: List<DBBill>): List<Transaction> {
|
||||
val membersWeight = members.associate { it.id to it.weight }
|
||||
// owed[debtor][creditor] = total the debtor owes the creditor over all bills
|
||||
val owed = HashMap<Long, HashMap<Long, Double>>()
|
||||
|
||||
for (b in bills) {
|
||||
if (b.state == DBBill.STATE_DELETED) continue
|
||||
val nbOwerShares = b.billOwers.sumOf { membersWeight[it.memberId] ?: 0.0 }
|
||||
if (nbOwerShares <= 0.0) continue
|
||||
for (bo in b.billOwers) {
|
||||
if (bo.memberId == b.payerId) continue
|
||||
val share = b.amount / nbOwerShares * (membersWeight[bo.memberId] ?: 0.0)
|
||||
val row = owed.getOrPut(bo.memberId) { HashMap() }
|
||||
row[b.payerId] = (row[b.payerId] ?: 0.0) + share
|
||||
}
|
||||
}
|
||||
|
||||
val results: MutableList<Transaction> = ArrayList()
|
||||
for (debtor in members) {
|
||||
for (creditor in members) {
|
||||
if (debtor.id == creditor.id) continue
|
||||
val net = (owed[debtor.id]?.get(creditor.id) ?: 0.0) -
|
||||
(owed[creditor.id]?.get(debtor.id) ?: 0.0)
|
||||
if (round2(net) > 0.0) {
|
||||
results.add(Transaction(debtor.id, creditor.id, round2(net)))
|
||||
}
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun settleBills(
|
||||
@@ -233,11 +266,11 @@ object SupportUtil {
|
||||
if (cd1.balance < cd2.balance) 1 else -1
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for (c in crediters) {
|
||||
Log.e(SupportUtil::class.java.simpleName, "* " + c.memberId + " : " + c.balance)
|
||||
}
|
||||
|
||||
|
||||
debiters.sortWith { cd2, cd1 ->
|
||||
if (cd1.balance == cd2.balance) {
|
||||
0
|
||||
|
||||
@@ -209,6 +209,7 @@
|
||||
|
||||
<!-- Settlement -->
|
||||
<string name="center_none">Keine (optimal)</string>
|
||||
<string name="center_none_direct">Keine (direkt)</string>
|
||||
<string name="settle_who">Wer zahlt</string>
|
||||
<string name="settle_to_whom">An wen</string>
|
||||
<string name="settle_how_much">Betrag</string>
|
||||
|
||||
@@ -210,6 +210,7 @@
|
||||
|
||||
<!-- Settlement -->
|
||||
<string name="center_none">Ninguno (óptimo)</string>
|
||||
<string name="center_none_direct">Ninguno (directo)</string>
|
||||
<string name="settle_who">Quién paga</string>
|
||||
<string name="settle_to_whom">A quién</string>
|
||||
<string name="settle_how_much">Importe</string>
|
||||
|
||||
@@ -213,6 +213,7 @@
|
||||
|
||||
<!-- Settlement -->
|
||||
<string name="center_none">Aucun (optimal)</string>
|
||||
<string name="center_none_direct">Aucun (direct)</string>
|
||||
<string name="settle_who">Qui paie</string>
|
||||
<string name="settle_to_whom">À qui</string>
|
||||
<string name="settle_how_much">Montant</string>
|
||||
|
||||
@@ -209,6 +209,7 @@
|
||||
|
||||
<!-- Settlement -->
|
||||
<string name="center_none">Нет (оптимально)</string>
|
||||
<string name="center_none_direct">Нет (напрямую)</string>
|
||||
<string name="settle_who">Кто платит</string>
|
||||
<string name="settle_to_whom">Кому</string>
|
||||
<string name="settle_how_much">Сумма</string>
|
||||
|
||||
@@ -247,6 +247,7 @@
|
||||
|
||||
<!-- Settlement -->
|
||||
<string name="center_none">None (Optimal)</string>
|
||||
<string name="center_none_direct">None (Direct)</string>
|
||||
<string name="settle_who">Who pays</string>
|
||||
<string name="settle_to_whom">To whom</string>
|
||||
<string name="settle_how_much">Amount</string>
|
||||
|
||||
Reference in New Issue
Block a user