6 Commits
14 changed files with 149 additions and 67 deletions

No files matched your search

+12
View File
@@ -6,6 +6,18 @@
<option name="selectionMode" value="DROPDOWN" /> <option name="selectionMode" value="DROPDOWN" />
<DialogSelection /> <DialogSelection />
</SelectionState> </SelectionState>
<SelectionState runConfigName="androidTest">
<option name="selectionMode" value="DROPDOWN" />
<DialogSelection />
</SelectionState>
<SelectionState runConfigName="main">
<option name="selectionMode" value="DROPDOWN" />
<DialogSelection />
</SelectionState>
<SelectionState runConfigName="unitTest">
<option name="selectionMode" value="DROPDOWN" />
<DialogSelection />
</SelectionState>
</selectionStates> </selectionStates>
</component> </component>
</project> </project>
+2 -2
View File
@@ -30,8 +30,8 @@ android {
applicationId = "com.oqba26.monthlypaymentapp" applicationId = "com.oqba26.monthlypaymentapp"
minSdk = 25 minSdk = 25
targetSdk = 34 targetSdk = 34
versionCode = 5 versionCode = 7
versionName = "1.0.4" versionName = "1.0.6"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables { useSupportLibrary = true } vectorDrawables { useSupportLibrary = true }
} }
@@ -490,7 +490,7 @@ fun AuthenticatedContent(viewModel: PersonViewModel, contactViewModel: ContactVi
) )
} }
composable(Screen.Archive.route) { composable(Screen.Archive.route) {
ArchiveScreen(viewModel) ArchiveScreen(viewModel = viewModel, navController = navController)
} }
composable(Screen.Settings.route) { composable(Screen.Settings.route) {
val authViewModel: AuthViewModel = hiltViewModel() val authViewModel: AuthViewModel = hiltViewModel()
@@ -25,6 +25,9 @@ interface PaymentDao {
@Query("DELETE FROM payments WHERE id = :paymentId") @Query("DELETE FROM payments WHERE id = :paymentId")
suspend fun deleteById(paymentId: String) suspend fun deleteById(paymentId: String)
@Query("DELETE FROM payments WHERE personId = :personId")
suspend fun deletePaymentsForPerson(personId: String)
/** حذف گروهی — برای اعمال [com.oqba26.monthlypaymentapp.data.sync.MergePlan] در یک کوئری. */ /** حذف گروهی — برای اعمال [com.oqba26.monthlypaymentapp.data.sync.MergePlan] در یک کوئری. */
@Query("DELETE FROM payments WHERE id IN (:ids)") @Query("DELETE FROM payments WHERE id IN (:ids)")
suspend fun deleteByIds(ids: List<String>) suspend fun deleteByIds(ids: List<String>)
@@ -1,11 +1,13 @@
@file:OptIn(kotlinx.serialization.InternalSerializationApi::class) @file:OptIn(InternalSerializationApi::class)
package com.oqba26.monthlypaymentapp.data.model package com.oqba26.monthlypaymentapp.data.model
import androidx.room.Entity import androidx.room.Entity
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import kotlinx.serialization.InternalSerializationApi
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import java.util.UUID import java.util.UUID
@Serializable @Serializable
@@ -14,22 +16,22 @@ data class PaymentRecord(
@PrimaryKey @PrimaryKey
@SerialName("id") val id: String = UUID.randomUUID().toString(), @SerialName("id") val id: String = UUID.randomUUID().toString(),
@SerialName("personId") val personId: String, @SerialName("person_id") val personId: String,
@SerialName("amount") val amount: Double, @SerialName("amount") val amount: Double,
@SerialName("shamsiYear") val shamsiYear: Int, @SerialName("shamsi_year") val shamsiYear: Int,
@SerialName("shamsiMonth") val shamsiMonth: Int, @SerialName("shamsi_month") val shamsiMonth: Int,
@SerialName("timestamp") val timestamp: Long = System.currentTimeMillis(), @SerialName("timestamp") val timestamp: Long = System.currentTimeMillis(),
@SerialName("description") val description: String? = "", // ⭐️ تغییر به String? برای پذیرش مقادیر null @SerialName("description") val description: String? = "",
@SerialName("createdAt") val createdAt: Long? = null, @SerialName("created_at") val createdAt: Long? = null,
@SerialName("category") val category: String = "salary", @SerialName("category") val category: String = "salary",
@kotlinx.serialization.Transient @Transient
val needsSync: Boolean = false val needsSync: Boolean = false
) )
@@ -1,11 +1,13 @@
@file:OptIn(kotlinx.serialization.InternalSerializationApi::class) @file:OptIn(InternalSerializationApi::class)
package com.oqba26.monthlypaymentapp.data.model package com.oqba26.monthlypaymentapp.data.model
import androidx.room.Entity import androidx.room.Entity
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import kotlinx.serialization.InternalSerializationApi
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
@Serializable @Serializable
@Entity(tableName = "persons") @Entity(tableName = "persons")
@@ -15,24 +17,24 @@ data class Person(
@SerialName("name") val name: String, @SerialName("name") val name: String,
@SerialName("isArchived") val isArchived: Boolean = false, @SerialName("is_archived") val isArchived: Boolean = false,
@SerialName("displayOrder") val displayOrder: Long? = 0L, @SerialName("display_order") val displayOrder: Long? = 0L,
@SerialName("createdAt") val createdAt: Long? = 0L, @SerialName("created_at") val createdAt: Long? = 0L,
@SerialName("phoneNumber") val phoneNumber: String? = "", @SerialName("phone_number") val phoneNumber: String? = "",
@SerialName("category") val category: String = "salary", @SerialName("category") val category: String = "salary",
@SerialName("isAnonymous") val isAnonymous: Boolean = false, @SerialName("is_anonymous") val isAnonymous: Boolean = false,
@SerialName("monthlyCommitment") val monthlyCommitment: Double = 0.0, @SerialName("monthly_commitment") val monthlyCommitment: Double = 0.0,
@SerialName("startMonth") val startMonth: Int = 1, @SerialName("start_month") val startMonth: Int = 1,
@SerialName("startYear") val startYear: Int = 1403, @SerialName("start_year") val startYear: Int = 1403,
@kotlinx.serialization.Transient @Transient
val needsSync: Boolean = false val needsSync: Boolean = false
) )
@@ -135,6 +135,7 @@ class LocalPersonRepository(
suspend fun deletePersonLocally(personId: String) { suspend fun deletePersonLocally(personId: String) {
database.withTransaction { database.withTransaction {
personDao.deleteById(personId) personDao.deleteById(personId)
paymentDao.deletePaymentsForPerson(personId)
syncQueueDao.insert(SyncQueue(entityId = personId, type = SyncType.PERSON, operation = SyncOperation.DELETE)) syncQueueDao.insert(SyncQueue(entityId = personId, type = SyncType.PERSON, operation = SyncOperation.DELETE))
} }
} }
@@ -92,6 +92,15 @@ fun UpdateDialog(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(12.dp) horizontalArrangement = Arrangement.spacedBy(12.dp)
) { ) {
Button(
onClick = onConfirm,
modifier = Modifier.weight(1f),
shape = MaterialTheme.shapes.medium,
elevation = ButtonDefaults.buttonElevation(defaultElevation = 2.dp)
) {
Text("بروزرسانی")
}
if (!updateInfo.isForceUpdate) { if (!updateInfo.isForceUpdate) {
OutlinedButton( OutlinedButton(
onClick = onDismiss, onClick = onDismiss,
@@ -103,15 +112,6 @@ fun UpdateDialog(
Text("بعداً") Text("بعداً")
} }
} }
Button(
onClick = onConfirm,
modifier = Modifier.weight(1f),
shape = MaterialTheme.shapes.medium,
elevation = ButtonDefaults.buttonElevation(defaultElevation = 2.dp)
) {
Text("تایید و بروزرسانی")
}
} }
} }
} }
@@ -1,5 +1,6 @@
package com.oqba26.monthlypaymentapp.ui.screens package com.oqba26.monthlypaymentapp.ui.screens
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@@ -38,12 +39,13 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.Dialog
import androidx.navigation.NavController
import com.oqba26.monthlypaymentapp.viewmodel.PersonScreenEvent import com.oqba26.monthlypaymentapp.viewmodel.PersonScreenEvent
import com.oqba26.monthlypaymentapp.viewmodel.PersonUiModel import com.oqba26.monthlypaymentapp.viewmodel.PersonUiModel
import com.oqba26.monthlypaymentapp.viewmodel.PersonViewModel import com.oqba26.monthlypaymentapp.viewmodel.PersonViewModel
@Composable @Composable
fun ArchiveScreen(viewModel: PersonViewModel) { fun ArchiveScreen(viewModel: PersonViewModel, navController: NavController) {
val uiState by viewModel.uiState.collectAsState() val uiState by viewModel.uiState.collectAsState()
var personToDelete by remember { mutableStateOf<PersonUiModel?>(null) } var personToDelete by remember { mutableStateOf<PersonUiModel?>(null) }
var personToRestore by remember { mutableStateOf<PersonUiModel?>(null) } var personToRestore by remember { mutableStateOf<PersonUiModel?>(null) }
@@ -63,6 +65,9 @@ fun ArchiveScreen(viewModel: PersonViewModel) {
items(uiState.archivedPersons, key = { it.id }) { person -> items(uiState.archivedPersons, key = { it.id }) { person ->
ArchivedPersonListItem( ArchivedPersonListItem(
person = person, person = person,
onPersonClick = {
navController.navigate("person_detail/${person.id}")
},
onRestore = { onRestore = {
personToRestore = person personToRestore = person
}, },
@@ -148,12 +153,14 @@ fun ArchiveScreen(viewModel: PersonViewModel) {
@Composable @Composable
fun ArchivedPersonListItem( fun ArchivedPersonListItem(
person: PersonUiModel, person: PersonUiModel,
onPersonClick: () -> Unit,
onRestore: () -> Unit, onRestore: () -> Unit,
onDelete: () -> Unit onDelete: () -> Unit
) { ) {
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.clickable(onClick = onPersonClick)
.padding(horizontal = 16.dp, vertical = 12.dp), .padding(horizontal = 16.dp, vertical = 12.dp),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween horizontalArrangement = Arrangement.SpaceBetween
@@ -25,6 +25,7 @@ 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.ChevronLeft import androidx.compose.material.icons.filled.ChevronLeft
import androidx.compose.material.icons.filled.ChevronRight import androidx.compose.material.icons.filled.ChevronRight
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Edit import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.Search import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.Button import androidx.compose.material3.Button
@@ -43,7 +44,6 @@ import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.RadioButton import androidx.compose.material3.RadioButton
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
@@ -128,7 +128,6 @@ fun PersonDetailScreen(
items(detailState.monthStates) { monthModel -> items(detailState.monthStates) { monthModel ->
MonthListItem( MonthListItem(
month = monthModel, month = monthModel,
personName = detailState.person?.name ?: "",
startMonth = detailState.person?.startMonth ?: 1, startMonth = detailState.person?.startMonth ?: 1,
onHeaderClick = { onHeaderClick = {
expandedMonth = if (expandedMonth == monthModel.month) null else monthModel.month expandedMonth = if (expandedMonth == monthModel.month) null else monthModel.month
@@ -207,12 +206,18 @@ fun PersonDetailScreen(
initialPhone = detailState.person?.phoneNumber, initialPhone = detailState.person?.phoneNumber,
initialCommitment = detailState.person?.monthlyCommitment ?: 0.0, initialCommitment = detailState.person?.monthlyCommitment ?: 0.0,
initialStartMonth = detailState.person?.startMonth ?: 1, initialStartMonth = detailState.person?.startMonth ?: 1,
initialStartYear = detailState.person?.startYear ?: com.oqba26.monthlypaymentapp.utils.getCurrentShamsiYear(), initialStartYear = detailState.person?.startYear ?: getCurrentShamsiYear(),
initialIsAnonymous = detailState.person?.isAnonymous ?: false,
isMosqueCategory = currentCategory == "mosque", isMosqueCategory = currentCategory == "mosque",
onConfirm = { name, phone, commitment, month, year -> onConfirm = { name, phone, commitment, month, year, isAnon ->
viewModel.onEvent(PersonScreenEvent.UpdatePerson(personId, name, phone, commitment, month, year)) viewModel.onEvent(PersonScreenEvent.UpdatePerson(personId, name, phone, commitment, month, year, isAnon))
editingPerson = false editingPerson = false
}, },
onDelete = {
viewModel.onEvent(PersonScreenEvent.DeletePerson(personId))
editingPerson = false
navController.popBackStack()
},
onDismiss = { editingPerson = false }, onDismiss = { editingPerson = false },
onSearchContact = { name -> onSearchContact = { name ->
contactViewModel.findSimilarContacts(name) contactViewModel.findSimilarContacts(name)
@@ -256,16 +261,20 @@ fun UpdatePersonDialog(
initialCommitment: Double = 0.0, initialCommitment: Double = 0.0,
initialStartMonth: Int = 1, initialStartMonth: Int = 1,
initialStartYear: Int = 1403, initialStartYear: Int = 1403,
initialIsAnonymous: Boolean = false,
isMosqueCategory: Boolean = false, isMosqueCategory: Boolean = false,
onConfirm: (String, String, Double, Int, Int) -> Unit, onConfirm: (String, String, Double, Int, Int, Boolean) -> Unit,
onDelete: () -> Unit,
onDismiss: () -> Unit, onDismiss: () -> Unit,
onSearchContact: (String) -> List<ContactMatch>, onSearchContact: (String) -> List<ContactMatch>,
) { ) {
var name by remember { mutableStateOf(initialName) } var name by remember { mutableStateOf(initialName) }
var phoneNumber by remember { mutableStateOf(initialPhone ?: "") } var phoneNumber by remember { mutableStateOf(initialPhone ?: "") }
var monthlyCommitment by remember { mutableStateOf(if (initialCommitment > 0) initialCommitment.toInt().toString() else "") } var monthlyCommitment by remember { mutableStateOf(if (initialCommitment > 0) initialCommitment.toInt().toString() else "") }
var isAnonymous by remember { mutableStateOf(initialIsAnonymous) }
var showDeleteConfirm by remember { mutableStateOf(false) }
val currentShamsiYear = com.oqba26.monthlypaymentapp.utils.getCurrentShamsiYear() val currentShamsiYear = getCurrentShamsiYear()
var startMonth by remember { mutableIntStateOf(initialStartMonth) } var startMonth by remember { mutableIntStateOf(initialStartMonth) }
var startYear by remember { mutableIntStateOf(initialStartYear) } var startYear by remember { mutableIntStateOf(initialStartYear) }
@@ -307,6 +316,20 @@ fun UpdatePersonDialog(
fontWeight = FontWeight.Bold fontWeight = FontWeight.Bold
) )
Row(
modifier = Modifier
.fillMaxWidth()
.clickable { isAnonymous = !isAnonymous },
verticalAlignment = Alignment.CenterVertically
) {
Checkbox(
checked = isAnonymous,
onCheckedChange = { isAnonymous = it }
)
Spacer(modifier = Modifier.width(8.dp))
Text("ثبت به صورت خیر ناشناس")
}
OutlinedTextField( OutlinedTextField(
value = name, value = name,
onValueChange = { name = it }, onValueChange = { name = it },
@@ -458,12 +481,12 @@ fun UpdatePersonDialog(
onClick = { expandedMonth = true }, onClick = { expandedMonth = true },
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) { ) {
Text(com.oqba26.monthlypaymentapp.utils.getPersianMonthName(startMonth)) Text(getPersianMonthName(startMonth))
} }
DropdownMenu(expanded = expandedMonth, onDismissRequest = { expandedMonth = false }) { DropdownMenu(expanded = expandedMonth, onDismissRequest = { expandedMonth = false }) {
(1..12).forEach { m -> (1..12).forEach { m ->
DropdownMenuItem( DropdownMenuItem(
text = { Text(com.oqba26.monthlypaymentapp.utils.getPersianMonthName(m)) }, text = { Text(getPersianMonthName(m)) },
onClick = { startMonth = m; expandedMonth = false } onClick = { startMonth = m; expandedMonth = false }
) )
} }
@@ -481,7 +504,7 @@ fun UpdatePersonDialog(
onClick = { onClick = {
if (name.isNotBlank()) { if (name.isNotBlank()) {
val commitment = monthlyCommitment.toDoubleOrNull() ?: 0.0 val commitment = monthlyCommitment.toDoubleOrNull() ?: 0.0
onConfirm(name, phoneNumber, commitment, startMonth, startYear) onConfirm(name, phoneNumber, commitment, startMonth, startYear, isAnonymous)
} }
}, },
enabled = name.isNotBlank() enabled = name.isNotBlank()
@@ -498,6 +521,47 @@ fun UpdatePersonDialog(
Text("لغو") Text("لغو")
} }
} }
Button(
onClick = { showDeleteConfirm = true },
modifier = Modifier.fillMaxWidth(),
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.errorContainer,
contentColor = MaterialTheme.colorScheme.error
)
) {
Icon(Icons.Default.Delete, contentDescription = null)
Spacer(Modifier.width(4.dp))
Text("حذف این شخص")
}
if (showDeleteConfirm) {
Dialog(onDismissRequest = { showDeleteConfirm = false }) {
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
Card(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(24.dp)
) {
Column(modifier = Modifier.padding(20.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) {
Text(text = "حذف شخص", style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold)
Text(text = "آیا از حذف این شخص و تمام پرداخت‌های او مطمئن هستید؟", style = MaterialTheme.typography.bodyMedium)
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically) {
Button(onClick = { showDeleteConfirm = false }) { Text("لغو") }
Button(
onClick = {
showDeleteConfirm = false
onDelete()
},
colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.error)
) {
Text("حذف")
}
}
}
}
}
}
}
} }
} }
} }
@@ -535,7 +599,6 @@ fun YearSelector(year: Int, onYearChange: (Int) -> Unit) {
@Composable @Composable
fun MonthListItem( fun MonthListItem(
month: MonthUiModel, month: MonthUiModel,
personName: String,
startMonth: Int, startMonth: Int,
onHeaderClick: () -> Unit, onHeaderClick: () -> Unit,
onEditClick: () -> Unit onEditClick: () -> Unit
@@ -201,10 +201,10 @@ fun PersonScreen(
if (currentCategory == "mosque" && uiState.unpaidPersons.isNotEmpty()) { if (currentCategory == "mosque" && uiState.unpaidPersons.isNotEmpty()) {
item { item {
Text( Text(
text = "لیست خیرین (بدهکار)", text = "لیست خیرین",
modifier = Modifier.padding(16.dp), modifier = Modifier.padding(16.dp),
style = MaterialTheme.typography.titleMedium, style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.error color = MaterialTheme.colorScheme.primary
) )
} }
} }
@@ -303,14 +303,12 @@ fun PersonScreen(
personForPaymentDialog?.let { person -> personForPaymentDialog?.let { person ->
val currentDay = getCurrentShamsiDay() val currentDay = getCurrentShamsiDay()
val currentMonth = getCurrentShamsiMonth() val currentMonth = getCurrentShamsiMonth()
val currentYear = getCurrentShamsiYear()
val targetMonth = if (currentDay >= 20) currentMonth else if (currentMonth == 1) 12 else currentMonth - 1 val targetMonth = if (currentDay >= 20) currentMonth else if (currentMonth == 1) 12 else currentMonth - 1
val targetYear = if (currentDay < 20 && currentMonth == 1) currentYear - 1 else currentYear
val monthName = getPersianMonthName(targetMonth) val monthName = getPersianMonthName(targetMonth)
val currentDateTime = formatTimestampToPersianDateTime(System.currentTimeMillis()) val currentDateTime = formatTimestampToPersianDateTime(System.currentTimeMillis())
val isCurrentMonth = (targetMonth == currentMonth) && (targetYear == currentYear) val isCurrentMonth = targetMonth == currentMonth
val initialDesc = if (isCurrentMonth) { val initialDesc = if (isCurrentMonth) {
"پرداخت برای ماه جاری در تاریخ $currentDateTime ثبت شد." "پرداخت برای ماه جاری در تاریخ $currentDateTime ثبت شد."
} else { } else {
@@ -189,18 +189,8 @@ class PersonViewModel @Inject constructor(
val uiModels = activePersons.map { person -> val uiModels = activePersons.map { person ->
val hasPaid = relevantPayments.any { it.personId == person.id } val hasPaid = relevantPayments.any { it.personId == person.id }
val unpaidResult = if (category == "mosque") { val unpaidResult = if (category == "mosque" || person.isAnonymous) {
if (person.monthlyCommitment > 0) { Triple(0, 0.0, emptyList<String>() to emptyList())
val debtEndMonth = if (currentDay >= 20) currentMonth else currentMonth - 1
val debtStartMonth = if (person.startYear == currentYear) person.startMonth else 1
val unpaid = (debtStartMonth..debtEndMonth).filter { m ->
allPayments.none { it.personId == person.id && it.shamsiYear == currentYear && it.shamsiMonth == m && it.category == category }
}
Triple(unpaid.size, unpaid.size * person.monthlyCommitment, unpaid.map { getPersianMonthName(it) } to unpaid)
} else {
Triple(0, 0.0, emptyList<String>() to emptyList<Int>())
}
} else { } else {
// Salary logic // Salary logic
val debtEndMonth = if (currentDay >= 20) currentMonth else currentMonth - 1 val debtEndMonth = if (currentDay >= 20) currentMonth else currentMonth - 1
@@ -374,7 +364,8 @@ class PersonViewModel @Inject constructor(
is PersonScreenEvent.UpdatePerson -> { is PersonScreenEvent.UpdatePerson -> {
val persons = localPersonRepository.getAllPersonsFlow().first() val persons = localPersonRepository.getAllPersonsFlow().first()
val isDuplicate = persons.any { it.id != event.personId && it.category == _currentCategory.value && it.name.trim().equals(event.name.trim(), ignoreCase = true) } val finalName = if (event.isAnonymous && event.name.isBlank()) "ناشناس" else event.name
val isDuplicate = !event.isAnonymous && persons.any { it.id != event.personId && it.category == _currentCategory.value && it.name.trim().equals(finalName.trim(), ignoreCase = true) }
if (isDuplicate) { if (isDuplicate) {
_toastMessage.emit("خطا: این نام از قبل وجود دارد.") _toastMessage.emit("خطا: این نام از قبل وجود دارد.")
@@ -382,13 +373,15 @@ class PersonViewModel @Inject constructor(
val currentPerson = persons.find { it.id == event.personId } val currentPerson = persons.find { it.id == event.personId }
if (currentPerson != null) { if (currentPerson != null) {
val updatedPerson = currentPerson.copy( val updatedPerson = currentPerson.copy(
name = event.name, name = finalName,
phoneNumber = event.phoneNumber, phoneNumber = event.phoneNumber,
monthlyCommitment = event.monthlyCommitment, monthlyCommitment = if (event.isAnonymous) 0.0 else event.monthlyCommitment,
startMonth = event.startMonth, startMonth = event.startMonth,
startYear = event.startYear startYear = event.startYear,
isAnonymous = event.isAnonymous
) )
localPersonRepository.updatePersonLocally(updatedPerson) localPersonRepository.updatePersonLocally(updatedPerson)
_toastMessage.emit("اطلاعات شخص با موفقیت بروز شد.")
} }
} }
} }
@@ -84,7 +84,8 @@ sealed class PersonScreenEvent {
val phoneNumber: String?, val phoneNumber: String?,
val monthlyCommitment: Double = 0.0, val monthlyCommitment: Double = 0.0,
val startMonth: Int = 1, val startMonth: Int = 1,
val startYear: Int = 1403 val startYear: Int = 1403,
val isAnonymous: Boolean = false
) : PersonScreenEvent() ) : PersonScreenEvent()
data class DeletePerson(val personId: String) : PersonScreenEvent() data class DeletePerson(val personId: String) : PersonScreenEvent()
data class ArchivePerson(val personId: String) : PersonScreenEvent() data class ArchivePerson(val personId: String) : PersonScreenEvent()
+4 -4
View File
@@ -1,7 +1,7 @@
{ {
"versionCode": 5, "versionCode": 7,
"versionName": "1.0.4", "versionName": "1.0.6",
"url": "https://github.com/oqba26/MonthlyPaymentApp/releases/download/v1.0.3/MonthlyPaymentApp-v1.0.3.apk", "url": "https://github.com/oqba26/MonthlyPaymentApp/releases/download/v1.0.6/MonthlyPaymentApp-v1.0.6.apk",
"releaseNotes": "بهبود طراحی دیالوگ آپدیت و رفع مشکل برش خوردگی دکمه‌ها", "releaseNotes": "بهینه‌سازی کدهای UI، رفع هشدارهای کامپایلر در صفحات شخص و جزئیات شخص، و بهبود پایداری برنامه",
"isForceUpdate": false "isForceUpdate": false
} }