From 271e9ad11747fa8efe2248685a28480c1b192362 Mon Sep 17 00:00:00 2001 From: oqba26 Date: Sun, 2 Aug 2026 10:36:39 +0330 Subject: [PATCH] =?UTF-8?q?=D8=A7=D8=B5=D9=84=D8=A7=D8=AD=20=D9=85=D9=87?= =?UTF-8?q?=D8=A7=D8=AC=D8=B1=D8=AA=20=D8=AF=DB=8C=D8=AA=D8=A7=D8=A8=DB=8C?= =?UTF-8?q?=D8=B3=D8=8C=20=D9=85=D8=B1=D8=AA=D8=A8=E2=80=8C=D8=B3=D8=A7?= =?UTF-8?q?=D8=B2=DB=8C=20=D8=AA=D8=A8=E2=80=8C=D9=87=D8=A7=20=D9=88=20?= =?UTF-8?q?=D8=A8=D9=87=E2=80=8C=D8=B1=D9=88=D8=B2=D8=B1=D8=B3=D8=A7=D9=86?= =?UTF-8?q?=DB=8C=20=D8=B9=D9=86=D8=A7=D9=88=DB=8C=D9=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - رفع تداخل در مهاجرت دیتابیس Room با اصلاح بازه fallbackToDestructiveMigrationFrom. - حذف فایل MigrationTest.kt جهت پاک‌سازی پروژه. - تغییر ترتیب تب‌ها: انتقال تب 'آرشیو' به کنار تب‌های 'پرداخت شده' و 'پرداخت نشده'. - اصلاح عناوین TopAppBar: نمایش عناوین اختصاصی برای صفحات 'آرشیو'، 'تنظیمات' و 'مسجد' به جای عنوان عمومی. --- .gitignore | 37 +- .idea/planningMode.xml | 11 - .../results.bin | 1 - .../persiandate/PersianDate$Dialect.dex | Bin 2080 -> 0 bytes .../saman/zamani/persiandate/PersianDate.dex | Bin 23420 -> 0 bytes .../persiandate/PersianDateFormat$1.dex | Bin 1616 -> 0 bytes ...nDateFormat$PersianDateNumberCharacter.dex | Bin 2032 -> 0 bytes .../zamani/persiandate/PersianDateFormat.dex | Bin 8372 -> 0 bytes .../desugar_graph.bin | Bin 1081 -> 0 bytes .../results.bin | 1 - .../classes/classes_dex/classes.dex | Bin 31688 -> 0 bytes .../results.bin | 1 - .../classes/classes_dex/classes.dex | Bin 31688 -> 0 bytes .../results.bin | 1 - .../persiandate/PersianDate$Dialect.dex | Bin 2076 -> 0 bytes .../saman/zamani/persiandate/PersianDate.dex | Bin 23420 -> 0 bytes .../persiandate/PersianDateFormat$1.dex | Bin 1616 -> 0 bytes ...nDateFormat$PersianDateNumberCharacter.dex | Bin 2032 -> 0 bytes .../zamani/persiandate/PersianDateFormat.dex | Bin 8372 -> 0 bytes .../desugar_graph.bin | Bin 931 -> 0 bytes .../aapt/AndroidManifest.xml | 10 - .../aapt/output-metadata.json | 18 - .../aar-metadata.properties | 6 - .../annotationProcessors.json | 1 - .../bundleLibCompileToJarDebug/classes.jar | Bin 43885 -> 0 bytes .../debug/generateDebugRFile/R.jar | Bin 888 -> 0 bytes .../debug/generateDebugRFile/R.txt | 1 - .../compile-file-map.properties | 1 - .../merged.dir/values/values.xml | 4 - .../debug/packageDebugResources/merger.xml | 2 - .../mergeDebugJniLibFolders/merger.xml | 2 - .../incremental/mergeDebugShaders/merger.xml | 2 - .../incremental/packageDebugAssets/merger.xml | 2 - .../persiandate/PersianDate$Dialect.class | Bin 1454 -> 0 bytes .../zamani/persiandate/PersianDate.class | Bin 28856 -> 0 bytes .../persiandate/PersianDateFormat$1.class | Bin 975 -> 0 bytes ...ateFormat$PersianDateNumberCharacter.class | Bin 1472 -> 0 bytes .../persiandate/PersianDateFormat.class | Bin 9014 -> 0 bytes .../debug/parseDebugLocalResources/R-def.txt | 3 - .../manifest-merger-blame-debug-report.txt | 12 - .../processDebugManifest/AndroidManifest.xml | 10 - .../extractDeepLinksDebug/navigation.json | 1 - .../nestedResourcesValidationReport.txt | 1 - .../packageDebugResources/values/values.xml | 4 - .../persiandate/PersianDate$Dialect.class | Bin 1454 -> 0 bytes .../zamani/persiandate/PersianDate.class | Bin 28856 -> 0 bytes .../persiandate/PersianDateFormat$1.class | Bin 975 -> 0 bytes ...ateFormat$PersianDateNumberCharacter.class | Bin 1472 -> 0 bytes .../persiandate/PersianDateFormat.class | Bin 9014 -> 0 bytes .../bundleLibRuntimeToJarDebug/classes.jar | Bin 43019 -> 0 bytes .../generateDebugRFile/package-aware-r.txt | 2 - .../logs/manifest-merger-debug-report.txt | 20 - .../previous-compilation-data.bin | Bin 13334 -> 0 bytes app/build.gradle.kts | 12 + .../18.json | 230 ++++++++++ .../oqba26/monthlypaymentapp/MainActivity.kt | 52 ++- .../core/PaymentApplication.kt | 54 ++- .../core/di/DatabaseModule.kt | 20 +- .../core/di/RepositoryModule.kt | 3 +- .../core/manager/BackupManager.kt | 147 ++++++ .../monthlypaymentapp/data/dao/PaymentDao.kt | 7 + .../monthlypaymentapp/data/dao/PersonDao.kt | 7 + .../data/dao/SyncQueueDao.kt | 36 ++ .../data/database/AppDatabase.kt | 61 +-- .../data/database/Migrations.kt | 33 ++ .../data/model/PaymentRecord.kt | 7 +- .../monthlypaymentapp/data/model/Person.kt | 15 +- .../monthlypaymentapp/data/model/SyncQueue.kt | 28 ++ .../data/remote/ApiClient.kt | 6 +- .../data/repository/LocalPersonRepository.kt | 165 +++++-- .../data/repository/NetworkRepository.kt | 156 +++++-- .../data/repository/SettingsRepository.kt | 11 + .../data/sync/SyncManager.kt | 276 +++++++++++ .../monthlypaymentapp/data/sync/SyncMerger.kt | 105 +++++ .../ui/screens/ArchiveScreen.kt | 61 +-- .../ui/screens/AuthScreen.kt | 7 + .../ui/screens/PersonDetailScreen.kt | 199 ++++++-- .../ui/screens/PersonScreen.kt | 429 +++++++++++++----- .../ui/screens/SettingsScreen.kt | 186 +++++++- .../ui/screens/SharedComponents.kt | 106 ++++- .../viewmodel/ContactViewModel.kt | 21 +- .../viewmodel/PersonViewModel.kt | 258 ++++++++--- .../viewmodel/SettingsViewModel.kt | 29 +- .../monthlypaymentapp/viewmodel/UiModels.kt | 33 +- app/src/main/res/values/strings.xml | 13 + .../data/sync/SyncMergerTest.kt | 202 +++++++++ gradle/libs.versions.toml | 2 + update.json | 2 +- 88 files changed, 2565 insertions(+), 568 deletions(-) delete mode 100644 .idea/planningMode.xml delete mode 100644 PersianDate/build/.transforms/804ae9461bc36181bb932ccf21e80d26/results.bin delete mode 100644 PersianDate/build/.transforms/804ae9461bc36181bb932ccf21e80d26/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/saman/zamani/persiandate/PersianDate$Dialect.dex delete mode 100644 PersianDate/build/.transforms/804ae9461bc36181bb932ccf21e80d26/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/saman/zamani/persiandate/PersianDate.dex delete mode 100644 PersianDate/build/.transforms/804ae9461bc36181bb932ccf21e80d26/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/saman/zamani/persiandate/PersianDateFormat$1.dex delete mode 100644 PersianDate/build/.transforms/804ae9461bc36181bb932ccf21e80d26/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/saman/zamani/persiandate/PersianDateFormat$PersianDateNumberCharacter.dex delete mode 100644 PersianDate/build/.transforms/804ae9461bc36181bb932ccf21e80d26/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/saman/zamani/persiandate/PersianDateFormat.dex delete mode 100644 PersianDate/build/.transforms/804ae9461bc36181bb932ccf21e80d26/transformed/bundleLibRuntimeToDirDebug/desugar_graph.bin delete mode 100644 PersianDate/build/.transforms/91dd66fd99e1bf96d3a5dda255469897/results.bin delete mode 100644 PersianDate/build/.transforms/91dd66fd99e1bf96d3a5dda255469897/transformed/classes/classes_dex/classes.dex delete mode 100644 PersianDate/build/.transforms/99c70c53220c636dd34a74b4e5874025/results.bin delete mode 100644 PersianDate/build/.transforms/99c70c53220c636dd34a74b4e5874025/transformed/classes/classes_dex/classes.dex delete mode 100644 PersianDate/build/.transforms/f7fb0ddd5afd91b957e9024aed94e90b/results.bin delete mode 100644 PersianDate/build/.transforms/f7fb0ddd5afd91b957e9024aed94e90b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/saman/zamani/persiandate/PersianDate$Dialect.dex delete mode 100644 PersianDate/build/.transforms/f7fb0ddd5afd91b957e9024aed94e90b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/saman/zamani/persiandate/PersianDate.dex delete mode 100644 PersianDate/build/.transforms/f7fb0ddd5afd91b957e9024aed94e90b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/saman/zamani/persiandate/PersianDateFormat$1.dex delete mode 100644 PersianDate/build/.transforms/f7fb0ddd5afd91b957e9024aed94e90b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/saman/zamani/persiandate/PersianDateFormat$PersianDateNumberCharacter.dex delete mode 100644 PersianDate/build/.transforms/f7fb0ddd5afd91b957e9024aed94e90b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/saman/zamani/persiandate/PersianDateFormat.dex delete mode 100644 PersianDate/build/.transforms/f7fb0ddd5afd91b957e9024aed94e90b/transformed/bundleLibRuntimeToDirDebug/desugar_graph.bin delete mode 100644 PersianDate/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/AndroidManifest.xml delete mode 100644 PersianDate/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/output-metadata.json delete mode 100644 PersianDate/build/intermediates/aar_metadata/debug/writeDebugAarMetadata/aar-metadata.properties delete mode 100644 PersianDate/build/intermediates/annotation_processor_list/debug/javaPreCompileDebug/annotationProcessors.json delete mode 100644 PersianDate/build/intermediates/compile_library_classes_jar/debug/bundleLibCompileToJarDebug/classes.jar delete mode 100644 PersianDate/build/intermediates/compile_r_class_jar/debug/generateDebugRFile/R.jar delete mode 100644 PersianDate/build/intermediates/compile_symbol_list/debug/generateDebugRFile/R.txt delete mode 100644 PersianDate/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties delete mode 100644 PersianDate/build/intermediates/incremental/debug/packageDebugResources/merged.dir/values/values.xml delete mode 100644 PersianDate/build/intermediates/incremental/debug/packageDebugResources/merger.xml delete mode 100644 PersianDate/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml delete mode 100644 PersianDate/build/intermediates/incremental/mergeDebugShaders/merger.xml delete mode 100644 PersianDate/build/intermediates/incremental/packageDebugAssets/merger.xml delete mode 100644 PersianDate/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/saman/zamani/persiandate/PersianDate$Dialect.class delete mode 100644 PersianDate/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/saman/zamani/persiandate/PersianDate.class delete mode 100644 PersianDate/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/saman/zamani/persiandate/PersianDateFormat$1.class delete mode 100644 PersianDate/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/saman/zamani/persiandate/PersianDateFormat$PersianDateNumberCharacter.class delete mode 100644 PersianDate/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/saman/zamani/persiandate/PersianDateFormat.class delete mode 100644 PersianDate/build/intermediates/local_only_symbol_list/debug/parseDebugLocalResources/R-def.txt delete mode 100644 PersianDate/build/intermediates/manifest_merge_blame_file/debug/processDebugManifest/manifest-merger-blame-debug-report.txt delete mode 100644 PersianDate/build/intermediates/merged_manifest/debug/processDebugManifest/AndroidManifest.xml delete mode 100644 PersianDate/build/intermediates/navigation_json/debug/extractDeepLinksDebug/navigation.json delete mode 100644 PersianDate/build/intermediates/nested_resources_validation_report/debug/generateDebugResources/nestedResourcesValidationReport.txt delete mode 100644 PersianDate/build/intermediates/packaged_res/debug/packageDebugResources/values/values.xml delete mode 100644 PersianDate/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/saman/zamani/persiandate/PersianDate$Dialect.class delete mode 100644 PersianDate/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/saman/zamani/persiandate/PersianDate.class delete mode 100644 PersianDate/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/saman/zamani/persiandate/PersianDateFormat$1.class delete mode 100644 PersianDate/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/saman/zamani/persiandate/PersianDateFormat$PersianDateNumberCharacter.class delete mode 100644 PersianDate/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/saman/zamani/persiandate/PersianDateFormat.class delete mode 100644 PersianDate/build/intermediates/runtime_library_classes_jar/debug/bundleLibRuntimeToJarDebug/classes.jar delete mode 100644 PersianDate/build/intermediates/symbol_list_with_package_name/debug/generateDebugRFile/package-aware-r.txt delete mode 100644 PersianDate/build/outputs/logs/manifest-merger-debug-report.txt delete mode 100644 PersianDate/build/tmp/compileDebugJavaWithJavac/previous-compilation-data.bin create mode 100644 app/schemas/com.oqba26.monthlypaymentapp.data.database.AppDatabase/18.json create mode 100644 app/src/main/java/com/oqba26/monthlypaymentapp/core/manager/BackupManager.kt create mode 100644 app/src/main/java/com/oqba26/monthlypaymentapp/data/dao/SyncQueueDao.kt create mode 100644 app/src/main/java/com/oqba26/monthlypaymentapp/data/database/Migrations.kt create mode 100644 app/src/main/java/com/oqba26/monthlypaymentapp/data/model/SyncQueue.kt create mode 100644 app/src/main/java/com/oqba26/monthlypaymentapp/data/sync/SyncManager.kt create mode 100644 app/src/main/java/com/oqba26/monthlypaymentapp/data/sync/SyncMerger.kt create mode 100644 app/src/test/java/com/oqba26/monthlypaymentapp/data/sync/SyncMergerTest.kt diff --git a/.gitignore b/.gitignore index 0b7c68d..7c9bb02 100644 --- a/.gitignore +++ b/.gitignore @@ -1,17 +1,28 @@ +# Gradle files +.gradle/ +build/ + +# IntelliJ IDEA files +.idea/ *.iml -.gradle -/local.properties -/.idea/caches -/.idea/libraries -/.idea/modules.xml -/.idea/workspace.xml -/.idea/navEditor.xml -/.idea/assetWizardSettings.xml -.DS_Store -/build -/captures -.externalNativeBuild -.cxx + +# Local configuration local.properties keystore.properties + +# Android Studio captures +captures/ + +# External native builds +.externalNativeBuild/ +.cxx/ + +# Key store files *.jks +*.keystore + +# Assistant Artifacts +.artifacts/ + +# System files +.DS_Store diff --git a/.idea/planningMode.xml b/.idea/planningMode.xml deleted file mode 100644 index 9192952..0000000 --- a/.idea/planningMode.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/PersianDate/build/.transforms/804ae9461bc36181bb932ccf21e80d26/results.bin b/PersianDate/build/.transforms/804ae9461bc36181bb932ccf21e80d26/results.bin deleted file mode 100644 index 7ed749e..0000000 --- a/PersianDate/build/.transforms/804ae9461bc36181bb932ccf21e80d26/results.bin +++ /dev/null @@ -1 +0,0 @@ -o/bundleLibRuntimeToDirDebug diff --git a/PersianDate/build/.transforms/804ae9461bc36181bb932ccf21e80d26/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/saman/zamani/persiandate/PersianDate$Dialect.dex b/PersianDate/build/.transforms/804ae9461bc36181bb932ccf21e80d26/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/saman/zamani/persiandate/PersianDate$Dialect.dex deleted file mode 100644 index 462def91ca38c2ad237958f01ee330952caa6071..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2080 zcmb7_&u<$=6vyA}uI)HUgJT+;1~)NVQvyot;3RDtM^ysO4-1n3Xs8K*dst`R?0;xfWUxHgDPDnjasYpdasyOlofW)2NIq-e!wFp%XW$e$q**EXK znR&B2qZ_;JBcm7TiKRYX-gvP0$2&J41wP$7z4zt)*XMh_Iv;Bzsu9s{ek=^(mbC!U zAX=|%4Zi74E2z&$Xf$zb6@Dq3h zegVINC!kv)x(M>X1pDAN_!|5G{sgCdL_OdY@ES;h_do?W;0Aaf+yZyN5m>__G69HO@BPn zi(m?q|D6|uBEb$@Mk5Hu>xiPh9uvq-Ck>#@`9a!T#$m&GE5M^51QjHi3bH~4#Ygeg zP70+uU3-2X7`Eq_1z?3ZV;r+-!*gdYp}GXA!m>}aQCO;tC=`-RjM3EjDI9L;gr{q# zu-ekep00z!sg_RhbbSQCut^?#HBW6*#j;JY!n@8o69cF=dvpcB(fC9F3&7xx%;iF%NbXhBPo|7IfqKk=~{Wm z+)iqiRdqDStXj!gt5~kurd4{gtl4&&`cJUQT9#4w`r{|`zil|1ReedTYZb#W>WFgo z1gjOZWNA*LZltL@x25f9$+BjZl3v`%@l=?*<2}XVfE?%=vJIM7Luk! z&ekfLmAt{VnXF+-re^7yVmO+YzJd%%V=-gL=0 zmaa~!y0NPcsl{rgW|rN(9j;V$!|f-Q)Tp}yOr{bei81xvA$3!;hl`s=aocWG z5Xz}Hj3KpRTEkk+R3}s8L+TDEzG`7Q6N#}z>VkTcrVkDd_SOHFF!&q##D($ER1py* zSsI9n3>}gh75(m$6sHjWYEDk733#JS#fNs5244e11Nj zK8uN?L-91yN3n;BxOJ#};u9YnDtG-N__-p2fAROu-|!bW2l*Sm2l)W-dGIsjWAGao z@(y~o;lh3~==e<{P&(-hg`TXaJvkl+#{{X>V Bb0Po$ diff --git a/PersianDate/build/.transforms/804ae9461bc36181bb932ccf21e80d26/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/saman/zamani/persiandate/PersianDate.dex b/PersianDate/build/.transforms/804ae9461bc36181bb932ccf21e80d26/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/saman/zamani/persiandate/PersianDate.dex deleted file mode 100644 index 5a596c6af938767babca390dfd1d4f7e571c8831..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23420 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 diff --git a/PersianDate/build/.transforms/804ae9461bc36181bb932ccf21e80d26/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/saman/zamani/persiandate/PersianDateFormat$PersianDateNumberCharacter.dex b/PersianDate/build/.transforms/804ae9461bc36181bb932ccf21e80d26/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/saman/zamani/persiandate/PersianDateFormat$PersianDateNumberCharacter.dex deleted file mode 100644 index ffa3b13126ce897df184c053ae8a594cd527f2d5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2032 zcmb`IO>7%Q6vyB0`lC)vaZFRU!AYEzBL(ExCZuh!qmqgwZG`(FQjAaq(Rg>`Y+~;^ zyXz=Wh%DfM!Uc&7AC-_m2!W80;J}d{xb(oO5<;TKBK3e!4;*>`iT@irB2*DlB}V(( zH~Z$jH}B1?W=w0lulVFCD(|koIZ?gy?7P|v@%rbbU#pw%ef`bl%kSKc619kEyE>hO zkSzz@IC`vq2!s@(2;>Z;2HAl;1o=KB9{L%tdtm_ihf4Gph`_KP41!@W1|A1bfN5|F zl))S@!CT-;1@8Cd8fcr;AyZ3&Vwp=8H^!u&NU9W zmc!s-kN^YV5pV*q|sE^ zAr<4cJn4+;|Zxn>%xPb_4*C{MGM#J@Or~XVs z_E!%ZR1Z~eM(?DfiY>7fcIQ2p= zZepKm!D%;XbPwBD!=VQ3n|&cF-2e%E_M6! z=r6inQv}(4nhWh_4TY|u5Ce6TDfa+@%5-#JsBZ8)mq|nW^!H6cN|m%~Wr@yRd4Q?@ zGv|I`7?$rZHbmX0FgDk*DCA&gD9mr@vSHUe;k`0xo0b#Ut2UNdFI{2ZeBinb zKachIT-z-5Tvpc2jTJcgD~9W=+V!?4Cn!zji}`8&rJTMd{7GZYGB*5n6QKgHZRPZ) z?M#Z6t(T@wr+)VojpD`k~${!S}K_uOocVV8UA5L`JkgP z>Tc#*NO`*xx*by9=!CusE3tc;61yIfXTWPX`}hplh5QKcS@J357vNj)C*a05-1v;S z3mvAQJCJ-QaO3lY8_RLP_aOW1-Gv;NL-BpcG5EWKKF8U+7kQlh`EFD)-7&r&HT2ns s&t(M@vM=8;D)c-T-=87$*`Mnn^f?Zn>srsAbB*-$eCBiFY{U2V-_IM3&Hw-a diff --git a/PersianDate/build/.transforms/804ae9461bc36181bb932ccf21e80d26/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/saman/zamani/persiandate/PersianDateFormat.dex b/PersianDate/build/.transforms/804ae9461bc36181bb932ccf21e80d26/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/saman/zamani/persiandate/PersianDateFormat.dex deleted file mode 100644 index f1e24b0194c42c9117b978137a6c7ccec595b52f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8372 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 diff --git a/PersianDate/build/.transforms/804ae9461bc36181bb932ccf21e80d26/transformed/bundleLibRuntimeToDirDebug/desugar_graph.bin b/PersianDate/build/.transforms/804ae9461bc36181bb932ccf21e80d26/transformed/bundleLibRuntimeToDirDebug/desugar_graph.bin deleted file mode 100644 index 6b26a447acea8cb1673dc59a237e31bbc4ce8b81..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1081 zcmcJN&o2W(6vtn;epX9KaC7QOVvF?VK%$DMABmgn!RyY%4%6K=Gu7qb=1-`gcKNoc^_ zJ#)RHu*7Me`qU-VwU3tt`VKTk%z#)CQlyzwavgmIL&beO!fZt`p4EZ5z}IJthcPpZ znMLLvP_ly3(b?SXhLz^G@4C|t44`dODK~`%2m}oO)HHW@_degk#h&o9*RlGQ%_PwOufnXETso>^d=c1>L@4W?c{;vs*RkIGRn(!l2E z?q#=?(Bd_@f?0`Wbgh$cW|mKdWZEiJ)Y@vS^Lm}hGvw4(rp-~xssUE;m*#D%p7`TV z?PYSp0Qjv^=Wp_7we#z%TwLe0O)@@YEB`Ovj6#6FhyNfs&GXFt4-Tq;bj&&DCTex8SuLyl%ZQ0v&XC{jL RMb>G97HYp=QHL%D@Ckl4WRd^? diff --git a/PersianDate/build/.transforms/91dd66fd99e1bf96d3a5dda255469897/results.bin b/PersianDate/build/.transforms/91dd66fd99e1bf96d3a5dda255469897/results.bin deleted file mode 100644 index 0d259dd..0000000 --- a/PersianDate/build/.transforms/91dd66fd99e1bf96d3a5dda255469897/results.bin +++ /dev/null @@ -1 +0,0 @@ -o/classes diff --git a/PersianDate/build/.transforms/91dd66fd99e1bf96d3a5dda255469897/transformed/classes/classes_dex/classes.dex b/PersianDate/build/.transforms/91dd66fd99e1bf96d3a5dda255469897/transformed/classes/classes_dex/classes.dex deleted file mode 100644 index 537a1344b3f5c0a1df272611e3fba715461ce170..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 31688 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 IzsLPQ09wMhvj6}9 diff --git a/PersianDate/build/.transforms/99c70c53220c636dd34a74b4e5874025/results.bin b/PersianDate/build/.transforms/99c70c53220c636dd34a74b4e5874025/results.bin deleted file mode 100644 index 0d259dd..0000000 --- a/PersianDate/build/.transforms/99c70c53220c636dd34a74b4e5874025/results.bin +++ /dev/null @@ -1 +0,0 @@ -o/classes diff --git a/PersianDate/build/.transforms/99c70c53220c636dd34a74b4e5874025/transformed/classes/classes_dex/classes.dex b/PersianDate/build/.transforms/99c70c53220c636dd34a74b4e5874025/transformed/classes/classes_dex/classes.dex deleted file mode 100644 index 934e7e57e6e11bfd1d817452866362bc87066d2f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 31688 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 Kq9cEg`+oq$KD0^z diff --git a/PersianDate/build/.transforms/f7fb0ddd5afd91b957e9024aed94e90b/results.bin b/PersianDate/build/.transforms/f7fb0ddd5afd91b957e9024aed94e90b/results.bin deleted file mode 100644 index 7ed749e..0000000 --- a/PersianDate/build/.transforms/f7fb0ddd5afd91b957e9024aed94e90b/results.bin +++ /dev/null @@ -1 +0,0 @@ -o/bundleLibRuntimeToDirDebug diff --git a/PersianDate/build/.transforms/f7fb0ddd5afd91b957e9024aed94e90b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/saman/zamani/persiandate/PersianDate$Dialect.dex b/PersianDate/build/.transforms/f7fb0ddd5afd91b957e9024aed94e90b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/saman/zamani/persiandate/PersianDate$Dialect.dex deleted file mode 100644 index 72147c92a74b7e1303baff205be67565785a28d5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2076 zcmb7_O>7%Q6vyA}uI)H+gJW8p1~&~`11U=EG)c*aqtbx$VPTS}NiC!ng|&C%Y+~=a z-gOij5d{#aLT#l=90-B~6`VlosS<}Cf>a@d;KqqlFWlkY1OK;Pi%{iI#{SKlee>R% znK!#Ly0P6pG%`j@_UOHXPkz1q)7-ht{l?lyXU8Ak4Bn0Wk!T~T5z%&GGzQ_7w?jnz zXtDknhzg=0WY(8AA>l?3An~WiT)`kA5$Q5f6>NbW@G-ar?tp!87km%yfgiw+;34=K z{09C8T?)|{C;$`efLq{ea3A~$js=KLfS185APwFHRp5f_-~(_2+yVRGD&`o*RG$G+ z&;@wW&w}INIS>ck;3P%F4R-k9w4o^#T%5Puw?b(M**z;x? z_TsVRJhE)}CCA|B9>^dm_%+8)disEu?=Z>uv0bx2OcJ&FUxYpat3IzEYrNR>$0I!t z#xeOvyciS-cGxl+LNH!O0`;{%f!svsG}@dWq|M_zY&dU4coc=8q9jvMR;Z{1D7g}$ z=y1fd=U0JYdyZKIR+KZwF`G6#cV+_B1f&Yf9??cIsWzlgR5CF}Qx~LItfdpauAO3P zODFrf4vG!8bc(O*q1fv!U4X86D~*te@pwNugFJ^UD$a3j2=9c{Nh3l<1(Q+g{1!#p z`G!W2B=+H41+ZhhKHfKeW*JHrrt_C)meJVKDhJnp(vuxzskBTbRq|mgh<2D3rT2y^!PXH!okxddfUW`8>&aRBB$=DqH4e zTC*(M)m+oI(lb`6Vmqc)exsr}PKJ7qu*q4LQTO|kNA$mCxa+pQq}8>m;Tm;BIdz29 zvRSq?w^27T)Ro`RwzPCbv&v~dZuF39#%feE^rU8rVux%NiyK&d=0Cb+w~mEmD3y1# zs%E9Hb8V(;n3Abky5<_`rDi*ecGAyahF*Fqd*ps;dU<|jk)ns9qcQ-q}qZYN&reW#o zq^cX+>VR6Zt2MLY?d@RI)(x+t*eIh*H=IUUs~h@Y#Vppf`g?<>Zdk6lW+Ex-pC3@SIPtcH>5QkwQs>6i_vy9W-Q6Aa|0N9mqZ6b0+N;9|Aj#6{ zgvin!sR_~RJxNK5;;$y8-d;%&XA&YVNkW|IZkHtKbn@iI7vdpLFUL>ByW@c%pH5%& ziTyqCB+{pS2a33{r+gL=AMPoi2SxZxMTGxS`1}0|XCQyS-$6bAd=@-}{000D`u#(m zZMg6mk${fR!{d;Ae{kVgTv+x4zBAdUb#HQ9g!ArBj=^odKRFJc9ejtfj{W%_6@AS< zzDs4a*@w?(fdM%d-$4@eJQm-z3fkmjr`&LgyC-=60h^!0r9^TgSP@A!WJH%D^U diff --git a/PersianDate/build/.transforms/f7fb0ddd5afd91b957e9024aed94e90b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/saman/zamani/persiandate/PersianDate.dex b/PersianDate/build/.transforms/f7fb0ddd5afd91b957e9024aed94e90b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/saman/zamani/persiandate/PersianDate.dex deleted file mode 100644 index e3ddc4f537492de57ef73b7b8412b8a716ea64df..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23420 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

}|=j)NA@sZ{9Q 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 diff --git a/PersianDate/build/.transforms/f7fb0ddd5afd91b957e9024aed94e90b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/saman/zamani/persiandate/PersianDateFormat$1.dex b/PersianDate/build/.transforms/f7fb0ddd5afd91b957e9024aed94e90b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/saman/zamani/persiandate/PersianDateFormat$1.dex deleted file mode 100644 index 72ab1d07ad0813c70a7cfc4a75ff3ccb95f5330c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1616 zcmb7_-)kII6vxlZZZ=KQ#x!k{wr)clLy@vS(lvF{omgy*fxy%T3VBGCaCh#_k<$JF^`TFK_&u{(s1VTJ>}SrIbI&<* z<_z~ls@>t)*X#8A<(nUWRa-Sn=YI8Xe*VLsgJ0Iax&PfS->(#i+C;RwIyVlHtxpDs zrlGNY6g&km(Q6hFek5_>`*8pM3&^kYM0X+2*+ez)2Dk{`1Xn-+?tpK>&)^Yw28MEY z7N~=D@G1Ba{01I?hu{zJ7(4~fz+b?|^M=4ta1y)%o&coGYQt;MAUFhuz%UpAhXKFS zOJEG}OCJYFnZ+GO0;I`ejzi~tj^T9-uwI1y1mqDg1AQ2oUxv;-IRg%5Rj{42vdq&Y zU}$o=G=q$L;9>IEDKav&_<`1$iC!~wM@3ROgYc<=v7aOa7S-fg)vboQXQG< zJH`*AM4RBMN;f0v($oP?%f_hq?NB6%OEU-T_hMH>ELtj6F}%(k;B{RGMx>osxpd6i z5<8+A3KLY9O{xMFyEOis@mh0B`RTuH*V7nvx^$xF*GY9)y&T8lmZuZA9%x>mvtA-v z!c;%xtFE?Do)$)mR8?1c`XY3fD*wxGDT-SnojLDP=>_~XEcf}X{ZmW}<8^)0!*j-X`SkIaS+I&t=ZMy)binRDJRIh!IW`!SAttlI)=5*e+?TI{{oU-#)v6!1YGC4GfBZ#x|*|fd4m%q#BkLWDUET8f@ z$OgCqB;dvSy!gDm2U~V@q4(`9r$HOAPk+|-XA#d^o<;1}pShfx=Pu7Tw)5<@rh7Wi v-`u{=v%|szIi6>l4Lg3>e9l8*JJ-%exMs5_2mgMpg?&5EB`%zO`2WH`i;P9s diff --git a/PersianDate/build/.transforms/f7fb0ddd5afd91b957e9024aed94e90b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/saman/zamani/persiandate/PersianDateFormat$PersianDateNumberCharacter.dex b/PersianDate/build/.transforms/f7fb0ddd5afd91b957e9024aed94e90b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/saman/zamani/persiandate/PersianDateFormat$PersianDateNumberCharacter.dex deleted file mode 100644 index 3b4e9934c4a94197e0e3d9d875a5b14df4fe93f4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2032 zcmb`I-)kII6vxlZ&hB=TwQjaew@o+6X4qhr{gK9K(rjo`OwtCnKQOo;MZ?{h$!3z> zna#{>h_;4Bd?@(?M6ICs zbI&>VoO_3RO>3)X?DJ`#ulCFz`G--WCJ}8_CXx`c z<)9lykM&OhO(BXvPCy+&4JVDJ!Vam> zcinu_h`5naP~XT;!~4RObDRf=!)wzV!#=4;DWMGG1(QBH z`uQhNb-f%M>c(rI$@n-?Z0@!g1U{1h(rG=N+T&`nFShsV;oK zL_>$zEIN+mb;q-Zj9;;WRoA>OJkhWM%R`jGL#&qUnj?aiXO*b0ye2k9u`Zliu^TsW zKsE2Q8YOyMGegM(HrJ|amJyWxrCSO-6jGu~WnVOeQ+$)(cCm?+Y~h$9u!`3^{W;cWyR-=kSS5b(8I!cs#fzqc#nv zl_B}1W+{PZIIRv1shW~WD=Ae~l+4Kml?x4xj6xu07`(>FW8+=fv5D@9n<<<*<(a diff --git a/PersianDate/build/.transforms/f7fb0ddd5afd91b957e9024aed94e90b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/saman/zamani/persiandate/PersianDateFormat.dex b/PersianDate/build/.transforms/f7fb0ddd5afd91b957e9024aed94e90b/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/saman/zamani/persiandate/PersianDateFormat.dex deleted file mode 100644 index b4e9905ed41e3d2d431acc427af5e259dd811265..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8372 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 R^?7>!Hp2sF8~(os{|oh;?85*6 diff --git a/PersianDate/build/.transforms/f7fb0ddd5afd91b957e9024aed94e90b/transformed/bundleLibRuntimeToDirDebug/desugar_graph.bin b/PersianDate/build/.transforms/f7fb0ddd5afd91b957e9024aed94e90b/transformed/bundleLibRuntimeToDirDebug/desugar_graph.bin deleted file mode 100644 index 56ac5637eaef5c4c184d589c0943345fbca10452..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 931 zcmb`Fy-ve05XY~nszNDhSzu%8)FGj$8v{sG1w>nr*eV9Du|)#T2ev~40~?P}o`N03 zRKyc7fsl9vo`401M52Nsg$hePpYCkm?|=678{`Csn&~+@x`cZ+(R+c-2<1AVq3s^( z?LcCW(FXOYOQ>s}ZE*B0h(|_)Obcd7l9Zy2zJyZi2v5-nq|FQ!SP6YqVRFZd`^5+&fyzjzra_4j!V|cFf{1MX`!y3N z3MFi(Dc - - - - - - - - \ No newline at end of file diff --git a/PersianDate/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/output-metadata.json b/PersianDate/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/output-metadata.json deleted file mode 100644 index 67bd344..0000000 --- a/PersianDate/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/output-metadata.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "version": 3, - "artifactType": { - "type": "AAPT_FRIENDLY_MERGED_MANIFESTS", - "kind": "Directory" - }, - "applicationId": "com.github.samanzamani.persiandate", - "variantName": "debug", - "elements": [ - { - "type": "SINGLE", - "filters": [], - "attributes": [], - "outputFile": "AndroidManifest.xml" - } - ], - "elementType": "File" -} \ No newline at end of file diff --git a/PersianDate/build/intermediates/aar_metadata/debug/writeDebugAarMetadata/aar-metadata.properties b/PersianDate/build/intermediates/aar_metadata/debug/writeDebugAarMetadata/aar-metadata.properties deleted file mode 100644 index 1211b1e..0000000 --- a/PersianDate/build/intermediates/aar_metadata/debug/writeDebugAarMetadata/aar-metadata.properties +++ /dev/null @@ -1,6 +0,0 @@ -aarFormatVersion=1.0 -aarMetadataVersion=1.0 -minCompileSdk=1 -minCompileSdkExtension=0 -minAndroidGradlePluginVersion=1.0.0 -coreLibraryDesugaringEnabled=false diff --git a/PersianDate/build/intermediates/annotation_processor_list/debug/javaPreCompileDebug/annotationProcessors.json b/PersianDate/build/intermediates/annotation_processor_list/debug/javaPreCompileDebug/annotationProcessors.json deleted file mode 100644 index 9e26dfe..0000000 --- a/PersianDate/build/intermediates/annotation_processor_list/debug/javaPreCompileDebug/annotationProcessors.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/PersianDate/build/intermediates/compile_library_classes_jar/debug/bundleLibCompileToJarDebug/classes.jar b/PersianDate/build/intermediates/compile_library_classes_jar/debug/bundleLibCompileToJarDebug/classes.jar deleted file mode 100644 index d4775dc2af7ca3aefa7a9529cd4d4c5d6387fc73..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 43885 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 GZgyYMEY8sY diff --git a/PersianDate/build/intermediates/compile_symbol_list/debug/generateDebugRFile/R.txt b/PersianDate/build/intermediates/compile_symbol_list/debug/generateDebugRFile/R.txt deleted file mode 100644 index a9551e5..0000000 --- a/PersianDate/build/intermediates/compile_symbol_list/debug/generateDebugRFile/R.txt +++ /dev/null @@ -1 +0,0 @@ -int string app_name 0x0 diff --git a/PersianDate/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties b/PersianDate/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties deleted file mode 100644 index 6e9f359..0000000 --- a/PersianDate/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +++ /dev/null @@ -1 +0,0 @@ -#Fri Jul 31 20:09:38 GMT+03:30 2026 diff --git a/PersianDate/build/intermediates/incremental/debug/packageDebugResources/merged.dir/values/values.xml b/PersianDate/build/intermediates/incremental/debug/packageDebugResources/merged.dir/values/values.xml deleted file mode 100644 index d81740a..0000000 --- a/PersianDate/build/intermediates/incremental/debug/packageDebugResources/merged.dir/values/values.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - PersianDate - \ No newline at end of file diff --git a/PersianDate/build/intermediates/incremental/debug/packageDebugResources/merger.xml b/PersianDate/build/intermediates/incremental/debug/packageDebugResources/merger.xml deleted file mode 100644 index d0fc3d4..0000000 --- a/PersianDate/build/intermediates/incremental/debug/packageDebugResources/merger.xml +++ /dev/null @@ -1,2 +0,0 @@ - -PersianDate \ No newline at end of file diff --git a/PersianDate/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml b/PersianDate/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml deleted file mode 100644 index 5ed22d7..0000000 --- a/PersianDate/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/PersianDate/build/intermediates/incremental/mergeDebugShaders/merger.xml b/PersianDate/build/intermediates/incremental/mergeDebugShaders/merger.xml deleted file mode 100644 index 103c790..0000000 --- a/PersianDate/build/intermediates/incremental/mergeDebugShaders/merger.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/PersianDate/build/intermediates/incremental/packageDebugAssets/merger.xml b/PersianDate/build/intermediates/incremental/packageDebugAssets/merger.xml deleted file mode 100644 index 1777a64..0000000 --- a/PersianDate/build/intermediates/incremental/packageDebugAssets/merger.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/PersianDate/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/saman/zamani/persiandate/PersianDate$Dialect.class b/PersianDate/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/saman/zamani/persiandate/PersianDate$Dialect.class deleted file mode 100644 index 1928020fb2fac973fbbc50c19488700b919c3578..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1454 zcmb7DZBNrs6n^gB+SMX+C<1~aOxXabli>@SxWa%f&OsD>S9`}-TYGCYnzrd4 zn4YC=clJ5j$8xsmSXD0!hJcD7LJaaop|n{n7i=U52Z_6V}~Ujc##b zIxTDK4Z~8r2fgN)&1SkMV0aDNJxuqWzzGEZw2Bz+kvg%LA}-TpNX8R=YC@;~0teRW2PDT}(RQs;tF~`7cFlcD!5AX1Ebpj(ux&Od$D=-_w)Bo{GGW6$bWN|- zAgDi{C{w3ps!mtKY!4S#I@8}pSJDiMQExS>)`rcCkMxYl0`CGktz3R? zGZzeM!F8>M=nso(kh;^BP128?pji!NNBPVFWNB)IrBM-<#zR;d2_Q1Y=YSAF%6kZn z@JaHf$nr;NW9SoZUSQ+{c?^$e4T*1yls}0@_(Xn+EJr9&HhYfo3oxGV`y)R^5w(+r zB|IkY3B5srz2~XayzmRMBw0Uwl4j?|&oK3ow*O+1@RU~}n8H3PhxYV76OiSI!dse- z#Ov!QJ0@yLVSw}*BZX$Dt`t?Mlg@!| ZQ5ig^d>>ZuLf9-;$?^ea$YL!!`V-0QJ3IgY diff --git a/PersianDate/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/saman/zamani/persiandate/PersianDate.class b/PersianDate/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/saman/zamani/persiandate/PersianDate.class deleted file mode 100644 index d0ff7239da7d4084164a452919808cf20a569d97..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28856 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 diff --git a/PersianDate/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/saman/zamani/persiandate/PersianDateFormat$PersianDateNumberCharacter.class b/PersianDate/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/saman/zamani/persiandate/PersianDateFormat$PersianDateNumberCharacter.class deleted file mode 100644 index 58e1d5a9bae8ee41b39bf72575f78c76c3b2c335..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1472 zcmb_cTTc@~6#k~WW!rV3r6Neh3m3Ipl!}6yln@#yP*Z`p+Jx{llo6I}cg^mW2cP^& z3W*SnhG&12@yxdB8Z_~tO?J+lnRC8#ne+AA_fKB|RPa&jgZFj#M zk01de{Jeo{7-Q&Luw6S`WEhS^bvZ@96UeL-GGv#bDH2mxwB-O;DlvScuM3{o?S_ERD<%{WT~{ zqms!0N#Y!k%}b`yO_KB}Op@Jg(k4jCpMDJeBL>cqc~2U{9a{UMyDZs{;4Tu8{TxZ@ zq32lf442Qrq2yB+5LRVUGdh8UVA87k8Bn|gu z6%kT8LFLe%zWb5OOjeg9p^&m96DqC1VDbytwsH)`+E$ODSr;oyp@f;PTcztZ{+YaT z*OgP;IDrx=H`Ped&T#7lx=gdvpn)j+L27Fbc}hp^=JNovWRt){Jc{HT9+Q-8W|+g1 GrO_W};%Ha^ diff --git a/PersianDate/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/saman/zamani/persiandate/PersianDateFormat.class b/PersianDate/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/saman/zamani/persiandate/PersianDateFormat.class deleted file mode 100644 index 91997aec5c656644ec51b7bc2ddb0ab7cd811f28..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9014 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 diff --git a/PersianDate/build/intermediates/local_only_symbol_list/debug/parseDebugLocalResources/R-def.txt b/PersianDate/build/intermediates/local_only_symbol_list/debug/parseDebugLocalResources/R-def.txt deleted file mode 100644 index 34ca0ec..0000000 --- a/PersianDate/build/intermediates/local_only_symbol_list/debug/parseDebugLocalResources/R-def.txt +++ /dev/null @@ -1,3 +0,0 @@ -R_DEF: Internal format may change without notice -local -string app_name diff --git a/PersianDate/build/intermediates/manifest_merge_blame_file/debug/processDebugManifest/manifest-merger-blame-debug-report.txt b/PersianDate/build/intermediates/manifest_merge_blame_file/debug/processDebugManifest/manifest-merger-blame-debug-report.txt deleted file mode 100644 index 8a229b9..0000000 --- a/PersianDate/build/intermediates/manifest_merge_blame_file/debug/processDebugManifest/manifest-merger-blame-debug-report.txt +++ /dev/null @@ -1,12 +0,0 @@ -1 -2 -4 -5 -6 -7 -7-->E:\AndroidStudioProjects\MonthlyPaymentApp\PersianDate\src\main\AndroidManifest.xml:5:5-7:19 -7-->E:\AndroidStudioProjects\MonthlyPaymentApp\PersianDate\src\main\AndroidManifest.xml:5:18-50 -8 -9 -10 diff --git a/PersianDate/build/intermediates/merged_manifest/debug/processDebugManifest/AndroidManifest.xml b/PersianDate/build/intermediates/merged_manifest/debug/processDebugManifest/AndroidManifest.xml deleted file mode 100644 index f6518bc..0000000 --- a/PersianDate/build/intermediates/merged_manifest/debug/processDebugManifest/AndroidManifest.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/PersianDate/build/intermediates/navigation_json/debug/extractDeepLinksDebug/navigation.json b/PersianDate/build/intermediates/navigation_json/debug/extractDeepLinksDebug/navigation.json deleted file mode 100644 index 0637a08..0000000 --- a/PersianDate/build/intermediates/navigation_json/debug/extractDeepLinksDebug/navigation.json +++ /dev/null @@ -1 +0,0 @@ -[] \ No newline at end of file diff --git a/PersianDate/build/intermediates/nested_resources_validation_report/debug/generateDebugResources/nestedResourcesValidationReport.txt b/PersianDate/build/intermediates/nested_resources_validation_report/debug/generateDebugResources/nestedResourcesValidationReport.txt deleted file mode 100644 index 08f4ebe..0000000 --- a/PersianDate/build/intermediates/nested_resources_validation_report/debug/generateDebugResources/nestedResourcesValidationReport.txt +++ /dev/null @@ -1 +0,0 @@ -0 Warning/Error \ No newline at end of file diff --git a/PersianDate/build/intermediates/packaged_res/debug/packageDebugResources/values/values.xml b/PersianDate/build/intermediates/packaged_res/debug/packageDebugResources/values/values.xml deleted file mode 100644 index d81740a..0000000 --- a/PersianDate/build/intermediates/packaged_res/debug/packageDebugResources/values/values.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - PersianDate - \ No newline at end of file diff --git a/PersianDate/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/saman/zamani/persiandate/PersianDate$Dialect.class b/PersianDate/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/saman/zamani/persiandate/PersianDate$Dialect.class deleted file mode 100644 index 1928020fb2fac973fbbc50c19488700b919c3578..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1454 zcmb7DZBNrs6n^gB+SMX+C<1~aOxXabli>@SxWa%f&OsD>S9`}-TYGCYnzrd4 zn4YC=clJ5j$8xsmSXD0!hJcD7LJaaop|n{n7i=U52Z_6V}~Ujc##b zIxTDK4Z~8r2fgN)&1SkMV0aDNJxuqWzzGEZw2Bz+kvg%LA}-TpNX8R=YC@;~0teRW2PDT}(RQs;tF~`7cFlcD!5AX1Ebpj(ux&Od$D=-_w)Bo{GGW6$bWN|- zAgDi{C{w3ps!mtKY!4S#I@8}pSJDiMQExS>)`rcCkMxYl0`CGktz3R? zGZzeM!F8>M=nso(kh;^BP128?pji!NNBPVFWNB)IrBM-<#zR;d2_Q1Y=YSAF%6kZn z@JaHf$nr;NW9SoZUSQ+{c?^$e4T*1yls}0@_(Xn+EJr9&HhYfo3oxGV`y)R^5w(+r zB|IkY3B5srz2~XayzmRMBw0Uwl4j?|&oK3ow*O+1@RU~}n8H3PhxYV76OiSI!dse- z#Ov!QJ0@yLVSw}*BZX$Dt`t?Mlg@!| ZQ5ig^d>>ZuLf9-;$?^ea$YL!!`V-0QJ3IgY diff --git a/PersianDate/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/saman/zamani/persiandate/PersianDate.class b/PersianDate/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/saman/zamani/persiandate/PersianDate.class deleted file mode 100644 index d0ff7239da7d4084164a452919808cf20a569d97..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28856 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 diff --git a/PersianDate/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/saman/zamani/persiandate/PersianDateFormat$PersianDateNumberCharacter.class b/PersianDate/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/saman/zamani/persiandate/PersianDateFormat$PersianDateNumberCharacter.class deleted file mode 100644 index 58e1d5a9bae8ee41b39bf72575f78c76c3b2c335..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1472 zcmb_cTTc@~6#k~WW!rV3r6Neh3m3Ipl!}6yln@#yP*Z`p+Jx{llo6I}cg^mW2cP^& z3W*SnhG&12@yxdB8Z_~tO?J+lnRC8#ne+AA_fKB|RPa&jgZFj#M zk01de{Jeo{7-Q&Luw6S`WEhS^bvZ@96UeL-GGv#bDH2mxwB-O;DlvScuM3{o?S_ERD<%{WT~{ zqms!0N#Y!k%}b`yO_KB}Op@Jg(k4jCpMDJeBL>cqc~2U{9a{UMyDZs{;4Tu8{TxZ@ zq32lf442Qrq2yB+5LRVUGdh8UVA87k8Bn|gu z6%kT8LFLe%zWb5OOjeg9p^&m96DqC1VDbytwsH)`+E$ODSr;oyp@f;PTcztZ{+YaT z*OgP;IDrx=H`Ped&T#7lx=gdvpn)j+L27Fbc}hp^=JNovWRt){Jc{HT9+Q-8W|+g1 GrO_W};%Ha^ diff --git a/PersianDate/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/saman/zamani/persiandate/PersianDateFormat.class b/PersianDate/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/saman/zamani/persiandate/PersianDateFormat.class deleted file mode 100644 index 91997aec5c656644ec51b7bc2ddb0ab7cd811f28..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9014 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 diff --git a/PersianDate/build/intermediates/runtime_library_classes_jar/debug/bundleLibRuntimeToJarDebug/classes.jar b/PersianDate/build/intermediates/runtime_library_classes_jar/debug/bundleLibRuntimeToJarDebug/classes.jar deleted file mode 100644 index 8c6c9f2928742ae1800e2300e538351c8b960bdc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 43019 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 O#*_#GM + Toast.makeText(context, message, Toast.LENGTH_SHORT).show() + } + } } // UI elements for update and permissions @@ -265,9 +273,18 @@ fun AuthenticatedContent(viewModel: PersonViewModel, contactViewModel: ContactVi val navBackStackEntry by navController.currentBackStackEntryAsState() val currentRoute = navBackStackEntry?.destination?.route ?: "" + LaunchedEffect(currentRoute) { + if (currentRoute == Screen.Mosque.route) { + viewModel.onEvent(PersonScreenEvent.SetCategory("mosque")) + } else if (currentRoute in listOf(Screen.Unpaid.route, Screen.Paid.route)) { + viewModel.onEvent(PersonScreenEvent.SetCategory("salary")) + } + } + val showBottomBar = currentRoute in listOf( Screen.Unpaid.route, Screen.Paid.route, + Screen.Mosque.route, Screen.Archive.route, Screen.Settings.route ) @@ -358,10 +375,17 @@ fun AuthenticatedContent(viewModel: PersonViewModel, contactViewModel: ContactVi title = { val titleText = when { isSelectionMode -> "انتخاب شده‌ها" - currentRoute == Screen.Settings.route -> "تنظیمات" + else -> { + val currentRoute = navController.currentBackStackEntry?.destination?.route + when (currentRoute) { + Screen.Mosque.route -> "مدیریت کمک‌های مسجد" + Screen.Settings.route -> "تنظیمات" + Screen.Archive.route -> "آرشیو" else -> "مدیریت حقوق ماهانه" } - Text(titleText) + } + } + Text(titleText) }, colors = TopAppBarDefaults.topAppBarColors( containerColor = if (isSelectionMode) MaterialTheme.colorScheme.secondaryContainer else MaterialTheme.colorScheme.primary, @@ -390,7 +414,7 @@ fun AuthenticatedContent(viewModel: PersonViewModel, contactViewModel: ContactVi IconButton(onClick = { showConfirmMoveDialog = true }) { Icon(Icons.Default.Check, contentDescription = "Confirm Move", tint = Color.Green) } - } else if (currentRoute == Screen.Unpaid.route) { + } else if (currentRoute == Screen.Unpaid.route || currentRoute == Screen.Mosque.route) { FilledIconButton( onClick = { viewModel.onAddPersonClicked() }, colors = IconButtonDefaults.filledIconButtonColors( @@ -437,6 +461,13 @@ fun AuthenticatedContent(viewModel: PersonViewModel, contactViewModel: ContactVi navController = navController ) } + composable(Screen.Mosque.route) { + PersonScreen( + viewModel = viewModel, + contactViewModel = contactViewModel, + navController = navController + ) + } composable(Screen.Paid.route) { PaidListScreen( viewModel = viewModel, @@ -475,17 +506,19 @@ fun AppBottomNavigationBar(navController: NavController) { Screen.Unpaid, Screen.Paid, Screen.Archive, + Screen.Mosque, Screen.Settings ) NavigationBar( - containerColor = MaterialTheme.colorScheme.primary // Match TopAppBar + containerColor = MaterialTheme.colorScheme.primary ) { val navBackStackEntry by navController.currentBackStackEntryAsState() val currentDestination = navBackStackEntry?.destination items.forEach { screen -> NavigationBarItem( + alwaysShowLabel = true, colors = NavigationBarItemDefaults.colors( selectedTextColor = Color.White, selectedIconColor = Color.White, @@ -494,7 +527,15 @@ fun AppBottomNavigationBar(navController: NavController) { unselectedIconColor = Color.White.copy(alpha = 0.7f) ), icon = { Icon(screen.icon, contentDescription = screen.title) }, - label = { Text(screen.title) }, + label = { + Text( + text = screen.title, + textAlign = TextAlign.Center, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + style = MaterialTheme.typography.labelSmall + ) + }, selected = currentDestination?.hierarchy?.any { it.route == screen.route } == true, onClick = { navController.navigate(screen.route) { @@ -517,6 +558,7 @@ sealed class Screen( ) { data object Unpaid : Screen("unpaid_list", "پرداخت نشده", Icons.AutoMirrored.Filled.List) data object Paid : Screen("paid_list", "پرداخت شده", Icons.Default.Paid) + data object Mosque : Screen("mosque_list", "مسجد", Icons.Default.AccountBalance) data object Archive : Screen("archive", "آرشیو", Icons.Default.Archive) data object Settings : Screen("settings", "تنظیمات", Icons.Default.Settings) } diff --git a/app/src/main/java/com/oqba26/monthlypaymentapp/core/PaymentApplication.kt b/app/src/main/java/com/oqba26/monthlypaymentapp/core/PaymentApplication.kt index 05b6eec..59974e7 100644 --- a/app/src/main/java/com/oqba26/monthlypaymentapp/core/PaymentApplication.kt +++ b/app/src/main/java/com/oqba26/monthlypaymentapp/core/PaymentApplication.kt @@ -1,7 +1,59 @@ package com.oqba26.monthlypaymentapp.core import android.app.Application +import android.util.Log +import com.oqba26.monthlypaymentapp.BuildConfig +import com.oqba26.monthlypaymentapp.core.manager.BackupManager +import com.oqba26.monthlypaymentapp.data.repository.SettingsRepository +import com.oqba26.monthlypaymentapp.data.sync.SyncManager import dagger.hilt.android.HiltAndroidApp +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.launch +import javax.inject.Inject @HiltAndroidApp -class PaymentApplication : Application() +class PaymentApplication : Application() { + @Inject + lateinit var syncManager: SyncManager + + @Inject + lateinit var backupManager: BackupManager + + @Inject + lateinit var settingsRepository: SettingsRepository + + private val appScope = CoroutineScope(Dispatchers.IO + SupervisorJob()) + + override fun onCreate() { + super.onCreate() + syncManager.startSync() + snapshotOnUpgrade() + } + + /** + * اولین اجرا بعد از هر ارتقای نسخه، یک نسخه‌ی پشتیبان می‌گیرد. + * + * ارتقای نسخه همان لحظه‌ای است که مهاجرت دیتابیس اجرا می‌شود؛ اگر آنجا چیزی خراب شد، + * این فایل تنها راه برگشت داده‌ی کاربر است. + */ + private fun snapshotOnUpgrade() { + appScope.launch { + try { + val current = BuildConfig.VERSION_CODE + val last = settingsRepository.getLastRunVersionCode() + if (last != current) { + if (last != null) { + // اجرای اول بعد از نصب، داده‌ای برای پشتیبان‌گیری ندارد؛ فقط + // ارتقای واقعی (last != null) ارزش snapshot دارد. + backupManager.createSnapshot(BackupManager.REASON_APP_UPGRADE) + } + settingsRepository.saveLastRunVersionCode(current) + } + } catch (e: Exception) { + Log.e("PaymentApplication", "snapshot ارتقای نسخه ناموفق بود", e) + } + } + } +} diff --git a/app/src/main/java/com/oqba26/monthlypaymentapp/core/di/DatabaseModule.kt b/app/src/main/java/com/oqba26/monthlypaymentapp/core/di/DatabaseModule.kt index 98b0797..81e1ed0 100644 --- a/app/src/main/java/com/oqba26/monthlypaymentapp/core/di/DatabaseModule.kt +++ b/app/src/main/java/com/oqba26/monthlypaymentapp/core/di/DatabaseModule.kt @@ -1,8 +1,10 @@ package com.oqba26.monthlypaymentapp.core.di import android.content.Context +import androidx.room.Room import com.oqba26.monthlypaymentapp.data.dao.PaymentDao import com.oqba26.monthlypaymentapp.data.dao.PersonDao +import com.oqba26.monthlypaymentapp.data.database.ALL_MIGRATIONS import com.oqba26.monthlypaymentapp.data.database.AppDatabase import dagger.Module import dagger.Provides @@ -18,7 +20,18 @@ object DatabaseModule { @Provides @Singleton fun provideAppDatabase(@ApplicationContext context: Context): AppDatabase { - return AppDatabase.getInstance(context) + return Room.databaseBuilder( + context.applicationContext, + AppDatabase::class.java, + AppDatabase.DATABASE_NAME + ) + .addMigrations(*ALL_MIGRATIONS) + // فقط نسخه‌های ۱ تا ۱۷ اجازه‌ی پاک شدن دارند — آن‌ها قبل از فعال شدن + // exportSchema ساخته شده‌اند و تاریخچه‌ی اسکیمایشان را نداریم. + // از ۱۸ به بعد مهاجرت اجباری است: تغییر اسکیمای بدون مهاجرت باعث خطا + // می‌شود، نه پاک شدن بی‌صدای داده‌ی کاربر. + .fallbackToDestructiveMigrationFrom(*(1..16).toList().toIntArray()) + .build() } @Provides @@ -30,4 +43,9 @@ object DatabaseModule { fun providePaymentDao(database: AppDatabase): PaymentDao { return database.paymentDao() } + + @Provides + fun provideSyncQueueDao(database: AppDatabase): com.oqba26.monthlypaymentapp.data.dao.SyncQueueDao { + return database.syncQueueDao() + } } diff --git a/app/src/main/java/com/oqba26/monthlypaymentapp/core/di/RepositoryModule.kt b/app/src/main/java/com/oqba26/monthlypaymentapp/core/di/RepositoryModule.kt index d843c5f..64a80d2 100644 --- a/app/src/main/java/com/oqba26/monthlypaymentapp/core/di/RepositoryModule.kt +++ b/app/src/main/java/com/oqba26/monthlypaymentapp/core/di/RepositoryModule.kt @@ -29,9 +29,10 @@ object RepositoryModule { fun provideLocalPersonRepository( personDao: PersonDao, paymentDao: PaymentDao, + syncQueueDao: com.oqba26.monthlypaymentapp.data.dao.SyncQueueDao, database: AppDatabase ): LocalPersonRepository { - return LocalPersonRepository(personDao, paymentDao, database) + return LocalPersonRepository(personDao, paymentDao, syncQueueDao, database) } @Provides diff --git a/app/src/main/java/com/oqba26/monthlypaymentapp/core/manager/BackupManager.kt b/app/src/main/java/com/oqba26/monthlypaymentapp/core/manager/BackupManager.kt new file mode 100644 index 0000000..b01813b --- /dev/null +++ b/app/src/main/java/com/oqba26/monthlypaymentapp/core/manager/BackupManager.kt @@ -0,0 +1,147 @@ +package com.oqba26.monthlypaymentapp.core.manager + +import android.content.Context +import android.util.Log +import com.oqba26.monthlypaymentapp.data.model.BackupData +import com.oqba26.monthlypaymentapp.data.repository.LocalPersonRepository +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import java.io.File +import javax.inject.Inject +import javax.inject.Singleton + +/** + * اطلاعات یک نسخه‌ی پشتیبان خودکار، برای نمایش در تنظیمات. + */ +data class SnapshotInfo( + val file: File, + val reason: String, + val timestamp: Long, + val personCount: Int, + val paymentCount: Int +) + +/** + * تور نجات: قبل از هر عملیات خطرناک، یک نسخه‌ی کامل از داده‌ی محلی روی حافظه‌ی خود گوشی + * ذخیره می‌کند تا اگر با وجود همه‌ی محافظت‌های دیگر داده‌ای از بین رفت، کاربر بتواند برش گرداند. + * + * فایل‌ها در `filesDir/snapshots` ذخیره می‌شوند — حافظه‌ی خصوصی اپ، پس نیازی به مجوز + * دسترسی به حافظه ندارد و اپ‌های دیگر نمی‌توانند بخوانندش. + * + * فقط [MAX_SNAPSHOTS] نسخه‌ی جدیدتر نگه داشته می‌شود؛ قدیمی‌ترها خودکار پاک می‌شوند تا + * حافظه‌ی گوشی پر نشود. + */ +@Singleton +class BackupManager @Inject constructor( + @ApplicationContext private val context: Context, + private val localPersonRepository: LocalPersonRepository +) { + private val json = Json { + ignoreUnknownKeys = true + encodeDefaults = true + } + + private val snapshotDir: File + get() = File(context.filesDir, DIR_NAME).apply { if (!exists()) mkdirs() } + + /** + * گرفتن یک نسخه‌ی پشتیبان. + * + * اگر داده‌ای برای پشتیبان‌گیری نباشد کاری نمی‌کند — snapshot خالی هم بی‌فایده است و هم + * جای snapshot مفیدِ قبلی را در سقف سه‌تایی می‌گیرد. + * + * این تابع عمداً هیچ استثنایی به بیرون نمی‌دهد: شکستِ گرفتن پشتیبان نباید عملیات اصلی + * (سینک، بازیابی، ارتقای نسخه) را متوقف کند. + * + * @param reason شناسه‌ی کوتاه انگلیسی برای علت — بخشی از نام فایل می‌شود. + * @return فایل ساخته‌شده یا `null` اگر ساخته نشد. + */ + suspend fun createSnapshot(reason: String): File? = withContext(Dispatchers.IO) { + try { + val data = localPersonRepository.getDataForBackup() + if (data.persons.isEmpty() && data.payments.isEmpty()) { + Log.d(TAG, "snapshot گرفته نشد: داده‌ای وجود ندارد") + return@withContext null + } + + val safeReason = reason.filter { it.isLetterOrDigit() || it == '-' || it == '_' } + .ifBlank { "manual" } + val file = File(snapshotDir, "$safeReason-${System.currentTimeMillis()}.json") + file.writeText(json.encodeToString(data)) + + pruneOldSnapshots() + Log.d(TAG, "snapshot ساخته شد: ${file.name} (${data.persons.size} شخص، ${data.payments.size} پرداخت)") + file + } catch (e: Exception) { + Log.e(TAG, "ساخت snapshot ناموفق بود — عملیات اصلی ادامه می‌یابد", e) + null + } + } + + /** لیست نسخه‌های موجود، جدیدترین اول. فایل‌های خراب نادیده گرفته می‌شوند. */ + suspend fun listSnapshots(): List = withContext(Dispatchers.IO) { + snapshotFiles().mapNotNull { file -> + try { + val data = json.decodeFromString(file.readText()) + SnapshotInfo( + file = file, + reason = file.nameWithoutExtension.substringBeforeLast('-'), + timestamp = file.nameWithoutExtension.substringAfterLast('-').toLongOrNull() + ?: file.lastModified(), + personCount = data.persons.size, + paymentCount = data.payments.size + ) + } catch (e: Exception) { + Log.w(TAG, "snapshot خراب نادیده گرفته شد: ${file.name}", e) + null + } + } + } + + /** + * بازگرداندن یک نسخه. + * + * قبل از بازگردانی، خودش یک snapshot از وضعیت فعلی می‌گیرد — تا اگر کاربر اشتباهی + * نسخه‌ی غلط را انتخاب کرد، راه برگشت داشته باشد. + */ + suspend fun restoreSnapshot(file: File): Boolean = withContext(Dispatchers.IO) { + try { + val data = json.decodeFromString(file.readText()) + createSnapshot(REASON_BEFORE_RESTORE) + localPersonRepository.restoreBackup(data) + true + } catch (e: Exception) { + Log.e(TAG, "بازگردانی snapshot ناموفق بود: ${file.name}", e) + false + } + } + + private fun snapshotFiles(): List = + snapshotDir.listFiles { f -> f.isFile && f.extension == "json" } + ?.sortedByDescending { it.lastModified() } + ?: emptyList() + + private fun pruneOldSnapshots() { + snapshotFiles().drop(MAX_SNAPSHOTS).forEach { old -> + if (old.delete()) Log.d(TAG, "snapshot قدیمی حذف شد: ${old.name}") + } + } + + companion object { + private const val TAG = "BackupManager" + private const val DIR_NAME = "snapshots" + private const val MAX_SNAPSHOTS = 3 + + /** قبل از اولین ادغام داده‌ی سرور در هر اجرا. */ + const val REASON_BEFORE_SYNC = "before-sync" + + /** قبل از بازگردانی نسخه‌ی پشتیبان (دستی یا خودکار). */ + const val REASON_BEFORE_RESTORE = "before-restore" + + /** بعد از ارتقای نسخه‌ی اپ. */ + const val REASON_APP_UPGRADE = "app-upgrade" + } +} diff --git a/app/src/main/java/com/oqba26/monthlypaymentapp/data/dao/PaymentDao.kt b/app/src/main/java/com/oqba26/monthlypaymentapp/data/dao/PaymentDao.kt index f505f0b..739b745 100644 --- a/app/src/main/java/com/oqba26/monthlypaymentapp/data/dao/PaymentDao.kt +++ b/app/src/main/java/com/oqba26/monthlypaymentapp/data/dao/PaymentDao.kt @@ -22,6 +22,13 @@ interface PaymentDao { @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertAllPaymentRecords(payments: List) + @Query("DELETE FROM payments WHERE id = :paymentId") + suspend fun deleteById(paymentId: String) + + /** حذف گروهی — برای اعمال [com.oqba26.monthlypaymentapp.data.sync.MergePlan] در یک کوئری. */ + @Query("DELETE FROM payments WHERE id IN (:ids)") + suspend fun deleteByIds(ids: List) + @Query("DELETE FROM payments") suspend fun deleteAllPaymentRecords() } \ No newline at end of file diff --git a/app/src/main/java/com/oqba26/monthlypaymentapp/data/dao/PersonDao.kt b/app/src/main/java/com/oqba26/monthlypaymentapp/data/dao/PersonDao.kt index fb7652b..a9e471a 100644 --- a/app/src/main/java/com/oqba26/monthlypaymentapp/data/dao/PersonDao.kt +++ b/app/src/main/java/com/oqba26/monthlypaymentapp/data/dao/PersonDao.kt @@ -28,6 +28,13 @@ interface PersonDao { @Query("UPDATE persons SET displayOrder = :displayOrder WHERE id = :personId") suspend fun updateDisplayOrder(personId: String, displayOrder: Int) + @Query("DELETE FROM persons WHERE id = :personId") + suspend fun deleteById(personId: String) + + /** حذف گروهی — برای اعمال [com.oqba26.monthlypaymentapp.data.sync.MergePlan] در یک کوئری. */ + @Query("DELETE FROM persons WHERE id IN (:ids)") + suspend fun deleteByIds(ids: List) + @Query("DELETE FROM persons") suspend fun deleteAll() } \ No newline at end of file diff --git a/app/src/main/java/com/oqba26/monthlypaymentapp/data/dao/SyncQueueDao.kt b/app/src/main/java/com/oqba26/monthlypaymentapp/data/dao/SyncQueueDao.kt new file mode 100644 index 0000000..cdbf881 --- /dev/null +++ b/app/src/main/java/com/oqba26/monthlypaymentapp/data/dao/SyncQueueDao.kt @@ -0,0 +1,36 @@ +package com.oqba26.monthlypaymentapp.data.dao + +import androidx.room.Dao +import androidx.room.Delete +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import com.oqba26.monthlypaymentapp.data.model.SyncQueue +import kotlinx.coroutines.flow.Flow + +@Dao +interface SyncQueueDao { + @Query("SELECT * FROM sync_queue ORDER BY createdAt ASC") + fun getAllPendingFlow(): Flow> + + @Query("SELECT * FROM sync_queue ORDER BY createdAt ASC") + suspend fun getAllPending(): List + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insert(item: SyncQueue) + + @Delete + suspend fun delete(item: SyncQueue) + + /** + * ثبت یک تلاش ناموفق. آیتم **حذف نمی‌شود** — فقط عقب می‌افتد تا دور بعد. + */ + @Query("UPDATE sync_queue SET attemptCount = :count, lastAttemptAt = :at WHERE id = :id") + suspend fun recordAttempt(id: String, count: Int, at: Long) + + @Query("DELETE FROM sync_queue WHERE entityId = :entityId") + suspend fun deleteByEntityId(entityId: String) + + @Query("DELETE FROM sync_queue") + suspend fun deleteAll() +} diff --git a/app/src/main/java/com/oqba26/monthlypaymentapp/data/database/AppDatabase.kt b/app/src/main/java/com/oqba26/monthlypaymentapp/data/database/AppDatabase.kt index cf154dc..355a402 100644 --- a/app/src/main/java/com/oqba26/monthlypaymentapp/data/database/AppDatabase.kt +++ b/app/src/main/java/com/oqba26/monthlypaymentapp/data/database/AppDatabase.kt @@ -1,61 +1,36 @@ -@file:Suppress("unused") - package com.oqba26.monthlypaymentapp.data.database -import android.content.Context import androidx.room.Database -import androidx.room.Room import androidx.room.RoomDatabase -import androidx.room.Transaction import com.oqba26.monthlypaymentapp.data.dao.PaymentDao import com.oqba26.monthlypaymentapp.data.dao.PersonDao -import com.oqba26.monthlypaymentapp.data.model.BackupData +import com.oqba26.monthlypaymentapp.data.dao.SyncQueueDao import com.oqba26.monthlypaymentapp.data.model.PaymentRecord import com.oqba26.monthlypaymentapp.data.model.Person -import java.util.UUID +import com.oqba26.monthlypaymentapp.data.model.SyncQueue +/** + * دیتابیس محلی اپ. + * + * ساخت instance اینجا انجام نمی‌شود؛ تنها مسیر ساخت، + * [com.oqba26.monthlypaymentapp.core.di.DatabaseModule] است. قبلاً علاوه بر هیلت یک + * `INSTANCE` دستی هم اینجا بود — دو مسیر ساخت یعنی ریسک دو اتصال جدا به یک فایل دیتابیس. + * + * از نسخه ۱۸، `exportSchema = true` است و اسکیما در `app/schemas/` ذخیره می‌شود. + * مهاجرت‌ها در [Migrations.kt] هستند. + */ @Database( - entities = [Person::class, PaymentRecord::class], - version = 13, // حتماً نسخه را بالا ببری که اسکیمای جدید اعمال شود - exportSchema = false + entities = [Person::class, PaymentRecord::class, SyncQueue::class], + version = 18, + exportSchema = true ) abstract class AppDatabase : RoomDatabase() { abstract fun personDao(): PersonDao abstract fun paymentDao(): PaymentDao - - @Transaction - open suspend fun performRestore(backupData: BackupData) { - // تمیزکاری قبل از درج بکاپ - val safePersons = backupData.persons.map { p -> - val name = p.name.trim() - val id = p.id.ifBlank { UUID.randomUUID().toString() } - Person(id = id, name = name) - } - personDao().deleteAll() - paymentDao().deleteAllPaymentRecords() - personDao().insertAll(safePersons) - paymentDao().insertAllPaymentRecords(backupData.payments) - } + abstract fun syncQueueDao(): SyncQueueDao companion object { - @Volatile - private var INSTANCE: AppDatabase? = null - - fun getInstance(context: Context): AppDatabase = getDatabase(context) - - private fun getDatabase(context: Context): AppDatabase { - return INSTANCE ?: synchronized(this) { - val instance = Room.databaseBuilder( - context.applicationContext, - AppDatabase::class.java, - "monthly_payment_db" - ) - .fallbackToDestructiveMigration() - .build() - INSTANCE = instance - instance - } - } + const val DATABASE_NAME = "monthly_payment_db" } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/oqba26/monthlypaymentapp/data/database/Migrations.kt b/app/src/main/java/com/oqba26/monthlypaymentapp/data/database/Migrations.kt new file mode 100644 index 0000000..2679c7c --- /dev/null +++ b/app/src/main/java/com/oqba26/monthlypaymentapp/data/database/Migrations.kt @@ -0,0 +1,33 @@ +package com.oqba26.monthlypaymentapp.data.database + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase + +/** + * مهاجرت‌های اسکیمای دیتابیس. + * + * تا نسخه ۱۷، اپ از `fallbackToDestructiveMigration()` استفاده می‌کرد؛ یعنی هر تغییر اسکیما + * کل داده‌ی کاربر را پاک می‌کرد. از نسخه ۱۸ به بعد مهاجرت اجباری است: اگر اسکیما تغییر کند + * و مهاجرتش نوشته نشده باشد، اپ در زمان بیلد/تست خطا می‌دهد — نه اینکه بی‌صدا داده را پاک کند. + * + * هنگام اضافه کردن نسخه‌ی جدید: + * ۱. `version` را در [AppDatabase] یکی زیاد کن. + * ۲. یک `MIGRATION_<قبلی>_<جدید>` اینجا بنویس و به `addMigrations` در `DatabaseModule` اضافه کن. + * ۳. فایل JSON تولیدشده در پوشه‌ی `app/schemas` را کامیت کن (مبنای تست مهاجرت است). + */ + +/** + * ۱۷ → ۱۸: دو ستون برای retry با backoff به صف سینک اضافه می‌شود. + * + * جدول‌های `persons` و `payments` دست‌نخورده‌اند، پس هیچ داده‌ی کاربری در این مهاجرت + * جابه‌جا یا بازنویسی نمی‌شود. + */ +val MIGRATION_17_18 = object : Migration(17, 18) { + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE sync_queue ADD COLUMN attemptCount INTEGER NOT NULL DEFAULT 0") + db.execSQL("ALTER TABLE sync_queue ADD COLUMN lastAttemptAt INTEGER NOT NULL DEFAULT 0") + } +} + +/** همه‌ی مهاجرت‌های شناخته‌شده — به `Room.databaseBuilder(...).addMigrations(*ALL_MIGRATIONS)` داده می‌شود. */ +val ALL_MIGRATIONS = arrayOf(MIGRATION_17_18) diff --git a/app/src/main/java/com/oqba26/monthlypaymentapp/data/model/PaymentRecord.kt b/app/src/main/java/com/oqba26/monthlypaymentapp/data/model/PaymentRecord.kt index 9c347b2..04e7cb3 100644 --- a/app/src/main/java/com/oqba26/monthlypaymentapp/data/model/PaymentRecord.kt +++ b/app/src/main/java/com/oqba26/monthlypaymentapp/data/model/PaymentRecord.kt @@ -26,5 +26,10 @@ data class PaymentRecord( @SerialName("description") val description: String? = "", // ⭐️ تغییر به String? برای پذیرش مقادیر null - @SerialName("createdAt") val createdAt: Long? = null + @SerialName("createdAt") val createdAt: Long? = null, + + @SerialName("category") val category: String = "salary", + + @kotlinx.serialization.Transient + val needsSync: Boolean = false ) diff --git a/app/src/main/java/com/oqba26/monthlypaymentapp/data/model/Person.kt b/app/src/main/java/com/oqba26/monthlypaymentapp/data/model/Person.kt index 26b0685..32f5501 100644 --- a/app/src/main/java/com/oqba26/monthlypaymentapp/data/model/Person.kt +++ b/app/src/main/java/com/oqba26/monthlypaymentapp/data/model/Person.kt @@ -21,5 +21,18 @@ data class Person( @SerialName("createdAt") val createdAt: Long? = 0L, - @SerialName("phoneNumber") val phoneNumber: String? = "" + @SerialName("phoneNumber") val phoneNumber: String? = "", + + @SerialName("category") val category: String = "salary", + + @SerialName("isAnonymous") val isAnonymous: Boolean = false, + + @SerialName("monthlyCommitment") val monthlyCommitment: Double = 0.0, + + @SerialName("startMonth") val startMonth: Int = 1, + + @SerialName("startYear") val startYear: Int = 1403, + + @kotlinx.serialization.Transient + val needsSync: Boolean = false ) diff --git a/app/src/main/java/com/oqba26/monthlypaymentapp/data/model/SyncQueue.kt b/app/src/main/java/com/oqba26/monthlypaymentapp/data/model/SyncQueue.kt new file mode 100644 index 0000000..87795e0 --- /dev/null +++ b/app/src/main/java/com/oqba26/monthlypaymentapp/data/model/SyncQueue.kt @@ -0,0 +1,28 @@ +package com.oqba26.monthlypaymentapp.data.model + +import androidx.room.Entity +import androidx.room.PrimaryKey +import java.util.UUID + +enum class SyncOperation { + INSERT, UPDATE, DELETE +} + +enum class SyncType { + PERSON, PAYMENT +} + +@Entity(tableName = "sync_queue") +data class SyncQueue( + @PrimaryKey val id: String = UUID.randomUUID().toString(), + val entityId: String, + val type: SyncType, + val operation: SyncOperation, + val createdAt: Long = System.currentTimeMillis(), + + /** تعداد تلاش‌های ناموفق. مبنای محاسبه‌ی فاصله‌ی تلاش بعدی (backoff) است. */ + val attemptCount: Int = 0, + + /** زمان آخرین تلاش (میلی‌ثانیه). صفر یعنی هنوز تلاشی نشده. */ + val lastAttemptAt: Long = 0L +) diff --git a/app/src/main/java/com/oqba26/monthlypaymentapp/data/remote/ApiClient.kt b/app/src/main/java/com/oqba26/monthlypaymentapp/data/remote/ApiClient.kt index 210e84f..6adf906 100644 --- a/app/src/main/java/com/oqba26/monthlypaymentapp/data/remote/ApiClient.kt +++ b/app/src/main/java/com/oqba26/monthlypaymentapp/data/remote/ApiClient.kt @@ -25,9 +25,9 @@ object ApiClient { httpConfig { install(HttpTimeout) { - requestTimeoutMillis = 30000 - connectTimeoutMillis = 30000 - socketTimeoutMillis = 30000 + requestTimeoutMillis = 60000 + connectTimeoutMillis = 60000 + socketTimeoutMillis = 60000 } } diff --git a/app/src/main/java/com/oqba26/monthlypaymentapp/data/repository/LocalPersonRepository.kt b/app/src/main/java/com/oqba26/monthlypaymentapp/data/repository/LocalPersonRepository.kt index 281f931..184649f 100644 --- a/app/src/main/java/com/oqba26/monthlypaymentapp/data/repository/LocalPersonRepository.kt +++ b/app/src/main/java/com/oqba26/monthlypaymentapp/data/repository/LocalPersonRepository.kt @@ -5,17 +5,22 @@ package com.oqba26.monthlypaymentapp.data.repository import androidx.room.withTransaction import com.oqba26.monthlypaymentapp.data.dao.PaymentDao import com.oqba26.monthlypaymentapp.data.dao.PersonDao +import com.oqba26.monthlypaymentapp.data.dao.SyncQueueDao import com.oqba26.monthlypaymentapp.data.database.AppDatabase import com.oqba26.monthlypaymentapp.data.model.BackupData import com.oqba26.monthlypaymentapp.data.model.PaymentRecord import com.oqba26.monthlypaymentapp.data.model.Person +import com.oqba26.monthlypaymentapp.data.model.SyncQueue +import com.oqba26.monthlypaymentapp.data.model.SyncOperation +import com.oqba26.monthlypaymentapp.data.model.SyncType +import com.oqba26.monthlypaymentapp.data.sync.SyncMerger import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.first import java.util.UUID class LocalPersonRepository( private val personDao: PersonDao, private val paymentDao: PaymentDao, + private val syncQueueDao: SyncQueueDao, private val database: AppDatabase ) { // Reads @@ -25,53 +30,35 @@ class LocalPersonRepository( fun getPaymentsForPersonFlow(personId: String): Flow> = paymentDao.getPaymentsForPersonFlow(personId) - suspend fun syncPersons(persons: List) { - val current = getAllPersonsFlow().first() - val currentByName = current.associateBy { normalize(it.name) } - - val safePersons = persons.map { p -> - val name = p.name.trim() - val norm = normalize(name) - val resolvedId = when { - p.id.isNotBlank() -> p.id - currentByName.containsKey(norm) -> currentByName[norm]!!.id - else -> UUID.randomUUID().toString() - } - p.copy(id = resolvedId, name = name) - } - - personDao.insertAll(safePersons) - } - - suspend fun syncPayments(payments: List) = - paymentDao.insertAllPaymentRecords(payments) - - // ⭐️ تابع جدید و اصلاح شده برای همگام‌سازی کامل - suspend fun syncAll(persons: List, payments: List) { + /** + * ادغام غیرمخربِ داده‌ی سرور با داده‌ی محلی. + * + * جانشین `syncAll` قبلی است که اول `deleteAll()` می‌زد و بعد داده‌ی سرور را درج می‌کرد؛ + * آن رفتار هر تغییر محلیِ هنوز-ارسال-نشده را نابود می‌کرد. + * + * اینجا هیچ `deleteAll`ی وجود ندارد. تصمیمِ «چه چیزی حذف شود» را [SyncMerger] می‌گیرد + * (که تست واحد دارد) و رکوردهای موجود در صف سینک یا دارای `needsSync` را کنار می‌گذارد. + * + * فقط با داده‌ی **معتبرِ** سرور صدا زده شود — یعنی بعد از اینکه + * [com.oqba26.monthlypaymentapp.data.repository.NetworkRepository.refresh] مقدار `true` برگردانده باشد. + */ + suspend fun mergeFromServer(persons: List, payments: List) { database.withTransaction { + // خواندن با تابع suspend و نه collect کردن Flow: + // collect کردن Flow روم داخل تراکنش خطر deadlock دارد. + val localPersons = personDao.getAllPersons() + val localPayments = paymentDao.getAllPayments() + val pendingIds = syncQueueDao.getAllPending().map { it.entityId }.toSet() - // ۱. اجرای منطق حل شناسه (ID Resolution) - val current = getAllPersonsFlow().first() - val currentByName = current.associateBy { normalize(it.name) } + val personPlan = SyncMerger.planPersons(localPersons, persons, pendingIds) + val paymentPlan = SyncMerger.planPayments(localPayments, payments, pendingIds) - val safePersons = persons.map { p -> - val name = p.name.trim() - val norm = normalize(name) - val resolvedId = when { - p.id.isNotBlank() -> p.id - currentByName.containsKey(norm) -> currentByName[norm]!!.id - else -> UUID.randomUUID().toString() - } - p.copy(id = resolvedId, name = name) - } + // پرداخت‌ها اول حذف می‌شوند تا اگر کلید خارجی اضافه شد، ترتیب درست باشد. + if (paymentPlan.deleteIds.isNotEmpty()) paymentDao.deleteByIds(paymentPlan.deleteIds) + if (personPlan.deleteIds.isNotEmpty()) personDao.deleteByIds(personPlan.deleteIds) - // ۲. حذف کامل تمام داده‌های محلی - personDao.deleteAll() - paymentDao.deleteAllPaymentRecords() - - // ۳. درج داده‌های جدید - personDao.insertAll(safePersons) - paymentDao.insertAllPaymentRecords(payments) + if (personPlan.upserts.isNotEmpty()) personDao.insertAll(personPlan.upserts) + if (paymentPlan.upserts.isNotEmpty()) paymentDao.insertAllPaymentRecords(paymentPlan.upserts) } } @@ -82,21 +69,105 @@ class LocalPersonRepository( return BackupData(persons = persons, payments = payments) } + /** + * بازگردانی کامل از یک نسخه‌ی پشتیبان. + * + * برخلاف [mergeFromServer]، این عملیات عمداً مخرب است — کاربر صریحاً خواسته وضعیت فعلی + * با نسخه‌ی پشتیبان جایگزین شود. صدازننده باید قبلش snapshot بگیرد + * ([com.oqba26.monthlypaymentapp.core.manager.BackupManager.restoreSnapshot] این کار را می‌کند). + * + * صف سینک هم بازسازی می‌شود. قبلاً دست‌نخورده می‌ماند و دو مشکل داشت: + * - آیتم‌های قدیمی به رکوردهای پاک‌شده اشاره می‌کردند. + * - داده‌ی بازیابی‌شده هرگز به سرور نمی‌رفت، پس اولین سینکِ بعدی دوباره پاکش می‌کرد. + */ suspend fun restoreBackup(backupData: BackupData) { database.withTransaction { personDao.deleteAll() paymentDao.deleteAllPaymentRecords() + syncQueueDao.deleteAll() val safePersons = backupData.persons.map { p -> val name = p.name.trim() val id = p.id.ifBlank { UUID.randomUUID().toString() } - p.copy(id = id, name = name) + p.copy(id = id, name = name, needsSync = true) } + val safePayments = backupData.payments.map { it.copy(needsSync = true) } personDao.insertAll(safePersons) - paymentDao.insertAllPaymentRecords(backupData.payments) + paymentDao.insertAllPaymentRecords(safePayments) + + // همه‌ی رکوردهای بازیابی‌شده باید دوباره به سرور برسند. + safePersons.forEach { + syncQueueDao.insert( + SyncQueue(entityId = it.id, type = SyncType.PERSON, operation = SyncOperation.INSERT) + ) + } + safePayments.forEach { + syncQueueDao.insert( + SyncQueue(entityId = it.id, type = SyncType.PAYMENT, operation = SyncOperation.INSERT) + ) + } } } - private fun normalize(name: String): String = name.trim().lowercase() + // Local-First Write Operations + suspend fun insertPersonLocally(person: Person) { + database.withTransaction { + personDao.insertAll(listOf(person.copy(needsSync = true))) + syncQueueDao.insert(SyncQueue(entityId = person.id, type = SyncType.PERSON, operation = SyncOperation.INSERT)) + } + } + + suspend fun updatePersonLocally(person: Person) { + database.withTransaction { + personDao.insertAll(listOf(person.copy(needsSync = true))) + syncQueueDao.insert(SyncQueue(entityId = person.id, type = SyncType.PERSON, operation = SyncOperation.UPDATE)) + } + } + + suspend fun archivePersonLocally(personId: String, isArchived: Boolean) { + database.withTransaction { + personDao.updateArchivedStatus(personId, isArchived) + syncQueueDao.insert(SyncQueue(entityId = personId, type = SyncType.PERSON, operation = SyncOperation.UPDATE)) + } + } + + suspend fun deletePersonLocally(personId: String) { + database.withTransaction { + personDao.deleteById(personId) + syncQueueDao.insert(SyncQueue(entityId = personId, type = SyncType.PERSON, operation = SyncOperation.DELETE)) + } + } + + suspend fun insertPaymentLocally(payment: PaymentRecord) { + database.withTransaction { + paymentDao.insertAllPaymentRecords(listOf(payment.copy(needsSync = true))) + syncQueueDao.insert(SyncQueue(entityId = payment.id, type = SyncType.PAYMENT, operation = SyncOperation.INSERT)) + } + } + + suspend fun deletePaymentLocally(paymentId: String) { + database.withTransaction { + paymentDao.deleteById(paymentId) + syncQueueDao.insert(SyncQueue(entityId = paymentId, type = SyncType.PAYMENT, operation = SyncOperation.DELETE)) + } + } + + /** خواندن یک‌باره (نه Flow) — برای استفاده داخل تراکنش و در [com.oqba26.monthlypaymentapp.data.sync.SyncManager]. */ + suspend fun getAllPersons(): List = personDao.getAllPersons() + suspend fun getAllPayments(): List = paymentDao.getAllPayments() + + fun getSyncQueueFlow() = syncQueueDao.getAllPendingFlow() + suspend fun getPendingSyncItems() = syncQueueDao.getAllPending() + suspend fun removeSyncItem(item: SyncQueue) = syncQueueDao.delete(item) + + /** ثبت تلاش ناموفق روی آیتم صف — آیتم نگه داشته می‌شود تا دور بعد. */ + suspend fun recordSyncAttempt(itemId: String, attemptCount: Int, at: Long) = + syncQueueDao.recordAttempt(itemId, attemptCount, at) + + /** حذف چند آیتم صف با هم — برای زمانی که coalesce آن‌ها را بی‌اثر تشخیص می‌دهد. */ + suspend fun removeSyncItems(items: List) { + database.withTransaction { items.forEach { syncQueueDao.delete(it) } } + } + suspend fun clearSyncQueue() = syncQueueDao.deleteAll() } \ No newline at end of file diff --git a/app/src/main/java/com/oqba26/monthlypaymentapp/data/repository/NetworkRepository.kt b/app/src/main/java/com/oqba26/monthlypaymentapp/data/repository/NetworkRepository.kt index 4671669..a203280 100644 --- a/app/src/main/java/com/oqba26/monthlypaymentapp/data/repository/NetworkRepository.kt +++ b/app/src/main/java/com/oqba26/monthlypaymentapp/data/repository/NetworkRepository.kt @@ -20,6 +20,8 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.put import java.util.UUID class NetworkRepository { @@ -29,20 +31,31 @@ class NetworkRepository { private val _paymentsFlow = MutableStateFlow>(emptyList()) private val repositoryScope = CoroutineScope(Dispatchers.IO) + /** + * تا وقتی حداقل یک [refresh] موفق انجام نشده باشد `false` است. + * + * چرا لازم است: مقدار اولیه‌ی `_personsFlow`/`_paymentsFlow` لیست خالی است و از لیست خالیِ + * واقعی (کاربری که هنوز داده‌ای ثبت نکرده) قابل تشخیص نیست. merge کردن روی آن لیست خالیِ + * قالبی، همه‌ی داده‌ی محلی را پاک می‌کند. مصرف‌کننده باید منتظر `true` بماند. + */ + private val _hasServerData = MutableStateFlow(false) + fun hasServerDataFlow(): Flow = _hasServerData.asStateFlow() + // --- Realtime Operations --- fun observeRealtimeChanges() { repositoryScope.launch { try { + // اطمینان از اتصال قبل از اشتراک در کانال supabase.realtime.connect() + val myChannel = supabase.channel("db-changes") // گوش دادن به تغییرات جدول اشخاص - launch { - myChannel.postgresChangeFlow(schema = "public") { - table = "persons" - }.collect { - refresh() - } + myChannel.postgresChangeFlow(schema = "public") { + table = "persons" + }.collect { + Log.d("Supabase-Realtime", "Change detected in persons table") + refresh() } // گوش دادن به تغییرات جدول پرداخت‌ها @@ -50,13 +63,15 @@ class NetworkRepository { myChannel.postgresChangeFlow(schema = "public") { table = "payments" }.collect { + Log.d("Supabase-Realtime", "Change detected in payments table") refresh() } } myChannel.subscribe() + Log.d("Supabase-Realtime", "Subscribed to all changes") } catch (e: Exception) { - Log.e("Supabase", "Realtime connection error", e) + Log.e("Supabase-Realtime", "Realtime connection error, retrying...", e) } } } @@ -91,26 +106,36 @@ class NetworkRepository { } // --- Person Operations --- - suspend fun addPerson(person: Person): Int? { + /** + * ارسال شخص به سرور با upsert. + * + * upsert (و نه insert) عمداً انتخاب شده تا این عملیات idempotent باشد: اگر تلاش اول + * به سرور رسیده باشد ولی پاسخش به‌خاطر timeout به ما نرسیده باشد، تلاش دوم رکورد + * تکراری نمی‌سازد. صف سینک متکی به همین خاصیت است تا بتواند بی‌خطر retry کند. + * + * اعتبارسنجی تکراری‌بودن نام اینجا انجام نمی‌شود؛ آن کار در PersonViewModel روی + * داده‌ی محلی (که همیشه کامل است) انجام می‌گیرد. کش _personsFlow ممکن است خالی یا + * قدیمی باشد و نتیجه‌ی غلط بدهد. + */ + suspend fun addPerson(person: Person): Boolean { return try { val trimmedName = person.name.trim() - if (trimmedName.isEmpty()) return 400 + if (trimmedName.isEmpty()) { + Log.w("Supabase", "addPerson skipped: blank name") + return false + } - // بررسی تکراری بودن نام در لیست موجود - val isDuplicate = _personsFlow.value.any { it.name.trim().equals(trimmedName, ignoreCase = true) } - if (isDuplicate) return 409 - - val personToInsert = if (person.id.isEmpty()) { + val personToUpsert = if (person.id.isEmpty()) { person.copy(id = UUID.randomUUID().toString(), name = trimmedName, createdAt = System.currentTimeMillis()) } else { person.copy(name = trimmedName) } - - supabase.from("persons").insert(personToInsert) - 201 + + supabase.from("persons").upsert(personToUpsert) + true } catch (e: Exception) { Log.e("Supabase", "addPerson error", e) - null + false } } @@ -125,22 +150,37 @@ class NetworkRepository { } } - suspend fun updatePerson(personId: String, name: String, phoneNumber: String?): Int { + suspend fun updatePerson( + personId: String, + name: String, + phoneNumber: String?, + monthlyCommitment: Double, + startMonth: Int, + startYear: Int + ): Int { return try { val trimmedName = name.trim() if (trimmedName.isEmpty()) return 400 - // بررسی تکراری بودن نام (به جز خودِ این شخص) + val currentPerson = _personsFlow.value.find { it.id == personId } + val currentCategory = currentPerson?.category ?: "salary" + + // بررسی تکراری بودن نام در همان دسته‌بندی (به جز خودِ این شخص) val isDuplicate = _personsFlow.value.any { - it.id != personId && it.name.trim().equals(trimmedName, ignoreCase = true) + it.id != personId && + it.category == currentCategory && + it.name.trim().equals(trimmedName, ignoreCase = true) } if (isDuplicate) return 409 supabase.from("persons").update( - mapOf( - "name" to trimmedName, - "phoneNumber" to phoneNumber?.trim() - ) + buildJsonObject { + put("name", trimmedName) + put("phoneNumber", phoneNumber?.trim()) + put("monthlyCommitment", monthlyCommitment) + put("startMonth", startMonth) + put("startYear", startYear) + }, ) { filter { eq("id", personId) } } @@ -154,7 +194,9 @@ class NetworkRepository { suspend fun updatePersonArchivedStatus(personId: String, isArchived: Boolean): Boolean { return try { supabase.from("persons").update( - mapOf("isArchived" to isArchived) + buildJsonObject { + put("isArchived", isArchived) + } ) { filter { eq("id", personId) } } @@ -167,7 +209,9 @@ class NetworkRepository { suspend fun updatePersonDisplayOrder(personId: String, displayOrder: Long): Boolean { return try { supabase.from("persons").update( - mapOf("displayOrder" to displayOrder) + buildJsonObject { + put("displayOrder", displayOrder) + } ) { filter { eq("id", personId) } } @@ -180,9 +224,14 @@ class NetworkRepository { fun getPersonsFlow(): Flow> = _personsFlow.asStateFlow() // --- Payment Operations --- + /** + * ارسال پرداخت به سرور با upsert — به همان دلیل idempotent بودن که در [addPerson] توضیح داده شد. + * ضمناً همین باعث می‌شود ویرایش پرداخت (SyncOperation.UPDATE) هم درست کار کند؛ قبلاً insert + * بود و روی رکورد موجود شکست می‌خورد. + */ suspend fun addPayment(paymentRecord: PaymentRecord): Boolean { return try { - supabase.from("payments").insert(paymentRecord) + supabase.from("payments").upsert(paymentRecord) true } catch (e: Exception) { Log.e("Supabase", "addPayment error", e); false @@ -190,33 +239,56 @@ class NetworkRepository { } suspend fun deletePayment(paymentId: String): Boolean { - return try { - supabase.from("payments").delete { - filter { eq("id", paymentId) } + var retries = 0 + val maxRetries = 2 + + while (retries <= maxRetries) { + try { + supabase.from("payments").delete { + filter { eq("id", paymentId) } + } + Log.d("Supabase", "deletePayment Success: $paymentId") + return true + } catch (e: Exception) { + retries++ + Log.e("Supabase", "deletePayment error (Attempt $retries): ${e.message}", e) + if (retries > maxRetries) return false + kotlinx.coroutines.delay(1000L * retries) // Exponential backoff-ish } - true - } catch (e: Exception) { - Log.e("Supabase", "deletePayment error", e); false } + return false } fun getPaymentsFlow(): Flow> = _paymentsFlow.asStateFlow() - suspend fun refresh() { - try { - // دریافت و تبدیل خودکار لیست اشخاص + /** + * واکشی کامل داده‌ها از سرور. + * + * @return true فقط اگر **هر دو** واکشی موفق بوده باشند. + * + * دو تضمین مهم که فراخوان‌ها به آن تکیه می‌کنند: + * ۱. در صورت شکست، flow‌ها **دست‌نخورده** می‌مانند. قبلاً خطا بلعیده می‌شد و صدازننده + * نمی‌فهمید واکشی شکست خورده؛ نتیجه‌اش merge روی داده‌ی ناقص و پاک شدن داده‌ی محلی بود. + * ۲. مقداردهی flow‌ها اتمیک است — یا هر دو با هم به‌روز می‌شوند یا هیچ‌کدام. قبلاً اگر + * واکشی پرداخت‌ها شکست می‌خورد، لیست اشخاص جدید کنار لیست پرداخت‌های قدیمی می‌نشست. + */ + suspend fun refresh(): Boolean { + return try { val persons = supabase.from("persons").select().decodeList() - _personsFlow.value = persons - - // دریافت و تبدیل خودکار لیست پرداخت‌ها val payments = supabase.from("payments").select { order("timestamp", order = Order.DESCENDING) }.decodeList() + + // فقط بعد از موفقیت هر دو واکشی مقداردهی می‌کنیم + _personsFlow.value = persons _paymentsFlow.value = payments - + _hasServerData.value = true + Log.d("Supabase", "Refresh Success: ${persons.size} persons, ${payments.size} payments") + true } catch (e: Exception) { - Log.e("Supabase", "refresh error", e) + Log.e("Supabase", "refresh error — داده‌های محلی دست‌نخورده باقی می‌مانند", e) + false } } } diff --git a/app/src/main/java/com/oqba26/monthlypaymentapp/data/repository/SettingsRepository.kt b/app/src/main/java/com/oqba26/monthlypaymentapp/data/repository/SettingsRepository.kt index 722688b..635e823 100644 --- a/app/src/main/java/com/oqba26/monthlypaymentapp/data/repository/SettingsRepository.kt +++ b/app/src/main/java/com/oqba26/monthlypaymentapp/data/repository/SettingsRepository.kt @@ -5,9 +5,11 @@ import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.doublePreferencesKey import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.intPreferencesKey import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.preferencesDataStore import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map private val Context.dataStore: DataStore by preferencesDataStore(name = "settings") @@ -25,10 +27,19 @@ class SettingsRepository(context: Context) { val CARD_NUMBERS = stringPreferencesKey("card_numbers") val IGNORED_CONTACT_SUGGESTIONS = stringPreferencesKey("ignored_contact_suggestions") + /** آخرین versionCodeی که با آن اجرا شده — مبنای تشخیص ارتقای نسخه برای گرفتن snapshot. */ + val LAST_RUN_VERSION_CODE = intPreferencesKey("last_run_version_code") + const val FALLBACK_AMOUNT = 200000.0 const val DEFAULT_FONT = "Estedad" } + suspend fun getLastRunVersionCode(): Int? = dataStore.data.first()[LAST_RUN_VERSION_CODE] + + suspend fun saveLastRunVersionCode(versionCode: Int) { + dataStore.edit { it[LAST_RUN_VERSION_CODE] = versionCode } + } + val reminderDayFlow: Flow = dataStore.data.map { it[REMINDER_DAY]?.toIntOrNull() } diff --git a/app/src/main/java/com/oqba26/monthlypaymentapp/data/sync/SyncManager.kt b/app/src/main/java/com/oqba26/monthlypaymentapp/data/sync/SyncManager.kt new file mode 100644 index 0000000..c171915 --- /dev/null +++ b/app/src/main/java/com/oqba26/monthlypaymentapp/data/sync/SyncManager.kt @@ -0,0 +1,276 @@ +package com.oqba26.monthlypaymentapp.data.sync + +import android.content.Context +import android.net.ConnectivityManager +import android.net.Network +import android.net.NetworkCapabilities +import android.net.NetworkRequest +import android.util.Log +import com.oqba26.monthlypaymentapp.data.model.SyncOperation +import com.oqba26.monthlypaymentapp.data.model.SyncQueue +import com.oqba26.monthlypaymentapp.data.model.SyncType +import com.oqba26.monthlypaymentapp.data.repository.LocalPersonRepository +import com.oqba26.monthlypaymentapp.data.repository.NetworkRepository +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import javax.inject.Inject +import javax.inject.Singleton + +/** + * ارسال تغییرات محلی به سرور. + * + * ## چه چیزی نسبت به نسخه‌ی قبل عوض شد + * + * ۱. **حلقه دیگر خودش را cancel نمی‌کند.** نسخه‌ی قبلی `collectLatest` روی Flow صف بود، ولی + * پردازش صف خودش صف را تغییر می‌دهد → Flow دوباره emit می‌کند → `collectLatest` پردازشِ + * در جریان را لغو می‌کند. اینجا Flow فقط نقش «زنگ» را دارد و پردازش در یک حلقه‌ی + * تک‌مصرف‌کننده با [Mutex] انجام می‌شود. + * + * ۲. **هیچ آیتمی بدون رسیدن به سرور حذف نمی‌شود.** قبلاً اگر رکورد محلی پیدا نمی‌شد، + * `else true` برمی‌گشت و آیتم به‌عنوان «موفق» از صف پاک می‌شد — یعنی داده‌ی کاربر بی‌صدا + * گم می‌شد. حالا آیتم نگه داشته می‌شود و تلاشش ثبت می‌گردد. + * + * ۳. **retry با backoff.** قبلاً اولین شکست `break` می‌زد و تا trigger بعدی هیچ کاری نمی‌شد؛ + * آفلاین که بودی، هیچ‌وقت خودش دوباره تلاش نمی‌کرد. حالا هر آیتم شکست‌خورده عقب می‌افتد + * ولی حلقه بقیه را ادامه می‌دهد، و برگشتن شبکه خودش یک trigger است. + * + * ۴. **coalesce.** چند عمل روی یک رکورد به یک عمل نهایی تبدیل می‌شود؛ این همان چیزی است که + * حالت متناقض «آیتم صف هست ولی رکورد محلی نیست» را از بین می‌برد. + */ +@Singleton +class SyncManager @Inject constructor( + private val localRepository: LocalPersonRepository, + private val networkRepository: NetworkRepository, + @ApplicationContext private val context: Context +) { + private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob()) + + /** + * زنگ شروع پردازش. `CONFLATED` است چون چند زنگ پشت‌سرهم معنایی جز «یک بار دیگر بررسی کن» + * ندارند؛ صف‌کردنشان فقط کار تکراری می‌سازد. + */ + private val trigger = Channel(Channel.CONFLATED) + + /** تضمین می‌کند فقط یک پردازش همزمان در جریان باشد. */ + private val mutex = Mutex() + + @Volatile + private var started = false + + fun startSync() { + if (started) return + started = true + + // مصرف‌کننده‌ی تنها + scope.launch { + for (ignored in trigger) { + mutex.withLock { + try { + processQueue() + } catch (e: Exception) { + // حلقه هرگز نباید بمیرد؛ وگرنه سینک تا ری‌استارت اپ متوقف می‌ماند. + Log.e(TAG, "خطای غیرمنتظره در پردازش صف", e) + } + } + } + } + + // زنگ ۱: هر تغییر در صف + scope.launch { + localRepository.getSyncQueueFlow().collect { queue -> + if (queue.isNotEmpty()) trigger.trySend(Unit) + } + } + + // زنگ ۲: برگشتن اینترنت + registerNetworkCallback() + + // زنگ ۳: خودِ راه‌اندازی (آیتم‌های باقی‌مانده از اجرای قبلی) + trigger.trySend(Unit) + } + + private fun registerNetworkCallback() { + try { + val cm = context.getSystemService(ConnectivityManager::class.java) ?: return + val request = NetworkRequest.Builder() + .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + .build() + cm.registerNetworkCallback(request, object : ConnectivityManager.NetworkCallback() { + override fun onAvailable(network: Network) { + Log.d(TAG, "شبکه برگشت — تلاش دوباره برای سینک") + trigger.trySend(Unit) + } + }) + } catch (e: Exception) { + // نبود این زنگ فقط باعث می‌شود سینک دیرتر (با زنگ‌های دیگر) اتفاق بیفتد. + Log.e(TAG, "ثبت NetworkCallback ناموفق بود", e) + } + } + + private suspend fun processQueue() { + val raw = localRepository.getPendingSyncItems() + if (raw.isEmpty()) return + + val (effective, obsolete) = coalesce(raw) + + // آیتم‌هایی که coalesce بی‌اثر تشخیص داد (مثلاً INSERT بعدش DELETE، یا UPDATEهای + // قدیمی‌ترِ یک رکورد) هرگز به سرور نمی‌رسند چون لازم نیست برسند. + if (obsolete.isNotEmpty()) { + localRepository.removeSyncItems(obsolete) + Log.d(TAG, "${obsolete.size} آیتم تکراری/بی‌اثر از صف حذف شد") + } + + val now = System.currentTimeMillis() + val persons = localRepository.getAllPersons().associateBy { it.id } + val payments = localRepository.getAllPayments().associateBy { it.id } + var failed = false + + for (item in effective) { + if (!isDue(item, now)) continue + + val success = try { + when (item.type) { + SyncType.PERSON -> syncPerson(item, persons[item.entityId]) + SyncType.PAYMENT -> syncPayment(item, payments[item.entityId]) + } + } catch (e: Exception) { + Log.e(TAG, "خطا در سینک آیتم ${item.id}", e) + false + } + + if (success) { + localRepository.removeSyncItem(item) + } else { + // آیتم نگه داشته می‌شود. `break` نمی‌زنیم: شکست یک رکورد نباید بقیه را + // معطل کند (مثلاً یک رکورد خرابِ همیشه-ناموفق کل صف را قفل می‌کرد). + localRepository.recordSyncAttempt(item.id, item.attemptCount + 1, now) + Log.w(TAG, "سینک آیتم ${item.id} ناموفق (تلاش ${item.attemptCount + 1}) — در صف می‌ماند") + failed = true + } + } + + // اگر چیزی شکست خورد، خودمان زنگ بعدی را کوک می‌کنیم. بدون این، آیتمِ شکست‌خورده + // تا تغییر بعدیِ صف یا برگشتن شبکه معطل می‌ماند — و در حالتی که شبکه وصل است ولی + // سرور خطا می‌دهد، هیچ‌کدام از آن دو رخ نمی‌دهد. + if (failed) scheduleRetry() + } + + /** یک زنگ تأخیری برای آیتم‌های شکست‌خورده. */ + private fun scheduleRetry() { + scope.launch { + delay(RETRY_TICK_MS) + trigger.trySend(Unit) + } + } + + /** + * آیا زمان تلاش بعدیِ این آیتم رسیده؟ + * + * آیتم تازه (`lastAttemptAt == 0`) همیشه due است. آیتم شکست‌خورده به اندازه‌ی + * [backoffMillis] صبر می‌کند تا در حالت آفلاین، لاگ و مصرف باتری منفجر نشود. + */ + private fun isDue(item: SyncQueue, now: Long): Boolean = + item.lastAttemptAt == 0L || now - item.lastAttemptAt >= backoffMillis(item.attemptCount) + + /** ۲ ثانیه، ۴، ۸، ... تا سقف ۵ دقیقه. */ + private fun backoffMillis(attemptCount: Int): Long { + if (attemptCount <= 0) return 0L + val exponent = attemptCount.coerceAtMost(8) // جلوگیری از سرریز + val delay = BASE_BACKOFF_MS shl (exponent - 1) + return delay.coerceAtMost(MAX_BACKOFF_MS) + } + + /** + * چند عمل روی یک رکورد را به یک عمل نهایی تبدیل می‌کند. + * + * قواعد (به ترتیب زمانی روی هر `entityId`): + * - `INSERT` سپس `DELETE` → **هیچ‌کدام**. رکورد هرگز به سرور نرفته، پس چیزی برای حذف نیست. + * - `INSERT` سپس `UPDATE` → `INSERT` (که با upsert همان حالت نهایی را می‌فرستد). + * - چند `UPDATE` → آخری. حالت فعلیِ رکورد فرستاده می‌شود، پس میانی‌ها بی‌معنی‌اند. + * - `DELETE` آخرین عمل باشد → `DELETE` و همه‌ی عمل‌های قبلی بی‌اثر می‌شوند. + * + * @return جفتِ (آیتم‌هایی که باید پردازش شوند، آیتم‌هایی که باید بی‌پردازش حذف شوند) + */ + private fun coalesce(items: List): Pair, List> { + val effective = mutableListOf() + val obsolete = mutableListOf() + + items.groupBy { it.entityId to it.type }.forEach { (_, group) -> + val ordered = group.sortedBy { it.createdAt } + val last = ordered.last() + val hadInsert = ordered.any { it.operation == SyncOperation.INSERT } + + when { + // ساخته و بعد پاک شد، بدون اینکه هرگز به سرور برسد → کل ماجرا بی‌اثر است. + last.operation == SyncOperation.DELETE && hadInsert -> obsolete += ordered + + // ساخته شد (و شاید بعد ویرایش) → یک INSERT کافی است؛ upsert حالت فعلی را می‌فرستد. + hadInsert -> { + val insert = ordered.first { it.operation == SyncOperation.INSERT } + effective += insert + obsolete += ordered.filter { it.id != insert.id } + } + + // فقط UPDATE/DELETE → آخری حرف آخر را می‌زند. + else -> { + effective += last + obsolete += ordered.filter { it.id != last.id } + } + } + } + + return effective.sortedBy { it.createdAt } to obsolete + } + + private suspend fun syncPerson( + item: SyncQueue, + person: com.oqba26.monthlypaymentapp.data.model.Person? + ): Boolean = when (item.operation) { + SyncOperation.DELETE -> networkRepository.deletePersonAndPayments(item.entityId) + + // INSERT و UPDATE هر دو upsert می‌شوند: idempotent است، پس retry بی‌خطر است و + // خطای ۴۰۹ (تکراری بودن) هم دیگر رخ نمی‌دهد. + SyncOperation.INSERT, SyncOperation.UPDATE -> { + if (person == null) { + // حالت متناقض. قبلاً اینجا `true` برمی‌گشت و آیتم پاک می‌شد؛ نتیجه‌اش + // گم شدن دائمی داده بود. حالا آیتم می‌ماند تا وضعیت قابل بررسی باشد. + Log.e(TAG, "رکورد محلی شخص ${item.entityId} پیدا نشد — آیتم در صف نگه داشته شد") + false + } else { + networkRepository.addPerson(person) + } + } + } + + private suspend fun syncPayment( + item: SyncQueue, + payment: com.oqba26.monthlypaymentapp.data.model.PaymentRecord? + ): Boolean = when (item.operation) { + SyncOperation.DELETE -> networkRepository.deletePayment(item.entityId) + + SyncOperation.INSERT, SyncOperation.UPDATE -> { + if (payment == null) { + Log.e(TAG, "رکورد محلی پرداخت ${item.entityId} پیدا نشد — آیتم در صف نگه داشته شد") + false + } else { + // upsert است، پس ویرایش پرداخت هم درست کار می‌کند؛ قبلاً insert بود و + // روی رکورد موجود شکست می‌خورد. + networkRepository.addPayment(payment) + } + } + } + + private companion object { + const val TAG = "SyncManager" + const val BASE_BACKOFF_MS = 2_000L + const val MAX_BACKOFF_MS = 5 * 60 * 1000L + const val RETRY_TICK_MS = 15_000L + } +} diff --git a/app/src/main/java/com/oqba26/monthlypaymentapp/data/sync/SyncMerger.kt b/app/src/main/java/com/oqba26/monthlypaymentapp/data/sync/SyncMerger.kt new file mode 100644 index 0000000..df8d911 --- /dev/null +++ b/app/src/main/java/com/oqba26/monthlypaymentapp/data/sync/SyncMerger.kt @@ -0,0 +1,105 @@ +package com.oqba26.monthlypaymentapp.data.sync + +import com.oqba26.monthlypaymentapp.data.model.PaymentRecord +import com.oqba26.monthlypaymentapp.data.model.Person +import java.util.UUID + +/** + * نتیجه‌ی تصمیم‌گیری برای همگام‌سازی: چه رکوردهایی نوشته/به‌روز شوند و چه شناسه‌هایی حذف شوند. + */ +data class MergePlan( + val upserts: List, + val deleteIds: List +) { + val isEmpty: Boolean get() = upserts.isEmpty() && deleteIds.isEmpty() +} + +/** + * منطق خالصِ ادغام داده‌ی سرور با داده‌ی محلی. + * + * این کلاس عمداً هیچ وابستگی‌ای به Room، Context یا شبکه ندارد تا کاملاً unit-testable باشد؛ + * تصمیم «چه چیزی حذف شود» حساس‌ترین بخش اپ است و باید تست داشته باشد. + * + * ## چرا این کلاس وجود دارد + * پیاده‌سازی قبلی (`LocalPersonRepository.syncAll`) اول `deleteAll()` می‌زد و بعد داده‌ی سرور را + * درج می‌کرد. نتیجه‌اش این بود که هر تغییر محلیِ هنوز-سینک-نشده‌ای نابود می‌شد — کاربر آفلاین + * پرداختی ثبت می‌کرد و آن پرداخت بی‌صدا از بین می‌رفت. + * + * ## قاعده‌ی حاکم + * **سرور منبع حقیقت است، مگر برای رکوردهایی که تغییر محلیِ ارسال‌نشده دارند.** + * آن رکوردها دست‌نخورده می‌مانند تا [SyncManager] موفق شود آن‌ها را بفرستد و از صف حذفشان کند؛ + * از آن لحظه به بعد سرور دربارهٔ آن‌ها هم حرف آخر را می‌زند. + */ +object SyncMerger { + + /** نرمال‌سازی نام برای تطبیق رکوردهای سرور که شناسه ندارند. */ + fun normalizeName(name: String): String = name.trim().lowercase() + + /** + * مجموعهٔ شناسه‌هایی که **نباید** لمس شوند. + * + * دو منبع دارد: + * - `pendingIds`: آیتم‌های موجود در صف سینک. + * - پرچم `needsSync` روی خودِ رکورد محلی. + * + * منبع دوم صرفاً تکرار اولی نیست: اگر صف به هر دلیلی از دست رفته باشد (مثلاً به‌خاطر باگ + * قبلی که آیتم‌ها را بی‌اینکه واقعاً ارسال شوند حذف می‌کرد)، پرچم `needsSync` هنوز روی رکورد + * هست و همان جلوی پاک شدنش را می‌گیرد. + */ + private fun protectedPersonIds(local: List, pendingIds: Set): Set = + pendingIds + local.filter { it.needsSync }.map { it.id } + + private fun protectedPaymentIds(local: List, pendingIds: Set): Set = + pendingIds + local.filter { it.needsSync }.map { it.id } + + /** + * @param idGenerator تولیدکنندهٔ شناسه؛ فقط برای تست‌پذیری قابل تزریق است. + */ + fun planPersons( + local: List, + server: List, + pendingIds: Set, + idGenerator: () -> String = { UUID.randomUUID().toString() } + ): MergePlan { + val protectedIds = protectedPersonIds(local, pendingIds) + val localByName = local.associateBy { normalizeName(it.name) } + + // حل شناسه: رکورد سرور که id ندارد، اگر همنامِ محلی داشت شناسهٔ او را می‌گیرد. + // (این منطق از پیاده‌سازی قبلی حفظ شده و رفتار سرورهای قدیمی را پوشش می‌دهد.) + val resolved = server.map { person -> + val name = person.name.trim() + val resolvedId = when { + person.id.isNotBlank() -> person.id + else -> localByName[normalizeName(name)]?.id ?: idGenerator() + } + person.copy(id = resolvedId, name = name, needsSync = false) + } + + val serverIds = resolved.map { it.id }.toSet() + + return MergePlan( + upserts = resolved.filterNot { it.id in protectedIds }, + deleteIds = local + .map { it.id } + .filterNot { it in serverIds || it in protectedIds } + ) + } + + fun planPayments( + local: List, + server: List, + pendingIds: Set + ): MergePlan { + val protectedIds = protectedPaymentIds(local, pendingIds) + val serverIds = server.map { it.id }.toSet() + + return MergePlan( + upserts = server + .filterNot { it.id in protectedIds } + .map { it.copy(needsSync = false) }, + deleteIds = local + .map { it.id } + .filterNot { it in serverIds || it in protectedIds } + ) + } +} diff --git a/app/src/main/java/com/oqba26/monthlypaymentapp/ui/screens/ArchiveScreen.kt b/app/src/main/java/com/oqba26/monthlypaymentapp/ui/screens/ArchiveScreen.kt index 1ae9a14..b6fc5f5 100644 --- a/app/src/main/java/com/oqba26/monthlypaymentapp/ui/screens/ArchiveScreen.kt +++ b/app/src/main/java/com/oqba26/monthlypaymentapp/ui/screens/ArchiveScreen.kt @@ -77,58 +77,15 @@ fun ArchiveScreen(viewModel: PersonViewModel) { // دیالوگ تایید حذف قطعی personToDelete?.let { person -> - Dialog( - onDismissRequest = { personToDelete = null } - ) { - CompositionLocalProvider( - LocalLayoutDirection provides LayoutDirection.Rtl - ) { - Card( - modifier = Modifier.fillMaxWidth().padding(16.dp), - 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 = "آیا از حذف قطعی ${person.name} و تمامی سوابق پرداخت او مطمئن هستید؟ این عمل غیرقابل بازگشت است.", - style = MaterialTheme.typography.bodyMedium - ) - - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Button( - onClick = { personToDelete = null } - ) { - Text("لغو") - } - - Button( - onClick = { - viewModel.onEvent(PersonScreenEvent.DeletePerson(person.id)) - personToDelete = null - }, - colors = ButtonDefaults.buttonColors( - containerColor = MaterialTheme.colorScheme.error - ) - ) { - Text("حذف قطعی") - } - } - } - } - } - } + ConfirmDeleteDialog( + title = "حذف قطعی شخص", + message = "آیا از حذف قطعی ${person.name} و تمامی سوابق پرداخت او مطمئن هستید؟ این عمل غیرقابل بازگشت است.", + onConfirm = { + viewModel.onEvent(PersonScreenEvent.DeletePerson(person.id)) + personToDelete = null + }, + onDismiss = { personToDelete = null } + ) } // دیالوگ تایید بازیابی diff --git a/app/src/main/java/com/oqba26/monthlypaymentapp/ui/screens/AuthScreen.kt b/app/src/main/java/com/oqba26/monthlypaymentapp/ui/screens/AuthScreen.kt index 2d44c00..da6b82a 100644 --- a/app/src/main/java/com/oqba26/monthlypaymentapp/ui/screens/AuthScreen.kt +++ b/app/src/main/java/com/oqba26/monthlypaymentapp/ui/screens/AuthScreen.kt @@ -42,6 +42,13 @@ fun AuthScreen( val authState by viewModel.authState.collectAsState() val isLoading = authState is AuthState.Loading var isLoginMode by remember { mutableStateOf(true) } + val context = androidx.compose.ui.platform.LocalContext.current + + androidx.compose.runtime.LaunchedEffect(Unit) { + viewModel.toastMessage.collect { message -> + android.widget.Toast.makeText(context, message, android.widget.Toast.LENGTH_SHORT).show() + } + } Column( modifier = Modifier diff --git a/app/src/main/java/com/oqba26/monthlypaymentapp/ui/screens/PersonDetailScreen.kt b/app/src/main/java/com/oqba26/monthlypaymentapp/ui/screens/PersonDetailScreen.kt index 79c87fb..4bbc066 100644 --- a/app/src/main/java/com/oqba26/monthlypaymentapp/ui/screens/PersonDetailScreen.kt +++ b/app/src/main/java/com/oqba26/monthlypaymentapp/ui/screens/PersonDetailScreen.kt @@ -34,9 +34,12 @@ import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.Checkbox import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.RadioButton import androidx.compose.material3.Surface @@ -47,6 +50,7 @@ import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -58,6 +62,7 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.KeyboardType +import com.oqba26.monthlypaymentapp.utils.PersianNumberVisualTransformation import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog @@ -92,18 +97,21 @@ fun PersonDetailScreen( val detailState by viewModel.personDetailState.collectAsState() val defaultPaymentAmount by viewModel.defaultPaymentAmountFlow.collectAsState(initial = 0.0) - val context = LocalContext.current var expandedMonth by remember { mutableStateOf(null) } var editingPayment by remember { mutableStateOf(null) } var addingPaymentMonth by remember { mutableStateOf(null) } var editingPerson by remember { mutableStateOf(value = false) } - var showBulkPaymentDialog by remember { mutableStateOf(false) } + var showBulkPaymentDialog by remember { mutableStateOf(value = false) } Column(modifier = Modifier.fillMaxSize()) { + val displayName = detailState.person?.let { p -> + if (p.isAnonymous && (p.category == "mosque")) "خیر ناشناس (${p.name})" else p.name + } ?: "..." + PersonDetailHeader( - name = detailState.person?.name ?: "...", - onBack = { navController.popBackStack() } + name = displayName, + onBack = { navController.popBackStack() }, ) { editingPerson = true } if (detailState.person == null) { @@ -113,14 +121,15 @@ fun PersonDetailScreen( } else { YearSelector( year = detailState.selectedYear, - onYearChange = { offset -> - viewModel.onEvent(PersonScreenEvent.ChangeYear(offset)) - } - ) + ) { offset -> + viewModel.onEvent(PersonScreenEvent.ChangeYear(offset)) + } val availableMonths = detailState.monthStates + .asSequence() .filter { it.status == MonthStatus.AVAILABLE } .map { it.month } + .toList() if (availableMonths.isNotEmpty()) { Button( @@ -140,6 +149,8 @@ fun PersonDetailScreen( items(detailState.monthStates) { monthModel -> MonthListItem( month = monthModel, + personName = detailState.person?.name ?: "", + startMonth = detailState.person?.startMonth ?: 1, onHeaderClick = { expandedMonth = if (expandedMonth == monthModel.month) null else monthModel.month }, @@ -190,7 +201,9 @@ fun PersonDetailScreen( AddPaymentDialog( personName = detailState.person?.name ?: "", - defaultAmount = defaultPaymentAmount, + defaultAmount = if ((detailState.person?.monthlyCommitment ?: 0.0) > 0) + detailState.person!!.monthlyCommitment + else defaultPaymentAmount, initialDescription = initialDesc, onConfirm = { amount, description -> viewModel.onEvent( @@ -204,18 +217,21 @@ fun PersonDetailScreen( ) addingPaymentMonth = null }, - onDismiss = { - addingPaymentMonth = null - } + onDismiss = { addingPaymentMonth = null } ) } if (editingPerson) { + val currentCategory by viewModel.currentCategory.collectAsState() UpdatePersonDialog( initialName = detailState.person?.name ?: "", initialPhone = detailState.person?.phoneNumber, - onConfirm = { name, phone -> - viewModel.onEvent(PersonScreenEvent.UpdatePerson(personId, name, phone)) + initialCommitment = detailState.person?.monthlyCommitment ?: 0.0, + initialStartMonth = detailState.person?.startMonth ?: 1, + initialStartYear = detailState.person?.startYear ?: com.oqba26.monthlypaymentapp.utils.getCurrentShamsiYear(), + isMosqueCategory = currentCategory == "mosque", + onConfirm = { name, phone, commitment, month, year -> + viewModel.onEvent(PersonScreenEvent.UpdatePerson(personId, name, phone, commitment, month, year)) editingPerson = false }, onDismiss = { editingPerson = false }, @@ -227,12 +243,18 @@ fun PersonDetailScreen( if (showBulkPaymentDialog) { val availableMonths = detailState.monthStates + .asSequence() .filter { it.status == MonthStatus.AVAILABLE } .map { it.month } + .toList() + val bulkDefaultAmount = if ((detailState.person?.monthlyCommitment ?: 0.0) > 0) + detailState.person!!.monthlyCommitment + else defaultPaymentAmount + BulkPaymentDialog( availableMonths = availableMonths, - defaultAmount = defaultPaymentAmount, + defaultAmount = bulkDefaultAmount, onConfirm = { selected, amount -> viewModel.onEvent( PersonScreenEvent.AddBulkPayments( @@ -244,8 +266,7 @@ fun PersonDetailScreen( ) showBulkPaymentDialog = false }, - onDismiss = { showBulkPaymentDialog = false } - ) + ) { showBulkPaymentDialog = false } } } @@ -254,7 +275,7 @@ fun BulkPaymentDialog( availableMonths: List, defaultAmount: Double, onConfirm: (List, Double) -> Unit, - onDismiss: () -> Unit + onDismiss: () -> Unit, ) { val selectedMonths = remember { mutableStateListOf().apply { addAll(availableMonths) } } var amountText by remember { mutableStateOf(defaultAmount.toInt().toString()) } @@ -307,17 +328,28 @@ fun BulkPaymentDialog( OutlinedTextField( value = amountText, - onValueChange = { amountText = it }, + onValueChange = { value -> + // اجازه ورود فقط به اعداد (چه انگلیسی چه فارسی) + val digitsOnly = value.filter { it.isDigit() || (((it in '\u0660'..'\u0669') || (it in '\u06f0'..'\u06f9'))) } + // تبدیل اعداد فارسی به انگلیسی برای ذخیره در استیت + amountText = digitsOnly.map { + when (it) { + in '\u0660'..'\u0669' -> ((it.code - '\u0660'.code) + '0'.code).toChar() + in '\u06f0'..'\u06f9' -> ((it.code - '\u06f0'.code) + '0'.code).toChar() + else -> it + } + }.joinToString("") + }, label = { Text("مبلغ برای هر ماه (تومان)") }, modifier = Modifier.fillMaxWidth(), keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), - visualTransformation = PersianDigitsTransformation() + visualTransformation = PersianNumberVisualTransformation() ) Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically + verticalAlignment = Alignment.CenterVertically, ) { Button( onClick = { @@ -343,7 +375,7 @@ fun BulkPaymentDialog( private fun PersonDetailHeader( name: String, onBack: () -> Unit, - onEdit: () -> Unit + onEdit: () -> Unit, ) { Surface(color = MaterialTheme.colorScheme.primary, contentColor = Color.White) { Row( @@ -371,12 +403,22 @@ private fun PersonDetailHeader( fun UpdatePersonDialog( initialName: String, initialPhone: String?, - onConfirm: (String, String) -> Unit, + initialCommitment: Double = 0.0, + initialStartMonth: Int = 1, + initialStartYear: Int = 1403, + isMosqueCategory: Boolean = false, + onConfirm: (String, String, Double, Int, Int) -> Unit, onDismiss: () -> Unit, - onSearchContact: (String) -> List + onSearchContact: (String) -> List, ) { var name by remember { mutableStateOf(initialName) } var phoneNumber by remember { mutableStateOf(initialPhone ?: "") } + var monthlyCommitment by remember { mutableStateOf(if (initialCommitment > 0) initialCommitment.toInt().toString() else "") } + + val currentShamsiYear = com.oqba26.monthlypaymentapp.utils.getCurrentShamsiYear() + var startMonth by remember { mutableIntStateOf(initialStartMonth) } + var startYear by remember { mutableIntStateOf(initialStartYear) } + var similarContacts by remember { mutableStateOf>(emptyList()) } val context = LocalContext.current @@ -469,6 +511,28 @@ fun UpdatePersonDialog( } } + if (isMosqueCategory) { + OutlinedTextField( + value = monthlyCommitment, + onValueChange = { value -> + if (value.length <= 12) { + val digitsOnly = value.filter { it.isDigit() || (((it in '\u0660'..'\u0669') || (it in '\u06f0'..'\u06f9'))) } + monthlyCommitment = digitsOnly.map { + when (it) { + in '\u0660'..'\u0669' -> ((it.code - '\u0660'.code) + '0'.code).toChar() + in '\u06f0'..'\u06f9' -> ((it.code - '\u06f0'.code) + '0'.code).toChar() + else -> it + } + }.joinToString("") + } + }, + label = { Text("مبلغ تعهد ماهانه (تومان)") }, + modifier = Modifier.fillMaxWidth(), + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), + visualTransformation = PersianNumberVisualTransformation() + ) + } + if (similarContacts.isNotEmpty()) { Text( "موارد مشابه پیدا شده:", @@ -511,15 +575,63 @@ fun UpdatePersonDialog( } } + // انتخاب ماه و سال شروع + Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { + Text("تاریخ شروع تعهد:", style = MaterialTheme.typography.labelLarge) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + // سال + Box(modifier = Modifier.weight(1f)) { + var expandedYear by remember { mutableStateOf(false) } + OutlinedButton( + onClick = { expandedYear = true }, + modifier = Modifier.fillMaxWidth() + ) { + Text("سال ${startYear.toString().toPersianDigits()}") + } + DropdownMenu(expanded = expandedYear, onDismissRequest = { expandedYear = false }) { + (currentShamsiYear - 1..currentShamsiYear + 1).forEach { y -> + DropdownMenuItem( + text = { Text(y.toString().toPersianDigits()) }, + onClick = { startYear = y; expandedYear = false } + ) + } + } + } + + // ماه + Box(modifier = Modifier.weight(1f)) { + var expandedMonth by remember { mutableStateOf(false) } + OutlinedButton( + onClick = { expandedMonth = true }, + modifier = Modifier.fillMaxWidth() + ) { + Text(com.oqba26.monthlypaymentapp.utils.getPersianMonthName(startMonth)) + } + DropdownMenu(expanded = expandedMonth, onDismissRequest = { expandedMonth = false }) { + (1..12).forEach { m -> + DropdownMenuItem( + text = { Text(com.oqba26.monthlypaymentapp.utils.getPersianMonthName(m)) }, + onClick = { startMonth = m; expandedMonth = false } + ) + } + } + } + } + } + Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically + verticalAlignment = Alignment.CenterVertically, ) { Button( onClick = { if (name.isNotBlank()) { - onConfirm(name, phoneNumber) + val commitment = monthlyCommitment.toDoubleOrNull() ?: 0.0 + onConfirm(name, phoneNumber, commitment, startMonth, startYear) } }, enabled = name.isNotBlank() @@ -573,19 +685,37 @@ fun YearSelector(year: Int, onYearChange: (Int) -> Unit) { @Composable fun MonthListItem( month: MonthUiModel, + personName: String, + startMonth: Int, onHeaderClick: () -> Unit, onEditClick: () -> Unit ) { + val context = LocalContext.current val containerColor = when (month.status) { MonthStatus.PAID -> MaterialTheme.colorScheme.primaryContainer MonthStatus.AVAILABLE -> MaterialTheme.colorScheme.surface + MonthStatus.NOT_COMMITTED -> MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f) else -> MaterialTheme.colorScheme.surfaceVariant } Card( - modifier = Modifier.padding(horizontal = 16.dp, vertical = 4.dp).fillMaxWidth().clickable(onClick = onHeaderClick), + modifier = Modifier + .padding(horizontal = 16.dp, vertical = 4.dp) + .fillMaxWidth() + .clickable { + if (month.status == MonthStatus.NOT_COMMITTED) { + val monthName = getPersianMonthName(startMonth) + Toast.makeText( + context, + "ماه‌های قبل از $monthName ماه جزو تعهدات این فرد نیست", + Toast.LENGTH_SHORT + ).show() + } else { + onHeaderClick() + } + }, colors = CardDefaults.cardColors(containerColor = containerColor), - elevation = CardDefaults.cardElevation(defaultElevation = 1.dp) + elevation = CardDefaults.cardElevation(defaultElevation = if (month.status == MonthStatus.NOT_COMMITTED) 0.dp else 1.dp) ) { Column(modifier = Modifier.fillMaxWidth().padding(horizontal = 12.dp, vertical = 10.dp)) { Row( @@ -594,13 +724,18 @@ fun MonthListItem( horizontalArrangement = Arrangement.SpaceBetween ) { Row(verticalAlignment = Alignment.CenterVertically) { - Text(text = getPersianMonthName(month.month), style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold) - if (month.status == MonthStatus.PAID && month.payment != null) { + Text( + text = getPersianMonthName(month.month), + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold, + color = if (month.status == MonthStatus.NOT_COMMITTED) Color.Gray else Color.Unspecified + ) + if ((month.status == MonthStatus.PAID) && (month.payment != null)) { Spacer(modifier = Modifier.width(8.dp)) Text(text = "${formatNumberAsPersian(month.payment.amount)} ت", style = MaterialTheme.typography.bodyMedium) } } - if (month.status == MonthStatus.PAID || month.status == MonthStatus.AVAILABLE) { + if ((month.status == MonthStatus.PAID) || (month.status == MonthStatus.AVAILABLE)) { Button( onClick = onEditClick, colors = ButtonDefaults.buttonColors( @@ -611,7 +746,7 @@ fun MonthListItem( } } } - if (month.status == MonthStatus.PAID && month.payment != null) { + if ((month.status == MonthStatus.PAID) && (month.payment != null)) { val desc = month.payment.description ?: "" if (desc.isNotBlank()) { Spacer(modifier = Modifier.height(4.dp)) diff --git a/app/src/main/java/com/oqba26/monthlypaymentapp/ui/screens/PersonScreen.kt b/app/src/main/java/com/oqba26/monthlypaymentapp/ui/screens/PersonScreen.kt index 6d4eb0b..66fa805 100644 --- a/app/src/main/java/com/oqba26/monthlypaymentapp/ui/screens/PersonScreen.kt +++ b/app/src/main/java/com/oqba26/monthlypaymentapp/ui/screens/PersonScreen.kt @@ -31,6 +31,7 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.Message import androidx.compose.material.icons.filled.Archive import androidx.compose.material.icons.filled.Check +import androidx.compose.material.icons.filled.Sync import androidx.compose.material.icons.filled.Payments import androidx.compose.material.icons.filled.Search import androidx.compose.material.pullrefresh.PullRefreshIndicator @@ -41,6 +42,9 @@ import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.Checkbox +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.LinearProgressIndicator @@ -56,6 +60,7 @@ import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -97,7 +102,7 @@ import org.burnoutcrew.reorderable.reorderable fun PersonScreen( viewModel: PersonViewModel, contactViewModel: ContactViewModel = hiltViewModel(), - navController: NavController + navController: NavController, ) { val searchQuery by viewModel.searchQuery.collectAsState() val uiState by viewModel.uiState.collectAsState() @@ -114,7 +119,7 @@ fun PersonScreen( val isRefreshing by viewModel.isRefreshing.collectAsState() val isSelectionMode by viewModel.isSelectionMode.collectAsState() val selectedIds by viewModel.selectedIds.collectAsState() - val context = LocalContext.current + val currentCategory by viewModel.currentCategory.collectAsState() LaunchedEffect(uiState.unpaidPersons, uiState.paidPersons) { val allPersons = uiState.unpaidPersons + uiState.paidPersons @@ -129,13 +134,19 @@ fun PersonScreen( val reorderableState = rememberReorderableLazyListState( onMove = { from, to -> if (searchQuery.isBlank()) { - viewModel.onEvent( - PersonScreenEvent.MovePersonNew( - fromIndex = from.index, - toIndex = to.index, - listType = PersonListType.UNPAID + val headerOffset = if (currentCategory == "mosque") 1 else 0 + val fromIdx = from.index - headerOffset + val toIdx = to.index - headerOffset + + if (fromIdx in uiState.unpaidPersons.indices && toIdx in uiState.unpaidPersons.indices) { + viewModel.onEvent( + PersonScreenEvent.MovePersonNew( + fromIndex = fromIdx, + toIndex = toIdx, + listType = PersonListType.UNPAID + ) ) - ) + } } }, onDragEnd = { _, _ -> @@ -181,6 +192,17 @@ fun PersonScreen( .fillMaxSize() .reorderable(reorderableState) ) { + if (currentCategory == "mosque" && uiState.unpaidPersons.isNotEmpty()) { + item { + Text( + text = "لیست خیرین (بدهکار)", + modifier = Modifier.padding(16.dp), + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.error + ) + } + } + itemsIndexed(uiState.unpaidPersons, key = { _, person -> person.id }) { index, person -> ReorderableItem(reorderableState, key = person.id) { isDragging -> val elevation = animateFloatAsState(if (isDragging) 8f else 0f, label = "").value @@ -214,6 +236,46 @@ fun PersonScreen( ) } } + + if (currentCategory == "mosque" && uiState.paidPersons.isNotEmpty()) { + item { + HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp, horizontal = 16.dp), thickness = 0.5.dp) + Text( + text = "لیست تسویه شدگان", + modifier = Modifier.padding(16.dp), + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.primary + ) + } + itemsIndexed(uiState.paidPersons, key = { _, person -> person.id }) { index, person -> + PersonListItem( + person = person, + index = index + 1 + uiState.unpaidPersons.size, + isSelectionMode = isSelectionMode, + isSelected = selectedIds.contains(person.id), + onPersonClick = { + if (isSelectionMode) { + viewModel.onEvent(PersonScreenEvent.ToggleSelection(person.id)) + } else { + navController.navigate("person_detail/${person.id}") + } + }, + onLongClick = { + if (!isSelectionMode) { + viewModel.onEvent(PersonScreenEvent.ToggleSelection(person.id)) + } + }, + onQuickPayClick = { }, + onArchiveClick = { selectedPerson -> + personToArchive = selectedPerson + }, + onSmsClick = { selectedPerson -> + contactViewModel.onSmsClicked(selectedPerson) + } + ) + } + } + item { Spacer(modifier = Modifier.height(60.dp)) } } } @@ -229,7 +291,7 @@ fun PersonScreen( personForPaymentDialog?.let { person -> AddPaymentDialog( personName = person.name, - defaultAmount = defaultPaymentAmount, + defaultAmount = if (person.monthlyCommitment > 0) person.monthlyCommitment else defaultPaymentAmount, onConfirm = { amount, description -> viewModel.onEvent(PersonScreenEvent.AddQuickPayment(person.id, amount, description)) viewModel.onDismissPaymentDialog() @@ -261,9 +323,11 @@ fun PersonScreen( } if (isAddPersonDialogShown) { + val currentCategory by viewModel.currentCategory.collectAsState() AddNewPersonDialog( - onConfirm = { name, phone -> - viewModel.onEvent(PersonScreenEvent.AddPerson(name, phone, context)) + isMosqueCategory = currentCategory == "mosque", + onConfirm = { name, phone, isAnon, commitment, month, year, initialPayment -> + viewModel.onEvent(PersonScreenEvent.AddPerson(name, phone, isAnon, commitment, month, year, initialPayment)) viewModel.onDismissAddPersonDialog() }, onDismiss = { viewModel.onDismissAddPersonDialog() }, @@ -436,12 +500,23 @@ fun ContactSuggestionDialog( @Composable fun AddNewPersonDialog( - onConfirm: (String, String) -> Unit, + isMosqueCategory: Boolean = false, + onConfirm: (String, String, Boolean, Double, Int, Int, Double) -> Unit, onDismiss: () -> Unit, onSearchContact: (String) -> List ) { var name by remember { mutableStateOf("") } var phoneNumber by remember { mutableStateOf("") } + var isAnonymous by remember { mutableStateOf(value = false) } + var monthlyCommitment by remember { mutableStateOf("") } + var initialPaymentAmount by remember { mutableStateOf("") } + + val currentShamsiYear = com.oqba26.monthlypaymentapp.utils.getCurrentShamsiYear() + val currentShamsiMonth = com.oqba26.monthlypaymentapp.utils.getCurrentShamsiMonth() + + var startMonth by remember { mutableIntStateOf(currentShamsiMonth) } + var startYear by remember { mutableIntStateOf(currentShamsiYear) } + var similarContacts by remember { mutableStateOf>(emptyList()) } val context = LocalContext.current @@ -471,97 +546,225 @@ fun AddNewPersonDialog( verticalArrangement = Arrangement.spacedBy(16.dp) ) { Text( - text = "افزودن شخص جدید", + text = if (isAnonymous) "ثبت کمک آنی" else "افزودن شخص جدید", style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold ) - Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { - OutlinedTextField( - value = name, - onValueChange = { name = it }, - label = { Text("نام شخص") }, - singleLine = true, - modifier = Modifier.fillMaxWidth() - ) + if (isMosqueCategory) { Row( - modifier = Modifier.fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .clickable { isAnonymous = !isAnonymous } + .padding(vertical = 4.dp) ) { - CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Ltr) { - OutlinedTextField( - value = phoneNumber, - onValueChange = { phoneNumber = it }, - label = { Text("شماره موبایل (اختیاری)") }, - singleLine = true, - modifier = Modifier.weight(1f), - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Phone), - visualTransformation = PersianDigitsTransformation(), - placeholder = { Text("۰۹---------", textAlign = androidx.compose.ui.text.style.TextAlign.Right) } - ) - } + Checkbox( + checked = isAnonymous, + onCheckedChange = { isAnonymous = it } + ) Spacer(modifier = Modifier.width(8.dp)) - IconButton( - onClick = { - if (name.isBlank()) { - Toast.makeText(context, "ابتدا نام را وارد کنید", Toast.LENGTH_SHORT).show() - return@IconButton - } - when (PackageManager.PERMISSION_GRANTED) { - ContextCompat.checkSelfPermission(context, Manifest.permission.READ_CONTACTS) -> { - val matches = onSearchContact(name) - if (matches.isNotEmpty()) { - similarContacts = matches - } else { - Toast.makeText(context, "مخاطبی با این نام پیدا نشد", Toast.LENGTH_SHORT).show() + Text("ثبت به صورت خیر ناشناس") + } + } + + if (!isAnonymous) { + Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { + OutlinedTextField( + value = name, + onValueChange = { name = it }, + label = { Text("نام شخص") }, + singleLine = true, + modifier = Modifier.fillMaxWidth() + ) + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Ltr) { + OutlinedTextField( + value = phoneNumber, + onValueChange = { phoneNumber = it }, + label = { Text("شماره موبایل (اختیاری)") }, + singleLine = true, + modifier = Modifier.weight(1f), + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Phone), + visualTransformation = PersianDigitsTransformation(), + placeholder = { + Text( + "۰۹---------", + textAlign = androidx.compose.ui.text.style.TextAlign.Right + ) + } + ) + } + Spacer(modifier = Modifier.width(8.dp)) + IconButton( + onClick = { + if (name.isBlank()) { + Toast.makeText(context, "ابتدا نام را وارد کنید", Toast.LENGTH_SHORT) + .show() + return@IconButton + } + when (PackageManager.PERMISSION_GRANTED) { + ContextCompat.checkSelfPermission( + context, + Manifest.permission.READ_CONTACTS + ) -> { + val matches = onSearchContact(name) + if (matches.isNotEmpty()) { + similarContacts = matches + } else { + Toast.makeText(context, "مخاطبی با این نام پیدا نشد", Toast.LENGTH_SHORT) + .show() + } + } + + else -> { + permissionLauncher.launch(Manifest.permission.READ_CONTACTS) } } - else -> { - permissionLauncher.launch(Manifest.permission.READ_CONTACTS) + } + ) { + Icon(Icons.Default.Search, contentDescription = "جستجو در مخاطبین") + } + } + + if (similarContacts.isNotEmpty()) { + Text( + "موارد مشابه پیدا شده:", + style = MaterialTheme.typography.labelMedium, + color = MaterialTheme.colorScheme.primary + ) + LazyColumn( + modifier = Modifier.heightIn(max = 200.dp) + ) { + items(similarContacts) { match -> + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { + phoneNumber = match.phoneNumber + similarContacts = emptyList() + } + .padding(vertical = 4.dp), + verticalAlignment = Alignment.CenterVertically + ) { + RadioButton( + selected = phoneNumber == match.phoneNumber, + onClick = { + phoneNumber = match.phoneNumber + similarContacts = emptyList() + } + ) + Column { + Text(match.nameInContacts, style = MaterialTheme.typography.bodySmall) + CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Ltr) { + Text( + match.phoneNumber.toPersianDigits(), + style = MaterialTheme.typography.labelSmall, + color = Color.Gray + ) + } + } } } } - ) { - Icon(Icons.Default.Search, contentDescription = "جستجو در مخاطبین") } } + } - if (similarContacts.isNotEmpty()) { - Text( - "موارد مشابه پیدا شده:", - style = MaterialTheme.typography.labelMedium, - color = MaterialTheme.colorScheme.primary + if (isMosqueCategory) { + if (isAnonymous) { + OutlinedTextField( + value = initialPaymentAmount, + onValueChange = { value -> + if (value.length <= 12) { + val digitsOnly = value.filter { it.isDigit() || (it in '\u0660'..'\u0669') || (it in '\u06f0'..'\u06f9') } + initialPaymentAmount = digitsOnly.map { + when (it) { + in '\u0660'..'\u0669' -> (it.code - '\u0660'.code + '0'.code).toChar() + in '\u06f0'..'\u06f9' -> (it.code - '\u06f0'.code + '0'.code).toChar() + else -> it + } + }.joinToString("") + } + }, + label = { Text("مبلغ پرداختی (تومان)") }, + modifier = Modifier.fillMaxWidth(), + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), + visualTransformation = com.oqba26.monthlypaymentapp.utils.PersianNumberVisualTransformation() ) - LazyColumn( - modifier = Modifier.heightIn(max = 200.dp) + } else { + OutlinedTextField( + value = monthlyCommitment, + onValueChange = { value -> + if (value.length <= 12) { + val digitsOnly = value.filter { it.isDigit() || (it in '\u0660'..'\u0669') || (it in '\u06f0'..'\u06f9') } + monthlyCommitment = digitsOnly.map { + when (it) { + in '\u0660'..'\u0669' -> (it.code - '\u0660'.code + '0'.code).toChar() + in '\u06f0'..'\u06f9' -> (it.code - '\u06f0'.code + '0'.code).toChar() + else -> it + } + }.joinToString("") + } + }, + label = { Text("مبلغ تعهد ماهانه (تومان) - اختیاری") }, + modifier = Modifier.fillMaxWidth(), + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), + visualTransformation = com.oqba26.monthlypaymentapp.utils.PersianNumberVisualTransformation(), + placeholder = { Text("اگر مبلغ ثابتی پرداخت می‌کند وارد کنید") } + ) + } + } + + if (!isAnonymous) { + // انتخاب ماه و سال شروع + Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { + Text("تاریخ شروع تعهد:", style = MaterialTheme.typography.labelLarge) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp) ) { - items(similarContacts) { match -> - Row( - modifier = Modifier - .fillMaxWidth() - .clickable { - phoneNumber = match.phoneNumber - similarContacts = emptyList() - } - .padding(vertical = 4.dp), - verticalAlignment = Alignment.CenterVertically + // سال + Box(modifier = Modifier.weight(1f)) { + var expandedYear by remember { mutableStateOf(false) } + OutlinedButton( + onClick = { expandedYear = true }, + modifier = Modifier.fillMaxWidth() ) { - RadioButton( - selected = phoneNumber == match.phoneNumber, - onClick = { - phoneNumber = match.phoneNumber - similarContacts = emptyList() - } - ) - Column { - Text(match.nameInContacts, style = MaterialTheme.typography.bodySmall) - CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Ltr) { - Text( - match.phoneNumber.toPersianDigits(), - style = MaterialTheme.typography.labelSmall, - color = Color.Gray - ) - } + Text("سال ${startYear.toString().toPersianDigits()}") + } + DropdownMenu( + expanded = expandedYear, + onDismissRequest = { expandedYear = false }) { + (currentShamsiYear - 1..currentShamsiYear + 1).forEach { y -> + DropdownMenuItem( + text = { Text(y.toString().toPersianDigits()) }, + onClick = { startYear = y; expandedYear = false } + ) + } + } + } + + // ماه + Box(modifier = Modifier.weight(1f)) { + var expandedMonth by remember { mutableStateOf(false) } + OutlinedButton( + onClick = { expandedMonth = true }, + modifier = Modifier.fillMaxWidth() + ) { + Text(com.oqba26.monthlypaymentapp.utils.getPersianMonthName(startMonth)) + } + DropdownMenu( + expanded = expandedMonth, + onDismissRequest = { expandedMonth = false }) { + (1..12).forEach { m -> + DropdownMenuItem( + text = { Text(com.oqba26.monthlypaymentapp.utils.getPersianMonthName(m)) }, + onClick = { startMonth = m; expandedMonth = false } + ) } } } @@ -576,13 +779,15 @@ fun AddNewPersonDialog( ) { Button( onClick = { - if (name.isNotBlank()) { - onConfirm(name, phoneNumber) + if (isAnonymous || name.isNotBlank()) { + val commitment = monthlyCommitment.toDoubleOrNull() ?: 0.0 + val initial = initialPaymentAmount.toDoubleOrNull() ?: 0.0 + onConfirm(name, phoneNumber, isAnonymous, commitment, startMonth, startYear, initial) } }, - enabled = name.isNotBlank() + enabled = isAnonymous || name.isNotBlank() ) { - Text("افزودن") + Text(if (isAnonymous) "ثبت کمک" else "افزودن") } Button( onClick = onDismiss, @@ -913,6 +1118,15 @@ fun PersonListItem( } } + if (person.needsSync) { + Icon( + imageVector = Icons.Default.Sync, + contentDescription = "Pending Sync", + tint = MaterialTheme.colorScheme.outline, + modifier = Modifier.size(16.dp).padding(horizontal = 2.dp) + ) + } + Spacer(Modifier.width(4.dp)) Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) { @@ -943,40 +1157,3 @@ fun PersonListItem( } } -@Composable -fun AddPaymentDialog( - personName: String, - defaultAmount: Double, - onConfirm: (Double, String) -> Unit, - onDismiss: () -> Unit -) { - var amount by remember { mutableStateOf(defaultAmount.toString()) } - var description by remember { mutableStateOf("") } - - Dialog(onDismissRequest = onDismiss) { - CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) { - Card(modifier = Modifier.fillMaxWidth().padding(16.dp), shape = RoundedCornerShape(24.dp)) { - Column(modifier = Modifier.padding(20.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) { - Text(text = "ثبت پرداخت سریع برای $personName", style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - OutlinedTextField( - value = amount, - onValueChange = { amount = it }, - label = { Text("مبلغ") }, - modifier = Modifier.fillMaxWidth(), - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number) - ) - OutlinedTextField( - value = description, - onValueChange = { description = it }, - label = { Text("توضیحات") }, - modifier = Modifier.fillMaxWidth() - ) - Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { - Button(onClick = onDismiss) { Text("لغو") } - Button(onClick = { onConfirm(amount.toDoubleOrNull() ?: 0.0, description) }) { Text("ثبت") } - } - } - } - } - } -} diff --git a/app/src/main/java/com/oqba26/monthlypaymentapp/ui/screens/SettingsScreen.kt b/app/src/main/java/com/oqba26/monthlypaymentapp/ui/screens/SettingsScreen.kt index f181ec3..f5da1ef 100644 --- a/app/src/main/java/com/oqba26/monthlypaymentapp/ui/screens/SettingsScreen.kt +++ b/app/src/main/java/com/oqba26/monthlypaymentapp/ui/screens/SettingsScreen.kt @@ -55,7 +55,10 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.window.Dialog import androidx.hilt.navigation.compose.hiltViewModel +import com.oqba26.monthlypaymentapp.core.manager.BackupManager +import com.oqba26.monthlypaymentapp.core.manager.SnapshotInfo import com.oqba26.monthlypaymentapp.utils.PersianDigitsTransformation +import com.oqba26.monthlypaymentapp.utils.formatTimestampToPersianDateTime import com.oqba26.monthlypaymentapp.utils.PersianNumberVisualTransformation import com.oqba26.monthlypaymentapp.utils.formatNumberAsPersian import com.oqba26.monthlypaymentapp.utils.toPersianDigits @@ -80,6 +83,9 @@ fun SettingsScreen(onLogout: () -> Unit) { val selectedFont by viewModel.selectedFont.collectAsState() var backupJsonToRestore by remember { mutableStateOf(null) } + var showSnapshotDialog by remember { mutableStateOf(false) } + var snapshotToRestore by remember { mutableStateOf(null) } + val snapshots by viewModel.snapshots.collectAsState() LaunchedEffect(Unit) { viewModel.toastMessage.collect { message -> @@ -195,6 +201,17 @@ fun SettingsScreen(onLogout: () -> Unit) { } } + // تور نجات: نسخه‌هایی که خودِ برنامه قبل از عملیات پرخطر گرفته است. + OutlinedButton( + onClick = { + viewModel.loadSnapshots() + showSnapshotDialog = true + }, + modifier = Modifier.fillMaxWidth().padding(top = 8.dp) + ) { + Text(stringResource(R.string.auto_backup_button), fontSize = 13.sp) + } + Spacer(Modifier.weight(1f)) Button( @@ -209,6 +226,63 @@ fun SettingsScreen(onLogout: () -> Unit) { } } + if (showSnapshotDialog) { + SnapshotListDialog( + snapshots = snapshots, + onSelect = { snapshotToRestore = it }, + onDismiss = { showSnapshotDialog = false } + ) + } + + snapshotToRestore?.let { snapshot -> + Dialog(onDismissRequest = { snapshotToRestore = null }) { + CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) { + Card( + modifier = Modifier.fillMaxWidth().padding(16.dp), + shape = RoundedCornerShape(24.dp) + ) { + Column( + modifier = Modifier.padding(20.dp), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + Text( + text = stringResource(R.string.restore_warning), + style = MaterialTheme.typography.headlineSmall, + fontWeight = FontWeight.Bold + ) + Text( + text = formatTimestampToPersianDateTime(snapshot.timestamp), + style = MaterialTheme.typography.bodyMedium, + fontWeight = FontWeight.Bold + ) + Text( + text = stringResource(R.string.auto_backup_restore_confirm), + style = MaterialTheme.typography.bodyMedium + ) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Button( + onClick = { + viewModel.restoreSnapshot(snapshot) + snapshotToRestore = null + showSnapshotDialog = false + }, + colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.error) + ) { Text(stringResource(R.string.yes)) } + + Button(onClick = { snapshotToRestore = null }) { + Text(stringResource(R.string.cancel)) + } + } + } + } + } + } + } + backupJsonToRestore?.let { json -> Dialog(onDismissRequest = { backupJsonToRestore = null }) { CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) { @@ -253,6 +327,92 @@ fun SettingsScreen(onLogout: () -> Unit) { } } +/** + * لیست نسخه‌های پشتیبان خودکار. + * + * تاریخ‌ها شمسی و اعداد فارسی نمایش داده می‌شوند تا با بقیه‌ی اپ یکدست باشد. + */ +@Composable +private fun SnapshotListDialog( + snapshots: List, + onSelect: (SnapshotInfo) -> Unit, + onDismiss: () -> Unit +) { + Dialog(onDismissRequest = onDismiss) { + CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) { + Card( + modifier = Modifier.fillMaxWidth().padding(16.dp), + shape = RoundedCornerShape(24.dp) + ) { + Column( + modifier = Modifier.padding(20.dp), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + Text( + text = stringResource(R.string.auto_backup_title), + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold + ) + Text( + text = stringResource(R.string.auto_backup_description), + style = MaterialTheme.typography.bodySmall + ) + + if (snapshots.isEmpty()) { + Text( + text = stringResource(R.string.auto_backup_empty), + style = MaterialTheme.typography.bodyMedium, + modifier = Modifier.padding(vertical = 8.dp) + ) + } else { + snapshots.forEach { snapshot -> + OutlinedButton( + onClick = { onSelect(snapshot) }, + modifier = Modifier.fillMaxWidth() + ) { + Column(modifier = Modifier.fillMaxWidth()) { + Text( + text = formatTimestampToPersianDateTime(snapshot.timestamp), + style = MaterialTheme.typography.bodyMedium, + fontWeight = FontWeight.Bold + ) + Text( + text = stringResource( + R.string.auto_backup_item_summary, + snapshot.personCount.toString().toPersianDigits(), + snapshot.paymentCount.toString().toPersianDigits() + ), + style = MaterialTheme.typography.bodySmall + ) + Text( + text = stringResource(snapshotReasonLabel(snapshot.reason)), + style = MaterialTheme.typography.bodySmall + ) + } + } + } + } + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.End + ) { + TextButton(onClick = onDismiss) { Text(stringResource(R.string.close)) } + } + } + } + } + } +} + +/** ترجمه‌ی علتِ ذخیره‌شده در نام فایل به متن قابل فهم کاربر. */ +private fun snapshotReasonLabel(reason: String): Int = when (reason) { + BackupManager.REASON_BEFORE_SYNC -> R.string.auto_backup_reason_before_sync + BackupManager.REASON_BEFORE_RESTORE -> R.string.auto_backup_reason_before_restore + BackupManager.REASON_APP_UPGRADE -> R.string.auto_backup_reason_app_upgrade + else -> R.string.auto_backup_reason_unknown +} + @Composable fun DefaultAmountSetting(defaultAmount: Double, onSave: (String) -> Unit) { var isEditing by remember { mutableStateOf(false) } @@ -267,7 +427,16 @@ fun DefaultAmountSetting(defaultAmount: Double, onSave: (String) -> Unit) { Column(modifier = Modifier.padding(12.dp)) { OutlinedTextField( value = amountText, - onValueChange = { amountText = it.filter { c -> c.isDigit() } }, + onValueChange = { value -> + val digitsOnly = value.filter { it.isDigit() || it in '\u0660'..'\u0669' || it in '\u06f0'..'\u06f9' } + amountText = digitsOnly.map { + when (it) { + in '\u0660'..'\u0669' -> (it.code - '\u0660'.code + '0'.code).toChar() + in '\u06f0'..'\u06f9' -> (it.code - '\u06f0'.code + '0'.code).toChar() + else -> it + } + }.joinToString("") + }, modifier = Modifier.fillMaxWidth(), label = { Text("مبلغ پیش‌فرض (تومان)") }, visualTransformation = PersianNumberVisualTransformation(), @@ -339,6 +508,7 @@ fun ReminderDaySetting(currentDay: Int?, onSave: (String) -> Unit) { fun CardNumbersSetting(cardNumbers: List, onAdd: (String) -> Unit, onRemove: (String) -> Unit) { var isAdding by remember { mutableStateOf(false) } var newCardText by remember { mutableStateOf("") } + var cardToRemove by remember { mutableStateOf(null) } Card( modifier = Modifier.fillMaxWidth(), @@ -366,11 +536,23 @@ fun CardNumbersSetting(cardNumbers: List, onAdd: (String) -> Unit, onRem cardNumbers.forEach { card -> Row(verticalAlignment = Alignment.CenterVertically) { Text(card.toPersianDigits(), modifier = Modifier.weight(1f), fontWeight = FontWeight.Bold, style = MaterialTheme.typography.bodyLarge) - IconButton(onClick = { onRemove(card) }) { Icon(Icons.Default.Delete, null, tint = MaterialTheme.colorScheme.error) } + IconButton(onClick = { cardToRemove = card }) { Icon(Icons.Default.Delete, null, tint = MaterialTheme.colorScheme.error) } } } } } + + cardToRemove?.let { card -> + ConfirmDeleteDialog( + title = "حذف شماره کارت", + message = "آیا از حذف شماره کارت ${card.toPersianDigits()} مطمئن هستید؟", + onConfirm = { + onRemove(card) + cardToRemove = null + }, + onDismiss = { cardToRemove = null } + ) + } } @Composable diff --git a/app/src/main/java/com/oqba26/monthlypaymentapp/ui/screens/SharedComponents.kt b/app/src/main/java/com/oqba26/monthlypaymentapp/ui/screens/SharedComponents.kt index 2ad275c..699c0ba 100644 --- a/app/src/main/java/com/oqba26/monthlypaymentapp/ui/screens/SharedComponents.kt +++ b/app/src/main/java/com/oqba26/monthlypaymentapp/ui/screens/SharedComponents.kt @@ -5,6 +5,7 @@ package com.oqba26.monthlypaymentapp.ui.screens 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.padding import androidx.compose.foundation.shape.RoundedCornerShape @@ -47,6 +48,7 @@ fun EditPaymentDialog( ) { var amountText by remember { mutableStateOf(paymentRecord.amount.toLong().toString()) } var description by remember { mutableStateOf(paymentRecord.description ?: "") } + var showDeleteConfirm by remember { mutableStateOf(false) } Dialog(onDismissRequest = onDismiss) { CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) { @@ -125,15 +127,90 @@ fun EditPaymentDialog( ) { Text("لغو") } + } - IconButton( - onClick = onDelete, - ) { - Icon( - imageVector = androidx.compose.material.icons.Icons.Default.Delete, - contentDescription = "حذف", - tint = MaterialTheme.colorScheme.error + Button( + onClick = { showDeleteConfirm = true }, + modifier = Modifier.fillMaxWidth(), + colors = ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.errorContainer, + contentColor = MaterialTheme.colorScheme.error + ) + ) { + Icon(androidx.compose.material.icons.Icons.Default.Delete, contentDescription = null) + Spacer(Modifier.padding(horizontal = 4.dp)) + Text("حذف این پرداخت") + } + } + } + } + } + + if (showDeleteConfirm) { + ConfirmDeleteDialog( + title = "حذف پرداخت", + message = "آیا از حذف این رکورد پرداخت مطمئن هستید؟", + onConfirm = { + onDelete() + showDeleteConfirm = false + }, + onDismiss = { showDeleteConfirm = false } + ) + } +} + +@Composable +fun ConfirmDeleteDialog( + title: String, + message: String, + onConfirm: () -> Unit, + onDismiss: () -> Unit +) { + Dialog(onDismissRequest = onDismiss) { + CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) { + Card( + modifier = Modifier.fillMaxWidth().padding(16.dp), + shape = RoundedCornerShape(24.dp) + ) { + Column( + modifier = Modifier.padding(20.dp), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + Text( + text = title, + style = MaterialTheme.typography.headlineSmall, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.error + ) + + Text( + text = message, + style = MaterialTheme.typography.bodyMedium + ) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(12.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Button( + onClick = onDismiss, + modifier = Modifier.weight(1f), + colors = ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.secondary ) + ) { + Text("لغو") + } + + Button( + onClick = onConfirm, + modifier = Modifier.weight(1f), + colors = ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.error + ) + ) { + Text("حذف") } } } @@ -153,6 +230,7 @@ fun AddPaymentDialog( ) { var rawAmount by remember { mutableStateOf(defaultAmount.toLong().toString()) } var description by remember { mutableStateOf(initialDescription) } + var showDeleteConfirm by remember { mutableStateOf(false) } val title = if (onDelete != null) "ویرایش پرداخت برای $personName" else "ثبت پرداخت برای $personName" @@ -220,7 +298,7 @@ fun AddPaymentDialog( Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { if (onDelete != null) { Button( - onClick = onDelete, + onClick = { showDeleteConfirm = true }, colors = ButtonDefaults.buttonColors( containerColor = MaterialTheme.colorScheme.error ) @@ -242,4 +320,16 @@ fun AddPaymentDialog( } } } + + if (showDeleteConfirm && onDelete != null) { + ConfirmDeleteDialog( + title = "حذف پرداخت", + message = "آیا از حذف این رکورد پرداخت برای $personName مطمئن هستید؟", + onConfirm = { + onDelete() + showDeleteConfirm = false + }, + onDismiss = { showDeleteConfirm = false } + ) + } } diff --git a/app/src/main/java/com/oqba26/monthlypaymentapp/viewmodel/ContactViewModel.kt b/app/src/main/java/com/oqba26/monthlypaymentapp/viewmodel/ContactViewModel.kt index f65ecd9..86ecf36 100644 --- a/app/src/main/java/com/oqba26/monthlypaymentapp/viewmodel/ContactViewModel.kt +++ b/app/src/main/java/com/oqba26/monthlypaymentapp/viewmodel/ContactViewModel.kt @@ -29,12 +29,12 @@ data class ContactUiState( val selectedCardForBulk: String? = null, val showBulkSmsDialog: Boolean = false, val personForSmsDialog: PersonUiModel? = null, - val bulkSmsQueueForDialog: List = emptyList() + val bulkSmsQueueForDialog: List = emptyList(), ) @HiltViewModel class ContactViewModel @Inject constructor( - @ApplicationContext private val context: Context, + @param:ApplicationContext private val context: Context, private val networkRepository: NetworkRepository, private val settingsRepository: SettingsRepository, private val smsManager: SmsManager @@ -148,7 +148,7 @@ class ContactViewModel @Inject constructor( val numberIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER) while (cursor.moveToNext()) { - if (nameIndex != -1 && numberIndex != -1) { + if ((nameIndex != -1) && (numberIndex != -1)) { val contactName = cursor.getString(nameIndex) ?: "" val rawNumber = cursor.getString(numberIndex) ?: "" @@ -163,7 +163,20 @@ class ContactViewModel @Inject constructor( fun confirmContactSuggestion(personId: String, personName: String, phoneNumber: String) { viewModelScope.launch { - val statusCode = networkRepository.updatePerson(personId, personName, phoneNumber) + // دریافت اطلاعات فعلی شخص برای حفظ مقادیر دیگر + val currentPerson = networkRepository.getPersonsFlow().first().find { it.id == personId } + val commitment = currentPerson?.monthlyCommitment ?: 0.0 + val startMonth = currentPerson?.startMonth ?: 1 + val startYear = currentPerson?.startYear ?: 1403 + + val statusCode = networkRepository.updatePerson( + personId, + personName, + phoneNumber, + commitment, + startMonth, + startYear + ) if (statusCode == 200) { networkRepository.refresh() _uiState.update { state -> diff --git a/app/src/main/java/com/oqba26/monthlypaymentapp/viewmodel/PersonViewModel.kt b/app/src/main/java/com/oqba26/monthlypaymentapp/viewmodel/PersonViewModel.kt index cb9fdf9..f3425be 100644 --- a/app/src/main/java/com/oqba26/monthlypaymentapp/viewmodel/PersonViewModel.kt +++ b/app/src/main/java/com/oqba26/monthlypaymentapp/viewmodel/PersonViewModel.kt @@ -1,15 +1,14 @@ package com.oqba26.monthlypaymentapp.viewmodel -import android.content.Context import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.oqba26.monthlypaymentapp.core.manager.BackupManager import com.oqba26.monthlypaymentapp.core.manager.ExportManager import com.oqba26.monthlypaymentapp.data.model.PaymentRecord import com.oqba26.monthlypaymentapp.data.model.Person import com.oqba26.monthlypaymentapp.data.repository.LocalPersonRepository import com.oqba26.monthlypaymentapp.data.repository.NetworkRepository import com.oqba26.monthlypaymentapp.data.repository.SettingsRepository -import com.oqba26.monthlypaymentapp.utils.formatTimestampToPersianDateTime import com.oqba26.monthlypaymentapp.utils.getCurrentShamsiDay import com.oqba26.monthlypaymentapp.utils.getCurrentShamsiMonth import com.oqba26.monthlypaymentapp.utils.getCurrentShamsiYear @@ -38,9 +37,13 @@ class PersonViewModel @Inject constructor( private val networkRepository: NetworkRepository, private val localPersonRepository: LocalPersonRepository, private val settingsRepository: SettingsRepository, - private val exportManager: ExportManager + private val exportManager: ExportManager, + private val backupManager: BackupManager, ) : ViewModel() { + /** فقط یک بار در هر اجرا snapshot قبل از ادغام گرفته می‌شود. */ + private var snapshotTakenThisSession = false + private val _searchQuery = MutableStateFlow("") val searchQuery: StateFlow = _searchQuery.asStateFlow() @@ -49,6 +52,8 @@ class PersonViewModel @Inject constructor( private val _selectedPersonId = MutableStateFlow(null) private val _selectedYear = MutableStateFlow(getCurrentShamsiYear()) + private val _currentCategory = MutableStateFlow("salary") + val currentCategory: StateFlow = _currentCategory.asStateFlow() private val _dashboardData = MutableStateFlow(DashboardUiModel()) val dashboardData: StateFlow = _dashboardData.asStateFlow() @@ -85,22 +90,31 @@ class PersonViewModel @Inject constructor( combine( localPersonRepository.getAllPersonsFlow(), localPersonRepository.getAllPaymentsFlow(), - _selectedYear - ) { persons, allPayments, year -> + _selectedYear, + _currentCategory + ) { persons, allPayments, year, category -> val person = persons.find { it.id == personId } + val personCategory = person?.category ?: category val paymentsForPersonInYear = allPayments.filter { - it.personId == personId && it.shamsiYear == year + (it.personId == personId) && (it.shamsiYear == year) && (it.category == personCategory) } val currentYear = getCurrentShamsiYear() val currentMonth = getCurrentShamsiMonth() val currentDay = getCurrentShamsiDay() val monthStates = (1..12).map { month -> val payment = paymentsForPersonInYear.find { it.shamsiMonth == month } + + // بررسی شروع تعهد + val isBeforeCommitment = if (person != null) { + year < person.startYear || (year == person.startYear && month < person.startMonth) + } else false + val status = when { + payment != null -> MonthStatus.PAID + isBeforeCommitment -> MonthStatus.NOT_COMMITTED year < currentYear -> MonthStatus.PAST_YEAR year > currentYear -> MonthStatus.FUTURE_YEAR month > currentMonth || (month == currentMonth && currentDay < 20) -> MonthStatus.FUTURE_MONTH - payment != null -> MonthStatus.PAID else -> MonthStatus.AVAILABLE } MonthUiModel(month, payment, status) @@ -119,12 +133,24 @@ class PersonViewModel @Inject constructor( networkRepository.observeRealtimeChanges() networkRepository.refresh() + // شرط قبلی `persons.isNotEmpty()` بود که دو مشکل داشت: هم لیست خالیِ + // معتبر سرور را نادیده می‌گرفت، و هم اگر واکشی شکست می‌خورد (چون refresh + // خطا را می‌بلعید) روی داده‌ی قدیمی/ناقص merge می‌کرد. + // حالا فقط بعد از یک refresh موفق merge می‌کنیم و خودِ merge هم غیرمخرب است. combine( networkRepository.getPersonsFlow(), - networkRepository.getPaymentsFlow() - ) { persons, payments -> - if (persons.isNotEmpty()) { - localPersonRepository.syncAll(persons, payments) + networkRepository.getPaymentsFlow(), + networkRepository.hasServerDataFlow() + ) { persons, payments, hasServerData -> + if (hasServerData) { + if (!snapshotTakenThisSession) { + // آخرین خط دفاع: قبل از اولین ادغام هر اجرا، وضعیت فعلی + // ذخیره می‌شود تا اگر با وجود همه‌ی محافظت‌ها چیزی خراب شد، + // کاربر بتواند از تنظیمات برش گرداند. + backupManager.createSnapshot(BackupManager.REASON_BEFORE_SYNC) + snapshotTakenThisSession = true + } + localPersonRepository.mergeFromServer(persons, payments) true } else false }.collect { isUpdated -> @@ -141,8 +167,9 @@ class PersonViewModel @Inject constructor( combine( localPersonRepository.getAllPersonsFlow(), localPersonRepository.getAllPaymentsFlow(), - _searchQuery - ) { persons, allPayments, query -> + _searchQuery, + _currentCategory + ) { persons, allPayments, query, category -> val currentMonth = getCurrentShamsiMonth() val currentYear = getCurrentShamsiYear() val currentDay = getCurrentShamsiDay() @@ -151,30 +178,59 @@ class PersonViewModel @Inject constructor( val targetYear = if (currentDay < 20 && currentMonth == 1) currentYear - 1 else currentYear val relevantPayments = allPayments.filter { - it.shamsiMonth == targetMonth && it.shamsiYear == targetYear + it.shamsiMonth == targetMonth && it.shamsiYear == targetYear && it.category == category } val activePersons = persons - .filter { !it.isArchived } + .filter { !it.isArchived && it.category == category } .sortedWith(compareBy({ it.displayOrder }, { it.createdAt })) val uiModels = activePersons.map { person -> val hasPaid = relevantPayments.any { it.personId == person.id } - val debtEndMonth = if (currentDay >= 20) currentMonth else currentMonth - 1 - val unpaidMonthsInCurrentYear = (1..debtEndMonth).filter { m -> - allPayments.none { it.personId == person.id && it.shamsiYear == currentYear && it.shamsiMonth == m } - } + val (debtCount, totalDebtAmount, unpaidMonths) = if (category == "mosque") { + if (person.monthlyCommitment > 0) { + 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) }) + } else { + Triple(0, 0.0, emptyList()) + } + } else { + // Salary logic + 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 } + } + val defaultAmount = settingsRepository.defaultPaymentAmountFlow.first() + Triple(unpaid.size, unpaid.size * defaultAmount, unpaid.map { getPersianMonthName(it) }) + } + + val displayName = if (person.isAnonymous && category == "mosque") { + "خیر ناشناس (${person.name})" + } else { + person.name + } + PersonUiModel( id = person.id, - name = person.name, + name = displayName, hasPaidThisMonth = hasPaid, displayOrder = person.displayOrder, createdAt = person.createdAt, - debtCount = unpaidMonthsInCurrentYear.size, - totalDebtAmount = unpaidMonthsInCurrentYear.size * (settingsRepository.defaultPaymentAmountFlow.first()), + debtCount = debtCount, + totalDebtAmount = totalDebtAmount, phoneNumber = person.phoneNumber, - unpaidMonthsNames = unpaidMonthsInCurrentYear.map { getPersianMonthName(it) } + isAnonymous = person.isAnonymous, + monthlyCommitment = person.monthlyCommitment, + unpaidMonthsNames = unpaidMonths, + needsSync = person.needsSync ) } @@ -185,8 +241,21 @@ class PersonViewModel @Inject constructor( val filteredPaid = if (query.isBlank()) paidModels else paidModels.filter { it.name.contains(query, ignoreCase = true) } val filteredUnpaid = if (query.isBlank()) unpaidModels else unpaidModels.filter { it.name.contains(query, ignoreCase = true) } - val archivedPersons = persons.filter { it.isArchived }.map { - PersonUiModel(it.id, it.name, false, it.displayOrder, it.createdAt) + val archivedPersons = persons.filter { it.isArchived && it.category == category }.map { person -> + val displayName = if (person.isAnonymous && category == "mosque") { + "خیر ناشناس (${person.name})" + } else { + person.name + } + PersonUiModel( + id = person.id, + name = displayName, + hasPaidThisMonth = false, + displayOrder = person.displayOrder, + createdAt = person.createdAt, + isAnonymous = person.isAnonymous, + needsSync = person.needsSync + ) } PersonListUiState( @@ -224,12 +293,46 @@ class PersonViewModel @Inject constructor( } is PersonScreenEvent.AddPerson -> { - if (event.name.isNotBlank()) { - val person = Person(name = event.name, phoneNumber = event.phoneNumber, displayOrder = 0) - val statusCode = networkRepository.addPerson(person) - if (statusCode == 409) _toastMessage.emit("خطا: این نام از قبل وجود دارد.") - else if (statusCode != 200 && statusCode != 201) _toastMessage.emit("خطا در افزودن شخص.") - else networkRepository.refresh() + if (event.isAnonymous || event.name.isNotBlank()) { + val persons = localPersonRepository.getAllPersonsFlow().first() + val finalName = if (event.isAnonymous && event.name.isBlank()) "ناشناس" else event.name + + val isDuplicate = if (!event.isAnonymous) { + persons.any { it.category == _currentCategory.value && it.name.trim().equals(finalName.trim(), ignoreCase = true) } + } else false + + if (isDuplicate) { + _toastMessage.emit("خطا: این نام از قبل وجود دارد.") + } else { + val personId = java.util.UUID.randomUUID().toString() + val person = Person( + id = personId, + name = finalName, + phoneNumber = event.phoneNumber, + displayOrder = 0, + category = _currentCategory.value, + isAnonymous = event.isAnonymous, + monthlyCommitment = if (event.isAnonymous) 0.0 else event.monthlyCommitment, + startMonth = event.startMonth, + startYear = event.startYear, + createdAt = System.currentTimeMillis() + ) + localPersonRepository.insertPersonLocally(person) + + // اگر مبلغ اولیه وارد شده باشد (مخصوصا برای ناشناس) + if (event.initialPaymentAmount > 0) { + val currentMonth = getCurrentShamsiMonth() + val currentYear = getCurrentShamsiYear() + val record = createPaymentRecord( + personId = personId, + month = currentMonth, + year = currentYear, + amount = event.initialPaymentAmount, + description = if (event.isAnonymous) "کمک آنی خیر ناشناس" else "پرداخت اولیه" + ).copy(category = _currentCategory.value) + localPersonRepository.insertPaymentLocally(record) + } + } } } @@ -241,8 +344,8 @@ class PersonViewModel @Inject constructor( val targetYear = if (currentDay < 20 && currentMonth == 1) currentYear - 1 else currentYear val record = createPaymentRecord(event.personId, targetMonth, targetYear, event.amount, event.description) - if (networkRepository.addPayment(record)) networkRepository.refresh() - else _toastMessage.emit("خطا در ثبت پرداخت.") + .copy(category = _currentCategory.value) + localPersonRepository.insertPaymentLocally(record) } is PersonScreenEvent.SelectPerson -> { @@ -251,28 +354,38 @@ class PersonViewModel @Inject constructor( } is PersonScreenEvent.DeletePerson -> { - if (networkRepository.deletePersonAndPayments(event.personId)) networkRepository.refresh() + localPersonRepository.deletePersonLocally(event.personId) } is PersonScreenEvent.ArchivePerson -> { - if (networkRepository.updatePersonArchivedStatus(event.personId, true)) { - _toastMessage.emit("شخص به آرشیو انتقال یافت.") - networkRepository.refresh() - } + localPersonRepository.archivePersonLocally(event.personId, true) + _toastMessage.emit("شخص به آرشیو انتقال یافت.") } is PersonScreenEvent.RestorePerson -> { - if (networkRepository.updatePersonArchivedStatus(event.personId, false)) { - _toastMessage.emit("شخص از آرشیو بازیابی شد.") - networkRepository.refresh() - } + localPersonRepository.archivePersonLocally(event.personId, false) + _toastMessage.emit("شخص از آرشیو بازیابی شد.") } is PersonScreenEvent.UpdatePerson -> { - val statusCode = networkRepository.updatePerson(event.personId, event.name, event.phoneNumber) - if (statusCode == 409) _toastMessage.emit("خطا: این نام از قبل وجود دارد.") - else if (statusCode == 200) networkRepository.refresh() - else _toastMessage.emit("خطا در ویرایش اطلاعات.") + 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) } + + if (isDuplicate) { + _toastMessage.emit("خطا: این نام از قبل وجود دارد.") + } else { + val currentPerson = persons.find { it.id == event.personId } + if (currentPerson != null) { + val updatedPerson = currentPerson.copy( + name = event.name, + phoneNumber = event.phoneNumber, + monthlyCommitment = event.monthlyCommitment, + startMonth = event.startMonth, + startYear = event.startYear + ) + localPersonRepository.updatePersonLocally(updatedPerson) + } + } } is PersonScreenEvent.ChangeYear -> { @@ -286,20 +399,21 @@ class PersonViewModel @Inject constructor( } is PersonScreenEvent.AddPaymentForMonth -> { + val person = localPersonRepository.getAllPersonsFlow().first().find { it.id == event.personId } + val category = person?.category ?: _currentCategory.value val record = createPaymentRecord(event.personId, event.month, event.year, event.amount, event.description) - if (networkRepository.addPayment(record)) networkRepository.refresh() - else _toastMessage.emit("خطا در ثبت پرداخت") + .copy(category = category) + localPersonRepository.insertPaymentLocally(record) } is PersonScreenEvent.UpdatePayment -> { val updatedRecord = event.payment.copy(amount = event.newAmount, description = event.newDescription, timestamp = System.currentTimeMillis()) - if (networkRepository.addPayment(updatedRecord)) networkRepository.refresh() - else _toastMessage.emit("خطا در ویرایش پرداخت") + localPersonRepository.insertPaymentLocally(updatedRecord) } is PersonScreenEvent.DeletePayment -> { - if (networkRepository.deletePayment(event.payment.id)) networkRepository.refresh() - else _toastMessage.emit("خطا در حذف پرداخت") + localPersonRepository.deletePaymentLocally(event.payment.id) + _toastMessage.emit("پرداخت با موفقیت حذف شد") } is PersonScreenEvent.ToggleSelection -> { @@ -320,22 +434,48 @@ class PersonViewModel @Inject constructor( _isSelectionMode.value = false } - is PersonScreenEvent.ExportYearlyReport -> exportYearlyReport(event.year, event.context) + is PersonScreenEvent.ExportYearlyReport -> exportYearlyReport(event.year) + + is PersonScreenEvent.AddBulkPayments -> { + try { + val person = localPersonRepository.getAllPersonsFlow().first().find { it.id == event.personId } + val category = person?.category ?: _currentCategory.value + event.months.forEach { month -> + val record = createPaymentRecord( + personId = event.personId, + month = month, + year = event.year, + amount = event.amount, + description = "پرداخت دسته جمعی" + ).copy(category = category) + localPersonRepository.insertPaymentLocally(record) + } + } catch (_: Exception) { + _toastMessage.emit("خطا در ثبت پرداخت‌های دسته جمعی") + } + } + + is PersonScreenEvent.SetCategory -> { + _currentCategory.value = event.category + _selectedIds.value = emptySet() + _isSelectionMode.value = false + } is PersonScreenEvent.CommitReorder -> { val list = if (event.listType == PersonListType.UNPAID) _uiState.value.unpaidPersons else _uiState.value.paidPersons if (list.size < 2) return@launch try { - list.forEachIndexed { index, person -> + list.forEachIndexed { index, personUi -> val newOrder = (index + 1) * 10_000L - if (person.displayOrder != newOrder) { - networkRepository.updatePersonDisplayOrder(person.id, newOrder) + if (personUi.displayOrder != newOrder) { + val person = localPersonRepository.getAllPersonsFlow().first().find { it.id == personUi.id } + person?.let { + localPersonRepository.updatePersonLocally(it.copy(displayOrder = newOrder)) + } } } - networkRepository.refresh() } catch (_: Exception) { _toastMessage.emit("خطا در ذخیره ترتیب") - networkRepository.refresh() } } @@ -349,7 +489,7 @@ class PersonViewModel @Inject constructor( } } - private fun exportYearlyReport(year: Int, context: Context) { + private fun exportYearlyReport(year: Int) { viewModelScope.launch { val persons = localPersonRepository.getAllPersonsFlow().first() val payments = localPersonRepository.getAllPaymentsFlow().first().filter { it.shamsiYear == year } diff --git a/app/src/main/java/com/oqba26/monthlypaymentapp/viewmodel/SettingsViewModel.kt b/app/src/main/java/com/oqba26/monthlypaymentapp/viewmodel/SettingsViewModel.kt index 51f9c36..e3fd341 100644 --- a/app/src/main/java/com/oqba26/monthlypaymentapp/viewmodel/SettingsViewModel.kt +++ b/app/src/main/java/com/oqba26/monthlypaymentapp/viewmodel/SettingsViewModel.kt @@ -2,15 +2,19 @@ package com.oqba26.monthlypaymentapp.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.oqba26.monthlypaymentapp.core.manager.BackupManager +import com.oqba26.monthlypaymentapp.core.manager.SnapshotInfo import com.oqba26.monthlypaymentapp.data.model.BackupData import com.oqba26.monthlypaymentapp.data.repository.LocalPersonRepository import com.oqba26.monthlypaymentapp.data.repository.SettingsRepository import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import kotlinx.serialization.encodeToString @@ -19,7 +23,8 @@ import kotlinx.serialization.json.Json @HiltViewModel class SettingsViewModel @Inject constructor( private val settingsRepository: SettingsRepository, - private val localPersonRepository: LocalPersonRepository + private val localPersonRepository: LocalPersonRepository, + private val backupManager: BackupManager ) : ViewModel() { private val json = Json { @@ -98,10 +103,32 @@ class SettingsViewModel @Inject constructor( return json.encodeToString(backupData) } + // ---------------------------------------------- نسخه‌های پشتیبان خودکار (تور نجات) + + private val _snapshots = MutableStateFlow>(emptyList()) + val snapshots: StateFlow> = _snapshots.asStateFlow() + + fun loadSnapshots() { + viewModelScope.launch { _snapshots.value = backupManager.listSnapshots() } + } + + fun restoreSnapshot(info: SnapshotInfo) { + viewModelScope.launch { + val ok = backupManager.restoreSnapshot(info.file) + _toastMessage.emit( + if (ok) "اطلاعات از نسخه‌ی خودکار بازگردانی شد. لطفاً برنامه را مجدد اجرا کنید." + else "بازگردانی ناموفق بود؛ فایل پشتیبان سالم نیست." + ) + loadSnapshots() + } + } + fun restoreFromBackupJson(jsonString: String) { viewModelScope.launch { try { val backupData = json.decodeFromString(jsonString) + // قبل از جایگزینی، وضعیت فعلی نگه داشته می‌شود تا راه برگشت بسته نشود. + backupManager.createSnapshot(BackupManager.REASON_BEFORE_RESTORE) localPersonRepository.restoreBackup(backupData) _toastMessage.emit("اطلاعات با موفقیت بازیابی شد! لطفاً برنامه را مجدد اجرا کنید.") } catch (_: Exception) { diff --git a/app/src/main/java/com/oqba26/monthlypaymentapp/viewmodel/UiModels.kt b/app/src/main/java/com/oqba26/monthlypaymentapp/viewmodel/UiModels.kt index 6f22665..410656d 100644 --- a/app/src/main/java/com/oqba26/monthlypaymentapp/viewmodel/UiModels.kt +++ b/app/src/main/java/com/oqba26/monthlypaymentapp/viewmodel/UiModels.kt @@ -1,6 +1,5 @@ package com.oqba26.monthlypaymentapp.viewmodel -import android.content.Context import com.oqba26.monthlypaymentapp.data.model.PaymentRecord import com.oqba26.monthlypaymentapp.data.model.Person @@ -13,7 +12,10 @@ data class PersonUiModel( val debtCount: Int = 0, val totalDebtAmount: Double = 0.0, val phoneNumber: String? = null, - val unpaidMonthsNames: List = emptyList() + val isAnonymous: Boolean = false, + val monthlyCommitment: Double = 0.0, + val unpaidMonthsNames: List = emptyList(), + val needsSync: Boolean = false, ) data class PersonListUiState( @@ -55,7 +57,8 @@ enum class MonthStatus { AVAILABLE, FUTURE_MONTH, PAST_YEAR, - FUTURE_YEAR + FUTURE_YEAR, + NOT_COMMITTED } enum class PersonListType { @@ -65,8 +68,23 @@ enum class PersonListType { sealed class PersonScreenEvent { data object RefreshData : PersonScreenEvent() - data class AddPerson(val name: String, val phoneNumber: String?, val context: Context) : PersonScreenEvent() - data class UpdatePerson(val personId: String, val name: String, val phoneNumber: String?) : PersonScreenEvent() + data class AddPerson( + val name: String, + val phoneNumber: String?, + val isAnonymous: Boolean = false, + val monthlyCommitment: Double = 0.0, + val startMonth: Int = 1, + val startYear: Int = 1403, + val initialPaymentAmount: Double = 0.0 + ) : PersonScreenEvent() + data class UpdatePerson( + val personId: String, + val name: String, + val phoneNumber: String?, + val monthlyCommitment: Double = 0.0, + val startMonth: Int = 1, + val startYear: Int = 1403 + ) : PersonScreenEvent() data class DeletePerson(val personId: String) : PersonScreenEvent() data class ArchivePerson(val personId: String) : PersonScreenEvent() data class RestorePerson(val personId: String) : PersonScreenEvent() @@ -110,14 +128,13 @@ sealed class PersonScreenEvent { val year: Int, val amount: Double ) : PersonScreenEvent() - data class ExportYearlyReport(val year: Int, val context: Context) : PersonScreenEvent() + data class ExportYearlyReport(val year: Int) : PersonScreenEvent() data class ToggleSelection(val personId: String) : PersonScreenEvent() data class MoveSelected(val direction: Int, val listType: PersonListType) : PersonScreenEvent() data object ClearSelection : PersonScreenEvent() - data class ConfirmContactSuggestion(val personId: String, val personName: String, val phoneNumber: String) : PersonScreenEvent() - data class DismissContactSuggestion(val suggestion: ContactSuggestion) : PersonScreenEvent() + data class SetCategory(val category: String) : PersonScreenEvent() } data class DashboardUiModel( diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e7dd9e6..8d6c2aa 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -21,6 +21,19 @@ خطا در ایجاد فایل پشتیبان هشدار بازیابی با بازیابی، تمام داده‌های فعلی حذف و اطلاعات فایل جایگزین می‌شود. مطمئن هستید؟ + + + نسخه‌های خودکار + بازگردانی از نسخه‌ی خودکار + برنامه قبل از هر عملیات پرخطر، خودش یک نسخه از اطلاعات شما ذخیره می‌کند. اگر داده‌ای گم شد، می‌توانید از اینجا برش گردانید. + هنوز نسخه‌ی خودکاری ذخیره نشده است. + %1$s نفر، %2$s پرداخت + با بازگردانی این نسخه، اطلاعات فعلی جایگزین می‌شود. مطمئن هستید؟ + قبل از همگام‌سازی با سرور + قبل از بازیابی نسخه‌ی پشتیبان + بعد از به‌روزرسانی برنامه + نسخه‌ی خودکار + بستن خروج از حساب کاربری مبلغ پیش‌فرض پرداخت روز یادآور ماهانه diff --git a/app/src/test/java/com/oqba26/monthlypaymentapp/data/sync/SyncMergerTest.kt b/app/src/test/java/com/oqba26/monthlypaymentapp/data/sync/SyncMergerTest.kt new file mode 100644 index 0000000..00e8980 --- /dev/null +++ b/app/src/test/java/com/oqba26/monthlypaymentapp/data/sync/SyncMergerTest.kt @@ -0,0 +1,202 @@ +package com.oqba26.monthlypaymentapp.data.sync + +import com.oqba26.monthlypaymentapp.data.model.PaymentRecord +import com.oqba26.monthlypaymentapp.data.model.Person +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test + +/** + * تست‌های [SyncMerger]. + * + * تمرکز این تست‌ها روی یک چیز است: **هیچ مسیری نباید داده‌ی سینک‌نشده را پاک کند.** + * این همان باگی است که در نسخهٔ قبلی بی‌صدا پرداخت‌های کاربر را از بین می‌برد. + */ +class SyncMergerTest { + + private fun person( + id: String, + name: String = "شخص $id", + needsSync: Boolean = false + ) = Person(id = id, name = name, needsSync = needsSync) + + private fun payment( + id: String, + personId: String = "p1", + amount: Double = 1000.0, + needsSync: Boolean = false + ) = PaymentRecord( + id = id, + personId = personId, + amount = amount, + shamsiYear = 1404, + shamsiMonth = 5, + needsSync = needsSync + ) + + // ---------------------------------------------------------------- اشخاص + + @Test + fun `شخص در صف سینک حذف نمی شود حتی اگر در سرور نباشد`() { + val local = listOf(person("a"), person("b")) + val server = listOf(person("a")) + + val plan = SyncMerger.planPersons(local, server, pendingIds = setOf("b")) + + assertFalse("شخص b در صف است و نباید حذف شود", plan.deleteIds.contains("b")) + assertEquals(emptyList(), plan.deleteIds) + } + + @Test + fun `شخص با needsSync حذف نمی شود حتی اگر صف خالی باشد`() { + // سناریوی بازماندهٔ باگ قبلی: صف از دست رفته ولی پرچم روی رکورد هست. + val local = listOf(person("a"), person("b", needsSync = true)) + val server = listOf(person("a")) + + val plan = SyncMerger.planPersons(local, server, pendingIds = emptySet()) + + assertEquals(emptyList(), plan.deleteIds) + } + + @Test + fun `داده سرور روی شخص در صف بازنویسی نمی شود`() { + val local = listOf(person("a", name = "نام محلی جدید", needsSync = true)) + val server = listOf(person("a", name = "نام قدیمی سرور")) + + val plan = SyncMerger.planPersons(local, server, pendingIds = setOf("a")) + + assertTrue("رکورد در صف نباید upsert شود", plan.upserts.isEmpty()) + } + + @Test + fun `شخص محلی غیر pending که در سرور نیست حذف می شود`() { + val local = listOf(person("a"), person("b")) + val server = listOf(person("a")) + + val plan = SyncMerger.planPersons(local, server, pendingIds = emptySet()) + + assertEquals(listOf("b"), plan.deleteIds) + } + + @Test + fun `سرور خالی هیچ رکورد محافظت شده ای را پاک نمی کند`() { + val local = listOf( + person("a", needsSync = true), + person("b"), + person("c") + ) + + val plan = SyncMerger.planPersons(local, server = emptyList(), pendingIds = setOf("b")) + + // a با needsSync و b از طریق صف محافظت شده‌اند؛ فقط c حذف می‌شود. + assertEquals(listOf("c"), plan.deleteIds) + assertTrue(plan.upserts.isEmpty()) + } + + @Test + fun `upsert پرچم needsSync را پاک می کند`() { + val plan = SyncMerger.planPersons( + local = listOf(person("a", needsSync = true)), + server = listOf(person("a")), + pendingIds = emptySet() + ) + + // needsSync محلی محافظت می‌کند، پس upsert خالی است. + assertTrue(plan.upserts.isEmpty()) + + // ولی وقتی محافظتی نباشد، رکورد سرور باید needsSync = false داشته باشد. + val clean = SyncMerger.planPersons( + local = listOf(person("a")), + server = listOf(person("a", needsSync = true)), + pendingIds = emptySet() + ) + assertEquals(1, clean.upserts.size) + assertFalse(clean.upserts.first().needsSync) + } + + @Test + fun `شخص سرور بدون شناسه به همنام محلی وصل می شود`() { + val local = listOf(person("local-id", name = "علی رضایی")) + val server = listOf(person("", name = " علی رضایی ")) + + val plan = SyncMerger.planPersons( + local, server, pendingIds = emptySet(), + idGenerator = { "شناسه-جدید-نباید-استفاده-شود" } + ) + + assertEquals(listOf("local-id"), plan.upserts.map { it.id }) + assertEquals("علی رضایی", plan.upserts.first().name) + // چون شناسه به رکورد محلی نگاشت شد، نباید حذفی رخ دهد. + assertEquals(emptyList(), plan.deleteIds) + } + + @Test + fun `شخص سرور بدون شناسه و بدون همنام محلی شناسه جدید می گیرد`() { + val plan = SyncMerger.planPersons( + local = emptyList(), + server = listOf(person("", name = "شخص تازه")), + pendingIds = emptySet(), + idGenerator = { "شناسه-تولیدی" } + ) + + assertEquals(listOf("شناسه-تولیدی"), plan.upserts.map { it.id }) + } + + // -------------------------------------------------------------- پرداخت‌ها + + @Test + fun `پرداخت آفلاین کاربر با merge از بین نمی رود`() { + // این دقیقاً همان سناریویی است که قبلاً داده را نابود می‌کرد: + // کاربر آفلاین پرداخت ثبت می‌کند، سرور هنوز خبر ندارد، merge اجرا می‌شود. + val offlinePayment = payment("جدید-آفلاین", needsSync = true) + val local = listOf(payment("قدیمی-سینک-شده"), offlinePayment) + val server = listOf(payment("قدیمی-سینک-شده")) + + val plan = SyncMerger.planPayments(local, server, pendingIds = setOf("جدید-آفلاین")) + + assertEquals(emptyList(), plan.deleteIds) + assertFalse(plan.upserts.any { it.id == "جدید-آفلاین" }) + } + + @Test + fun `مبلغ ویرایش شده محلی با داده قدیمی سرور بازنویسی نمی شود`() { + val local = listOf(payment("x", amount = 500_000.0, needsSync = true)) + val server = listOf(payment("x", amount = 200_000.0)) + + val plan = SyncMerger.planPayments(local, server, pendingIds = setOf("x")) + + assertTrue(plan.upserts.isEmpty()) + } + + @Test + fun `پرداخت حذف شده در سرور از گوشی هم حذف می شود`() { + val local = listOf(payment("x"), payment("y")) + val server = listOf(payment("x")) + + val plan = SyncMerger.planPayments(local, server, pendingIds = emptySet()) + + assertEquals(listOf("y"), plan.deleteIds) + } + + @Test + fun `سرور خالی پرداخت های در صف را نگه می دارد`() { + val local = listOf( + payment("pending", needsSync = true), + payment("synced") + ) + + val plan = SyncMerger.planPayments(local, server = emptyList(), pendingIds = emptySet()) + + assertEquals(listOf("synced"), plan.deleteIds) + } + + @Test + fun `merge بدون تغییر نقشه خالی می دهد`() { + val rows = listOf(payment("x"), payment("y")) + val plan = SyncMerger.planPayments(rows, rows, pendingIds = emptySet()) + + assertEquals(emptyList(), plan.deleteIds) + assertEquals(2, plan.upserts.size) // upsert بی‌ضرر است، حذف نیست + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a0573ea..c8a3abc 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -59,9 +59,11 @@ google-gson = { group = "com.google.code.gson", name = "gson", version.ref = "gs androidx-room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" } androidx-room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room" } androidx-room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" } +androidx-room-testing = { group = "androidx.room", name = "room-testing", version.ref = "room" } kotlinx-coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "coroutines" } kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "coroutines" } +kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "coroutines" } androidx-datastore-preferences = { group = "androidx.datastore", name = "datastore-preferences", version.ref = "datastore" } diff --git a/update.json b/update.json index 49e7283..9907632 100644 --- a/update.json +++ b/update.json @@ -3,5 +3,5 @@ "versionName": "1.0.0", "url": "https://github.com/oqba26/MonthlyPaymentApp/releases/download/v1.0.0/MonthlyPaymentApp.apk", "releaseNotes": "نسخه اولیه برنامه مدیریت حقوق ماهانه:\n- مدیریت لیست افراد\n- تفکیک وضعیت پرداخت\n- سیستم آپدیت خودکار\n- مدیریت دسترسی‌های اجباری", - "isForceUpdate": false + "isForceUpdate": true }