3 Commits
4 changed files with 205 additions and 143 deletions

No files matched your search

+2 -2
View File
@@ -30,8 +30,8 @@ android {
applicationId = "com.oqba26.monthlypaymentapp"
minSdk = 25
targetSdk = 34
versionCode = 9
versionName = "1.0.8"
versionCode = 11
versionName = "1.1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables { useSupportLibrary = true }
}
@@ -1117,39 +1117,22 @@ fun DashboardCard(
if (financialReportData.totalOverdueDebt > 0 || financialReportData.debtors.isNotEmpty()) {
HorizontalDivider(modifier = Modifier.padding(vertical = 2.dp))
Column(
Row(
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(4.dp)
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "🔴 مجموع بدهی‌های معوقه:",
style = MaterialTheme.typography.titleSmall,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.error
)
Text(
text = "${formatNumberAsPersian(financialReportData.totalOverdueDebt)} تومان",
style = MaterialTheme.typography.bodyMedium,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.error
)
}
val debtorNamesText = financialReportData.debtors.joinToString("، ") {
"${it.personName} (${formatNumberAsPersian(it.totalDebtAmount)})"
}
Text(
text = "بدهکاران (${financialReportData.totalDebtorsCount.toString().toPersianDigits()} نفر): $debtorNamesText",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
maxLines = 3,
overflow = TextOverflow.Ellipsis
text = "🔴 مجموع بدهی‌های معوقه:",
style = MaterialTheme.typography.titleSmall,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.error
)
Text(
text = "${formatNumberAsPersian(financialReportData.totalOverdueDebt)} تومان",
style = MaterialTheme.typography.bodyMedium,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.error
)
}
}
@@ -1185,7 +1168,7 @@ fun FinancialReportDialog(
Card(
modifier = Modifier
.fillMaxWidth()
.heightIn(max = 600.dp),
.heightIn(max = 650.dp),
shape = RoundedCornerShape(24.dp),
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface)
) {
@@ -1212,56 +1195,90 @@ fun FinancialReportDialog(
Spacer(modifier = Modifier.height(8.dp))
Card(
LazyColumn(
modifier = Modifier.fillMaxWidth(),
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.4f))
verticalArrangement = Arrangement.spacedBy(10.dp)
) {
Column(
modifier = Modifier.padding(12.dp),
verticalArrangement = Arrangement.spacedBy(6.dp)
) {
Row(
item {
Card(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.4f))
) {
Text("💰 کل درآمدهای تمام زمان‌ها:", style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.SemiBold)
Text("${formatNumberAsPersian(data.totalAllTimeIncome)} تومان", style = MaterialTheme.typography.bodyLarge, fontWeight = FontWeight.Bold)
}
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
Text("🔴 کل بدهی‌های معوقه فعلی:", style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.SemiBold, color = MaterialTheme.colorScheme.error)
Text("${formatNumberAsPersian(data.totalOverdueDebt)} تومان (${data.totalDebtorsCount.toString().toPersianDigits()} نفر)", style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.error)
Column(
modifier = Modifier.padding(12.dp),
verticalArrangement = Arrangement.spacedBy(6.dp)
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
Text("💰 کل درآمدهای تمام زمان‌ها:", style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.SemiBold)
Text("${formatNumberAsPersian(data.totalAllTimeIncome)} تومان", style = MaterialTheme.typography.bodyLarge, fontWeight = FontWeight.Bold)
}
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
Text("🔴 کل بدهی‌های معوقه فعلی:", style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.SemiBold, color = MaterialTheme.colorScheme.error)
Text("${formatNumberAsPersian(data.totalOverdueDebt)} تومان (${data.totalDebtorsCount.toString().toPersianDigits()} نفر)", style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.error)
}
}
}
}
}
Spacer(modifier = Modifier.height(12.dp))
HorizontalDivider()
Spacer(modifier = Modifier.height(8.dp))
Text(
text = "تفکیک درآمد ماهانه ماه‌های گذشته",
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(bottom = 8.dp)
)
if (data.monthlyReports.isEmpty()) {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(24.dp),
contentAlignment = Alignment.Center
) {
Text("هیچ دریافتی تا کنون ثبت نشده است.", style = MaterialTheme.typography.bodyMedium)
if (data.debtors.isNotEmpty()) {
item {
Card(
modifier = Modifier.fillMaxWidth(),
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.errorContainer.copy(alpha = 0.15f))
) {
Column(
modifier = Modifier.padding(12.dp),
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
Text(
text = "لیست بدهکاران (${data.totalDebtorsCount.toString().toPersianDigits()} نفر):",
style = MaterialTheme.typography.titleSmall,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.error
)
val debtorNamesText = data.debtors.joinToString("، ") {
"${it.personName} (${formatNumberAsPersian(it.totalDebtAmount)})"
}
Text(
text = debtorNamesText,
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
}
}
} else {
LazyColumn(
modifier = Modifier.weight(1f),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
item {
Column {
HorizontalDivider(modifier = Modifier.padding(vertical = 4.dp))
Text(
text = "تفکیک درآمد ماهانه ماه‌های گذشته",
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(top = 4.dp)
)
}
}
if (data.monthlyReports.isEmpty()) {
item {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(24.dp),
contentAlignment = Alignment.Center
) {
Text("هیچ دریافتی تا کنون ثبت نشده است.", style = MaterialTheme.typography.bodyMedium)
}
}
} else {
items(data.monthlyReports) { report ->
Card(
modifier = Modifier.fillMaxWidth(),
@@ -1308,15 +1325,6 @@ fun FinancialReportDialog(
}
}
}
Spacer(modifier = Modifier.height(12.dp))
TextButton(
onClick = onDismiss,
modifier = Modifier.align(Alignment.End)
) {
Text("بستن")
}
}
}
}
@@ -5,24 +5,32 @@ import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.AnimatedContent
import androidx.compose.foundation.clickable
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
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.fillMaxHeight
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.PaddingValues
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.Logout
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.AdminPanelSettings
import androidx.compose.material.icons.filled.ArrowDropDown
import androidx.compose.material.icons.filled.ContentCopy
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.Lock
import androidx.compose.material.icons.filled.LockOpen
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.input.PasswordVisualTransformation
@@ -38,6 +46,7 @@ import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
@@ -136,16 +145,18 @@ fun SettingsScreen(onLogout: () -> Unit) {
}
)
val scrollState = rememberScrollState()
Column(
modifier = Modifier
.padding(horizontal = 16.dp, vertical = 8.dp)
.fillMaxWidth()
.fillMaxHeight()
.fillMaxSize()
.verticalScroll(scrollState)
.padding(horizontal = 16.dp, vertical = 12.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
Text(
stringResource(R.string.settings),
style = MaterialTheme.typography.titleMedium,
modifier = Modifier.padding(bottom = 8.dp)
style = MaterialTheme.typography.titleMedium
)
DefaultAmountSetting(
@@ -163,16 +174,12 @@ fun SettingsScreen(onLogout: () -> Unit) {
onChangePin = { viewModel.setAdminPin(it) }
)
Spacer(Modifier.weight(1f))
val reminderDay by viewModel.reminderDay.collectAsState()
ReminderDaySetting(
currentDay = reminderDay,
onSave = { viewModel.saveReminderDay(it) }
)
Spacer(Modifier.weight(1f))
val cardNumbers by viewModel.cardNumbers.collectAsState()
CardNumbersSetting(
cardNumbers = cardNumbers,
@@ -181,20 +188,17 @@ fun SettingsScreen(onLogout: () -> Unit) {
onRemove = { viewModel.removeCardNumber(it) }
)
Spacer(Modifier.weight(1f))
FontSelectionDropdown(
selectedFont = selectedFont,
onFontSelected = { viewModel.onFontSelected(it) }
)
if (!isOperatorMode) {
HorizontalDivider(modifier = Modifier.padding(vertical = 16.dp))
HorizontalDivider(modifier = Modifier.padding(vertical = 4.dp))
Text(
"پشتیبان‌گیری و بازیابی",
style = MaterialTheme.typography.titleMedium,
modifier = Modifier.padding(bottom = 8.dp)
style = MaterialTheme.typography.titleMedium
)
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) {
@@ -224,24 +228,26 @@ fun SettingsScreen(onLogout: () -> Unit) {
viewModel.loadSnapshots()
showSnapshotDialog = true
},
modifier = Modifier.fillMaxWidth().padding(top = 8.dp)
modifier = Modifier.fillMaxWidth()
) {
Text(stringResource(R.string.auto_backup_button), fontSize = 13.sp)
}
}
Spacer(Modifier.weight(1f))
Spacer(Modifier.height(8.dp))
Button(
onClick = onLogout,
modifier = Modifier.fillMaxWidth(),
colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.error),
contentPadding = androidx.compose.foundation.layout.PaddingValues(vertical = 8.dp)
contentPadding = PaddingValues(vertical = 12.dp)
) {
Icon(Icons.AutoMirrored.Filled.Logout, contentDescription = null)
Spacer(Modifier.width(8.dp))
Text(stringResource(R.string.logout))
}
Spacer(Modifier.height(16.dp))
}
if (showSnapshotDialog) {
@@ -621,57 +627,105 @@ fun OperatorModeSetting(
Card(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(16.dp),
colors = CardDefaults.cardColors(
containerColor = if (isOperatorMode) MaterialTheme.colorScheme.errorContainer.copy(alpha = 0.2f) else MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.2f)
containerColor = if (isOperatorMode) MaterialTheme.colorScheme.errorContainer.copy(alpha = 0.15f) else MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.3f)
)
) {
Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
Row(verticalAlignment = Alignment.CenterVertically) {
Column(modifier = Modifier.weight(1f)) {
Text("حالت دسترسی برنامه", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold)
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier.fillMaxWidth()
) {
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(
imageVector = if (isOperatorMode) Icons.Default.Lock else Icons.Default.AdminPanelSettings,
contentDescription = null,
tint = if (isOperatorMode) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.primary,
modifier = Modifier.size(24.dp)
)
Spacer(modifier = Modifier.width(8.dp))
Text(
if (isOperatorMode) "وضعیت فعلی: حالت اپراتور (محدود)" else "وضعیت فعلی: حالت مدیریت (کامل)",
style = MaterialTheme.typography.bodySmall,
color = if (isOperatorMode) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.primary
text = "حالت دسترسی برنامه",
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold
)
}
Surface(
color = if (isOperatorMode) MaterialTheme.colorScheme.errorContainer else MaterialTheme.colorScheme.primaryContainer,
shape = RoundedCornerShape(12.dp)
) {
Text(
text = if (isOperatorMode) "اپراتور (محدود)" else "مدیریت (کامل)",
modifier = Modifier.padding(horizontal = 10.dp, vertical = 4.dp),
style = MaterialTheme.typography.labelMedium,
fontWeight = FontWeight.Bold,
color = if (isOperatorMode) MaterialTheme.colorScheme.onErrorContainer else MaterialTheme.colorScheme.onPrimaryContainer
)
}
}
Row(horizontalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.fillMaxWidth()) {
if (isOperatorMode) {
Button(
onClick = {
pinAction = PinAction.UNLOCK_ADMIN
enteredPin = ""
errorMessage = ""
showPinDialog = true
},
modifier = Modifier.fillMaxWidth()
) {
Text("خروج از حالت اپراتور")
}
} else {
Button(
onClick = {
onToggleMode(true)
},
modifier = Modifier.weight(1f)
) {
Text("فعال‌سازی حالت اپراتور")
}
HorizontalDivider(thickness = 0.5.dp)
OutlinedButton(
onClick = {
if (isOperatorMode) {
Button(
onClick = {
pinAction = PinAction.UNLOCK_ADMIN
enteredPin = ""
errorMessage = ""
showPinDialog = true
},
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(10.dp),
colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.primary)
) {
Icon(Icons.Default.LockOpen, contentDescription = null, modifier = Modifier.size(18.dp))
Spacer(modifier = Modifier.width(8.dp))
Text("خروج از حالت اپراتور (ورود رمز مدیر)", style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.Bold)
}
} else {
OutlinedButton(
onClick = { onToggleMode(true) },
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(10.dp)
) {
Icon(Icons.Default.Lock, contentDescription = null, modifier = Modifier.size(18.dp))
Spacer(modifier = Modifier.width(8.dp))
Text("فعال‌سازی حالت اپراتور (محدودسازی)", style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.Bold)
}
Row(
modifier = Modifier
.fillMaxWidth()
.clickable {
pinAction = PinAction.CHANGE_PIN
enteredPin = ""
newPinInput = ""
errorMessage = ""
showPinDialog = true
},
modifier = Modifier.weight(1f)
) {
Text("تغییر رمز مدیر")
}
}
.padding(vertical = 4.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center
) {
Icon(
imageVector = Icons.Default.Edit,
contentDescription = null,
tint = MaterialTheme.colorScheme.primary,
modifier = Modifier.size(18.dp)
)
Spacer(modifier = Modifier.width(6.dp))
Text(
text = "تغییر رمز عبور مدیریت",
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.primary,
fontWeight = FontWeight.SemiBold
)
}
}
}
+4 -4
View File
@@ -1,7 +1,7 @@
{
"versionCode": 9,
"versionName": "1.0.8",
"url": "https://github.com/oqba26/MonthlyPaymentApp/releases/download/v1.0.8/MonthlyPaymentApp-v1.0.8.apk",
"releaseNotes": "بهبودهای تب مسجد و افزودن حالت اپراتور (محدودسازی دسترسی)",
"versionCode": 11,
"versionName": "1.1.0",
"url": "https://github.com/oqba26/MonthlyPaymentApp/releases/download/v1.1.0/MonthlyPaymentApp-v1.1.0.apk",
"releaseNotes": "اصلاح اسکرول یکپارچه دیالوگ گزارش مالی و نمایش کامل تفکیک ماه‌های گذشته",
"isForceUpdate": false
}