3 Commits
7 changed files with 98 additions and 39 deletions

No files matched your search

+27
View File
@@ -46,3 +46,30 @@ jobs:
files: app/build/outputs/apk/release/MonthlyPaymentApp-${{ github.ref_name }}.apk files: app/build/outputs/apk/release/MonthlyPaymentApp-${{ github.ref_name }}.apk
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Update update.json after release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
TAG_NAME="${{ github.ref_name }}"
VERSION_NAME="${TAG_NAME#v}"
VERSION_CODE=$(grep 'versionCode' app/build.gradle.kts | head -n 1 | awk '{print $3}')
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git fetch origin main
git checkout main
cat <<EOF > update.json
{
"versionCode": ${VERSION_CODE},
"versionName": "${VERSION_NAME}",
"url": "https://github.com/oqba26/MonthlyPaymentApp/releases/download/${TAG_NAME}/MonthlyPaymentApp-${TAG_NAME}.apk",
"releaseNotes": "بهبودهای پایداری و عملکرد همگام‌سازی",
"isForceUpdate": false
}
EOF
git add update.json
git commit -m "auto-update update.json for release ${TAG_NAME}" || true
git push origin main
+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 = 12 versionCode = 14
versionName = "1.1.1" versionName = "1.1.3"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables { useSupportLibrary = true } vectorDrawables { useSupportLibrary = true }
} }
@@ -30,7 +30,6 @@ import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.Message import androidx.compose.material.icons.automirrored.filled.Message
import androidx.compose.material.icons.filled.Archive import androidx.compose.material.icons.filled.Archive
import androidx.compose.material.icons.filled.Assessment
import androidx.compose.material.icons.filled.Check import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Sync import androidx.compose.material.icons.filled.Sync
@@ -117,7 +116,6 @@ fun PersonScreen(
val dashboardData by viewModel.dashboardData.collectAsState() val dashboardData by viewModel.dashboardData.collectAsState()
val financialReportData by viewModel.financialReportData.collectAsState() val financialReportData by viewModel.financialReportData.collectAsState()
val showFinancialReportDialog by viewModel.showFinancialReportDialog.collectAsState() val showFinancialReportDialog by viewModel.showFinancialReportDialog.collectAsState()
val isOperatorMode by viewModel.isOperatorMode.collectAsState()
val defaultPaymentAmount by viewModel.defaultPaymentAmountFlow.collectAsState(initial = 0.0) val defaultPaymentAmount by viewModel.defaultPaymentAmountFlow.collectAsState(initial = 0.0)
val cardNumbers by viewModel.cardNumbersFlow.collectAsState(initial = emptyList()) val cardNumbers by viewModel.cardNumbersFlow.collectAsState(initial = emptyList())
val personForPaymentDialog by viewModel.personForPaymentDialog.collectAsState() val personForPaymentDialog by viewModel.personForPaymentDialog.collectAsState()
@@ -188,8 +186,7 @@ fun PersonScreen(
DashboardCard( DashboardCard(
data = dashboardData, data = dashboardData,
financialReportData = financialReportData, financialReportData = financialReportData,
isOperatorMode = isOperatorMode, isMosqueCategory = (currentCategory == "mosque")
onOpenReportClick = { viewModel.onShowFinancialReport() }
) )
OutlinedTextField( OutlinedTextField(
@@ -582,7 +579,7 @@ fun AddNewPersonDialog(
val currentShamsiYear = getCurrentShamsiYear() val currentShamsiYear = getCurrentShamsiYear()
val currentShamsiMonth = getCurrentShamsiMonth() val currentShamsiMonth = getCurrentShamsiMonth()
var startMonth by remember { mutableIntStateOf(currentShamsiMonth) } var startMonth by remember { mutableIntStateOf(if (isMosqueCategory) currentShamsiMonth else 1) }
var startYear by remember { mutableIntStateOf(currentShamsiYear) } var startYear by remember { mutableIntStateOf(currentShamsiYear) }
var similarContacts by remember { mutableStateOf<List<ContactMatch>>(emptyList()) } var similarContacts by remember { mutableStateOf<List<ContactMatch>>(emptyList()) }
@@ -1072,8 +1069,7 @@ fun SelectCardDialog(
fun DashboardCard( fun DashboardCard(
data: DashboardUiModel, data: DashboardUiModel,
financialReportData: FinancialReportData, financialReportData: FinancialReportData,
isOperatorMode: Boolean = false, isMosqueCategory: Boolean = false
onOpenReportClick: () -> Unit
) { ) {
val animatedProgress by animateFloatAsState( val animatedProgress by animateFloatAsState(
targetValue = data.progress, targetValue = data.progress,
@@ -1110,10 +1106,27 @@ fun DashboardCard(
horizontalArrangement = Arrangement.SpaceBetween, horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
Text("مجموع درآمد ماه جاری", style = MaterialTheme.typography.titleSmall, fontWeight = FontWeight.Bold) Text(
text = if (isMosqueCategory) "کمک‌های جمع‌آوری شده ماه جاری" else "حقوق جمع‌آوری شده ماه جاری",
style = MaterialTheme.typography.titleSmall,
fontWeight = FontWeight.Bold
)
Text("${formatNumberAsPersian(data.totalIncome)} تومان", style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.Bold) Text("${formatNumberAsPersian(data.totalIncome)} تومان", style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.Bold)
} }
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = if (isMosqueCategory) "مجموع کمک‌های جمع‌آوری شده دوره جاری" else "مجموع حقوق جمع‌آوری شده دوره جاری",
style = MaterialTheme.typography.titleSmall,
fontWeight = FontWeight.Bold
)
Text("${formatNumberAsPersian(data.totalCycleIncome)} تومان", style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.Bold)
}
if (financialReportData.totalOverdueDebt > 0 || financialReportData.debtors.isNotEmpty()) { if (financialReportData.totalOverdueDebt > 0 || financialReportData.debtors.isNotEmpty()) {
HorizontalDivider(modifier = Modifier.padding(vertical = 2.dp)) HorizontalDivider(modifier = Modifier.padding(vertical = 2.dp))
@@ -1136,24 +1149,6 @@ fun DashboardCard(
) )
} }
} }
if (!isOperatorMode) {
OutlinedButton(
onClick = onOpenReportClick,
modifier = Modifier
.fillMaxWidth()
.padding(top = 2.dp),
shape = MaterialTheme.shapes.medium
) {
Icon(
imageVector = Icons.Default.Assessment,
contentDescription = null,
modifier = Modifier.size(18.dp)
)
Spacer(modifier = Modifier.width(6.dp))
Text("مشاهده گزارش کامل مالی و درآمدهای گذشته", style = MaterialTheme.typography.labelLarge)
}
}
} }
} }
} }
@@ -30,3 +30,32 @@ fun formatTimestampToPersianDateTime(timestamp: Long): String {
val time = formatTimestampToPersianTime(timestamp) val time = formatTimestampToPersianTime(timestamp)
return "$date، ساعت $time" return "$date، ساعت $time"
} }
fun isTimestampInCurrentCycle(timestamp: Long): Boolean {
val now = PersianDate()
val curYear = now.shYear
val curMonth = now.shMonth
val curDay = now.shDay
val (startYear, startMonth) = if (curDay >= 20) {
curYear to curMonth
} else {
if (curMonth == 1) (curYear - 1) to 12 else curYear to (curMonth - 1)
}
val (endYear, endMonth) = if (startMonth == 12) {
(startYear + 1) to 1
} else {
startYear to (startMonth + 1)
}
val recordDate = PersianDate(Date(timestamp))
val rYear = recordDate.shYear
val rMonth = recordDate.shMonth
val rDay = recordDate.shDay
val isFirstHalf = (rYear == startYear && rMonth == startMonth && rDay >= 20)
val isSecondHalf = (rYear == endYear && rMonth == endMonth && rDay < 20)
return isFirstHalf || isSecondHalf
}
@@ -14,6 +14,7 @@ import com.oqba26.monthlypaymentapp.utils.getCurrentShamsiDay
import com.oqba26.monthlypaymentapp.utils.getCurrentShamsiMonth import com.oqba26.monthlypaymentapp.utils.getCurrentShamsiMonth
import com.oqba26.monthlypaymentapp.utils.getCurrentShamsiYear import com.oqba26.monthlypaymentapp.utils.getCurrentShamsiYear
import com.oqba26.monthlypaymentapp.utils.getPersianMonthName import com.oqba26.monthlypaymentapp.utils.getPersianMonthName
import com.oqba26.monthlypaymentapp.utils.isTimestampInCurrentCycle
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
@@ -69,9 +70,6 @@ class PersonViewModel @Inject constructor(
private val _infoMessage = MutableSharedFlow<String>() private val _infoMessage = MutableSharedFlow<String>()
val infoMessage = _infoMessage.asSharedFlow() val infoMessage = _infoMessage.asSharedFlow()
val isOperatorMode: StateFlow<Boolean> = settingsRepository.isOperatorModeFlow
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000L), false)
private val _uiState = MutableStateFlow(PersonListUiState()) private val _uiState = MutableStateFlow(PersonListUiState())
val uiState: StateFlow<PersonListUiState> = _uiState.asStateFlow() val uiState: StateFlow<PersonListUiState> = _uiState.asStateFlow()
@@ -248,13 +246,13 @@ class PersonViewModel @Inject constructor(
) )
} }
updateDashboard(uiModels.size, relevantPayments) val categoryPayments = allPayments.filter { it.category == category }
updateDashboard(uiModels.size, relevantPayments, categoryPayments)
val (paidModels, unpaidModels) = uiModels.partition { it.hasPaidThisMonth } val (paidModels, unpaidModels) = uiModels.partition { it.hasPaidThisMonth }
// محاسبه گزارشات مالی جامع // محاسبه گزارشات مالی جامع
val personsMap = persons.associateBy { it.id } val personsMap = persons.associateBy { it.id }
val categoryPayments = allPayments.filter { it.category == category }
val totalAllTimeIncome = categoryPayments.sumOf { it.amount } val totalAllTimeIncome = categoryPayments.sumOf { it.amount }
val debtorsList = unpaidModels.filter { it.debtCount > 0 }.map { personModel -> val debtorsList = unpaidModels.filter { it.debtCount > 0 }.map { personModel ->
@@ -590,11 +588,20 @@ class PersonViewModel @Inject constructor(
exportManager.shareReceipt(payment, personName) exportManager.shareReceipt(payment, personName)
} }
private fun updateDashboard(totalPersonCount: Int, paymentsThisMonth: List<PaymentRecord>) { private fun updateDashboard(
totalPersonCount: Int,
paymentsThisMonth: List<PaymentRecord>,
categoryPayments: List<PaymentRecord>
) {
val paidCount = paymentsThisMonth.map { it.personId }.distinct().size val paidCount = paymentsThisMonth.map { it.personId }.distinct().size
val totalIncome = paymentsThisMonth.sumOf { it.amount } val totalIncome = paymentsThisMonth.sumOf { it.amount }
val cyclePayments = categoryPayments.filter { payment ->
val time = payment.createdAt ?: payment.timestamp
isTimestampInCurrentCycle(time)
}
val totalCycleIncome = cyclePayments.sumOf { it.amount }
val progress = if (totalPersonCount > 0) paidCount.toFloat() / totalPersonCount.toFloat() else 0f val progress = if (totalPersonCount > 0) paidCount.toFloat() / totalPersonCount.toFloat() else 0f
_dashboardData.value = DashboardUiModel(paidCount, totalPersonCount, totalIncome, progress) _dashboardData.value = DashboardUiModel(paidCount, totalPersonCount, totalIncome, totalCycleIncome, progress)
} }
private fun createPaymentRecord(personId: String, month: Int, year: Int, amount: Double, description: String): PaymentRecord { private fun createPaymentRecord(personId: String, month: Int, year: Int, amount: Double, description: String): PaymentRecord {
@@ -143,6 +143,7 @@ data class DashboardUiModel(
val paidCount: Int = 0, val paidCount: Int = 0,
val totalCount: Int = 0, val totalCount: Int = 0,
val totalIncome: Double = 0.0, val totalIncome: Double = 0.0,
val totalCycleIncome: Double = 0.0,
val progress: Float = 0f val progress: Float = 0f
) )
+4 -4
View File
@@ -1,7 +1,7 @@
{ {
"versionCode": 12, "versionCode": 13,
"versionName": "1.1.1", "versionName": "1.1.2",
"url": "https://github.com/oqba26/MonthlyPaymentApp/releases/download/v1.1.1/MonthlyPaymentApp-v1.1.1.apk", "url": "https://github.com/oqba26/MonthlyPaymentApp/releases/download/v1.1.2/MonthlyPaymentApp-v1.1.2.apk",
"releaseNotes": "بهبودهای همگام‌سازی شبکه و پایداری لایو PocketBase", "releaseNotes": "بهبودهای پایداری و عملکرد همگام‌سازی",
"isForceUpdate": false "isForceUpdate": false
} }