amin 1 год назад
Родитель
Сommit
d336ef2cb6
99 измененных файлов: 7593 добавлений и 961 удалений
  1. +7
    -1
      android/app/src/main/AndroidManifest.xml
  2. Двоичные данные
      android/app/src/main/res/mipmap-hdpi/ic_launcher.png
  3. Двоичные данные
      android/app/src/main/res/mipmap-mdpi/ic_launcher.png
  4. Двоичные данные
      android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
  5. Двоичные данные
      android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
  6. Двоичные данные
      android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
  7. Двоичные данные
      assets/images/001.jpg
  8. Двоичные данные
      assets/images/5893208387750053755.jpg
  9. Двоичные данные
      assets/images/logoaboutus.png
  10. Двоичные данные
      assets/images/logoapp.jpg
  11. Двоичные данные
      assets/images/logomizban.png
  12. +2
    -2
      ios/Runner.xcodeproj/project.pbxproj
  13. +1
    -122
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
  14. Двоичные данные
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
  15. Двоичные данные
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
  16. Двоичные данные
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
  17. Двоичные данные
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
  18. Двоичные данные
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
  19. Двоичные данные
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
  20. Двоичные данные
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
  21. Двоичные данные
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
  22. Двоичные данные
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
  23. Двоичные данные
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
  24. Двоичные данные
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png
  25. Двоичные данные
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png
  26. Двоичные данные
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png
  27. Двоичные данные
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png
  28. Двоичные данные
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
  29. Двоичные данные
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
  30. Двоичные данные
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png
  31. Двоичные данные
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png
  32. Двоичные данные
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
  33. Двоичные данные
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
  34. Двоичные данные
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
  35. +1
    -1
      ios/Runner/Info.plist
  36. +1
    -0
      lib/config/ui_config.dart
  37. +24
    -19
      lib/diologs/diolog_add_location.dart
  38. +25
    -20
      lib/diologs/diolog_add_subject.dart
  39. +36
    -36
      lib/diologs/diolog_add_user.dart
  40. +150
    -97
      lib/drawer_navigation_bar.dart
  41. +8
    -0
      lib/global/global_class/selected_item.dart
  42. +23
    -26
      lib/global/global_state/global_state.dart
  43. +121
    -42
      lib/l10n/app_en.arb
  44. +79
    -2
      lib/l10n/app_fa.arb
  45. +16
    -1
      lib/main.dart
  46. +2
    -2
      lib/models/meetings/meetings_model.dart
  47. +279
    -0
      lib/models/private_meeting/one_private_meeting_model.dart
  48. +338
    -0
      lib/models/private_meeting/private_meetings_model.dart
  49. +68
    -0
      lib/router/router.dart
  50. +48
    -0
      lib/screens/aboutUs/screen.dart
  51. +1
    -0
      lib/screens/auth/login_screen.dart
  52. +3
    -3
      lib/screens/auth/state/state.dart
  53. +311
    -138
      lib/screens/home/screen.dart
  54. +35
    -4
      lib/screens/home/state.dart
  55. +34
    -16
      lib/screens/meeting/diolog_meetings_filters.dart
  56. +160
    -118
      lib/screens/meeting/screen.dart
  57. +37
    -7
      lib/screens/meeting/state.dart
  58. +505
    -0
      lib/screens/meeting_add/screen.dart
  59. +74
    -0
      lib/screens/meeting_add/state.dart
  60. +33
    -83
      lib/screens/meeting_edit/screen.dart
  61. +10
    -9
      lib/screens/meeting_edit/state.dart
  62. +359
    -111
      lib/screens/meeting_summary/screen.dart
  63. +101
    -3
      lib/screens/meeting_summary/state.dart
  64. +417
    -0
      lib/screens/private_meeting/dilog_privateMeetings_filters.dart
  65. +476
    -0
      lib/screens/private_meeting/screen.dart
  66. +301
    -0
      lib/screens/private_meeting/state.dart
  67. +550
    -0
      lib/screens/private_meeting_add/screen.dart
  68. +81
    -0
      lib/screens/private_meeting_add/state.dart
  69. +444
    -0
      lib/screens/private_meeting_edit/screen.dart
  70. +165
    -0
      lib/screens/private_meeting_edit/state.dart
  71. +590
    -0
      lib/screens/private_meeting_summary/screen.dart
  72. +161
    -0
      lib/screens/private_meeting_summary/state.dart
  73. +422
    -0
      lib/screens/report/screen.dart
  74. +113
    -0
      lib/screens/report/state.dart
  75. +15
    -7
      lib/services/global/global.dart
  76. +31
    -1
      lib/services/home/home.dart
  77. +172
    -9
      lib/services/meetings/meetings.dart
  78. +427
    -0
      lib/services/private_meetings/private_meetings.dart
  79. +57
    -0
      lib/services/report/report.dart
  80. +3
    -0
      lib/setting/app_setting.dart
  81. +5
    -2
      lib/setting/setting.dart
  82. +13
    -8
      lib/splash_screen.dart
  83. +5
    -5
      lib/utils/tools/tools.dart
  84. +8
    -5
      lib/widgets/ExpansionTileCustom.dart
  85. +25
    -16
      lib/widgets/card_meeting.dart
  86. +58
    -0
      lib/widgets/checkBox_inTile.dart
  87. +2
    -1
      lib/widgets/custom_appbar.dart
  88. +12
    -15
      lib/widgets/custom_background.dart
  89. +4
    -20
      lib/widgets/custom_button.dart
  90. +8
    -3
      lib/widgets/custom_textfield.dart
  91. +2
    -2
      lib/widgets/empty_widget.dart
  92. +7
    -3
      lib/widgets/error_widget.dart
  93. +4
    -0
      linux/flutter/generated_plugin_registrant.cc
  94. +1
    -0
      linux/flutter/generated_plugins.cmake
  95. +2
    -0
      macos/Flutter/GeneratedPluginRegistrant.swift
  96. +112
    -0
      pubspec.lock
  97. +4
    -1
      pubspec.yaml
  98. +3
    -0
      windows/flutter/generated_plugin_registrant.cc
  99. +1
    -0
      windows/flutter/generated_plugins.cmake

+ 7
- 1
android/app/src/main/AndroidManifest.xml Просмотреть файл

@@ -1,6 +1,12 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

<application
android:label="qadirneyriz"
android:label="Mizban"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity


Двоичные данные
android/app/src/main/res/mipmap-hdpi/ic_launcher.png Просмотреть файл

До После
Ширина: 72  |  Высота: 72  |  Размер: 544 B Ширина: 72  |  Высота: 72  |  Размер: 6.1 KiB

Двоичные данные
android/app/src/main/res/mipmap-mdpi/ic_launcher.png Просмотреть файл

До После
Ширина: 48  |  Высота: 48  |  Размер: 442 B Ширина: 48  |  Высота: 48  |  Размер: 3.3 KiB

Двоичные данные
android/app/src/main/res/mipmap-xhdpi/ic_launcher.png Просмотреть файл

До После
Ширина: 96  |  Высота: 96  |  Размер: 721 B Ширина: 96  |  Высота: 96  |  Размер: 9.2 KiB

Двоичные данные
android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png Просмотреть файл

До После
Ширина: 144  |  Высота: 144  |  Размер: 1.0 KiB Ширина: 144  |  Высота: 144  |  Размер: 16 KiB

Двоичные данные
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png Просмотреть файл

До После
Ширина: 192  |  Высота: 192  |  Размер: 1.4 KiB Ширина: 192  |  Высота: 192  |  Размер: 23 KiB

Двоичные данные
assets/images/001.jpg Просмотреть файл

До После
Ширина: 1080  |  Высота: 1920  |  Размер: 448 KiB

Двоичные данные
assets/images/5893208387750053755.jpg Просмотреть файл

До После
Ширина: 1215  |  Высота: 1097  |  Размер: 57 KiB

Двоичные данные
assets/images/logoaboutus.png Просмотреть файл

До После
Ширина: 500  |  Высота: 500  |  Размер: 83 KiB

Двоичные данные
assets/images/logoapp.jpg Просмотреть файл

До После
Ширина: 500  |  Высота: 500  |  Размер: 27 KiB

Двоичные данные
assets/images/logomizban.png Просмотреть файл

До После
Ширина: 512  |  Высота: 512  |  Размер: 30 KiB

+ 2
- 2
ios/Runner.xcodeproj/project.pbxproj Просмотреть файл

@@ -427,7 +427,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
@@ -484,7 +484,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";


+ 1
- 122
ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json Просмотреть файл

@@ -1,122 +1 @@
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@3x.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@3x.png",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@1x.png",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@1x.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@1x.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "Icon-App-1024x1024@1x.png",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
{"images":[{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@3x.png","scale":"3x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@3x.png","scale":"3x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@3x.png","scale":"3x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@1x.png","scale":"1x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@3x.png","scale":"3x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@1x.png","scale":"1x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@1x.png","scale":"1x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@1x.png","scale":"1x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@2x.png","scale":"2x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@1x.png","scale":"1x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@2x.png","scale":"2x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@1x.png","scale":"1x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@2x.png","scale":"2x"},{"size":"83.5x83.5","idiom":"ipad","filename":"Icon-App-83.5x83.5@2x.png","scale":"2x"},{"size":"1024x1024","idiom":"ios-marketing","filename":"Icon-App-1024x1024@1x.png","scale":"1x"}],"info":{"version":1,"author":"xcode"}}

Двоичные данные
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png Просмотреть файл

До После
Ширина: 1024  |  Высота: 1024  |  Размер: 11 KiB Ширина: 1024  |  Высота: 1024  |  Размер: 233 KiB

Двоичные данные
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png Просмотреть файл

До После
Ширина: 20  |  Высота: 20  |  Размер: 295 B Ширина: 20  |  Высота: 20  |  Размер: 593 B

Двоичные данные
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png Просмотреть файл

До После
Ширина: 40  |  Высота: 40  |  Размер: 406 B Ширина: 40  |  Высота: 40  |  Размер: 1.6 KiB

Двоичные данные
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png Просмотреть файл

До После
Ширина: 60  |  Высота: 60  |  Размер: 450 B Ширина: 60  |  Высота: 60  |  Размер: 2.8 KiB

Двоичные данные
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png Просмотреть файл

До После
Ширина: 29  |  Высота: 29  |  Размер: 282 B Ширина: 29  |  Высота: 29  |  Размер: 1.0 KiB

Двоичные данные
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png Просмотреть файл

До После
Ширина: 58  |  Высота: 58  |  Размер: 462 B Ширина: 58  |  Высота: 58  |  Размер: 2.7 KiB

Двоичные данные
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png Просмотреть файл

До После
Ширина: 87  |  Высота: 87  |  Размер: 704 B Ширина: 87  |  Высота: 87  |  Размер: 5.0 KiB

Двоичные данные
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png Просмотреть файл

До После
Ширина: 40  |  Высота: 40  |  Размер: 406 B Ширина: 40  |  Высота: 40  |  Размер: 1.6 KiB

Двоичные данные
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png Просмотреть файл

До После
Ширина: 80  |  Высота: 80  |  Размер: 586 B Ширина: 80  |  Высота: 80  |  Размер: 4.4 KiB

Двоичные данные
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png Просмотреть файл

До После
Ширина: 120  |  Высота: 120  |  Размер: 862 B Ширина: 120  |  Высота: 120  |  Размер: 8.4 KiB

Двоичные данные
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png Просмотреть файл

До После
Ширина: 50  |  Высота: 50  |  Размер: 2.2 KiB

Двоичные данные
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png Просмотреть файл

До После
Ширина: 100  |  Высота: 100  |  Размер: 6.3 KiB

Двоичные данные
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png Просмотреть файл

До После
Ширина: 57  |  Высота: 57  |  Размер: 2.6 KiB

Двоичные данные
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png Просмотреть файл

До После
Ширина: 114  |  Высота: 114  |  Размер: 7.7 KiB

Двоичные данные
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png Просмотреть файл

До После
Ширина: 120  |  Высота: 120  |  Размер: 862 B Ширина: 120  |  Высота: 120  |  Размер: 8.4 KiB

Двоичные данные
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png Просмотреть файл

До После
Ширина: 180  |  Высота: 180  |  Размер: 1.6 KiB Ширина: 180  |  Высота: 180  |  Размер: 16 KiB

Двоичные данные
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png Просмотреть файл

До После
Ширина: 72  |  Высота: 72  |  Размер: 3.7 KiB

Двоичные данные
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png Просмотреть файл

До После
Ширина: 144  |  Высота: 144  |  Размер: 11 KiB

Двоичные данные
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png Просмотреть файл

До После
Ширина: 76  |  Высота: 76  |  Размер: 762 B Ширина: 76  |  Высота: 76  |  Размер: 4.1 KiB

Двоичные данные
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png Просмотреть файл

До После
Ширина: 152  |  Высота: 152  |  Размер: 1.2 KiB Ширина: 152  |  Высота: 152  |  Размер: 12 KiB

Двоичные данные
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png Просмотреть файл

До После
Ширина: 167  |  Высота: 167  |  Размер: 1.4 KiB Ширина: 167  |  Высота: 167  |  Размер: 14 KiB

+ 1
- 1
ios/Runner/Info.plist Просмотреть файл

@@ -5,7 +5,7 @@
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Qadirneyriz</string>
<string>Mizban</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>


+ 1
- 0
lib/config/ui_config.dart Просмотреть файл

@@ -5,5 +5,6 @@ class UIConfig {
final Color buttongreen = const Color(0xff04A54F);
final Color secendGreen = const Color.fromARGB(255, 75, 173, 78);
final Color mainGray = const Color(0xff333333);
final Color backGroundColor = const Color(0xffedf7ee);
const UIConfig();
}

lib/screens/meeting_edit/diolog_add_location.dart → lib/diologs/diolog_add_location.dart Просмотреть файл

@@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
import 'package:qadirneyriz/global_state/global_state.dart';
import 'package:qadirneyriz/global/global_state/global_state.dart';
import 'package:qadirneyriz/utils/enums/status.dart';
import 'package:qadirneyriz/utils/tools/tools.dart';
import 'package:qadirneyriz/widgets/custom_button.dart';
@@ -12,7 +12,9 @@ class AddLocationDiolog extends StatelessWidget {
AddLocationDiolog({
super.key,
});
final TextEditingController addressController = TextEditingController();
final TextEditingController farsiAddressController = TextEditingController();
final TextEditingController englishAddressController =
TextEditingController();
@override
Widget build(BuildContext context) {
return Consumer<GlobalState>(
@@ -23,11 +25,18 @@ class AddLocationDiolog extends StatelessWidget {
child: Column(
mainAxisSize: MainAxisSize.min, // برای اندازه‌گیری درست دیالوگ
children: [
Text('آدرس جدید'),
Text(
AppLocalizations.of(context)!.newlocation,
),
CustomTextField(
label: AppLocalizations.of(context)!.farsi,
hintText: '',
textEditingController: farsiAddressController,
textInputType: TextInputType.text),
CustomTextField(
label: 'آدرس',
label: AppLocalizations.of(context)!.english,
hintText: '',
textEditingController: addressController,
textEditingController: englishAddressController,
textInputType: TextInputType.text),
SizedBox(
height: 20,
@@ -50,29 +59,28 @@ class AddLocationDiolog extends StatelessWidget {
text: AppLocalizations.of(context)!.loading,
fontSize: 13,
onPressed: null,
topRightRadius: 10,
topLeftRadius: 10,
bottomLeftRadius: 10,
bottomRightRadius: 10,
borderRadius: 10,
);

default:
return CustomButton(
hieght: 40,
width: double.infinity,
text: 'اظافه کردن',
text: AppLocalizations.of(context)!.add,
fontSize: 13,
onPressed: () async {
if (addressController.text != '') {
if (farsiAddressController.text != '' &&
englishAddressController.text != '') {
// call add new subject
final status =
await state.addNewAddress(address: addressController.text);
final status = await state.addNewAddress(
address: farsiAddressController.text,
addressEn: englishAddressController.text);

if (status == Status.ready) {
// call refrresh subjects
await state.getLocations(refresh: true);
Tools.showCustomSnackBar(
text: 'آدرس اظافه شد!',
text: AppLocalizations.of(context)!.addressadded,
isError: false,
context,
);
@@ -90,16 +98,13 @@ class AddLocationDiolog extends StatelessWidget {
}
} else {
Tools.showCustomSnackBar(
text: 'آدرس وارد کنید!',
text: AppLocalizations.of(context)!.erroraddress,
isError: true,
context,
);
}
},
topRightRadius: 10,
topLeftRadius: 10,
bottomLeftRadius: 10,
bottomRightRadius: 10,
borderRadius: 10,
);
}
}

lib/screens/meeting_edit/diolog_add_subject.dart → lib/diologs/diolog_add_subject.dart Просмотреть файл

@@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
import 'package:qadirneyriz/global_state/global_state.dart';
import 'package:qadirneyriz/global/global_state/global_state.dart';
import 'package:qadirneyriz/utils/enums/status.dart';
import 'package:qadirneyriz/utils/tools/tools.dart';
import 'package:qadirneyriz/widgets/custom_button.dart';
@@ -12,7 +12,9 @@ class AddSubjectDiolog extends StatelessWidget {
AddSubjectDiolog({
super.key,
});
final TextEditingController subjectController = TextEditingController();
final TextEditingController farsiSubjectController = TextEditingController();
final TextEditingController englishSubjectController =
TextEditingController();
@override
Widget build(BuildContext context) {
return Consumer<GlobalState>(
@@ -23,11 +25,18 @@ class AddSubjectDiolog extends StatelessWidget {
child: Column(
mainAxisSize: MainAxisSize.min, // برای اندازه‌گیری درست دیالوگ
children: [
Text('موضوع جدید'),
Text(
AppLocalizations.of(context)!.newsubject,
),
CustomTextField(
label: AppLocalizations.of(context)!.farsi,
hintText: '',
textEditingController: farsiSubjectController,
textInputType: TextInputType.text),
CustomTextField(
label: 'موضوع',
label: AppLocalizations.of(context)!.english,
hintText: '',
textEditingController: subjectController,
textEditingController: englishSubjectController,
textInputType: TextInputType.text),
SizedBox(
height: 20,
@@ -50,29 +59,28 @@ class AddSubjectDiolog extends StatelessWidget {
text: AppLocalizations.of(context)!.loading,
fontSize: 13,
onPressed: null,
topRightRadius: 10,
topLeftRadius: 10,
bottomLeftRadius: 10,
bottomRightRadius: 10,
borderRadius: 10,
);

default:
return CustomButton(
hieght: 40,
width: double.infinity,
text: 'اظافه کردن',
text: AppLocalizations.of(context)!.add,
fontSize: 13,
onPressed: () async {
if (subjectController.text != '') {
if (farsiSubjectController.text != '' &&
englishSubjectController.text != '') {
// call add new subject
final status =
await state.addNewSubject(subject: subjectController.text);
final status = await state.addNewSubject(
enSubject: englishSubjectController.text,
subject: farsiSubjectController.text);

if (status == Status.ready) {
// call refrresh subjects
await state.getSubjects(refresh: true);
await state.getSubjects();
Tools.showCustomSnackBar(
text: 'موضوع اظافه شد!',
text: AppLocalizations.of(context)!.addedsubject,
isError: false,
context,
);
@@ -90,16 +98,13 @@ class AddSubjectDiolog extends StatelessWidget {
}
} else {
Tools.showCustomSnackBar(
text: 'موضوع وارد کنید!',
text: AppLocalizations.of(context)!.erroraddsubject,
isError: true,
context,
);
}
},
topRightRadius: 10,
topLeftRadius: 10,
bottomLeftRadius: 10,
bottomRightRadius: 10,
borderRadius: 10,
);
}
}

lib/screens/meeting_edit/diolog_add_user.dart → lib/diologs/diolog_add_user.dart Просмотреть файл

@@ -3,14 +3,14 @@ import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';

import 'package:qadirneyriz/global_state/global_state.dart';
import 'package:qadirneyriz/screens/meeting_edit/screen.dart';
import 'package:qadirneyriz/global/global_state/global_state.dart';
import 'package:qadirneyriz/utils/enums/status.dart';
import 'package:qadirneyriz/utils/tools/tools.dart';
import 'package:qadirneyriz/widgets/ExpansionTileCustom.dart';
import 'package:qadirneyriz/widgets/checkBox_inTile.dart';
import 'package:qadirneyriz/widgets/custom_button.dart';
import 'package:qadirneyriz/widgets/custom_textfield.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';

class AddUserDiolog extends StatefulWidget {
AddUserDiolog({
@@ -28,15 +28,17 @@ class _AddUserDiologState extends State<AddUserDiolog> {

final TextEditingController passwordController = TextEditingController();

int? selectedRole;

final List<MemberRole> roles = [
MemberRole(roleId: 1, roleName: 'کاربر معمولی'),
MemberRole(roleId: 2, roleName: 'اپراتور'),
];
int selectedRole = 1;

@override
Widget build(BuildContext context) {
final List<MemberRole> roles = [
MemberRole(
roleId: 1,
roleName: AppLocalizations.of(context)!.normaluser,
),
// MemberRole(roleId: 2, roleName: 'اپراتور'),
];
return Consumer<GlobalState>(
builder: (context, value, child) {
return Dialog(
@@ -46,23 +48,25 @@ class _AddUserDiologState extends State<AddUserDiolog> {
child: Column(
mainAxisSize: MainAxisSize.min, // برای اندازه‌گیری درست دیالوگ
children: [
Text('عضو جدید'),
Text(
AppLocalizations.of(context)!.newmember,
),
CustomTextField(
label: '',
hintText: 'نام و نام خانوادگی',
hintText: AppLocalizations.of(context)!.nameandfamilyname,
paddingVertical: 0,
textEditingController: nameController,
textInputType: TextInputType.text),
CustomTextField(
label: '',
paddingVertical: 0,
hintText: 'شماره موبایل',
hintText: AppLocalizations.of(context)!.phonenumber,
textEditingController: mobileController,
textInputType: TextInputType.phone),
CustomTextField(
label: '',
paddingVertical: 0,
hintText: 'رمز عبور',
hintText: AppLocalizations.of(context)!.password,
isPass: true,
textEditingController: passwordController,
textInputType: TextInputType.visiblePassword),
@@ -71,12 +75,12 @@ class _AddUserDiologState extends State<AddUserDiolog> {
child: ExpansionTileCustom(
isForm: true,
subTitile: '',
title: 'نقش کاربر',
title: AppLocalizations.of(context)!.role,
widgets: <Widget>[
Column(
children: roles.map((role) {
bool isSelected = selectedRole == role.roleId;
return ItemInTile(
return CheckBoxInTile(
backColor:
isSelected ? Color(0xff06CF64) : Colors.white,
textColor:
@@ -116,56 +120,55 @@ class _AddUserDiologState extends State<AddUserDiolog> {
text: AppLocalizations.of(context)!.loading,
fontSize: 13,
onPressed: null,
topRightRadius: 10,
topLeftRadius: 10,
bottomLeftRadius: 10,
bottomRightRadius: 10,
borderRadius: 10,
);

default:
return CustomButton(
hieght: 40,
width: double.infinity,
text: 'اظافه کردن',
text: AppLocalizations.of(context)!.add,
fontSize: 13,
onPressed: () async {
if (nameController.text == '') {
// call add new subject

Tools.showCustomSnackBar(
text: 'اسم وارد کنید!',
text: AppLocalizations.of(context)!.enternameandfamily,
isError: true,
context,
);
} else if (mobileController.text == '') {
Tools.showCustomSnackBar(
text: 'موبایل وارد کنید!',
text: AppLocalizations.of(context)!.enterphonenumber,
isError: true,
context,
);
} else if (passwordController.text == '') {
Tools.showCustomSnackBar(
text: 'پسورد وارد کنید!',
text: AppLocalizations.of(context)!.enterpassword,
isError: true,
context,
);
} else if (selectedRole == null) {
Tools.showCustomSnackBar(
text: 'نقش کاربر وارد کنید!',
isError: true,
context,
);
} else {
}
// else if (selectedRole == null) {
// Tools.showCustomSnackBar(
// text: 'نقش کاربر وارد کنید!',
// isError: true,
// context,
// );
// }
else {
final status = await state.addNewUser(
name: nameController.text,
mobile: mobileController.text,
role: selectedRole!,
role: selectedRole,
password: passwordController.text);
if (status == Status.ready) {
// call refrresh users
await state.getUsers(refresh: true);
Tools.showCustomSnackBar(
text: 'کاربر اظافه شد!',
text: AppLocalizations.of(context)!.useradded,
isError: false,
context,
);
@@ -183,10 +186,7 @@ class _AddUserDiologState extends State<AddUserDiolog> {
}
}
},
topRightRadius: 10,
topLeftRadius: 10,
bottomLeftRadius: 10,
bottomRightRadius: 10,
borderRadius: 10,
);
}
}

+ 150
- 97
lib/drawer_navigation_bar.dart Просмотреть файл

@@ -1,11 +1,19 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';

import 'package:qadirneyriz/config/config.dart';
import 'package:qadirneyriz/screens/aboutUs/screen.dart';
import 'package:qadirneyriz/screens/auth/state/state.dart';
import 'package:qadirneyriz/screens/home/screen.dart';
import 'package:qadirneyriz/screens/home/state.dart';
import 'package:qadirneyriz/screens/meeting/screen.dart';
import 'package:qadirneyriz/screens/private_meeting/screen.dart';
import 'package:qadirneyriz/screens/report/screen.dart';

import 'package:qadirneyriz/setting/setting.dart';

class CustomDrawerNavigation extends StatefulWidget {
@@ -34,7 +42,10 @@ class _CustomDrawerNavigationState extends State<CustomDrawerNavigation> {

final List<Widget> _bottomBarPages = [
const HomeScreen(),
const MeetingsScreen()
const MeetingsScreen(),
const PrivateMeetingsScreen(),
const ReportScreen(),
const AboutUsScreen()
// Add more screens here
];

@@ -48,10 +59,13 @@ class _CustomDrawerNavigationState extends State<CustomDrawerNavigation> {

@override
Widget build(BuildContext context) {
final userRole = setting.userLocalDb.getUser().role;
return Scaffold(
// backgroundColor: Colors.green.withOpacity(.2),
drawer: Consumer<AuthState>(
builder: (context, value, child) {
return Drawer(
backgroundColor: config.ui.backGroundColor,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
@@ -62,53 +76,92 @@ class _CustomDrawerNavigationState extends State<CustomDrawerNavigation> {
height: 60,
),
),
NewSessionButton(),
Expanded(
child: ListView(
padding: EdgeInsets.zero,
children: <Widget>[
_buildDrawerItem(
icon: FontAwesomeIcons.house,
text: 'خانه',
index: 0,
),
_buildDrawerItem(
icon: FontAwesomeIcons.pencil,
text: 'جلسات',
index: 1,
),
_buildDrawerItem(
icon: FontAwesomeIcons.pencil,
text: 'ملاقات ها',
index: 2,
),
_buildDrawerItem(
icon: FontAwesomeIcons.pencil,
text: 'گزارشات',
index: 3,
if (userRole == 0 || userRole == 2)
Row(
children: [
Expanded(
child: Consumer<HomeState>(
builder: (context, value, child) {
return NewSessionButton(
title: AppLocalizations.of(context)!.newmeeting,
icon: Icons.person_outlined,
onPressed: () async {
await context.pushNamed('meetingadd');
value.getTodayMeetings();
},
);
},
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: BorderRadius.circular(10),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_buildLanguageButton('fa', 'فارسی', () {
value.setLocale('fa');
}),
_buildLanguageButton('en', 'English', () {
value.setLocale('en');
}),
],
),
Expanded(
child: NewSessionButton(
title:
AppLocalizations.of(context)!.newprivatemeeting,
icon: Icons.people_outlined,
onPressed: () {
context.pushNamed('privatemeetingadd');
},
),
)
),
],
),
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 50),
child: ListView(
padding: EdgeInsets.zero,
children: <Widget>[
_buildDrawerItem(
icon: FontAwesomeIcons.house,
text: AppLocalizations.of(context)!.home,
index: 0,
),
_buildDrawerItem(
icon: FontAwesomeIcons.peopleGroup,
text: AppLocalizations.of(context)!.meetings,
index: 1,
),
_buildDrawerItem(
icon: FontAwesomeIcons.calendar,
text: AppLocalizations.of(context)!.privatemeeting,
index: 2,
),
_buildDrawerItem(
icon: FontAwesomeIcons.chartColumn,
text: AppLocalizations.of(context)!.reports,
index: 3,
),
_buildDrawerItem(
icon: FontAwesomeIcons.info,
text: AppLocalizations.of(context)!.aboutus,
index: 4,
),
],
),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
decoration: BoxDecoration(
color: config.ui.secendGreen.withOpacity(.1),
borderRadius: BorderRadius.circular(10),
),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_buildLanguageButton('fa', 'فارسی', () {
value.setLocale('fa');
}),
_buildLanguageButton('en', 'English', () {
value.setLocale('en');
}),
],
),
),
),
),
const Divider(),
],
@@ -116,10 +169,15 @@ class _CustomDrawerNavigationState extends State<CustomDrawerNavigation> {
);
},
),
body: PageView(
controller: _pageController,
physics: const NeverScrollableScrollPhysics(),
children: _bottomBarPages,
body: Container(
decoration: BoxDecoration(
color: config.ui.backGroundColor,
),
child: PageView(
controller: _pageController,
physics: const NeverScrollableScrollPhysics(),
children: _bottomBarPages,
),
),
);
}
@@ -131,7 +189,7 @@ class _CustomDrawerNavigationState extends State<CustomDrawerNavigation> {
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 2),
child: Material(
color: isSelected
? config.ui.mainGreen.withOpacity(.2)
? config.ui.secendGreen.withOpacity(.1)
: Colors.transparent,
borderRadius: BorderRadius.circular(8),
child: InkWell(
@@ -145,13 +203,13 @@ class _CustomDrawerNavigationState extends State<CustomDrawerNavigation> {
leading: FaIcon(
icon,
size: 19,
color: isSelected ? config.ui.mainGreen : config.ui.mainGray,
color: isSelected ? config.ui.secendGreen : config.ui.mainGray,
),
title: Text(
text,
style: TextStyle(
color:
isSelected ? config.ui.mainGreen : config.ui.mainGray,
isSelected ? config.ui.secendGreen : config.ui.mainGray,
fontWeight:
isSelected ? FontWeight.bold : FontWeight.normal,
fontSize: 15),
@@ -176,16 +234,15 @@ class _CustomDrawerNavigationState extends State<CustomDrawerNavigation> {
onPressed(); // اجرای متد تغییر زبان
},
style: ElevatedButton.styleFrom(
backgroundColor: isSelected ? Colors.green : Colors.grey[300],
backgroundColor: isSelected ? Colors.green : Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.0),
borderRadius: BorderRadius.circular(5.0),
),
),
child: Text(
text,
style: TextStyle(
color: isSelected ? Colors.white : Colors.black,
),
color: isSelected ? Colors.white : Colors.black, fontSize: 13),
),
);
}
@@ -198,53 +255,49 @@ class _CustomDrawerNavigationState extends State<CustomDrawerNavigation> {
}

class NewSessionButton extends StatelessWidget {
final void Function()? onPressed;
final String title;
final IconData icon;
const NewSessionButton({
Key? key,
this.onPressed,
required this.title,
required this.icon,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green, // رنگ لبه‌ها
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8), // گرد کردن گوشه‌ها
),
elevation: 0, // بدون سایه
padding: EdgeInsets.symmetric(vertical: 20, horizontal: 16),
),
onPressed: () {
// کاری که باید انجام شود
},
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.person_outline,
color: Colors.white, // رنگ آیکون
size: 40,
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 5),
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: config.ui.mainGreen, // رنگ لبه‌ها
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8), // گرد کردن گوشه‌ها
),
SizedBox(height: 8),
Text(
'جلسه جدید',
style: TextStyle(
color: Colors.white, // رنگ متن
fontSize: 16,
fontWeight: FontWeight.bold,
elevation: 0, // بدون سایه
padding: EdgeInsets.symmetric(vertical: 20, horizontal: 16),
),
onPressed: onPressed,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
icon,
color: Colors.white, // رنگ آیکون
size: 40,
),
),
],
SizedBox(height: 8),
Text(
title,
style: TextStyle(
color: Colors.white, // رنگ متن
fontSize: 11,
fontWeight: FontWeight.normal,
),
),
],
),
),
);
}
}

class CustomScalfod extends StatefulWidget {
const CustomScalfod({super.key});

@override
State<CustomScalfod> createState() => _CustomScalfodState();
}

class _CustomScalfodState extends State<CustomScalfod> {
@override
Widget build(BuildContext context) {
return Scaffold();
}
}

+ 8
- 0
lib/global/global_class/selected_item.dart Просмотреть файл

@@ -0,0 +1,8 @@
class ItemSelected {
final String? text;
final int? id;
ItemSelected({
this.text,
this.id,
});
}

lib/global_state/global_state.dart → lib/global/global_state/global_state.dart Просмотреть файл

@@ -30,7 +30,7 @@ class GlobalState extends ChangeNotifier {
}
} catch (e) {
usersStatus = Status.error;
print('$e error usersModel');
// print('$e error usersModel');
}
notifyListeners();
} else {
@@ -44,11 +44,11 @@ class GlobalState extends ChangeNotifier {
notifyListeners();
} catch (e) {
usersStatus = Status.error;
print('$e error usersModel');
// print('$e error usersModel');
}
}
notifyListeners();
print('$usersStatus usersModel');
// print('$usersStatus usersModel');
return usersStatus;
}

@@ -74,7 +74,7 @@ class GlobalState extends ChangeNotifier {
}
} catch (e) {
locationsStatus = Status.error;
print(e);
// print(e);
}
notifyListeners();
} else {
@@ -88,11 +88,11 @@ class GlobalState extends ChangeNotifier {
notifyListeners();
} catch (e) {
locationsStatus = Status.error;
print(e);
// print(e);
}
}
notifyListeners();
print(locationsStatus);
// print(locationsStatus);
return locationsStatus;
}

@@ -119,7 +119,7 @@ class GlobalState extends ChangeNotifier {
}
} catch (e) {
subjectsStatus = Status.error;
print(e);
// print(e);
}
notifyListeners();
} else {
@@ -133,11 +133,11 @@ class GlobalState extends ChangeNotifier {
notifyListeners();
} catch (e) {
subjectsStatus = Status.error;
print(e);
// print(e);
}
}
notifyListeners();
print(subjectsStatus);
// print(subjectsStatus);
return subjectsStatus;
}

@@ -164,7 +164,7 @@ class GlobalState extends ChangeNotifier {
}
} catch (e) {
meetingsManagerStatus = Status.error;
print(e);
// print(e);
}
notifyListeners();
} else {
@@ -178,26 +178,21 @@ class GlobalState extends ChangeNotifier {
notifyListeners();
} catch (e) {
meetingsManagerStatus = Status.error;
print(e);
// print(e);
}
}
notifyListeners();
print(meetingsManagerStatus);
// print(meetingsManagerStatus);
return meetingsManagerStatus;
}

// statuses meetings
List<MeetingsStatus> meetingStatuses = [
MeetingsStatus(id: 1, title: 'جلسات برگذار شده'),
MeetingsStatus(id: 2, title: 'جلسات موکول شده'),
MeetingsStatus(id: 3, title: 'جلسات لغو شده'),
MeetingsStatus(id: 4, title: 'جلسات منتظر برگذاری'),
];
// load all items together
Status allFiltersStatus = Status.empty;

Future<Status> getAllFiltersItems({bool refresh = false}) async {
if (_isDataAlreadyLoaded()) {
allFiltersStatus = Status.loading;
notifyListeners();
if (_isDataAlreadyLoaded() && !refresh) {
allFiltersStatus = Status.ready;
} else {
allFiltersStatus = Status.loading;
@@ -249,12 +244,13 @@ class GlobalState extends ChangeNotifier {
String? messageAddNewSubject;
Map? errorsAddNewSubject;

Future<Status> addNewSubject({required String subject}) async {
Future<Status> addNewSubject(
{required String subject, required String enSubject}) async {
statusAddNewSubject = Status.loading;
notifyListeners();
try {
final result =
await setting.globalServices.addNewSubject(subject: subject);
final result = await setting.globalServices
.addNewSubject(subject: subject, enSubject: enSubject);
if (result.isOk) {
statusAddNewSubject = Status.ready;
messageAddNewSubject = result.message;
@@ -280,12 +276,13 @@ class GlobalState extends ChangeNotifier {
String? messageAddNewAddress;
Map? errorsAddNewAddress;

Future<Status> addNewAddress({required String address}) async {
Future<Status> addNewAddress(
{required String address, required String addressEn}) async {
statusAddNewAddress = Status.loading;
notifyListeners();
try {
final result =
await setting.globalServices.addNewLocation(address: address);
final result = await setting.globalServices
.addNewLocation(address: address, addressEn: addressEn);
if (result.isOk) {
statusAddNewAddress = Status.ready;
messageAddNewAddress = result.message;

+ 121
- 42
lib/l10n/app_en.arb Просмотреть файл

@@ -1,43 +1,122 @@
{
"helloWorld": "Hello World!",
"phonenumber":"PhoneNumber",
"hintphonenumber":"Please enter your phonenumber ...",
"hintpass":"Please enter your password ...",
"password":"Password",
"submit":"Submit",
"submitwithotp":"Submit with OTP",
"submitwithphone":"Submit with phonenumber",
"enterotp":"Enter OTP",
"an4digitotp":"An 4 digit OTP has been sent to",
"loading":"loading ...",
"phoneerror":"Please enter your phonenumber!",
"passerror":"Please enter your password!",
"haserror":"Something went wrong. Please try again",
"resend":"Resend code!",
"today":"Today",
"to":"To",
"reports":"Reports",
"meetings":"Meetings",
"events":"Events",
"exit":"Exit",
"appname":"Foolad QadirNeyriz",
"nomeetingfortoday":"No Meetings for today",
"todaymeetings":"Today Meetings",
"empty":"The list is empty.",
"back":"Back",
"searchFor":"جستوجو براساس",
"date":"تاریخ",
"location":"مکان",
"meetingmanager":"مدیر جلسه",
"subject":"موضوع",
"donemeetings":"جلسات برگذار شده",
"adjournedmeetings":"جلسات موکول شده",
"canceldmeetings":"جلسات لغو شده",
"meetingswaitingtobeheld":"جلسات منتظر برگذاری",
"selectdate":"انتخاب تاریخ",
"editmeeting":"ویرایش جلسه",
"meetingsubject":"موضوع جلسه",
"clock":"ساعت",
"users":"کاربران",
"selectusers":"انتخاب کاربران"
}
"helloWorld": "Hello World!",
"phonenumber": "Phone Number",
"hintphonenumber": "Please enter your mobile number...",
"hintpass": "Please enter your password...",
"password": "Password",
"submit": "Submit",
"submitwithotp": "Login with OTP",
"submitwithphone": "Login with Phone Number",
"enterotp": "One-Time Password",
"an4digitotp": "The 4-digit code sent to the number",
"loading": "Please wait...",
"phoneerror": "Please enter your phone number!",
"passerror": "Please enter your password!",
"haserror": "An error occurred! Please try again!",
"resend": "Resend code!",
"today": "Today",
"to": "To",
"reports": "Reports",
"meetings": "Meetings",
"events": "Appointments",
"exit": "Exit",
"appname": "Foolad Ghadir Neyriz",
"nomeetingfortoday": "No meeting is scheduled for today.",
"todaymeetings": "Today's Meetings",
"empty": "No data available.",
"back": "Back",
"searchFor": "Search by",
"date": "Date",
"location": "Location",
"meetingmanager": "Meeting Manager",
"subject": "Subject",
"donemeetings": "Completed Meetings",
"adjournedmeetings": "Postponed Meetings",
"canceldmeetings": "Canceled Meetings",
"meetingswaitingtobeheld": "Meetings Waiting to be Held",
"selectdate": "Select Date",
"editmeeting": "Edit Meeting",
"meetingsubject": "Meeting Subject",
"clock": "Time",
"users": "Users",
"selectusers": "Select Users",
"addNewMeeting": "Create New Meeting",
"selectsubject": "Select Subject",
"newsubject": "New Subject",
"selectlocation": "Select Location",
"newlocation": "New Location",
"members": "Users",
"selectmembers": "Select Users",
"newmember": "New User",
"selectmeetingmanager": "Select Meeting Manager",
"acceptmeeting": "Confirm Meeting",
"cancelmeeting": "Cancel Meeting",
"meetingsummary": "Meeting Summary",
"meetingcanceled": "Meeting Canceled!",
"meetingaccepted": "Meeting Confirmed!",
"error": "An error occurred. Please try again!",
"home": "Home",
"privatemeeting": "Appointments",
"submitsummarymeeting": "Submit Meeting Summary",
"fileupload": "File Upload",
"selectfile": "Select File",
"descriptionofthemeeting": "Meeting Description",
"normaluser": "Regular User",
"oprator": "Operator",
"nameandfamilyname": "Name and Surname",
"userrole": "User Role",
"add": "Add",
"entersubject": "Enter the subject!",
"enternameandfamily": "Enter name and surname!",
"enterpassword": "Enter the password!",
"enterphonenumber": "Enter the mobile number!",
"enteruserrole": "Select user role!",
"useradded": "User added!",
"subjectadded": "Subject added!",
"enersubject": "Enter the subject!",
"addressadded": "Address added!",
"enteraddress": "Enter the address!",
"enterfile": "Add a file!",
"createnewmeeting": "Create New Meeting",
"addnewprivatemeeting": "Add New Appointment",
"editprivatemeeting": "Edit Appointment",
"visitorname": "Visitor's Name",
"visitorrole": "Role",
"companyname": "Company Name",
"editdone": "Edit Complete!",
"accept": "Accept",
"cancel": "Cancel",
"enterdescription": "Enter the meeting description!",
"donesummary": "Meeting summary submitted!",
"downloadreport": "Download Report",
"newmeeting": "New Meeting",
"newprivatemeeting": "New Appointment",
"thereisnosummary": "No meeting summary available!",
"needzipapp": "To open the downloaded zip file, the RAR app is required.",
"needpermission": "Permission denied. Please allow storage access.",
"logout": "Log Out",
"areusurelog": "Are you sure you want to log out?",
"yes": "Yes",
"no": "No",
"aboutus":"About us",
"tryagain":"Try again!",
"addprivatemeetingdone": "Appointment added!",
"addmeetingdone": "Meeting added!",
"privatemeetingcanceld": "Appointment canceled!",
"privatemeetingaccept": "Appointment confirmed!",
"english":"English",
"farsi":"Farsi",
"addedaddress":"address added!",
"erroraddress":"Please enter fasri and english address!",
"addedsubject":"subject added!",
"erroraddsubject":"Please enter fasri and english subject!",
"role":"Role",
"isprivatemeeting":"Private meeting",
"isprivateprivatemeeting":"Private Appointment",
"canceled":"Canceled",
"accepted":"Accepted",
"files":"Files",
"acceptoperetion":"Accept Operetion",
"areusuretodeletfile": "Are you sure you want to delete this file?",
"textaboutus":"The Mizban meeting and appointment management software has been designed and developed with the aim of facilitating and optimizing the processes of organizing organizational and personal meetings under the leadership of Dr. Mohsen Mostafapour. This innovative and user-centric software serves as an efficient tool to enhance coordination and realize the motto **The Codeword of Empathy** within the esteemed **Foulad Ghadir Neyriz** organization. The Mizban project was initiated and launched with the invaluable support and backing of the esteemed CEO, Dr. Mohsen Mostafapour, representing a significant step forward in the organization's path toward growth and excellence."
}

+ 79
- 2
lib/l10n/app_fa.arb Просмотреть файл

@@ -20,7 +20,7 @@
"meetings":"جلسات",
"events":"ملاقات ها",
"exit":"خروج",
"appname":"فولاد غدیر نیریز",
"appname":"فولاد غدیر نی ریز",
"nomeetingfortoday":"برای امروز جلسه ایی تعریف نشده است.",
"todaymeetings":"جلسه های امروز",
"empty":"داده ایی وجود ندارد.",
@@ -39,5 +39,82 @@
"meetingsubject":"موضوع جلسه",
"clock":"ساعت",
"users":"کاربران",
"selectusers":"انتخاب کاربران"
"selectusers":"انتخاب کاربران",
"addNewMeeting":"جلسه جدید",
"selectsubject":"انتخاب موضوع",
"newsubject":"موضوع جدید",
"selectlocation":"انتخاب مکان",
"newlocation":"مکان جدید",
"members":"کاربران",
"selectmembers":"انتخاب کاربران",
"newmember":"کاربر جدید",
"selectmeetingmanager":"انتخاب مدیر جلسه",
"acceptmeeting":"تایید جلسه",
"cancelmeeting":"لغو جلسه",
"meetingsummary":"صورت جلسه",
"meetingcanceled":"جلسه لغو شد!",
"meetingaccepted":"جلسه تایید شد!",
"error":"خطایی رخ داده است. دوباره تلاش کنید!",
"home":"خانه",
"privatemeeting":"ملاقات ها",
"submitsummarymeeting":"ثبت صورت جلسه",
"fileupload":"آپلود فایل",
"selectfile":"انتخاب فایل",
"descriptionofthemeeting":"شرح جلسه",
"normaluser":"کاربر معمولی",
"oprator":"اپراتور",
"nameandfamilyname":"نام و نام خانوادگی",
"userrole":"نقش کاربر",
"add":"اضافه کردن",
"entersubject":"موضوع را وارد کنید!",
"enternameandfamily":"نام و نام خانوادگی را وارد کنید!",
"enterpassword":"رمزعبور را وارد کنید!",
"enterphonenumber":"شماره موبایل را وارد کنید!",
"enteruserrole":"نقش کاربر را انتخاب کنید!",
"useradded":"کاربر اضافه شد!",
"subjectadded":"موضوع اضافه شد!",
"enersubject":"موضوع را وارد کنید!",
"addressadded":"آدرس اضافه شد!",
"enteraddress":"آدرس را وارد کنید!",
"enterfile":"فایل اضافه کنید!",
"createnewmeeting":"ایجاد جلسه جدید",
"addnewprivatemeeting":"ملاقات جدید",
"editprivatemeeting":"ویرایش ملاقات",
"visitorname":"نام فرد ملاقات شونده",
"visitorrole":"سمت",
"companyname":"نام شرکت",
"editdone":"ویرایش انجام شد!",
"accept":"تایید",
"cancel":"رد",
"enterdescription":"شرح جلسه را وارد کنید!",
"donesummary":"صورت جلسه ارسال شد!",
"downloadreport":"دانلود صورت جلسه",
"newmeeting":"جلسه جدید",
"newprivatemeeting":"ملاقات جدید",
"thereisnosummary":"صورت جلسه ایی وجود ندارد!",
"needzipapp":" 'برای باز کردن فایل zip دانلود شده نیاز به اپلیکیشن RAR داریم.'",
"needpermission":"نیاز به دسترسی برای دانلود فایل داریم!",
"logout":"خروج از برنامه",
"areusurelog":"آیا اطمینان دارید که میخواهید خارج شوید؟",
"yes":"بله",
"no":"خیر",
"aboutus":"درباره ما",
"tryagain":"تلاش دوباره!",
"addprivatemeetingdone":"ملاقات اضافه شد!",
"addmeetingdone":"جلسه اضافه شد!",
"english":"انگلیسی",
"farsi":"فارسی",
"addedaddress":"آدرس اضافه شد!",
"erroraddress":"آدرس فارسی و انگلیسی را وارد کنید!",
"addedsubject":"موضوع اضافه شد!",
"erroraddsubject":"موضوع فارسی و انگلیسی را وارد کنید!",
"role":"نقش کاربر",
"isprivatemeeting":"جلسه خصوصی",
"isprivateprivatemeeting":"ملاقات خصوصی",
"canceled":"رد شده",
"accepted":"تایید شده",
"files":"فایل ها",
"acceptoperetion":"تایید عملیات",
"areusuretodeletfile":"آیا اطمینان دارید که میخواهید این فایل را حذف کنید؟",
"textaboutus":"نرم‌افزار مدیریت جلسات و ملاقات‌های “میزبان” با هدف تسهیل و بهینه‌سازی فرآیندهای برگزاری جلسات سازمانی و شخصی جناب آقای دکتر محسن مصطفی پور طراحی و توسعه یافته تا ابزاری کارآمد و نوآورانه برای تحقق بیشتر اسم رمز همدلی در مجموعه معظم فولاد غدیر نی ریز باشد . این نرم افزار با حمایت ، همت و پشتیبانی بی‌دریغ مدیریت محترم عامل ( دکتر محسن مصطفی پور ) ایجاد ، توسعه و راه اندازی شده است."
}

+ 16
- 1
lib/main.dart Просмотреть файл

@@ -2,12 +2,15 @@ import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:provider/provider.dart';
import 'package:qadirneyriz/global_state/global_state.dart';
import 'package:qadirneyriz/config/config.dart';
import 'package:qadirneyriz/global/global_state/global_state.dart';
import 'package:qadirneyriz/router/router.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:qadirneyriz/screens/auth/state/state.dart';
import 'package:qadirneyriz/screens/home/state.dart';
import 'package:qadirneyriz/screens/meeting/state.dart';
import 'package:qadirneyriz/screens/private_meeting/state.dart';
import 'package:qadirneyriz/screens/report/state.dart';
import 'package:qadirneyriz/setting/setting.dart';

void main() async {
@@ -20,6 +23,8 @@ void main() async {
ChangeNotifierProvider(create: (_) => AuthState()),
ChangeNotifierProvider(create: (_) => HomeState()),
ChangeNotifierProvider(create: (_) => MeetingsState()),
ChangeNotifierProvider(create: (_) => PrivateMeetingsState()),
ChangeNotifierProvider(create: (_) => ReportState()),
],
child: const MyApp(),
),
@@ -54,6 +59,16 @@ class _MyAppState extends State<MyApp> {
builder: (context, value, child) {
return MaterialApp.router(
theme: ThemeData(
colorScheme: ColorScheme.light(
// تغییر رنگ اصلی تایم پیکر
primary: config.ui.mainGreen,
// تغییر رنگ متن
),
buttonTheme: ButtonThemeData(
colorScheme: ColorScheme.light(
primary: Colors.green, // رنگ دکمه‌ها
),
),
useMaterial3: true,
fontFamily: 'Font',
scaffoldBackgroundColor: Colors.white),


+ 2
- 2
lib/models/meetings/meetings_model.dart Просмотреть файл

@@ -23,7 +23,7 @@ class MeetingsModel {
? []
: List<dynamic>.from(data!.map((x) => x.toJson())),
};
hasData() => data!.isNotEmpty;
hasData() => this.data != null && this.data!.isNotEmpty;
}

class Datum {
@@ -34,7 +34,7 @@ class Datum {
int? ownerId;
String? azHour;
String? taHour;
dynamic description;
String? description;
int? status;
int? accepted;
DateTime? dateMeeting;


+ 279
- 0
lib/models/private_meeting/one_private_meeting_model.dart Просмотреть файл

@@ -0,0 +1,279 @@
import 'dart:convert';

class OnePrivateMeetingModel {
int? id;
int? locationsId;
int? subjectId;
int? managerId;
int? ownerId;
String? azHour;
String? taHour;
dynamic description;
int? status;
int? accepted;
String? visitName;
String? visitMobile;
String? visitRole;
String? visitCompany;
DateTime? dateMeeting;
DateTime? endDate;
DateTime? createdAt;
DateTime? updatedAt;
String? dateJalali;
String? statusTxt;
String? az;
String? ta;
Location? location;
Subject? subject;
Manager? manager;

OnePrivateMeetingModel({
this.id,
this.locationsId,
this.subjectId,
this.managerId,
this.ownerId,
this.azHour,
this.taHour,
this.description,
this.status,
this.accepted,
this.visitName,
this.visitMobile,
this.visitRole,
this.visitCompany,
this.dateMeeting,
this.endDate,
this.createdAt,
this.updatedAt,
this.dateJalali,
this.statusTxt,
this.az,
this.ta,
this.location,
this.subject,
this.manager,
});

factory OnePrivateMeetingModel.fromRawJson(String str) =>
OnePrivateMeetingModel.fromJson(json.decode(str));

String toRawJson() => json.encode(toJson());

factory OnePrivateMeetingModel.fromJson(Map<String, dynamic> json) =>
OnePrivateMeetingModel(
id: json["id"],
locationsId: json["locations_id"],
subjectId: json["subject_id"],
managerId: json["manager_id"],
ownerId: json["owner_id"],
azHour: json["az_hour"],
taHour: json["ta_hour"],
description: json["description"],
status: json["status"],
accepted: json["accepted"],
visitName: json["visit_name"],
visitMobile: json["visit_mobile"],
visitRole: json["visit_role"],
visitCompany: json["visit_company"],
dateMeeting: json["date_meeting"] == null
? null
: DateTime.parse(json["date_meeting"]),
endDate:
json["end_date"] == null ? null : DateTime.parse(json["end_date"]),
createdAt: json["created_at"] == null
? null
: DateTime.parse(json["created_at"]),
updatedAt: json["updated_at"] == null
? null
: DateTime.parse(json["updated_at"]),
dateJalali: json["date_jalali"],
statusTxt: json["status_txt"],
az: json["az"],
ta: json["ta"],
location: json["location"] == null
? null
: Location.fromJson(json["location"]),
subject:
json["subject"] == null ? null : Subject.fromJson(json["subject"]),
manager:
json["manager"] == null ? null : Manager.fromJson(json["manager"]),
);

Map<String, dynamic> toJson() => {
"id": id,
"locations_id": locationsId,
"subject_id": subjectId,
"manager_id": managerId,
"owner_id": ownerId,
"az_hour": azHour,
"ta_hour": taHour,
"description": description,
"status": status,
"accepted": accepted,
"visit_name": visitName,
"visit_mobile": visitMobile,
"visit_role": visitRole,
"visit_company": visitCompany,
"date_meeting": dateMeeting?.toIso8601String(),
"end_date": endDate?.toIso8601String(),
"created_at": createdAt?.toIso8601String(),
"updated_at": updatedAt?.toIso8601String(),
"date_jalali": dateJalali,
"status_txt": statusTxt,
"az": az,
"ta": ta,
"location": location?.toJson(),
"subject": subject?.toJson(),
"manager": manager?.toJson(),
};
}

class Location {
int? id;
String? address;
String? addressEn;
DateTime? createdAt;
DateTime? updatedAt;

Location({
this.id,
this.address,
this.addressEn,
this.createdAt,
this.updatedAt,
});

factory Location.fromRawJson(String str) =>
Location.fromJson(json.decode(str));

String toRawJson() => json.encode(toJson());

factory Location.fromJson(Map<String, dynamic> json) => Location(
id: json["id"],
address: json["address"],
addressEn: json["address_en"],
createdAt: json["created_at"] == null
? null
: DateTime.parse(json["created_at"]),
updatedAt: json["updated_at"] == null
? null
: DateTime.parse(json["updated_at"]),
);

Map<String, dynamic> toJson() => {
"id": id,
"address": address,
"address_en": addressEn,
"created_at": createdAt?.toIso8601String(),
"updated_at": updatedAt?.toIso8601String(),
};
}

class Manager {
int? id;
String? name;
int? role;
String? mobile;
dynamic otp;
dynamic access;
dynamic managerId;
dynamic firebaseToken;
int? isBlock;
int? getSms;
DateTime? createdAt;
DateTime? updatedAt;

Manager({
this.id,
this.name,
this.role,
this.mobile,
this.otp,
this.access,
this.managerId,
this.firebaseToken,
this.isBlock,
this.getSms,
this.createdAt,
this.updatedAt,
});

factory Manager.fromRawJson(String str) => Manager.fromJson(json.decode(str));

String toRawJson() => json.encode(toJson());

factory Manager.fromJson(Map<String, dynamic> json) => Manager(
id: json["id"],
name: json["name"],
role: json["role"],
mobile: json["mobile"],
otp: json["otp"],
access: json["access"],
managerId: json["manager_id"],
firebaseToken: json["firebase_token"],
isBlock: json["is_block"],
getSms: json["get_sms"],
createdAt: json["created_at"] == null
? null
: DateTime.parse(json["created_at"]),
updatedAt: json["updated_at"] == null
? null
: DateTime.parse(json["updated_at"]),
);

Map<String, dynamic> toJson() => {
"id": id,
"name": name,
"role": role,
"mobile": mobile,
"otp": otp,
"access": access,
"manager_id": managerId,
"firebase_token": firebaseToken,
"is_block": isBlock,
"get_sms": getSms,
"created_at": createdAt?.toIso8601String(),
"updated_at": updatedAt?.toIso8601String(),
};
}

class Subject {
int? id;
String? subject;
dynamic subjectEn;
DateTime? createdAt;
DateTime? updatedAt;

Subject({
this.id,
this.subject,
this.subjectEn,
this.createdAt,
this.updatedAt,
});

factory Subject.fromRawJson(String str) => Subject.fromJson(json.decode(str));

String toRawJson() => json.encode(toJson());

factory Subject.fromJson(Map<String, dynamic> json) => Subject(
id: json["id"],
subject: json["subject"],
subjectEn: json["subject_en"],
createdAt: json["created_at"] == null
? null
: DateTime.parse(json["created_at"]),
updatedAt: json["updated_at"] == null
? null
: DateTime.parse(json["updated_at"]),
);

Map<String, dynamic> toJson() => {
"id": id,
"subject": subject,
"subject_en": subjectEn,
"created_at": createdAt?.toIso8601String(),
"updated_at": updatedAt?.toIso8601String(),
};
}

+ 338
- 0
lib/models/private_meeting/private_meetings_model.dart Просмотреть файл

@@ -0,0 +1,338 @@
import 'dart:convert';

class PrivateMeetingsModel {
List<DatumInPrivateMeeting>? data;

PrivateMeetingsModel({
this.data,
});

factory PrivateMeetingsModel.fromRawJson(String str) =>
PrivateMeetingsModel.fromJson(json.decode(str));

String toRawJson() => json.encode(toJson());

factory PrivateMeetingsModel.fromJson(Map<String, dynamic> json) =>
PrivateMeetingsModel(
data: json["data"] == null
? []
: List<DatumInPrivateMeeting>.from(
json["data"]!.map((x) => DatumInPrivateMeeting.fromJson(x))),
);

Map<String, dynamic> toJson() => {
"data": data == null
? []
: List<dynamic>.from(data!.map((x) => x.toJson())),
};
hasData() => this.data != null && this.data!.isNotEmpty;
}

class DatumInPrivateMeeting {
int? id;
int? locationsId;
int? subjectId;
int? managerId;
int? ownerId;
String? azHour;
String? taHour;
String? description;
int? status;
int? accepted;
String? visitName;
String? visitMobile;
String? visitRole;
String? visitCompany;
DateTime? dateMeeting;
DateTime? endDate;
DateTime? createdAt;
DateTime? updatedAt;
String? dateJalali;
StatusTxt? statusTxt;
String? az;
String? ta;
List<String>? minutes;
Location? location;
Subject? subject;
Manager? manager;

DatumInPrivateMeeting({
this.id,
this.locationsId,
this.subjectId,
this.managerId,
this.ownerId,
this.azHour,
this.taHour,
this.description,
this.status,
this.accepted,
this.visitName,
this.visitMobile,
this.visitRole,
this.visitCompany,
this.dateMeeting,
this.endDate,
this.createdAt,
this.updatedAt,
this.dateJalali,
this.statusTxt,
this.az,
this.ta,
this.minutes,
this.location,
this.subject,
this.manager,
});

factory DatumInPrivateMeeting.fromRawJson(String str) =>
DatumInPrivateMeeting.fromJson(json.decode(str));

String toRawJson() => json.encode(toJson());

factory DatumInPrivateMeeting.fromJson(Map<String, dynamic> json) =>
DatumInPrivateMeeting(
id: json["id"],
locationsId: json["locations_id"],
subjectId: json["subject_id"],
managerId: json["manager_id"],
ownerId: json["owner_id"],
azHour: json["az_hour"],
taHour: json["ta_hour"],
description: json["description"],
status: json["status"],
accepted: json["accepted"],
visitName: json["visit_name"],
visitMobile: json["visit_mobile"],
visitRole: json["visit_role"],
visitCompany: json["visit_company"],
dateMeeting: json["date_meeting"] == null
? null
: DateTime.parse(json["date_meeting"]),
endDate:
json["end_date"] == null ? null : DateTime.parse(json["end_date"]),
createdAt: json["created_at"] == null
? null
: DateTime.parse(json["created_at"]),
updatedAt: json["updated_at"] == null
? null
: DateTime.parse(json["updated_at"]),
dateJalali: json["date_jalali"],
statusTxt: statusTxtValues.map[json["status_txt"]]!,
az: json["az"],
ta: json["ta"],
minutes: json["minutes"] == null
? []
: List<String>.from(json["minutes"]!.map((x) => x)),
location: json["location"] == null
? null
: Location.fromJson(json["location"]),
subject:
json["subject"] == null ? null : Subject.fromJson(json["subject"]),
manager:
json["manager"] == null ? null : Manager.fromJson(json["manager"]),
);

Map<String, dynamic> toJson() => {
"id": id,
"locations_id": locationsId,
"subject_id": subjectId,
"manager_id": managerId,
"owner_id": ownerId,
"az_hour": azHour,
"ta_hour": taHour,
"description": description,
"status": status,
"accepted": accepted,
"visit_name": visitName,
"visit_mobile": visitMobile,
"visit_role": visitRole,
"visit_company": visitCompany,
"date_meeting": dateMeeting?.toIso8601String(),
"end_date": endDate?.toIso8601String(),
"created_at": createdAt?.toIso8601String(),
"updated_at": updatedAt?.toIso8601String(),
"date_jalali": dateJalali,
"status_txt": statusTxtValues.reverse[statusTxt],
"az": az,
"ta": ta,
"minutes":
minutes == null ? [] : List<dynamic>.from(minutes!.map((x) => x)),
"location": location?.toJson(),
"subject": subject?.toJson(),
"manager": manager?.toJson(),
};
}

class Location {
int? id;
String? address;
String? addressEn;
DateTime? createdAt;
DateTime? updatedAt;

Location({
this.id,
this.address,
this.addressEn,
this.createdAt,
this.updatedAt,
});

factory Location.fromRawJson(String str) =>
Location.fromJson(json.decode(str));

String toRawJson() => json.encode(toJson());

factory Location.fromJson(Map<String, dynamic> json) => Location(
id: json["id"],
address: json["address"],
addressEn: json["address_en"],
createdAt: json["created_at"] == null
? null
: DateTime.parse(json["created_at"]),
updatedAt: json["updated_at"] == null
? null
: DateTime.parse(json["updated_at"]),
);

Map<String, dynamic> toJson() => {
"id": id,
"address": address,
"address_en": addressEn,
"created_at": createdAt?.toIso8601String(),
"updated_at": updatedAt?.toIso8601String(),
};
}

class Manager {
int? id;
Name? name;
int? role;
String? mobile;
dynamic otp;
dynamic access;
dynamic managerId;
dynamic firebaseToken;
int? isBlock;
int? getSms;
DateTime? createdAt;
DateTime? updatedAt;

Manager({
this.id,
this.name,
this.role,
this.mobile,
this.otp,
this.access,
this.managerId,
this.firebaseToken,
this.isBlock,
this.getSms,
this.createdAt,
this.updatedAt,
});

factory Manager.fromRawJson(String str) => Manager.fromJson(json.decode(str));

String toRawJson() => json.encode(toJson());

factory Manager.fromJson(Map<String, dynamic> json) => Manager(
id: json["id"],
name: nameValues.map[json["name"]],
role: json["role"],
mobile: json["mobile"],
otp: json["otp"],
access: json["access"],
managerId: json["manager_id"],
firebaseToken: json["firebase_token"],
isBlock: json["is_block"],
getSms: json["get_sms"],
createdAt: json["created_at"] == null
? null
: DateTime.parse(json["created_at"]),
updatedAt: json["updated_at"] == null
? null
: DateTime.parse(json["updated_at"]),
);

Map<String, dynamic> toJson() => {
"id": id,
"name": nameValues.reverse[name],
"role": role,
"mobile": mobile,
"otp": otp,
"access": access,
"manager_id": managerId,
"firebase_token": firebaseToken,
"is_block": isBlock,
"get_sms": getSms,
"created_at": createdAt?.toIso8601String(),
"updated_at": updatedAt?.toIso8601String(),
};
}

enum Name { ADMIN, ALI }

final nameValues = EnumValues({"Admin": Name.ADMIN, "Ali": Name.ALI});

enum StatusTxt { EMPTY, PURPLE, STATUS_TXT }

final statusTxtValues = EnumValues({
"لغو شده": StatusTxt.EMPTY,
"منتظر برگزاری": StatusTxt.PURPLE,
"برگزار شده": StatusTxt.STATUS_TXT
});

class Subject {
int? id;
String? subject;
String? subjectEn;
DateTime? createdAt;
DateTime? updatedAt;

Subject({
this.id,
this.subject,
this.subjectEn,
this.createdAt,
this.updatedAt,
});

factory Subject.fromRawJson(String str) => Subject.fromJson(json.decode(str));

String toRawJson() => json.encode(toJson());

factory Subject.fromJson(Map<String, dynamic> json) => Subject(
id: json["id"],
subject: json["subject"],
subjectEn: json["subject_en"],
createdAt: json["created_at"] == null
? null
: DateTime.parse(json["created_at"]),
updatedAt: json["updated_at"] == null
? null
: DateTime.parse(json["updated_at"]),
);

Map<String, dynamic> toJson() => {
"id": id,
"subject": subject,
"subject_en": subjectEn,
"created_at": createdAt?.toIso8601String(),
"updated_at": updatedAt?.toIso8601String(),
};
}

class EnumValues<T> {
Map<String, T> map;
late Map<T, String> reverseMap;

EnumValues(this.map);

Map<T, String> get reverse {
reverseMap = map.map((k, v) => MapEntry(v, k));
return reverseMap;
}
}

+ 68
- 0
lib/router/router.dart Просмотреть файл

@@ -3,15 +3,27 @@ import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
import 'package:qadirneyriz/drawer_navigation_bar.dart';
import 'package:qadirneyriz/models/meetings/meetings_model.dart';
import 'package:qadirneyriz/models/private_meeting/private_meetings_model.dart';
// import 'package:qadirneyriz/models/meetings/meetings_model.dart';
import 'package:qadirneyriz/screens/auth/login_screen.dart';
import 'package:qadirneyriz/screens/auth/login_with_otp_screen.dart';
import 'package:qadirneyriz/screens/auth/otp_screen.dart';
import 'package:qadirneyriz/screens/home/screen.dart';
import 'package:qadirneyriz/screens/meeting/screen.dart';
import 'package:qadirneyriz/screens/meeting_add/screen.dart';
import 'package:qadirneyriz/screens/meeting_add/state.dart';
import 'package:qadirneyriz/screens/meeting_edit/screen.dart';
import 'package:qadirneyriz/screens/meeting_edit/state.dart';
import 'package:qadirneyriz/screens/meeting_summary/screen.dart';
import 'package:qadirneyriz/screens/meeting_summary/state.dart';
import 'package:qadirneyriz/screens/private_meeting_add/screen.dart';
import 'package:qadirneyriz/screens/private_meeting_add/state.dart';
import 'package:qadirneyriz/screens/private_meeting_edit/screen.dart';
import 'package:qadirneyriz/screens/private_meeting_edit/state.dart';
import 'package:qadirneyriz/screens/private_meeting_summary/screen.dart';
import 'package:qadirneyriz/screens/private_meeting_summary/state.dart';
import 'package:qadirneyriz/screens/report/screen.dart';
import 'package:qadirneyriz/screens/report/state.dart';
import 'package:qadirneyriz/splash_screen.dart';

final GoRouter router = GoRouter(
@@ -75,6 +87,16 @@ final GoRouter router = GoRouter(
);
},
),
GoRoute(
path: '/meetingadd',
name: 'meetingadd',
builder: (context, state) {
return ChangeNotifierProvider(
child: MeetingAddScreen(),
create: (context) => MeetinAddState(),
);
},
),
GoRoute(
path: '/meetinsammary',
name: 'meetinsammary',
@@ -88,5 +110,51 @@ final GoRouter router = GoRouter(
);
},
),
GoRoute(
path: '/privatemeetingadd',
name: 'privatemeetingadd',
builder: (context, state) {
return ChangeNotifierProvider(
child: PrivateMeetingAddScreen(),
create: (context) => PrivateMeetingAddState(),
);
},
),
GoRoute(
path: '/privatemeetingedit/:id',
name: 'privatemeetingedit',
builder: (context, state) {
return ChangeNotifierProvider(
child: EditPrivateMeetingScreen(
id: int.parse(state.pathParameters['id']!),
),
create: (context) => EditPrivateMeetingState(),
);
},
),
GoRoute(
path: '/privatemeetinsammary',
name: 'privatemeetinsammary',
builder: (context, state) {
DatumInPrivateMeeting meetingData =
state.extra as DatumInPrivateMeeting;
return ChangeNotifierProvider(
create: (context) => PrivateMeetingSummaryState(),
child: PrivateMeetingSummaryScreen(
itemInPrivateMeeting: meetingData,
),
);
},
),
GoRoute(
path: '/report',
name: 'report',
builder: (context, state) {
return ChangeNotifierProvider(
create: (context) => ReportState(),
child: ReportScreen(),
);
},
),
],
);

+ 48
- 0
lib/screens/aboutUs/screen.dart Просмотреть файл

@@ -0,0 +1,48 @@
import 'package:flutter/material.dart';
import 'package:qadirneyriz/widgets/custom_appbar.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';

class AboutUsScreen extends StatelessWidget {
const AboutUsScreen({super.key});

@override
Widget build(BuildContext context) {
return CustomScrollView(
slivers: [
const CustomAppbar(),
SliverToBoxAdapter(
child: Image.asset('assets/images/logomizban.png'),
),
SliverToBoxAdapter(
child: Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Text(
textAlign: TextAlign.justify,
AppLocalizations.of(context)!.textaboutus,
),
),
SizedBox(
height: 10,
),
Column(
children: [
Image.asset(
'assets/images/logoaboutus.png',
width: 100,
height: 100,
),
Text(
'نسخه 1.0.0',
style: TextStyle(fontSize: 12),
),
],
)
],
),
)
],
);
}
}

+ 1
- 0
lib/screens/auth/login_screen.dart Просмотреть файл

@@ -72,6 +72,7 @@ class _LoginScreenState extends State<LoginScreen> {
AppLocalizations.of(context)!.hintphonenumber,
textEditingController: phoneController,
textInputType: TextInputType.phone,
textInputAction: TextInputAction.next,
),
const SizedBox(height: 16),
// Password field


+ 3
- 3
lib/screens/auth/state/state.dart Просмотреть файл

@@ -66,7 +66,7 @@ class AuthState extends ChangeNotifier {
if (result == null) {
statusSendotp = Status.error;
} else {
print(result);
// print(result);
if (result.isOk) {
statusSendotp = Status.ready;
messageSendOtp = result.message;
@@ -82,10 +82,10 @@ class AuthState extends ChangeNotifier {
notifyListeners();
} catch (e) {
statusSendotp = Status.error;
print(e);
// print(e);
}
notifyListeners();
print(statusSendotp);
// print(statusSendotp);
return statusSendotp;
}



+ 311
- 138
lib/screens/home/screen.dart Просмотреть файл

@@ -2,12 +2,16 @@
import 'package:flutter/material.dart';
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
import 'package:go_router/go_router.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
import 'package:qadirneyriz/setting/setting.dart';
import 'package:qadirneyriz/utils/tools/tools.dart';
import 'package:qadirneyriz/widgets/card_meeting.dart';
import 'package:qadirneyriz/widgets/custom_appbar.dart';
import 'package:qadirneyriz/widgets/custom_button.dart';
import 'package:qadirneyriz/widgets/empty_widget.dart';
import 'package:qadirneyriz/widgets/error_widget.dart';
import 'package:qadirneyriz/widgets/today_widget.dart';
import 'package:shamsi_date/shamsi_date.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:qadirneyriz/config/config.dart';
import 'package:qadirneyriz/screens/home/state.dart';
@@ -33,162 +37,333 @@ class _HomeScreenState extends State<HomeScreen> {

@override
Widget build(BuildContext context) {
final Jalali shamsi = Jalali.now(); // دریافت تاریخ شمسی کنونی
String formattedDate =
'${shamsi.day} ${Tools.getMonthName(shamsi.month)} ${shamsi.year}'; // فرمت کردن تاریخ
DateTime now = DateTime.now();
String dateMiladi = DateFormat('yyyy-MM-dd').format(now);
String dateJalali =
'${setting.timeNow.day} ${Tools.getMonthName(setting.timeNow.month)} ${setting.timeNow.year}'; // فرمت کردن تاریخ

return Consumer<HomeState>(
builder: (context, value, child) {
switch (value.todayMettingsStatus) {
case Status.ready:
return CustomScrollView(
slivers: [
const CustomAppbar(),
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 20, vertical: 10),
child: Container(
decoration: BoxDecoration(
color: const Color(0xffF4F9F6),
boxShadow: [
BoxShadow(
color: config.ui.mainGray.withOpacity(.1),
spreadRadius: .1,
offset: const Offset(0, 5),
blurRadius: 6)
],
borderRadius: BorderRadius.circular(25)),
width: double.infinity,
child: Padding(
padding: const EdgeInsets.all(20),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(
Icons.edit_outlined,
color: config.ui.mainGreen,
),
const SizedBox(
width: 10,
),
Expanded(
child: Text(
style: const TextStyle(fontSize: 13),
value.todayMeetingsModel!.note ?? ''),
),
],
return RefreshIndicator(
onRefresh: () async {
await value.getTodayMeetings();
},
child: CustomScrollView(
slivers: [
const CustomAppbar(),
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 20, vertical: 10),
child: Container(
decoration: BoxDecoration(
color: const Color(0xffF4F9F6),
boxShadow: [
BoxShadow(
color: config.ui.mainGray.withOpacity(.1),
spreadRadius: .1,
offset: const Offset(0, 5),
blurRadius: 6)
],
borderRadius: BorderRadius.circular(25)),
width: double.infinity,
child: Padding(
padding: const EdgeInsets.all(20),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(
Icons.edit_outlined,
color: config.ui.mainGreen,
),
const SizedBox(
width: 10,
),
Expanded(
child: Text(
style: const TextStyle(fontSize: 13),
value.todayMeetingsModel!.note ?? ''),
),
],
),
),
),
),
),
),
SliverToBoxAdapter(
child: TodayWidget(formattedDate: formattedDate),
),
SliverToBoxAdapter(
child: SizedBox(
height: 165,
child: value.todayMeetingsModel!.meetings!.isNotEmpty
? ListView.builder(
scrollDirection: Axis.horizontal,
itemCount:
value.todayMeetingsModel!.meetings!.length,
itemBuilder: (BuildContext context, int index) {
final items =
value.todayMeetingsModel!.meetings![index];
return CustomCardMeeting(
titel: items.subject!.subject ?? '',
fromTime: items.azHour ?? '',
toTime: items.taHour ?? "",
location: items.location!.address ?? '',
date: items.dateJalali ?? '',
cardId: items.id ?? 0,
);
},
)
: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.error_outline,
size: 40,
color: config.ui.mainGray.withOpacity(.5)),
const SizedBox(
height: 20,
),
Text(
AppLocalizations.of(context)!
.nomeetingfortoday,
style: TextStyle(
SliverToBoxAdapter(
child: TodayWidget(
formattedDate:
setting.userLocalDb.getUser().language == 'en'
? dateMiladi
: dateJalali),
),
SliverToBoxAdapter(
child: SizedBox(
height: 170,
child: value.todayMeetingsModel!.meetings!.isNotEmpty
? ListView.builder(
scrollDirection: Axis.horizontal,
itemCount:
value.todayMeetingsModel!.meetings!.length,
itemBuilder: (BuildContext context, int index) {
final items =
value.todayMeetingsModel!.meetings![index];
return Padding(
padding:
const EdgeInsets.only(right: 5, left: 1),
child: CustomCardMeeting(
status: items.accepted ?? 0,
titel: items.subject != null
? items.subject!.subject ?? ''
: '',
fromTime: items.azHour ?? '',
toTime: items.taHour ?? "",
location: items.location != null
? items.location!.address ?? ''
: '',
date: items.dateJalali ?? '',
cardId: items.id ?? 0,
),
);
},
)
: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.error_outline,
size: 40,
color:
config.ui.mainGray.withOpacity(.5)),
),
],
const SizedBox(
height: 20,
),
Text(
AppLocalizations.of(context)!
.nomeetingfortoday,
style: TextStyle(
color:
config.ui.mainGray.withOpacity(.5)),
),
],
),
),
),
),
),
),
SliverPadding(
padding:
const EdgeInsets.symmetric(vertical: 30, horizontal: 10),
sliver: SliverToBoxAdapter(
child: StaggeredGrid.count(
crossAxisCount: 4,
mainAxisSpacing: 4,
crossAxisSpacing: 4,
children: [
StaggeredGridTile.count(
crossAxisCellCount: 2,
mainAxisCellCount: 1,
child: ItemInGrid(
icon: Icons.assessment,
backColor: const Color(0xff03C85F),
text: AppLocalizations.of(context)!.reports,
onTap: () {},
SliverPadding(
padding: const EdgeInsets.symmetric(
vertical: 30, horizontal: 10),
sliver: SliverToBoxAdapter(
child: StaggeredGrid.count(
crossAxisCount: 4,
mainAxisSpacing: 4,
crossAxisSpacing: 4,
children: [
StaggeredGridTile.count(
crossAxisCellCount: 2,
mainAxisCellCount: 1,
child: ItemInGrid(
icon: Icons.assessment,
backColor: const Color(0xff03C85F),
text: AppLocalizations.of(context)!.reports,
onTap: () {
context.pushNamed('navigate',
pathParameters: {'tab': '3'});
},
),
),
),
StaggeredGridTile.count(
crossAxisCellCount: 2,
mainAxisCellCount: 2,
child: ItemInGrid(
icon: Icons.people,
backColor: const Color(0xff04A54F),
text: AppLocalizations.of(context)!.meetings,
onTap: () {
context.pushNamed('navigate',
pathParameters: {'tab': '1'});
},
StaggeredGridTile.count(
crossAxisCellCount: 2,
mainAxisCellCount: 2,
child: ItemInGrid(
icon: Icons.people,
backColor: const Color(0xff04A54F),
text: AppLocalizations.of(context)!.meetings,
onTap: () {
context.pushNamed('navigate',
pathParameters: {'tab': '1'});
},
),
),
),
StaggeredGridTile.count(
crossAxisCellCount: 2,
mainAxisCellCount: 2,
child: ItemInGrid(
icon: Icons.calendar_today,
backColor: const Color(0xff37A068),
text: AppLocalizations.of(context)!.events,
onTap: () {},
StaggeredGridTile.count(
crossAxisCellCount: 2,
mainAxisCellCount: 2,
child: ItemInGrid(
icon: Icons.calendar_today,
backColor: const Color(0xff37A068),
text: AppLocalizations.of(context)!.events,
onTap: () {
context.pushNamed('navigate',
pathParameters: {'tab': '2'});
},
),
),
),
StaggeredGridTile.count(
crossAxisCellCount: 2,
mainAxisCellCount: 1,
child: ItemInGrid(
icon: Icons.exit_to_app,
backColor: const Color(0xff00843D),
text: AppLocalizations.of(context)!.exit,
onTap: () {},
StaggeredGridTile.count(
crossAxisCellCount: 2,
mainAxisCellCount: 1,
child: ItemInGrid(
icon: Icons.exit_to_app,
backColor: const Color(0xff00843D),
text: AppLocalizations.of(context)!.exit,
onTap: () {
showModalBottomSheet(
context: context,
builder: (context) {
return DraggableScrollableSheet(
initialChildSize: .5,
expand: false,
snap: false,
builder: (context, scrollController) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.only(
top: 8, bottom: 30),
child: Container(
width: 60,
height: 4,
decoration: BoxDecoration(
color: Colors.black
.withOpacity(.4),
borderRadius:
BorderRadius.circular(
10)),
),
),
Text(
AppLocalizations.of(context)!
.exit,
style: TextStyle(
color: config.ui.mainGreen,
fontSize: 18,
fontWeight: FontWeight.w500),
),
const SizedBox(
height: 15,
),
Text(
AppLocalizations.of(context)!
.areusurelog,
style: TextStyle(
color: Colors.black,
fontSize: 14,
),
),
const SizedBox(
height: 30,
),
Consumer<HomeState>(
builder: (context, value, child) {
switch (value.statusLogOut) {
case Status.loading:
return const LoadingWidget();

default:
return Row(
mainAxisAlignment:
MainAxisAlignment
.center,
crossAxisAlignment:
CrossAxisAlignment
.center,
children: [
CustomButton(
fontSize: 13,
color: config
.ui.mainGreen,
onPressed: () {
Navigator.pop(
context);
},
hieght: 50,

// width: 150,
text: AppLocalizations
.of(context)!
.no,
),
const SizedBox(
width: 10,
),
CustomButton(
fontSize: 13,
hieght: 50,
// width: 150,
text: AppLocalizations
.of(context)!
.logout,
textColor:
Colors.black,
color: const Color(
0xffD0D5ED),

onPressed: () async {
final status =
await value
.logOut();
if (status ==
Status.error) {
Tools.showCustomSnackBar(
context,
text: value
.messageLogOut ??
AppLocalizations.of(
context)!
.error,
isError:
true);
} else if (status ==
Status.ready) {
context
.pushReplacementNamed(
'login');
Tools.showCustomSnackBar(
context,
text: value
.messageLogOut ??
'Done successfully',
isError:
false);
}
},
),
],
);
}
},
),
const SizedBox(
height: 40,
)
],
);
},
);
},
);
},
),
),
),
],
],
),
),
),
),
],
],
),
);
case Status.loading:
return const LoadingWidget();
case Status.error:
return CustomErrorWidget(
onPressed: () async {
await value.getTodayMeetings(refresh: true);
},
);
case Status.empty:
return EmptyStateWidget();
default:
return Container();
}
@@ -240,9 +415,7 @@ class ItemInGrid extends StatelessWidget {
),
Text(
text,
style: const TextStyle(
color: Colors.white,
),
style: const TextStyle(color: Colors.white, fontSize: 16),
),
],
),


+ 35
- 4
lib/screens/home/state.dart Просмотреть файл

@@ -8,7 +8,7 @@ class HomeState extends ChangeNotifier {

Status todayMettingsStatus = Status.empty;
TodayMeetingModel? todayMeetingsModel;
getTodayMeetings({bool refresh = false}) async {
Future<Status> getTodayMeetings({bool refresh = false}) async {
todayMettingsStatus = Status.loading;
notifyListeners();
if (refresh) {
@@ -27,7 +27,6 @@ class HomeState extends ChangeNotifier {
}
} catch (e) {
todayMettingsStatus = Status.error;
// print(e);
}
notifyListeners();
} else {
@@ -41,12 +40,44 @@ class HomeState extends ChangeNotifier {
notifyListeners();
} catch (e) {
todayMettingsStatus = Status.error;
print(e);
}
}
notifyListeners();
print(todayMettingsStatus);
return todayMettingsStatus;
}

// log out
// log out
Status statusLogOut = Status.empty;
String? messageLogOut;
Map? errorsLogOut;

Future<Status> logOut() async {
statusLogOut = Status.loading;
notifyListeners();
try {
final result = await homeApi.logOutApi();
if (result == null) {
statusLogOut = Status.error;
} else {
if (result.isOk) {
statusLogOut = Status.ready;
messageLogOut = result.message;
} else if (result.isOk == false) {
errorsLogOut = result.errors;
messageLogOut = result.message;
statusLogOut = Status.error;
} else {
statusLogOut = Status.error;
}
}
notifyListeners();
} catch (e) {
statusLogOut = Status.error;
// print(e);
}
notifyListeners();
// print(statusLogOut);
return statusLogOut;
}
}

+ 34
- 16
lib/screens/meeting/diolog_meetings_filters.dart Просмотреть файл

@@ -1,7 +1,7 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:qadirneyriz/global_state/global_state.dart';
import 'package:qadirneyriz/global/global_state/global_state.dart';
import 'package:qadirneyriz/widgets/ExpansionTileCustom.dart';
import 'package:qadirneyriz/widgets/error_widget.dart';
import 'package:qadirneyriz/config/config.dart';
@@ -24,30 +24,37 @@ class DiologMeetingsFilters extends StatefulWidget {
class _DiologMeetingsFiltersState extends State<DiologMeetingsFilters> {
MeetingsState? meetingsState;
GlobalState? globalState;

@override
void initState() {
super.initState();
meetingsState = Provider.of<MeetingsState>(context, listen: false);
globalState = Provider.of<GlobalState>(context, listen: false);
Future.delayed(Duration.zero, () async {
final status = await globalState!.getAllFiltersItems();

print(status);
await globalState!.getAllFiltersItems();
});
super.initState();

// ذخیره فیلترهای اولیه برای مقایسه در آینده
meetingsState!.setAllFiltersForThen();
}

@override
void dispose() {
Future.delayed(Duration.zero, () async {
await meetingsState!.getMeetings(
// بررسی تغییرات فیلترها
if (meetingsState!.isAnyChangesInFilters()) {
Future.microtask(() async {
await meetingsState!.getMeetings(
refresh: true,
toDate: meetingsState!.toDate,
fromDate: meetingsState!.fromDate,
location: meetingsState!.selectedLocationId,
subject: meetingsState!.selectedSubjectId,
meetingManager: meetingsState!.selectedManagersId,
meetingStatus: meetingsState!.selectedStatusId);
});
meetingStatus: meetingsState!.selectedStatusId,
);
});
}

super.dispose();
}

@@ -58,6 +65,18 @@ class _DiologMeetingsFiltersState extends State<DiologMeetingsFilters> {
expand: false,
snap: false,
builder: (context, scrollController) {
// statuses meetings
List<MeetingsStatus> meetingStatuses = [
MeetingsStatus(
id: 1, title: AppLocalizations.of(context)!.donemeetings),
MeetingsStatus(
id: 2, title: AppLocalizations.of(context)!.adjournedmeetings),
MeetingsStatus(
id: 3, title: AppLocalizations.of(context)!.canceldmeetings),
MeetingsStatus(
id: 4,
title: AppLocalizations.of(context)!.meetingswaitingtobeheld),
];
return Consumer2<MeetingsState, GlobalState>(
builder: (context, meetingsState, globalState, child) {
switch (globalState.allFiltersStatus) {
@@ -226,8 +245,8 @@ class _DiologMeetingsFiltersState extends State<DiologMeetingsFilters> {
primary: false,
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount:
globalState.meetingsManagerModel!.length,
itemCount: globalState
.meetingsManagerModel!.length,
itemBuilder:
(BuildContext context, int index) {
final items = globalState
@@ -305,12 +324,11 @@ class _DiologMeetingsFiltersState extends State<DiologMeetingsFilters> {
NeverScrollableScrollPhysics(),
shrinkWrap: true,
primary: false,
itemCount: globalState
.meetingStatuses.length,
itemCount: meetingStatuses.length,
itemBuilder: (BuildContext context,
int index) {
final items = globalState
.meetingStatuses[index];
final items =
meetingStatuses[index];
return RadioListTile<int>(
toggleable: true,
groupValue: meetingsState
@@ -350,7 +368,7 @@ class _DiologMeetingsFiltersState extends State<DiologMeetingsFilters> {
case Status.error:
return CustomErrorWidget(
onPressed: () async {
// await meetingsState!.getAllFiltersItems(refresh: true);
await globalState.getAllFiltersItems(refresh: true);
},
);
default:


+ 160
- 118
lib/screens/meeting/screen.dart Просмотреть файл

@@ -3,10 +3,12 @@ import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:go_router/go_router.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
import 'package:qadirneyriz/config/config.dart';
import 'package:qadirneyriz/screens/meeting/diolog_meetings_filters.dart';
import 'package:qadirneyriz/screens/meeting/state.dart';
import 'package:qadirneyriz/setting/setting.dart';
import 'package:qadirneyriz/utils/enums/status.dart';
import 'package:qadirneyriz/utils/tools/tools.dart';
import 'package:qadirneyriz/widgets/card_meeting.dart';
@@ -16,7 +18,6 @@ import 'package:qadirneyriz/widgets/error_widget.dart';
import 'package:qadirneyriz/widgets/icon_button.dart';
import 'package:qadirneyriz/widgets/loading_widget.dart';
import 'package:qadirneyriz/widgets/today_widget.dart';
import 'package:shamsi_date/shamsi_date.dart';

class MeetingsScreen extends StatefulWidget {
const MeetingsScreen({super.key});
@@ -65,65 +66,76 @@ class _MeetingsScreenState extends State<MeetingsScreen> {

@override
Widget build(BuildContext context) {
Jalali nowShamsi = Jalali.now();
String todayDateForShow =
'${nowShamsi.day} ${Tools.getMonthName(nowShamsi.month)} ${nowShamsi.year}';
DateTime now = DateTime.now();
String dateMiladi = DateFormat('yyyy-MM-dd').format(now);
String dateJalali =
'${setting.timeNow.day} ${Tools.getMonthName(setting.timeNow.month)} ${setting.timeNow.year}'; // فرمت کردن تاریخ
return Consumer<MeetingsState>(
builder: (context, value, child) {
return CustomScrollView(
controller: _scrollController,
slivers: <Widget>[
const CustomAppbar(),
SliverToBoxAdapter(
child: TodayWidget(formattedDate: todayDateForShow),
),
SliverToBoxAdapter(
child: Padding(
padding:
const EdgeInsets.symmetric(vertical: 30, horizontal: 15),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
style: const TextStyle(fontSize: 14),
AppLocalizations.of(context)!.meetings,
),
IconButtonCustom(
iconColor: value.hasActiveFilters()
? Colors.white
: config.ui.secendGreen,
backColor: value.hasActiveFilters()
? config.ui.secendGreen
: Colors.white,
icon: FontAwesomeIcons.sliders,
onTap: () {
showModalBottomSheet(
isScrollControlled: true,
useSafeArea: true,
context: context,
builder: (context) {
return DiologMeetingsFilters();
},
);
},
)
],
return RefreshIndicator(
onRefresh: () async {
await meetingsState.getMeetings();
},
child: CustomScrollView(
physics: AlwaysScrollableScrollPhysics(),
controller: _scrollController,
slivers: <Widget>[
const CustomAppbar(),
SliverToBoxAdapter(
child: TodayWidget(
formattedDate:
setting.userLocalDb.getUser().language == 'en'
? dateMiladi
: dateJalali),
),
SliverToBoxAdapter(
child: Padding(
padding:
const EdgeInsets.symmetric(vertical: 30, horizontal: 15),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
style: const TextStyle(fontSize: 14),
AppLocalizations.of(context)!.meetings,
),
IconButtonCustom(
iconColor: value.hasActiveFilters()
? Colors.white
: config.ui.secendGreen,
backColor: value.hasActiveFilters()
? config.ui.secendGreen
: Colors.white,
icon: FontAwesomeIcons.sliders,
onTap: () {
showModalBottomSheet(
isScrollControlled: true,
useSafeArea: true,
context: context,
builder: (context) {
return DiologMeetingsFilters();
},
);
},
)
],
),
),
),
),
meetingsList(value),
(value.paginationMeetings == Status.ready ||
value.paginationMeetings == Status.empty)
? const SliverToBoxAdapter()
: const SliverToBoxAdapter(
child: Center(
child: LoadingWidget(
size: 10,
meetingsList(value),
(value.paginationMeetings == Status.ready ||
value.paginationMeetings == Status.empty)
? const SliverToBoxAdapter()
: const SliverToBoxAdapter(
child: Center(
child: LoadingWidget(
size: 10,
),
),
),
)
],
)
],
),
);
},
);
@@ -134,14 +146,19 @@ class _MeetingsScreenState extends State<MeetingsScreen> {
case Status.ready:
return SliverList.builder(
itemBuilder: (context, index) {
final userRole = setting.userLocalDb.getUser().role;
final items = state.meetingsModel!.data![index];
return Padding(
padding: const EdgeInsets.all(8.0),
child: CustomCardMeeting(
titel: items.subject!.subject ?? '',
status: items.accepted ?? 0,
titel:
items.subject != null ? items.subject!.subject ?? '' : '',
fromTime: items.azHour ?? '',
toTime: items.taHour ?? "",
location: items.location!.address ?? '',
location: items.location != null
? items.location!.address ?? ''
: '',
date: items.dateJalali ?? '',
cardId: items.id ?? 0,
onSelectedMoreButton: (value) async {
@@ -150,79 +167,102 @@ class _MeetingsScreenState extends State<MeetingsScreen> {
await context.pushNamed('meetingedit',
pathParameters: {'id': items.id.toString()});

meetingsState.getMeetings(refresh: true);
meetingsState.getMeetings();

case 'confirm':
acceptMeeting(state, context, items.id ?? -1);
case 'cancel':
cancelMeeting(state, context, items.id ?? -1);
case 'report':
await context.pushNamed(
'meetinsammary',
extra: items, // `items` should be a Datum instance
);
if (userRole == 1 && items.description != null) {
await context.pushNamed(
'meetinsammary',
extra: items, // `items` should be a Datum instance
);
await meetingsState.getMeetings();
} else if (userRole == 1 && items.description == null) {
Tools.showCustomSnackBar(
text:
AppLocalizations.of(context)!.thereisnosummary,
isError: true,
context,
);
} else {
await context.pushNamed(
'meetinsammary',
extra: items, // `items` should be a Datum instance
);
await meetingsState.getMeetings();
}

default:
}
},
itemBuilderMoreButton: (context) => [
PopupMenuItem(
value: 'edit',
child: Row(
children: const [
Icon(
Icons.edit,
color: Colors.green,
size: 17,
),
SizedBox(width: 8),
Text(
'ویرایش قرار',
style: TextStyle(fontSize: 12),
),
],
if (userRole == 0 || userRole == 2)
PopupMenuItem(
value: 'edit',
child: Row(
children: [
Icon(
Icons.edit,
color: Colors.green,
size: 17,
),
SizedBox(width: 8),
Text(
AppLocalizations.of(context)!.editmeeting,
style: TextStyle(fontSize: 12),
),
],
),
),
),
PopupMenuItem(
enabled: state.statusAcceptMeeting != Status.loading,
value: 'confirm',
child: Row(
children: const [
Icon(
Icons.check_circle,
color: Colors.green,
size: 17,
),
SizedBox(width: 8),
Text(
'تایید جلسه',
style: TextStyle(fontSize: 12),
),
],
if ((userRole == 0 || userRole == 2) &&
items.accepted == 0)
PopupMenuItem(
enabled:
state.statusAcceptMeeting != Status.loading,
value: 'confirm',
child: Row(
children: [
Icon(
Icons.check_circle,
color: Colors.green,
size: 17,
),
SizedBox(width: 8),
Text(
AppLocalizations.of(context)!.acceptmeeting,
style: TextStyle(fontSize: 12),
),
],
),
),
),
PopupMenuItem(
enabled: state.statusCancelMeeting != Status.loading,
value: 'cancel',
child: Row(
children: const [
Icon(
Icons.cancel,
color: Colors.green,
size: 17,
),
SizedBox(width: 8),
Text(
'لغو قرار',
style: TextStyle(fontSize: 12),
),
],
if ((userRole == 0 || userRole == 2) &&
items.accepted == 0)
PopupMenuItem(
enabled:
state.statusCancelMeeting != Status.loading,
value: 'cancel',
child: Row(
children: [
Icon(
Icons.cancel,
color: Colors.green,
size: 17,
),
SizedBox(width: 8),
Text(
AppLocalizations.of(context)!.cancelmeeting,
style: TextStyle(fontSize: 12),
),
],
),
),
),
PopupMenuItem(
value: 'report',
child: Row(
children: const [
children: [
Icon(
Icons.receipt_long,
color: Colors.green,
@@ -230,7 +270,7 @@ class _MeetingsScreenState extends State<MeetingsScreen> {
),
SizedBox(width: 8),
Text(
'صورت جلسه',
AppLocalizations.of(context)!.meetingsummary,
style: TextStyle(fontSize: 12),
),
],
@@ -263,13 +303,14 @@ class _MeetingsScreenState extends State<MeetingsScreen> {
final status = await state.cancelMeeting(id: cardId);
if (status == Status.ready) {
Tools.showCustomSnackBar(
text: 'جلسه لغو شد!',
text: AppLocalizations.of(context)!.meetingcanceled,
isError: false,
context,
);
await meetingsState.getMeetings();
} else {
Tools.showCustomSnackBar(
text: 'مشکلی رخ داده است. دوباره تلاش کنید!',
text: AppLocalizations.of(context)!.error,
isError: true,
context,
);
@@ -281,13 +322,14 @@ class _MeetingsScreenState extends State<MeetingsScreen> {
final status = await state.acceptMeeting(id: cardId);
if (status == Status.ready) {
Tools.showCustomSnackBar(
text: 'جلسه تایید شد!',
text: AppLocalizations.of(context)!.meetingaccepted,
isError: false,
context,
);
await meetingsState.getMeetings();
} else {
Tools.showCustomSnackBar(
text: 'مشکلی رخ داده است. دوباره تلاش کنید!',
text: AppLocalizations.of(context)!.error,
isError: true,
context,
);


+ 37
- 7
lib/screens/meeting/state.dart Просмотреть файл

@@ -4,6 +4,35 @@ import 'package:qadirneyriz/services/meetings/meetings.dart';
import 'package:qadirneyriz/utils/enums/status.dart';

class MeetingsState extends ChangeNotifier {
// ذخیره فیلترهای قبلی برای مقایسه
String? previousFromDate;
String? previousToDate;
int? previousLocationId;
int? previousSubjectId;
int? previousManagersId;
int? previousStatusId;
void setAllFiltersForThen() {
previousFromDate = fromDate;
previousToDate = toDate;
previousLocationId = selectedLocationId;
previousSubjectId = selectedSubjectId;
previousManagersId = selectedManagersId;
previousStatusId = selectedStatusId;
}

bool isAnyChangesInFilters() {
if (previousFromDate != fromDate ||
previousToDate != toDate ||
previousLocationId != selectedLocationId ||
previousSubjectId != selectedSubjectId ||
previousManagersId != selectedManagersId ||
previousStatusId != selectedStatusId) {
return true;
} else {
return false;
}
}

// api meetings
MeetingsApi meetingsApi = MeetingsApi();

@@ -27,6 +56,7 @@ class MeetingsState extends ChangeNotifier {
statusMeetings = Status.loading;
notifyListeners();
}

if (meetingsModel != null && meetingsModel!.data!.isNotEmpty && !refresh) {
statusMeetings = Status.ready;
notifyListeners();
@@ -78,7 +108,7 @@ class MeetingsState extends ChangeNotifier {
notifyListeners();
}
notifyListeners();
print(statusMeetings);
// print(statusMeetings);
return statusMeetings;
}

@@ -211,7 +241,7 @@ class MeetingsState extends ChangeNotifier {
statusCancelMeeting = Status.ready;
messageCancelMeeting = result.message;
} else if (result.isOk == false) {
print(result.isOk);
// print(result.isOk);
errorsCancelMeeting = result.errors;
messageCancelMeeting = result.message;
statusCancelMeeting = Status.error;
@@ -221,10 +251,10 @@ class MeetingsState extends ChangeNotifier {
notifyListeners();
} catch (e) {
statusCancelMeeting = Status.error;
print(e);
// print(e);
}
notifyListeners();
print(statusCancelMeeting);
// print(statusCancelMeeting);
return statusCancelMeeting;
}

@@ -246,7 +276,7 @@ class MeetingsState extends ChangeNotifier {
statusAcceptMeeting = Status.ready;
messageAcceptMeeting = result.message;
} else if (result.isOk == false) {
print(result.isOk);
// print(result.isOk);
errorsAcceptMeeting = result.errors;
messageAcceptMeeting = result.message;
statusAcceptMeeting = Status.error;
@@ -256,10 +286,10 @@ class MeetingsState extends ChangeNotifier {
notifyListeners();
} catch (e) {
statusAcceptMeeting = Status.error;
print(e);
// print(e);
}
notifyListeners();
print(statusAcceptMeeting);
// print(statusAcceptMeeting);
return statusAcceptMeeting;
}
}

+ 505
- 0
lib/screens/meeting_add/screen.dart Просмотреть файл

@@ -0,0 +1,505 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
import 'package:qadirneyriz/diologs/diolog_add_location.dart';
import 'package:qadirneyriz/diologs/diolog_add_subject.dart';
import 'package:qadirneyriz/diologs/diolog_add_user.dart';
import 'package:qadirneyriz/global/global_class/selected_item.dart';
import 'package:qadirneyriz/global/global_state/global_state.dart';
import 'package:qadirneyriz/screens/meeting_add/state.dart';
import 'package:qadirneyriz/utils/enums/status.dart';
import 'package:qadirneyriz/utils/tools/tools.dart';
import 'package:qadirneyriz/widgets/ExpansionTileCustom.dart';
import 'package:qadirneyriz/widgets/checkBox_inTile.dart';
import 'package:qadirneyriz/widgets/custom_appbar.dart';
import 'package:qadirneyriz/widgets/custom_button.dart';
import 'package:qadirneyriz/widgets/ink_warpper.dart';
import 'package:qadirneyriz/widgets/loading_widget.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:qadirneyriz/widgets/picker.dart';

class MeetingAddScreen extends StatefulWidget {
const MeetingAddScreen({super.key});

@override
State<MeetingAddScreen> createState() => _MeetingAddScreenState();
}

class _MeetingAddScreenState extends State<MeetingAddScreen> {
bool isPrivateMeeting = false;
final _formKey = GlobalKey<FormState>(); // Key for form validation
// all states we have

late GlobalState globalState;

@override
void initState() {
super.initState();

//set states

globalState = Provider.of<GlobalState>(context, listen: false);
Future.delayed(Duration.zero, () async {
// get items
await globalState.getAllFiltersItems();
});
}

@override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: <Widget>[
CustomAppbar(
title: AppLocalizations.of(context)!.createnewmeeting,
),
SliverFillRemaining(child: content(context)),
],
),
);
}

Widget content(BuildContext context) {
return Consumer2<GlobalState, MeetinAddState>(
builder: (context, stateGlobal, meetingAddState, child) {
switch (stateGlobal.allFiltersStatus) {
case Status.ready:
return Padding(
// This is now wrapped inside SliverToBoxAdapter
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// subject ExpansionTile
Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: ExpansionTileCustom(
isForm: true,
subTitile:
AppLocalizations.of(context)!.meetingsubject,
title: meetingAddState.selectedSubject.id != null
? meetingAddState.selectedSubject.text ?? ''
: AppLocalizations.of(context)!.selectsubject,
widgets: <Widget>[
CheckBoxInTile(
text: AppLocalizations.of(context)!.newsubject,
onTap: () async {
await showDialog(
context:
context, // این باید کانتکست فعلی باشد
builder: (BuildContext context) {
return AddSubjectDiolog();
},
);
},
hasIcon: true,
backColor: Colors.white,
textColor: Colors.black.withOpacity(.5),
),
Column(
children:
globalState.subjectsModel!.map((subject) {
bool isSelected =
meetingAddState.selectedSubject.id ==
subject.id;
return CheckBoxInTile(
backColor: isSelected
? Color(0xff06CF64)
: Colors.white,
textColor:
isSelected ? Colors.white : Colors.black,
text: subject.subject ?? '',
hasIcon: false,
onTap: () {
setState(() {
meetingAddState.selectedSubject =
ItemSelected(
text: subject.subject ?? '',
id: subject.id ??
0); // Update selected location
});
},
);
}).toList(),
),
],
),
),

// Date Picker
Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: PickerCustom(
showDate: meetingAddState.fromDate ??
AppLocalizations.of(context)!.selectdate,
onTap: () {
showDialog(
context: context,
builder: (context) {
return Dialog(
child: Tools.shamsiDateCalendarWidget(
context,
(newDate) {
setState(() {
String fromDateString =
'${newDate.year}/${newDate.month}/${newDate.day}';
meetingAddState
.setFromDate(fromDateString);
}); // Update the selected date
},
),
);
},
);
},
isForm: true,
title: AppLocalizations.of(context)!.date,
),
),

// From and To time Range Pickers
Padding(
padding: const EdgeInsets.symmetric(vertical: 15.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
PickerCustom(
showDate: Tools.formatTime(
meetingAddState.selectedStartTime.hour,
meetingAddState.selectedStartTime.minute),
onTap: () async {
TimeOfDay? picked = await showTimePicker(
context: context,
initialTime:
meetingAddState.selectedStartTime,
);
if (picked != null &&
picked != meetingAddState.selectedStartTime)
setState(() {
meetingAddState.selectedStartTime = picked;
});
},
isForm: true,
icon: Icons.access_time_outlined,
title: AppLocalizations.of(context)!.clock,
),
Text(AppLocalizations.of(context)!.to),
PickerCustom(
showDate: Tools.formatTime(
meetingAddState.selectedEndTime.hour,
meetingAddState.selectedEndTime.minute),
isForm: true,
icon: Icons.access_time_outlined,
onTap: () async {
TimeOfDay? picked = await showTimePicker(
context: context,
initialTime: meetingAddState.selectedEndTime,
);
if (picked != null &&
picked != meetingAddState.selectedEndTime)
setState(() {
meetingAddState.selectedEndTime = picked;
});
},
),
],
),
),

// Location ExpansionTile
Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: ExpansionTileCustom(
isForm: true,
subTitile: AppLocalizations.of(context)!.location,
title: meetingAddState.selectedLocation.text ??
AppLocalizations.of(context)!.selectlocation,
widgets: <Widget>[
CheckBoxInTile(
text: AppLocalizations.of(context)!.newlocation,
onTap: () async {
await showDialog(
context:
context, // این باید کانتکست فعلی باشد
builder: (BuildContext context) {
return AddLocationDiolog();
},
);
},
hasIcon: true,
backColor: Colors.white,
textColor: Colors.black.withOpacity(.5),
),
Column(
children:
globalState.locationsModel!.map((location) {
bool isSelected =
meetingAddState.selectedLocation.id ==
location.id;
return CheckBoxInTile(
backColor: isSelected
? Color(0xff06CF64)
: Colors.white,
textColor:
isSelected ? Colors.white : Colors.black,
text: location.address ?? '',
hasIcon: false,
onTap: () {
setState(() {
meetingAddState.selectedLocation =
ItemSelected(
text: location.address,
id: location
.id); // Update selected location
});
},
);
}).toList(),
),
],
),
),

// Another ExpansionTile for users
Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: ExpansionTileCustom(
isForm: true,
subTitile: AppLocalizations.of(context)!.users,
title: AppLocalizations.of(context)!.selectusers,
widgets: <Widget>[
CheckBoxInTile(
text: AppLocalizations.of(context)!.newmember,
onTap: () async {
await showDialog(
context:
context, // این باید کانتکست فعلی باشد
builder: (BuildContext context) {
return AddUserDiolog();
},
);
},
hasIcon: true,
backColor: Colors.white,
textColor: Colors.black.withOpacity(.5),
),
Column(
children: globalState.usersModel != null
? globalState.usersModel!.map((user) {
bool isSelected = meetingAddState
.selectedUsersItems
.contains(user.id);
return Container(
margin: EdgeInsets.symmetric(
vertical: 5.0, horizontal: 10),
decoration: BoxDecoration(
color: isSelected
? Color(0xff06CF64)
: Colors.white,
borderRadius:
BorderRadius.circular(10),
boxShadow: [
BoxShadow(
color: Colors.black12,
blurRadius: 8,
offset: Offset(0, 4),
),
],
),
child: InkWrapper(
onTap: () {
setState(() {
if (isSelected) {
meetingAddState
.selectedUsersItems
.remove(user.id);
} else {
meetingAddState
.selectedUsersItems
.add(user.id ?? 0);
}
});
},
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Row(
children: [
Text(
maxLines: 1,
overflow:
TextOverflow.ellipsis,
user.name ?? '',
style: TextStyle(
fontSize: 12,
color: isSelected
? Colors.white
: Colors.black,
),
),
],
),
),
),
);
}).toList()
: [],
),
],
),
),
InkWell(
onTap: () {
setState(() {
isPrivateMeeting = !isPrivateMeeting;
});
},
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 15),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
AppLocalizations.of(context)!.isprivatemeeting,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontWeight: FontWeight.normal,
fontSize: 13,
color: Colors.black.withOpacity(.8),
),
),
Checkbox(
value: isPrivateMeeting,
onChanged: (f) {
setState(() {
isPrivateMeeting = f ?? false;
});
}),
],
),
),
),
// Final ExpansionTile if required
Visibility(
visible: !isPrivateMeeting,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 10.0),
child: ExpansionTileCustom(
isForm: true,
subTitile:
AppLocalizations.of(context)!.meetingmanager,
title: meetingAddState.selectedManager.text ??
AppLocalizations.of(context)!
.selectmeetingmanager,
widgets: <Widget>[
Column(
children: globalState.meetingsManagerModel!
.map((manager) {
bool isSelected =
meetingAddState.selectedManager.id ==
manager.id;
return CheckBoxInTile(
backColor: isSelected
? Color(0xff06CF64)
: Colors.white,
textColor: isSelected
? Colors.white
: Colors.black,
text: manager.name ?? '',
hasIcon: false,
onTap: () {
setState(() {
meetingAddState.selectedManager =
ItemSelected(
id: manager.id,
text: manager
.name); // Update selected manager
});
},
);
}).toList(),
),
],
),
),
),

// Submit Button

SizedBox(
height: 60,
),
Consumer<MeetinAddState>(
builder: (context, value, child) {
return submit(context, value);
},
),
],
),
),
),
);
case Status.loading:
return const LoadingWidget();
default:
return Container();
}
},
);
}

CustomButton submit(BuildContext context, MeetinAddState state) {
switch (state.statusAddMeeting) {
case Status.loading:
return CustomButton(
width: double.infinity,
hieght: 50,
fontSize: 16,
onPressed: null,
text: AppLocalizations.of(context)!.loading);

default:
return CustomButton(
width: double.infinity,
hieght: 50,
fontSize: 16,
onPressed: () async {
final status = await state.addMeeting(
locationId: state.selectedLocation.id,
subjectId: state.selectedSubject.id,
managerId:
!isPrivateMeeting ? state.selectedManager.id : null,
fromHour: Tools.formatTime(state.selectedStartTime.hour,
state.selectedStartTime.minute),
toHour: Tools.formatTime(
state.selectedEndTime.hour, state.selectedEndTime.minute),
dateMeeting: state.fromDate ?? '',
members: state.selectedUsersItems);
if (status == Status.ready) {
context.pushNamed('navigate', pathParameters: {'tab': '1'});
Tools.showCustomSnackBar(
text: AppLocalizations.of(context)!.addmeetingdone,
isError: false,
context,
);
} else {
Tools.showCustomSnackBar(
text: state.errorsAddMeeting == null
? state.messageAddMeeting ??
AppLocalizations.of(context)!.haserror
: Tools.combineErrorMessages(
state.errorsAddMeeting ?? {}),
isError: true,
context,
);
}
},
text: AppLocalizations.of(context)!.submit);
}
}
}

+ 74
- 0
lib/screens/meeting_add/state.dart Просмотреть файл

@@ -0,0 +1,74 @@
import 'package:flutter/material.dart';
import 'package:qadirneyriz/global/global_class/selected_item.dart';
import 'package:qadirneyriz/services/meetings/meetings.dart';
import 'package:qadirneyriz/utils/enums/status.dart';

class MeetinAddState extends ChangeNotifier {
MeetingsApi meetingApi = MeetingsApi();
// date
String? fromDate;
void setFromDate(String date) {
fromDate = date;
notifyListeners();
}

// subject
ItemSelected selectedSubject = ItemSelected();
// location
ItemSelected selectedLocation = ItemSelected();
// manager
ItemSelected selectedManager = ItemSelected();
//users
List<int> selectedUsersItems = [];
// time
TimeOfDay selectedStartTime =
TimeOfDay(hour: TimeOfDay.now().hour, minute: TimeOfDay.now().minute);
TimeOfDay selectedEndTime =
TimeOfDay(hour: TimeOfDay.now().hour, minute: TimeOfDay.now().minute);

// add meeting

Status statusAddMeeting = Status.empty;
String? messageAddMeeting;
Map? errorsAddMeeting;

Future<Status> addMeeting(
{int? locationId,
int? subjectId,
int? managerId,
required String fromHour,
required String toHour,
required String dateMeeting,
required List<int> members}) async {
statusAddMeeting = Status.loading;
notifyListeners();
try {
final result = await meetingApi.addMeetingApi(
locationId: locationId,
subjectId: subjectId,
managerId: managerId,
fromHour: fromHour,
toHour: toHour,
dateMeeting: dateMeeting,
members: members);
if (result.isOk) {
statusAddMeeting = Status.ready;
messageAddMeeting = result.message;
} else if (result.isOk == false) {
// print(result.isOk);
errorsAddMeeting = result.errors;
messageAddMeeting = result.message;
statusAddMeeting = Status.error;
} else {
statusAddMeeting = Status.error;
}
notifyListeners();
} catch (e) {
statusAddMeeting = Status.error;
// print(e);
}
notifyListeners();
// print(statusAddMeeting);
return statusAddMeeting;
}
}

+ 33
- 83
lib/screens/meeting_edit/screen.dart Просмотреть файл

@@ -3,14 +3,16 @@ import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
import 'package:qadirneyriz/global_state/global_state.dart';
import 'package:qadirneyriz/screens/meeting_edit/diolog_add_location.dart';
import 'package:qadirneyriz/screens/meeting_edit/diolog_add_subject.dart';
import 'package:qadirneyriz/screens/meeting_edit/diolog_add_user.dart';
import 'package:qadirneyriz/global/global_class/selected_item.dart';
import 'package:qadirneyriz/global/global_state/global_state.dart';
import 'package:qadirneyriz/diologs/diolog_add_location.dart';
import 'package:qadirneyriz/diologs/diolog_add_subject.dart';
import 'package:qadirneyriz/diologs/diolog_add_user.dart';
import 'package:qadirneyriz/screens/meeting_edit/state.dart';
import 'package:qadirneyriz/utils/enums/status.dart';
import 'package:qadirneyriz/utils/tools/tools.dart';
import 'package:qadirneyriz/widgets/ExpansionTileCustom.dart';
import 'package:qadirneyriz/widgets/checkBox_inTile.dart';
import 'package:qadirneyriz/widgets/custom_appbar.dart';
import 'package:qadirneyriz/widgets/custom_button.dart';
import 'package:qadirneyriz/widgets/ink_warpper.dart';
@@ -90,12 +92,16 @@ class _MeetingEditScreenState extends State<MeetingEditScreen> {
subTitile: AppLocalizations.of(context)!.meetingsubject,
title: meetingEditState.selectedSubject.id != null
? meetingEditState.selectedSubject.text ?? ''
: meetingEditState.oneMeetingModel![widget.id]!.subject!
.subject ??
'',
: meetingEditState
.oneMeetingModel![widget.id]!.subject !=
null
? meetingEditState.oneMeetingModel![widget.id]!
.subject!.subject ??
''
: '',
widgets: <Widget>[
ItemInTile(
text: 'عضو جدید',
CheckBoxInTile(
text: AppLocalizations.of(context)!.newsubject,
onTap: () async {
await showDialog(
context: context, // این باید کانتکست فعلی باشد
@@ -112,7 +118,7 @@ class _MeetingEditScreenState extends State<MeetingEditScreen> {
children: globalState.subjectsModel!.map((subject) {
bool isSelected =
meetingEditState.selectedSubject.id == subject.id;
return ItemInTile(
return CheckBoxInTile(
backColor:
isSelected ? Color(0xff06CF64) : Colors.white,
textColor: isSelected ? Colors.white : Colors.black,
@@ -224,10 +230,12 @@ class _MeetingEditScreenState extends State<MeetingEditScreen> {
subTitile: AppLocalizations.of(context)!.location,
title: meetingEditState.selectedLocation.id != null
? meetingEditState.selectedLocation.text ?? ''
: itemInOneMeeting.location!.address ?? '',
: itemInOneMeeting.location != null
? itemInOneMeeting.location!.address ?? ''
: '',
widgets: <Widget>[
ItemInTile(
text: 'مکان جدید',
CheckBoxInTile(
text: AppLocalizations.of(context)!.newlocation,
onTap: () async {
await showDialog(
context: context, // این باید کانتکست فعلی باشد
@@ -245,7 +253,7 @@ class _MeetingEditScreenState extends State<MeetingEditScreen> {
bool isSelected =
meetingEditState.selectedLocation.id ==
location.id;
return ItemInTile(
return CheckBoxInTile(
backColor:
isSelected ? Color(0xff06CF64) : Colors.white,
textColor: isSelected ? Colors.white : Colors.black,
@@ -275,8 +283,8 @@ class _MeetingEditScreenState extends State<MeetingEditScreen> {
subTitile: AppLocalizations.of(context)!.users,
title: AppLocalizations.of(context)!.selectusers,
widgets: <Widget>[
ItemInTile(
text: 'کاربر جدید',
CheckBoxInTile(
text: AppLocalizations.of(context)!.newmember,
onTap: () async {
await showDialog(
context: context, // این باید کانتکست فعلی باشد
@@ -358,14 +366,16 @@ class _MeetingEditScreenState extends State<MeetingEditScreen> {
subTitile: AppLocalizations.of(context)!.meetingmanager,
title: meetingEditState.selectedManager.id != null
? meetingEditState.selectedManager.text ?? ''
: itemInOneMeeting.manager!.name ?? '',
: itemInOneMeeting.manager != null
? itemInOneMeeting.manager!.name ?? ''
: '',
widgets: <Widget>[
Column(
children:
globalState.meetingsManagerModel!.map((manager) {
bool isSelected =
meetingEditState.selectedManager.id == manager.id;
return ItemInTile(
return CheckBoxInTile(
backColor:
isSelected ? Color(0xff06CF64) : Colors.white,
textColor: isSelected ? Colors.white : Colors.black,
@@ -435,6 +445,11 @@ class _MeetingEditScreenState extends State<MeetingEditScreen> {
members: meetingEditState.selectedUsersItems);
if (status == Status.ready) {
context.pop();
Tools.showCustomSnackBar(
text: AppLocalizations.of(context)!.editdone,
isError: false,
context,
);
} else {
Tools.showCustomSnackBar(
text: meetingEditState.errorsEditMeeting == null
@@ -451,68 +466,3 @@ class _MeetingEditScreenState extends State<MeetingEditScreen> {
}
}
}

class ItemInTile extends StatelessWidget {
final void Function()? onTap;
final String text;
final bool hasIcon;
final Color backColor;
final Color textColor;

const ItemInTile({
Key? key,
this.onTap,
required this.text,
required this.hasIcon,
required this.backColor,
required this.textColor,
}) : super(key: key);

@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
child: InkWrapper(
borderRadius: 10,
onTap: onTap,
child: Container(
decoration: BoxDecoration(boxShadow: [
BoxShadow(
color: Colors.black12,
blurRadius: 8,
offset: Offset(0, 4),
),
], color: backColor, borderRadius: BorderRadius.circular(10)),
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
maxLines: 1,
overflow: TextOverflow.ellipsis,
text,
style: TextStyle(color: textColor, fontSize: 12),
),
),
if (hasIcon)
Icon(Icons.add_circle_outline,
color: Colors.black.withOpacity(.3))
],
),
),
),
),
);
}
}

class ItemSelected {
final String? text;
final int? id;
ItemSelected({
this.text,
this.id,
});
}

+ 10
- 9
lib/screens/meeting_edit/state.dart Просмотреть файл

@@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:qadirneyriz/global/global_class/selected_item.dart';
import 'package:qadirneyriz/models/meetings/one_meeting_model.dart';
import 'package:qadirneyriz/screens/meeting_edit/screen.dart';
import 'package:qadirneyriz/services/meetings/meetings.dart';
import 'package:qadirneyriz/utils/enums/status.dart';

@@ -39,7 +39,7 @@ class MeetingEditState extends ChangeNotifier {
}
} catch (e) {
oneMeetingStatus[id] = Status.error;
print(e);
// print(e);
}

notifyListeners();
@@ -70,13 +70,16 @@ class MeetingEditState extends ChangeNotifier {
final item = oneMeetingModel![id]!;

selectedLocation = ItemSelected(
id: item.locationsId ?? -1, text: item.location!.address ?? '');
id: item.locationsId ?? -1,
text: item.location != null ? item.location!.address ?? '' : '');

selectedSubject = ItemSelected(
text: item.subject!.subject ?? '', id: item.subject!.id ?? -1);
text: item.subject != null ? item.subject!.subject ?? '' : '',
id: item.subject != null ? item.subject!.id ?? -1 : -1);

selectedManager = ItemSelected(
id: item.managerId ?? -1, text: item.manager!.name ?? '');
id: item.managerId ?? -1,
text: item.manager != null ? item.manager!.name ?? '' : '');

fromDate = item.dateJalali;

@@ -128,7 +131,6 @@ class MeetingEditState extends ChangeNotifier {
statusEitMeeting = Status.ready;
messageEditMeeting = result.message;
} else if (result.isOk == false) {
print(result.isOk);
errorsEditMeeting = result.errors;
messageEditMeeting = result.message;
statusEitMeeting = Status.error;
@@ -138,11 +140,10 @@ class MeetingEditState extends ChangeNotifier {
notifyListeners();
} catch (e) {
statusEitMeeting = Status.error;
print(e);
// print(e);
}
notifyListeners();
print(statusEitMeeting);
// print(statusEitMeeting);
return statusEitMeeting;
}

}

+ 359
- 111
lib/screens/meeting_summary/screen.dart Просмотреть файл

@@ -1,16 +1,26 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
import 'dart:io';

import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:open_file/open_file.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:provider/provider.dart';

import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:qadirneyriz/config/config.dart';
import 'package:qadirneyriz/diologs/diolog_add_location.dart';
import 'package:qadirneyriz/models/meetings/meetings_model.dart';
import 'package:qadirneyriz/screens/meeting_summary/state.dart';
import 'package:qadirneyriz/screens/private_meeting_summary/state.dart';
import 'package:qadirneyriz/setting/setting.dart';
import 'package:qadirneyriz/utils/enums/status.dart';
import 'package:qadirneyriz/utils/tools/tools.dart';
import 'package:qadirneyriz/widgets/card_meeting.dart';
import 'package:qadirneyriz/widgets/custom_appbar.dart';
import 'package:qadirneyriz/widgets/custom_button.dart';
import 'package:qadirneyriz/widgets/error_widget.dart';
import 'package:qadirneyriz/widgets/loading_widget.dart';

class MeetingSummaryScreen extends StatefulWidget {
final Datum meetingItem;
@@ -25,11 +35,18 @@ class MeetingSummaryScreen extends StatefulWidget {

class _MeetingSummaryScreenState extends State<MeetingSummaryScreen> {
late TextEditingController _textControllerDescription;
late MeetingSummaryState state;
@override
void initState() {
super.initState();
_textControllerDescription = TextEditingController();
if (widget.meetingItem.description != null) {
_textControllerDescription.text = widget.meetingItem.description ?? '';
}
Future.delayed(Duration.zero, () async {
state = Provider.of<MeetingSummaryState>(context, listen: false);
await state.getStringFiles(id: widget.meetingItem.id ?? 0);
});
}

@override
@@ -40,90 +57,251 @@ class _MeetingSummaryScreenState extends State<MeetingSummaryScreen> {

@override
Widget build(BuildContext context) {
final int id = widget.meetingItem.id ?? 0;
return Scaffold(
body: Consumer<MeetingSummaryState>(
builder: (context, value, child) {
return CustomScrollView(
slivers: <Widget>[
CustomAppbar(
title: 'صورت جلسه',
),
SliverPadding(
padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 2),
sliver: SliverToBoxAdapter(
child: CustomCardMeeting(
titel: widget.meetingItem.subject!.subject ?? '',
date: widget.meetingItem.dateJalali ?? '',
location: widget.meetingItem.location!.address ?? '',
fromTime: widget.meetingItem.azHour ?? '',
toTime: widget.meetingItem.taHour ?? '',
cardId: widget.meetingItem.id ?? -1,
switch (value.stringsFilsStatus[id]) {
case Status.ready:
return CustomScrollView(
slivers: <Widget>[
CustomAppbar(
title: AppLocalizations.of(context)!.meetingsummary,
),
),
),
SliverPadding(
padding:
const EdgeInsets.symmetric(vertical: 20, horizontal: 10),
sliver: SliverToBoxAdapter(
child: Container(
decoration: BoxDecoration(
color: const Color(0xffF4F9F6),
boxShadow: [
BoxShadow(
color: config.ui.mainGray.withOpacity(.1),
spreadRadius: .1,
offset: const Offset(0, 2),
blurRadius: 6,
SliverPadding(
padding:
const EdgeInsets.symmetric(vertical: 5, horizontal: 2),
sliver: SliverToBoxAdapter(
child: CustomCardMeeting(
status: widget.meetingItem.accepted ?? 0,
titel: widget.meetingItem.subject != null
? widget.meetingItem.subject!.subject ?? ''
: '',
date: widget.meetingItem.dateJalali ?? '',
location: widget.meetingItem.location != null
? widget.meetingItem.location!.address ?? ''
: '',
fromTime: widget.meetingItem.azHour ?? '',
toTime: widget.meetingItem.taHour ?? '',
cardId: widget.meetingItem.id ?? -1,
),
),
),
// if (widget.meetingItem.description == null)
SliverToBoxAdapter(
child: Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(
vertical: 20, horizontal: 8),
child: Container(
decoration: BoxDecoration(
color: const Color(0xffF4F9F6),
boxShadow: [
BoxShadow(
color: config.ui.mainGray.withOpacity(.1),
spreadRadius: .1,
offset: const Offset(0, 2),
blurRadius: 6,
),
],
borderRadius:
const BorderRadius.all(Radius.circular(12)),
),
child: CustomTextArea(
hintText: AppLocalizations.of(context)!
.descriptionofthemeeting,
controller: _textControllerDescription,
),
),
),
if (state.filesStringModel[id] != null &&
state.filesStringModel[id]!.isNotEmpty)
Padding(
padding: EdgeInsets.all(10),
child: Container(
decoration: BoxDecoration(
color: const Color(0xffF4F9F6),
boxShadow: [
BoxShadow(
color: config.ui.mainGray.withOpacity(.1),
spreadRadius: .1,
offset: const Offset(0, 2),
blurRadius: 6,
),
],
borderRadius:
const BorderRadius.all(Radius.circular(12)),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 10, vertical: 20),
child: Text(
AppLocalizations.of(context)!.files,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: config.ui.mainGreen,
),
),
),
ListView.builder(
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
padding: EdgeInsets.all(0),
itemCount:
state.filesStringModel[id]!.length,
itemBuilder:
(BuildContext context, int index) {
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: 20, vertical: 10),
child: deleteFilesButton(state, id,
state.filesStringModel[id]![index]),
);
},
),
],
),
),
),
Padding(
padding: const EdgeInsets.symmetric(
vertical: 15, horizontal: 8),
child: ReceiptUploadDialog(
state: value,
),
),
submitSummaryButton(context, state),
],
borderRadius: const BorderRadius.all(Radius.circular(12)),
),
child: CustomTextArea(
hintText: 'شرح جلسه',
controller: _textControllerDescription,
),
),
),
),
SliverToBoxAdapter(
child: ReceiptUploadDialog(
state: value,
),
),
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.all(30.0),
child: submitSammaryButton(context, value),
),
)
],
);
if (widget.meetingItem.description != null &&
state.filesStringModel[id] != null &&
state.filesStringModel[id]!.isNotEmpty)
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.only(
top: 5, bottom: 40, left: 10, right: 10),
child: downloadButton(state, id),
),
)
],
);
case Status.loading:
return const LoadingWidget();
case Status.error:
return CustomErrorWidget(
onPressed: () async {
await state.getStringFiles(id: id);
},
);

default:
return Container();
}
},
),
);
}

Widget submitSammaryButton(BuildContext context, MeetingSummaryState state) {
Widget deleteFilesButton(MeetingSummaryState state, int id, String text) {
switch (state.statusDeleteFile) {
case Status.loading:
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Icon(Icons.cancel_outlined),
Text(text),
],
);

default:
return InkWell(
onTap: () async {
final shouldProceed = await showDialog<bool>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text(
AppLocalizations.of(context)!.acceptoperetion,
),
content: Text(
AppLocalizations.of(context)!.areusuretodeletfile,
),
actions: [
TextButton(
onPressed: () {
// لغو عملیات
Navigator.of(context).pop(false);
},
child: Text(
AppLocalizations.of(context)!.cancel,
),
),
TextButton(
onPressed: () {
// تأیید عملیات
Navigator.of(context).pop(true);
},
child: Text(
AppLocalizations.of(context)!.accept,
),
),
],
);
},
);

// اگر کاربر تأیید کرد، عملیات انجام شود
if (shouldProceed == true) {
final status = await state.deleteFileSummary(id: id, text: text);
if (status == Status.ready) {
await state.getStringFiles(id: id);
// context.pop();
}
}
},
child: state.filesStringModel[id] != null
? Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Icon(Icons.cancel_outlined),
Text(text),
],
)
: Container(),
);
}
}

CustomButton submitSummaryButton(
BuildContext context, MeetingSummaryState state) {
switch (state.statusMinuteMeeting) {
case Status.loading:
return CustomButton(hieght: 50, text: 'صبر کنید!');
return CustomButton(
hieght: 50, text: AppLocalizations.of(context)!.loading);

default:
return CustomButton(
hieght: 50,
text: 'ثبت صورت جلسه',
text: AppLocalizations.of(context)!.submitsummarymeeting,
onPressed: () async {
if (_textControllerDescription.text == '') {
// call add new subject
Tools.showCustomSnackBar(
text: 'موضوع وارد کنید!',
text: AppLocalizations.of(context)!.enterdescription,
isError: true,
context,
);
} else if (state.selectedFiles == null) {
// call add new subject
Tools.showCustomSnackBar(
text: 'فایل وارد کنید!',
text: AppLocalizations.of(context)!.enterfile,
isError: true,
context,
);
@@ -134,12 +312,18 @@ class _MeetingSummaryScreenState extends State<MeetingSummaryScreen> {
meetingFiles: state.selectedFiles ?? []);

if (status == Status.ready) {
// call refrresh subjects
await state.getStringFiles(id: widget.meetingItem.id ?? -1);
context.pop();
Tools.showCustomSnackBar(
text: AppLocalizations.of(context)!.donesummary,
isError: false,
context,
);
} else {
Tools.showCustomSnackBar(
text: state.errorsMinuteMeeting == null
? state.messageMinuteMeeting ??
' AppLocalizations.of(context)!.haserror'
AppLocalizations.of(context)!.haserror
: Tools.combineErrorMessages(
state.errorsMinuteMeeting ?? {}),
isError: true,
@@ -151,6 +335,73 @@ class _MeetingSummaryScreenState extends State<MeetingSummaryScreen> {
);
}
}

Future<bool> hasStoragePermission() async {
if (Platform.isAndroid) {
final status = await Permission.storage.status;
if (status != PermissionStatus.granted) {
final result = await Permission.manageExternalStorage.request();
if (result == PermissionStatus.granted) {
return true;
}
} else {
return true;
}
} else {
return true;
}
return false;
}

CustomButton downloadButton(MeetingSummaryState state, int id) {
switch (state.statusDownload) {
case Status.loading:
return CustomButton(
borderRadius: 15,
hieght: 50,
text: AppLocalizations.of(context)!.loading,
width: double.infinity,
);

default:
return CustomButton(
borderRadius: 15,
hieght: 50,
text: AppLocalizations.of(context)!.downloadreport,
width: double.infinity,
onPressed: () async {
bool hasPermission = await hasStoragePermission();
if (!hasPermission) {
Tools.showCustomSnackBar(context,
text: AppLocalizations.of(context)!.needpermission,
isError: true);
return;
}

// Download the file
await state.downloadSummary(id: id);

if (state.statusDownload == Status.ready) {
try {
await OpenFile.open(state.messageDownload);
} catch (e) {
Tools.showCustomSnackBar(
context,
text: AppLocalizations.of(context)!.needzipapp,
isError: true,
);
}
} else {
Tools.showCustomSnackBar(
context,
text: AppLocalizations.of(context)!.error,
isError: true,
);
}
},
);
}
}
}

class CustomTextArea extends StatelessWidget {
@@ -197,63 +448,60 @@ class ReceiptUploadDialog extends StatefulWidget {
class _ReceiptUploadDialogState extends State<ReceiptUploadDialog> {
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: Container(
decoration: BoxDecoration(
color: const Color(0xffF4F9F6),
boxShadow: [
BoxShadow(
color: config.ui.mainGray.withOpacity(.1),
spreadRadius: .1,
offset: const Offset(0, 2),
blurRadius: 6,
),
],
borderRadius: const BorderRadius.all(Radius.circular(12)),
),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 25, horizontal: 10),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'آپلود فایل',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: config.ui.mainGreen,
),
return Container(
decoration: BoxDecoration(
color: const Color(0xffF4F9F6),
boxShadow: [
BoxShadow(
color: config.ui.mainGray.withOpacity(.1),
spreadRadius: .1,
offset: const Offset(0, 2),
blurRadius: 6,
),
],
borderRadius: const BorderRadius.all(Radius.circular(12)),
),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 25, horizontal: 10),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
AppLocalizations.of(context)!.fileupload,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: config.ui.mainGreen,
),
const SizedBox(height: 20),
),
const SizedBox(height: 20),

// Upload box
FileBorderBox(
child: GestureDetector(
onTap: widget.state.pickFiles,
child: Column(
children: [
Icon(Icons.cloud_upload_outlined,
size: 30, color: config.ui.mainGreen),
Text(
'انتخاب فایل',
style:
TextStyle(fontSize: 12, color: config.ui.mainGreen),
),
],
),
// Upload box
FileBorderBox(
child: GestureDetector(
onTap: widget.state.pickFiles,
child: Column(
children: [
Icon(Icons.cloud_upload_outlined,
size: 30, color: config.ui.mainGreen),
Text(
AppLocalizations.of(context)!.selectfile,
style:
TextStyle(fontSize: 12, color: config.ui.mainGreen),
),
],
),
),
),

// File preview
if (widget.state.selectedFiles != null)
FilePreview(
files: widget.state.selectedFiles!,
onDelete: widget.state.removeFile,
),
],
),
// File preview
if (widget.state.selectedFiles != null)
FilePreview(
files: widget.state.selectedFiles!,
onDelete: widget.state.removeFile,
),
],
),
),
);


+ 101
- 3
lib/screens/meeting_summary/state.dart Просмотреть файл

@@ -23,7 +23,7 @@ class MeetingSummaryState extends ChangeNotifier {
statusMinuteMeeting = Status.ready;
messageMinuteMeeting = result.message;
} else if (result.isOk == false) {
print(result.isOk);
// print(result.isOk);
errorsMinuteMeeting = result.errors;
messageMinuteMeeting = result.message;
statusMinuteMeeting = Status.error;
@@ -33,10 +33,10 @@ class MeetingSummaryState extends ChangeNotifier {
notifyListeners();
} catch (e) {
statusMinuteMeeting = Status.error;
print(e);
// print(e);
}
notifyListeners();
print(statusMinuteMeeting);
// print(statusMinuteMeeting);
return statusMinuteMeeting;
}

@@ -58,4 +58,102 @@ class MeetingSummaryState extends ChangeNotifier {
selectedFiles!.removeAt(index);
notifyListeners();
}
// download summary

Status statusDownload = Status.empty;
String? messageDownload;

Future<Status> downloadSummary({required int id}) async {
statusDownload = Status.loading;
notifyListeners();
try {
final result = await meetingsApi.downloadSummary(id: id);

if (result == null) {
statusDownload = Status.error;
} else {
if (result.isOk) {
statusDownload = Status.ready;
messageDownload = result.message ?? '';
} else {
statusDownload = Status.error;
}
}
} catch (e) {
statusDownload = Status.error;
}

notifyListeners();
return statusDownload;
}

// get file string
Map<int, Status> stringsFilsStatus = {};
Map<int, List<String>> filesStringModel = {};
Map<int, List<String>?> messageStringFiles = {};
Map? errorsStringFiles;
Future<Map<int, Status>> getStringFiles({required int id}) async {
if (filesStringModel[id] != null && filesStringModel[id]!.isNotEmpty) {
try {
filesStringModel[id] = await meetingsApi.getListStringFils(id: id);
print('${filesStringModel[id]}');

stringsFilsStatus[id] = Status.ready;
print('${filesStringModel} filesStringModel[id]');
} catch (e) {
stringsFilsStatus[id] = Status.error;
print('$e');
}
} else {
stringsFilsStatus[id] = Status.ready;
notifyListeners();
try {
filesStringModel[id] = await meetingsApi.getListStringFils(id: id);
print('${filesStringModel[id]}');

stringsFilsStatus[id] = Status.ready;
print('${filesStringModel} filesStringModel[id]');
} catch (e) {
stringsFilsStatus[id] = Status.error;
print('$e');
}
}

notifyListeners();
print('${stringsFilsStatus} stringsFilsStatus');

return stringsFilsStatus;
}

// delete file of summary
Status statusDeleteFile = Status.empty;
String? messageDeleteFile;
Map? errorsDeleteFile;

Future<Status> deleteFileSummary({
required int id,
required String text,
}) async {
statusDeleteFile = Status.loading;
notifyListeners();
try {
final result = await meetingsApi.deleteFileSummary(id: id, text: text);
if (result.isOk) {
statusDeleteFile = Status.ready;
messageDeleteFile = result.message;
} else if (result.isOk == false) {
print(result.isOk);
errorsDeleteFile = result.errors;
messageDeleteFile = result.message;
statusDeleteFile = Status.error;
}
notifyListeners();
} catch (e) {
statusDeleteFile = Status.error;
print(e);
}
notifyListeners();
print(statusDeleteFile);
return statusDeleteFile;
}
}

+ 417
- 0
lib/screens/private_meeting/dilog_privateMeetings_filters.dart Просмотреть файл

@@ -0,0 +1,417 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:qadirneyriz/global/global_state/global_state.dart';
import 'package:qadirneyriz/screens/private_meeting/state.dart';
import 'package:qadirneyriz/widgets/ExpansionTileCustom.dart';
import 'package:qadirneyriz/widgets/error_widget.dart';
import 'package:qadirneyriz/config/config.dart';
import 'package:qadirneyriz/utils/enums/status.dart';
import 'package:qadirneyriz/utils/tools/tools.dart';
import 'package:qadirneyriz/widgets/picker.dart';
import 'package:qadirneyriz/widgets/loading_widget.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';

class DiologPrivateMeetingsFilters extends StatefulWidget {
const DiologPrivateMeetingsFilters({
super.key,
});

@override
State<DiologPrivateMeetingsFilters> createState() =>
_DiologPrivateMeetingsFiltersState();
}

class _DiologPrivateMeetingsFiltersState
extends State<DiologPrivateMeetingsFilters> {
PrivateMeetingsState? privateMeetingsState;
GlobalState? globalState;
@override
void initState() {
privateMeetingsState =
Provider.of<PrivateMeetingsState>(context, listen: false);
globalState = Provider.of<GlobalState>(context, listen: false);
Future.delayed(Duration.zero, () async {
await globalState!.getAllFiltersItems();

// print(status);
});
privateMeetingsState!.setAllFiltersForThen();
super.initState();
}

@override
void dispose() {
// بررسی تغییرات فیلترها
if (privateMeetingsState!.isAnyChangesInFilters()) {
Future.microtask(() async {
await privateMeetingsState!.getPrivateMeetings(
refresh: true,
toDate: privateMeetingsState!.toDate,
fromDate: privateMeetingsState!.fromDate,
location: privateMeetingsState!.selectedLocationId,
subject: privateMeetingsState!.selectedSubjectId,
meetingManager: privateMeetingsState!.selectedManagersId,
meetingStatus: privateMeetingsState!.selectedStatusId,
);
});
}

super.dispose();
}

@override
Widget build(BuildContext context) {
return DraggableScrollableSheet(
initialChildSize: .7,
expand: false,
snap: false,
builder: (context, scrollController) {
// statuses meetings
List<MeetingsStatus> meetingStatuses = [
MeetingsStatus(
id: 1, title: AppLocalizations.of(context)!.donemeetings),
MeetingsStatus(
id: 2, title: AppLocalizations.of(context)!.adjournedmeetings),
MeetingsStatus(
id: 3, title: AppLocalizations.of(context)!.canceldmeetings),
MeetingsStatus(
id: 4,
title: AppLocalizations.of(context)!.meetingswaitingtobeheld),
];
return Consumer2<PrivateMeetingsState, GlobalState>(
builder: (context, privateMeetingsState, globalState, child) {
switch (globalState.allFiltersStatus) {
case Status.ready:
return Column(
children: [
LineButtomSheet(),
Expanded(
child: SingleChildScrollView(
controller: scrollController,
child: Column(
children: [
Column(
mainAxisSize: MainAxisSize.max,
children: [
Padding(
padding: const EdgeInsets.symmetric(
vertical: 25, horizontal: 15),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
AppLocalizations.of(context)!
.searchFor,
),
GestureDetector(
onTap: () {
privateMeetingsState
.clearFilters();
},
child: privateMeetingsState
.hasActiveFilters()
? Icon(
Icons.delete_outline,
color: Colors.red,
)
: Icon(
Icons.delete_outline,
color: Colors.black
.withOpacity(.3),
))
],
),
),
Divider(),
SizedBox(
height: 10,
),
ExpansionTileCustom(
title: AppLocalizations.of(context)!.date,
widgets: <Widget>[
Padding(
padding: const EdgeInsets.all(20.0),
child: Row(
crossAxisAlignment:
CrossAxisAlignment.end,
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
PickerCustom(
showDate: privateMeetingsState
.fromDate.isNotEmpty
? privateMeetingsState
.fromDate
: AppLocalizations.of(
context)!
.selectdate, // Show selected date or prompt
onTap: () {
showDialog(
context: context,
builder: (context) {
return Dialog(
child: Tools
.shamsiDateCalendarWidget(
context,
(newDate) {
String
fromDateString =
'${newDate.year}/${newDate.month}/${newDate.day}';
privateMeetingsState
.setFromDates(
fromDateString); // Update the selected date
},
),
);
},
);
},
),
Text(
AppLocalizations.of(context)!.to,
),
PickerCustom(
showDate: privateMeetingsState
.toDate.isNotEmpty
? privateMeetingsState.toDate
: AppLocalizations.of(
context)!
.selectdate, // Show selected date or prompt
onTap: () {
showDialog(
context: context,
builder: (context) {
return Dialog(
child: Tools
.shamsiDateCalendarWidget(
context,
(newDate) {
String toDateString =
'${newDate.year}/${newDate.month}/${newDate.day}';
privateMeetingsState
.setToDates(
toDateString); // Update the selected date
},
),
);
},
);
},
),
],
),
)
],
),
ExpansionTileCustom(
title:
AppLocalizations.of(context)!.location,
widgets: <Widget>[
ListView.builder(
primary: false,
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount:
globalState.locationsModel!.length,
itemBuilder:
(BuildContext context, int index) {
final items = globalState
.locationsModel![index];
return RadioListTile<int>(
toggleable: true,
groupValue: privateMeetingsState
.selectedLocationId,
value: items.id ?? -1,
title: Text(
items.address ?? '',
maxLines: 1,
style: TextStyle(
fontWeight: FontWeight.w100,
fontSize: 14),
overflow: TextOverflow.ellipsis,
),
activeColor: config.ui.secendGreen,
onChanged: (int? newValue) {
privateMeetingsState
.selectLocation(
newValue ?? null);
},
);
},
),
],
),
ExpansionTileCustom(
title: AppLocalizations.of(context)!
.meetingmanager,
widgets: <Widget>[
ListView.builder(
primary: false,
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: globalState
.meetingsManagerModel!.length,
itemBuilder:
(BuildContext context, int index) {
final items = globalState
.meetingsManagerModel![index];
return RadioListTile<int>(
toggleable: true,
groupValue: privateMeetingsState
.selectedManagersId,
value: items.id ?? -1,
title: Text(
items.name ?? '',
style: TextStyle(
fontWeight: FontWeight.w100,
fontSize: 14),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
activeColor: config.ui.secendGreen,
onChanged: (int? newValue) {
privateMeetingsState
.selectManager(
newValue ?? null);
},
);
},
),
],
),
ExpansionTileCustom(
title:
AppLocalizations.of(context)!.subject,
widgets: <Widget>[
ListView.builder(
primary: false,
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount:
globalState.subjectsModel!.length,
itemBuilder:
(BuildContext context, int index) {
final items =
globalState.subjectsModel![index];
return RadioListTile<int>(
toggleable: true,
groupValue: privateMeetingsState
.selectedSubjectId,
value: items.id ?? -1,
title: Text(
items.subject ?? '',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontWeight: FontWeight.w100,
fontSize: 14),
),
activeColor: config.ui.secendGreen,
onChanged: (int? newValue) {
privateMeetingsState
.selectSubject(
newValue ?? null);
},
);
},
),
],
),
SizedBox(
height: 10,
),
Divider(),
Column(
children: [
SizedBox(
height: 250,
child: ListView.builder(
physics:
NeverScrollableScrollPhysics(),
shrinkWrap: true,
primary: false,
itemCount: meetingStatuses.length,
itemBuilder: (BuildContext context,
int index) {
final items =
meetingStatuses[index];
return RadioListTile<int>(
toggleable: true,
groupValue: privateMeetingsState
.selectedStatusId,
value: items.id,
title: Text(
items.title,
maxLines: 1,
style: TextStyle(
fontWeight: FontWeight.w100,
fontSize: 14),
overflow: TextOverflow.ellipsis,
),
activeColor:
config.ui.secendGreen,
onChanged: (int? newValue) {
privateMeetingsState
.selectStatusMeeting(
newValue ?? null);
},
);
},
),
),
],
)
],
),
],
),
),
),
],
);
case Status.loading:
return const LoadingWidget();
case Status.error:
return CustomErrorWidget(
onPressed: () async {
await globalState.getAllFiltersItems(refresh: true);
},
);
default:
return Container();
}
},
);
});
}
}

class LineButtomSheet extends StatelessWidget {
const LineButtomSheet({
super.key,
});

@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.only(top: 8.0),
width: 30.0,
height: 3.0,
decoration: BoxDecoration(
color: Colors.grey.shade400,
borderRadius: BorderRadius.circular(24.0),
),
);
}
}

class MeetingsStatus {
int id;
String title;
MeetingsStatus({
required this.id,
required this.title,
});
}

+ 476
- 0
lib/screens/private_meeting/screen.dart Просмотреть файл

@@ -0,0 +1,476 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:go_router/go_router.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';

import 'package:qadirneyriz/config/config.dart';
import 'package:qadirneyriz/screens/private_meeting/dilog_privateMeetings_filters.dart';
import 'package:qadirneyriz/screens/private_meeting/state.dart';
import 'package:qadirneyriz/setting/setting.dart';
import 'package:qadirneyriz/utils/enums/status.dart';
import 'package:qadirneyriz/utils/tools/tools.dart';
import 'package:qadirneyriz/widgets/custom_appbar.dart';
import 'package:qadirneyriz/widgets/custom_button.dart';
import 'package:qadirneyriz/widgets/empty_widget.dart';
import 'package:qadirneyriz/widgets/error_widget.dart';
import 'package:qadirneyriz/widgets/icon_button.dart';
import 'package:qadirneyriz/widgets/loading_widget.dart';
import 'package:qadirneyriz/widgets/today_widget.dart';

class PrivateMeetingsScreen extends StatefulWidget {
const PrivateMeetingsScreen({super.key});

@override
State<PrivateMeetingsScreen> createState() => _PrivateMeetingsScreenState();
}

class _PrivateMeetingsScreenState extends State<PrivateMeetingsScreen> {
late ScrollController _scrollController;
late PrivateMeetingsState privateMeetingsState;
@override
void initState() {
super.initState();
_scrollController = ScrollController();
_scrollController.addListener(_scrollListener);
privateMeetingsState =
Provider.of<PrivateMeetingsState>(context, listen: false);
Future.delayed(Duration.zero, () async {
privateMeetingsState.clearFilters();
await privateMeetingsState.getPrivateMeetings();
});
privateMeetingsState.setAllFiltersForThen();
}
// ذخیره فیلترهای اولیه برای مقایسه در آیند

_scrollListener() async {
if (_scrollController.offset >=
_scrollController.position.maxScrollExtent &&
!_scrollController.position.outOfRange) {
if (!privateMeetingsState.pageEndedPrivateMeetings) {
await privateMeetingsState.nextPagePrivateMeetings(
toDate: privateMeetingsState.toDate,
fromDate: privateMeetingsState.fromDate,
location: privateMeetingsState.selectedLocationId,
subject: privateMeetingsState.selectedSubjectId,
meetingManager: privateMeetingsState.selectedManagersId,
meetingStatus: privateMeetingsState.selectedStatusId);
}
}
}

@override
Widget build(BuildContext context) {
DateTime now = DateTime.now();
String dateMiladi = DateFormat('yyyy-MM-dd').format(now);
String dateJalali =
'${setting.timeNow.day} ${Tools.getMonthName(setting.timeNow.month)} ${setting.timeNow.year}';
return Consumer<PrivateMeetingsState>(
builder: (context, value, child) {
return RefreshIndicator(
onRefresh: () async {
await privateMeetingsState.getPrivateMeetings();
},
child: CustomScrollView(
controller: _scrollController,
physics: AlwaysScrollableScrollPhysics(),
slivers: <Widget>[
const CustomAppbar(),
SliverToBoxAdapter(
child: TodayWidget(
formattedDate:
setting.userLocalDb.getUser().language == 'en'
? dateMiladi
: dateJalali),
),
SliverToBoxAdapter(
child: Padding(
padding:
const EdgeInsets.symmetric(vertical: 30, horizontal: 15),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
style: const TextStyle(fontSize: 14),
AppLocalizations.of(context)!.privatemeeting,
),
IconButtonCustom(
iconColor: value.hasActiveFilters()
? Colors.white
: config.ui.secendGreen,
backColor: value.hasActiveFilters()
? config.ui.secendGreen
: Colors.white,
icon: FontAwesomeIcons.sliders,
onTap: () {
showModalBottomSheet(
isScrollControlled: true,
useSafeArea: true,
context: context,
builder: (context) {
return DiologPrivateMeetingsFilters();
},
);
},
)
],
),
),
),
privateMeetingsList(value),
(value.privatePaginationMeetings == Status.ready ||
value.privatePaginationMeetings == Status.empty)
? const SliverToBoxAdapter()
: const SliverToBoxAdapter(
child: Center(
child: LoadingWidget(
size: 10,
),
),
)
],
),
);
},
);
}

Widget privateMeetingsList(PrivateMeetingsState state) {
switch (state.privateStatusMeetings) {
case Status.ready:
return SliverList.builder(
itemBuilder: (context, index) {
final userRole = setting.userLocalDb.getUser().role;
final items = state.privateMeetingsModel!.data![index];
return PrivateMeetingWidget(
status: items.accepted ?? 0,
date: items.dateJalali ?? '',
time: items.azHour ?? '',
subject:
items.subject != null ? items.subject!.subject ?? '' : '',
location:
items.location != null ? items.location!.address ?? '' : '',
onAcceptButton:
state.statusAcceptMeeting[items.id] != Status.loading
? () {
acceptPrivateMeeting(state, context, items.id ?? -1);
}
: null,
onCancelButton:
state.statusCancelMeeting[items.id] != Status.loading
? () {
cancelPrivateMeeting(state, context, items.id ?? -1);
}
: null,
onSelectedMoreButton: (value) async {
switch (value) {
case 'edit':
await context.pushNamed('privatemeetingedit',
pathParameters: {'id': items.id.toString()});

state.getPrivateMeetings();

case 'report':
await context.pushNamed(
'privatemeetinsammary',
extra: items, // `items` should be a Datum instance
);

default:
}
},
itemBuilderMoreButton: (context) => [
if (userRole == 0 || userRole == 2)
PopupMenuItem(
value: 'edit',
child: Row(
children: [
Icon(
Icons.edit,
color: Colors.green,
size: 17,
),
SizedBox(width: 8),
Text(
AppLocalizations.of(context)!.editprivatemeeting,
style: TextStyle(fontSize: 12),
),
],
),
),
PopupMenuItem(
value: 'report',
child: Row(
children: [
Icon(
Icons.receipt_long,
color: Colors.green,
size: 17,
),
SizedBox(width: 8),
Text(
AppLocalizations.of(context)!.meetingsummary,
style: TextStyle(fontSize: 12),
),
],
),
),
],
);
},
itemCount: state.privateMeetingsModel!.data!.length,
);
case Status.loading:
return SliverFillRemaining(child: const LoadingWidget());
case Status.error:
return SliverFillRemaining(
child: CustomErrorWidget(
onPressed: () async {
await state.getPrivateMeetings(refresh: true);
},
),
);
case Status.empty:
return SliverFillRemaining(child: EmptyStateWidget());
default:
return Container();
}
}

void cancelPrivateMeeting(
PrivateMeetingsState state, BuildContext context, int cardId) async {
final status = await state.cancelMeeting(id: cardId);
if (status == Status.ready) {
Tools.showCustomSnackBar(
text: AppLocalizations.of(context)!.privatemeetingcanceld,
isError: false,
context,
);
await privateMeetingsState.getPrivateMeetings();
} else {
Tools.showCustomSnackBar(
text: AppLocalizations.of(context)!.error,
isError: true,
context,
);
}
}

void acceptPrivateMeeting(
PrivateMeetingsState state, BuildContext context, int cardId) async {
final status = await state.acceptMeeting(id: cardId);
if (status == Status.ready) {
Tools.showCustomSnackBar(
text: AppLocalizations.of(context)!.privatemeetingaccept,
isError: false,
context,
);
await privateMeetingsState.getPrivateMeetings();
} else {
Tools.showCustomSnackBar(
text: AppLocalizations.of(context)!.error,
isError: true,
context,
);
}
}
}

class PrivateMeetingWidget extends StatelessWidget {
final String date;

final String time;
final String subject;
final String location;
final int status;

final void Function()? onAcceptButton;
final void Function()? onCancelButton;
final void Function(String)? onSelectedMoreButton;

final List<PopupMenuEntry<String>> Function(BuildContext)?
itemBuilderMoreButton;
const PrivateMeetingWidget({
Key? key,
required this.date,
required this.time,
required this.subject,
required this.location,
required this.status,
this.onAcceptButton,
this.onCancelButton,
this.onSelectedMoreButton,
this.itemBuilderMoreButton,
}) : super(key: key);

@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 15),
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: Text(
this.date,
style: TextStyle(fontSize: 12),
),
),
Divider(),
SizedBox(
height: 5,
),
Container(
decoration: BoxDecoration(
boxShadow: [
BoxShadow(
color: config.ui.mainGray.withOpacity(.1),
spreadRadius: .1,
offset: const Offset(0, 2),
blurRadius: 6)
],
color: const Color(0xffF4F9F6),
borderRadius: BorderRadius.circular(10)),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 10),
child: Column(
children: [
Row(
children: [
Text(this.time),
SizedBox(
width: 15,
),
Container(
width: 3,
height: 45,
decoration: BoxDecoration(
color: Colors.green,
),
),
SizedBox(
width: 5,
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
this.subject,
),
SizedBox(
height: 5,
),
Text(
this.location,
style: TextStyle(
fontSize: 12, color: Color(0xff9AA8C7)),
),
],
),
),
_moreButton(context)
],
),
if (this.status == 0)
Padding(
padding: const EdgeInsets.only(top: 15),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CustomButton(
hieght: 30,
text: AppLocalizations.of(context)!.accept,
borderRadius: 5,
color: Color(0xff00A848),
fontSize: 12,
onPressed: this.onAcceptButton,
),
SizedBox(
width: 7,
),
CustomButton(
hieght: 30,
text: AppLocalizations.of(context)!.cancel,
color: Colors.red,
textColor: Colors.white,
fontSize: 12,
borderRadius: 5,
onPressed: this.onCancelButton,
),
SizedBox(
width: 90,
),
],
),
),
if (this.status == 1 || this.status == 2)
Padding(
padding: const EdgeInsets.only(top: 10),
child: Row(
children: [
SizedBox(
width: 60,
),
PrivateMeetingLabel(
status: this.status,
),
],
),
)
],
),
),
)
],
),
);
}

Widget _moreButton(BuildContext context) {
return PopupMenuButton<String>(
color: Colors.white,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(10.0),
),
),
onSelected: onSelectedMoreButton,
itemBuilder: itemBuilderMoreButton!,
icon: const Icon(Icons.more_horiz),
);
}
}

class PrivateMeetingLabel extends StatelessWidget {
final int status;
const PrivateMeetingLabel({
Key? key,
required this.status,
}) : super(key: key);

@override
Widget build(BuildContext context) {
return Container(
width: 82,
child: Center(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 5),
child: Text(
this.status == 1
? AppLocalizations.of(context)!.accepted
: AppLocalizations.of(context)!.canceled,
style: TextStyle(fontSize: 12, color: Colors.white),
),
),
),
decoration: BoxDecoration(
color: this.status == 1 ? Colors.green : Colors.red,
borderRadius: BorderRadius.circular(4)),
);
}
}

+ 301
- 0
lib/screens/private_meeting/state.dart Просмотреть файл

@@ -0,0 +1,301 @@
import 'package:flutter/material.dart';
import 'package:qadirneyriz/models/private_meeting/private_meetings_model.dart';
import 'package:qadirneyriz/services/private_meetings/private_meetings.dart';
import 'package:qadirneyriz/utils/enums/status.dart';

class PrivateMeetingsState extends ChangeNotifier {
// ذخیره فیلترهای قبلی برای مقایسه
String? previousFromDate;
String? previousToDate;
int? previousLocationId;
int? previousSubjectId;
int? previousManagersId;
int? previousStatusId;
void setAllFiltersForThen() {
previousFromDate = fromDate;
previousToDate = toDate;
previousLocationId = selectedLocationId;
previousSubjectId = selectedSubjectId;
previousManagersId = selectedManagersId;
previousStatusId = selectedStatusId;
}

bool isAnyChangesInFilters() {
if (previousFromDate != fromDate ||
previousToDate != toDate ||
previousLocationId != selectedLocationId ||
previousSubjectId != selectedSubjectId ||
previousManagersId != selectedManagersId ||
previousStatusId != selectedStatusId) {
return true;
} else {
return false;
}
}

// api meetings
PrivateMeetingsApi privateMeetingsApi = PrivateMeetingsApi();

// meetings list
Status privateStatusMeetings = Status.loading;
Status privatePaginationMeetings = Status.ready;
PrivateMeetingsModel? privateMeetingsModel;
int pagePrivateMeetings = 1;
int limitPrivateMeetings = 10;
bool pageEndedPrivateMeetings = false;

Future<Status> getPrivateMeetings(
{bool refresh = false,
String? fromDate,
String? toDate,
int? location,
int? subject,
int? meetingManager,
int? meetingStatus}) async {
if (refresh) {
privateStatusMeetings = Status.loading;
notifyListeners();
}
if (privateMeetingsModel != null &&
privateMeetingsModel!.data!.isNotEmpty &&
!refresh) {
privateStatusMeetings = Status.ready;
notifyListeners();
pagePrivateMeetings = 1;
pageEndedPrivateMeetings = false;
privatePaginationMeetings = Status.ready;
privateMeetingsModel = await privateMeetingsApi.getPrivateMeetings(
page: pagePrivateMeetings,
count: limitPrivateMeetings,
fromDate: fromDate,
toDate: toDate,
location: location,
subject: subject,
meetingManager: meetingManager,
status: meetingStatus);
} else {
pagePrivateMeetings = 1;
pageEndedPrivateMeetings = false;
privatePaginationMeetings = Status.ready;

try {
privateStatusMeetings = Status.loading;
notifyListeners();
privateMeetingsModel = await privateMeetingsApi.getPrivateMeetings(
page: pagePrivateMeetings,
count: limitPrivateMeetings,
fromDate: fromDate,
toDate: toDate,
location: location,
subject: subject,
meetingManager: meetingManager,
status: meetingStatus);

if (privateMeetingsModel != null &&
privateMeetingsModel!.data!.isNotEmpty) {
if (privateMeetingsModel!.data!.isNotEmpty) {
privateStatusMeetings = Status.ready;
} else if (!privateMeetingsModel!.hasData() &&
privateMeetingsModel == null) {
privateStatusMeetings = Status.empty;
} else if (privateMeetingsModel!.data!.length <
limitPrivateMeetings) {
pageEndedPrivateMeetings = true;
privateStatusMeetings = Status.empty;
}
} else {
privateStatusMeetings = Status.empty;
}
} catch (e) {
privateStatusMeetings = Status.error;
print('$e');
}
notifyListeners();
}
notifyListeners();
print(privateStatusMeetings);
return privateStatusMeetings;
}

Future<void> nextPagePrivateMeetings(
{String? fromDate,
String? toDate,
int? location,
int? subject,
int? meetingManager,
int? meetingStatus}) async {
if (pageEndedPrivateMeetings == false &&
privatePaginationMeetings == Status.ready) {
privatePaginationMeetings = Status.loading;
notifyListeners();

int p = pagePrivateMeetings;
pagePrivateMeetings = p + 1;
try {
final adsPaginationModel = await privateMeetingsApi.getPrivateMeetings(
page: pagePrivateMeetings,
count: limitPrivateMeetings,
fromDate: fromDate,
toDate: toDate,
location: location,
subject: subject,
meetingManager: meetingManager,
status: meetingStatus);

if (adsPaginationModel.hasData()) {
privateMeetingsModel!.data!.addAll(adsPaginationModel.data!);

if (adsPaginationModel.data!.length < limitPrivateMeetings) {
pageEndedPrivateMeetings = true;
privatePaginationMeetings = Status.empty;
} else {
privatePaginationMeetings = Status.ready;
}
} else if (!adsPaginationModel.hasData()) {
pageEndedPrivateMeetings = true;
privatePaginationMeetings = Status.empty;
}
} catch (e) {
pageEndedPrivateMeetings = true;
privatePaginationMeetings = Status.empty;
}
notifyListeners();
}
}

// set date for filters
String fromDate = '';
String toDate = '';

void setFromDates(String? newFromDate) {
fromDate = newFromDate ?? '';
notifyListeners();
}

void setToDates(String? newToDate) {
toDate = newToDate ?? '';
notifyListeners();
}

// clear filters
void clearFilters() {
selectedLocationId = null;
selectedManagersId = null;
selectedStatusId = null;
selectedSubjectId = null;
fromDate = '';
toDate = '';
notifyListeners();
}

// is filter Not empty
bool hasActiveFilters() {
return selectedLocationId != null ||
selectedManagersId != null ||
selectedStatusId != null ||
selectedSubjectId != null ||
fromDate.isNotEmpty ||
toDate.isNotEmpty;
}

// get filters location meetings

int? selectedLocationId;
void selectLocation(int? locationId) {
selectedLocationId = locationId;
notifyListeners();
}

// get filters subjects meetings

int? selectedSubjectId;
void selectSubject(int? subjectId) {
selectedSubjectId = subjectId;
notifyListeners();
}
// get filters meeting managers

int? selectedManagersId;
void selectManager(int? managerId) {
selectedManagersId = managerId;
notifyListeners();
}

// all meeting status filters

int? selectedStatusId;
void selectStatusMeeting(int? statusId) {
selectedStatusId = statusId;
notifyListeners();
}

// cancel meeting
Map<int, Status> statusCancelMeeting = {};
String? messageCancelMeeting;
Map? errorsCancelMeeting;

Future<Status> cancelMeeting({
required int id,
}) async {
statusCancelMeeting[id] = Status.loading;
notifyListeners();
try {
final result = await privateMeetingsApi.cancelPrivateMeetingApi(
id: id,
);
if (result.isOk) {
statusCancelMeeting[id] = Status.ready;
messageCancelMeeting = result.message;
} else if (result.isOk == false) {
// print(result.isOk);
errorsCancelMeeting = result.errors;
messageCancelMeeting = result.message;
statusCancelMeeting[id] = Status.error;
} else {
statusCancelMeeting[id] = Status.error;
}
notifyListeners();
} catch (e) {
statusCancelMeeting[id] = Status.error;
// print(e);
}
notifyListeners();
// print(statusCancelMeeting);
return statusCancelMeeting[id]!;
}

// accept meeting
Map<int, Status> statusAcceptMeeting = {};
String? messageAcceptMeeting;
Map? errorsAcceptMeeting;

Future<Status> acceptMeeting({
required int id,
}) async {
statusAcceptMeeting[id] = Status.loading;
notifyListeners();
try {
final result = await privateMeetingsApi.acceptPrivateMeetingApi(
id: id,
);
if (result.isOk) {
statusAcceptMeeting[id] = Status.ready;
messageAcceptMeeting = result.message;
} else if (result.isOk == false) {
// print(result.isOk);
errorsAcceptMeeting = result.errors;
messageAcceptMeeting = result.message;
statusAcceptMeeting[id] = Status.error;
} else {
statusAcceptMeeting[id] = Status.error;
}
notifyListeners();
} catch (e) {
statusAcceptMeeting[id] = Status.error;
// print(e);
}
notifyListeners();
// print(statusAcceptMeeting);
return statusAcceptMeeting[id]!;
}
}

+ 550
- 0
lib/screens/private_meeting_add/screen.dart Просмотреть файл

@@ -0,0 +1,550 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
import 'package:qadirneyriz/config/config.dart';
import 'package:qadirneyriz/diologs/diolog_add_location.dart';
import 'package:qadirneyriz/diologs/diolog_add_subject.dart';
import 'package:qadirneyriz/diologs/diolog_add_user.dart';
import 'package:qadirneyriz/global/global_class/selected_item.dart';
import 'package:qadirneyriz/global/global_state/global_state.dart';
import 'package:qadirneyriz/screens/meeting_add/state.dart';
import 'package:qadirneyriz/screens/private_meeting_add/state.dart';
import 'package:qadirneyriz/utils/enums/status.dart';
import 'package:qadirneyriz/utils/tools/tools.dart';
import 'package:qadirneyriz/widgets/ExpansionTileCustom.dart';
import 'package:qadirneyriz/widgets/checkBox_inTile.dart';
import 'package:qadirneyriz/widgets/custom_appbar.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:qadirneyriz/widgets/custom_button.dart';
import 'package:qadirneyriz/widgets/custom_textfield.dart';
import 'package:qadirneyriz/widgets/ink_warpper.dart';
import 'package:qadirneyriz/widgets/loading_widget.dart';
import 'package:qadirneyriz/widgets/picker.dart';

class PrivateMeetingAddScreen extends StatefulWidget {
const PrivateMeetingAddScreen({super.key});

@override
State<PrivateMeetingAddScreen> createState() =>
_PrivateMeetingAddScreenState();
}

class _PrivateMeetingAddScreenState extends State<PrivateMeetingAddScreen> {
bool isPrivateMeeting = false;
final _formKey = GlobalKey<FormState>(); // Key for form validation
// all states we have
TextEditingController visitorName = TextEditingController();
TextEditingController visitorPhoneController = TextEditingController();
TextEditingController visitorRole = TextEditingController();
TextEditingController visitorCompanyNameController = TextEditingController();
late GlobalState globalState;

@override
void initState() {
super.initState();

//set states

globalState = Provider.of<GlobalState>(context, listen: false);
Future.delayed(Duration.zero, () async {
// get items
await globalState.getAllFiltersItems();
});
}

@override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: <Widget>[
CustomAppbar(
title: AppLocalizations.of(context)!.addnewprivatemeeting,
),
SliverFillRemaining(child: content(context)),
],
),
);
}

Widget content(BuildContext context) {
return Consumer2<GlobalState, PrivateMeetingAddState>(
builder: (context, stateGlobal, meetingAddState, child) {
switch (stateGlobal.allFiltersStatus) {
case Status.ready:
return Padding(
// This is now wrapped inside SliverToBoxAdapter
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CustomTextField(
paddingHarizon: 0,
paddingVertical: 10,
label: AppLocalizations.of(context)!.visitorname,
hintText:
AppLocalizations.of(context)!.nameandfamilyname,
textInputAction: TextInputAction.next,
textEditingController: visitorName,
textInputType: TextInputType.text),
CustomTextField(
paddingHarizon: 0,
paddingVertical: 10,
label: AppLocalizations.of(context)!.visitorrole,
hintText: AppLocalizations.of(context)!.visitorrole,
textEditingController: visitorRole,
textInputAction: TextInputAction.next,
textInputType: TextInputType.text),
CustomTextField(
paddingHarizon: 0,
paddingVertical: 10,
label: AppLocalizations.of(context)!.phonenumber,
hintText: AppLocalizations.of(context)!.phonenumber,
textEditingController: visitorPhoneController,
textInputAction: TextInputAction.next,
textInputType: TextInputType.phone),
CustomTextField(
paddingHarizon: 0,
paddingVertical: 10,
label: AppLocalizations.of(context)!.companyname,
hintText: AppLocalizations.of(context)!.companyname,
textEditingController: visitorCompanyNameController,
textInputAction: TextInputAction.done,
textInputType: TextInputType.text),

// subject ExpansionTile
Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: ExpansionTileCustom(
isForm: true,
subTitile:
AppLocalizations.of(context)!.meetingsubject,
title: meetingAddState.selectedSubject.id != null
? meetingAddState.selectedSubject.text ?? ''
: AppLocalizations.of(context)!.selectsubject,
widgets: <Widget>[
CheckBoxInTile(
text: AppLocalizations.of(context)!.newsubject,
onTap: () async {
await showDialog(
context:
context, // این باید کانتکست فعلی باشد
builder: (BuildContext context) {
return AddSubjectDiolog();
},
);
},
hasIcon: true,
backColor: Colors.white,
textColor: Colors.black.withOpacity(.5),
),
Column(
children:
globalState.subjectsModel!.map((subject) {
bool isSelected =
meetingAddState.selectedSubject.id ==
subject.id;
return CheckBoxInTile(
backColor: isSelected
? Color(0xff06CF64)
: Colors.white,
textColor:
isSelected ? Colors.white : Colors.black,
text: subject.subject ?? '',
hasIcon: false,
onTap: () {
setState(() {
meetingAddState.selectedSubject =
ItemSelected(
text: subject.subject ?? '',
id: subject.id ??
0); // Update selected location
});
},
);
}).toList(),
),
],
),
),

// Date Picker
Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: PickerCustom(
showDate: meetingAddState.fromDate ??
AppLocalizations.of(context)!.selectdate,
onTap: () {
showDialog(
context: context,
builder: (context) {
return Dialog(
child: Tools.shamsiDateCalendarWidget(
context,
(newDate) {
setState(() {
String fromDateString =
'${newDate.year}/${newDate.month}/${newDate.day}';
meetingAddState
.setFromDate(fromDateString);
}); // Update the selected date
},
),
);
},
);
},
isForm: true,
title: AppLocalizations.of(context)!.date,
),
),

// From and To time Range Pickers
Padding(
padding: const EdgeInsets.symmetric(vertical: 15.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
PickerCustom(
showDate: Tools.formatTime(
meetingAddState.selectedStartTime.hour,
meetingAddState.selectedStartTime.minute),
onTap: () async {
TimeOfDay? picked = await showTimePicker(
context: context,
initialTime:
meetingAddState.selectedStartTime,
);
if (picked != null &&
picked != meetingAddState.selectedStartTime)
setState(() {
meetingAddState.selectedStartTime = picked;
});
},
isForm: true,
icon: Icons.access_time_outlined,
title: AppLocalizations.of(context)!.clock,
),
Text(AppLocalizations.of(context)!.to),
PickerCustom(
showDate: Tools.formatTime(
meetingAddState.selectedEndTime.hour,
meetingAddState.selectedEndTime.minute),
isForm: true,
icon: Icons.access_time_outlined,
onTap: () async {
TimeOfDay? picked = await showTimePicker(
context: context,
initialTime: meetingAddState.selectedEndTime,
);
if (picked != null &&
picked != meetingAddState.selectedEndTime)
setState(() {
meetingAddState.selectedEndTime = picked;
});
},
),
],
),
),

// Location ExpansionTile
Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: ExpansionTileCustom(
isForm: true,
subTitile: AppLocalizations.of(context)!.location,
title: meetingAddState.selectedLocation.text ??
AppLocalizations.of(context)!.selectlocation,
widgets: <Widget>[
CheckBoxInTile(
text: AppLocalizations.of(context)!.newlocation,
onTap: () async {
await showDialog(
context:
context, // این باید کانتکست فعلی باشد
builder: (BuildContext context) {
return AddLocationDiolog();
},
);
},
hasIcon: true,
backColor: Colors.white,
textColor: Colors.black.withOpacity(.5),
),
Column(
children:
globalState.locationsModel!.map((location) {
bool isSelected =
meetingAddState.selectedLocation.id ==
location.id;
return CheckBoxInTile(
backColor: isSelected
? Color(0xff06CF64)
: Colors.white,
textColor:
isSelected ? Colors.white : Colors.black,
text: location.address ?? '',
hasIcon: false,
onTap: () {
setState(() {
meetingAddState.selectedLocation =
ItemSelected(
text: location.address,
id: location
.id); // Update selected location
});
},
);
}).toList(),
),
],
),
),

// Another ExpansionTile for users
Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: ExpansionTileCustom(
isForm: true,
subTitile: AppLocalizations.of(context)!.users,
title: AppLocalizations.of(context)!.selectusers,
widgets: <Widget>[
CheckBoxInTile(
text: AppLocalizations.of(context)!.newmember,
onTap: () async {
await showDialog(
context:
context, // این باید کانتکست فعلی باشد
builder: (BuildContext context) {
return AddUserDiolog();
},
);
},
hasIcon: true,
backColor: Colors.white,
textColor: Colors.black.withOpacity(.5),
),
Column(
children: globalState.usersModel != null
? globalState.usersModel!.map((user) {
bool isSelected = meetingAddState
.selectedUsersItems
.contains(user.id);
return Container(
margin: EdgeInsets.symmetric(
vertical: 5.0, horizontal: 10),
decoration: BoxDecoration(
color: isSelected
? Color(0xff06CF64)
: Colors.white,
borderRadius:
BorderRadius.circular(10),
boxShadow: [
BoxShadow(
color: Colors.black12,
blurRadius: 8,
offset: Offset(0, 4),
),
],
),
child: InkWrapper(
onTap: () {
setState(() {
if (isSelected) {
meetingAddState
.selectedUsersItems
.remove(user.id);
} else {
meetingAddState
.selectedUsersItems
.add(user.id ?? 0);
}
});
},
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Row(
children: [
Text(
maxLines: 1,
overflow:
TextOverflow.ellipsis,
user.name ?? '',
style: TextStyle(
fontSize: 12,
color: isSelected
? Colors.white
: Colors.black,
),
),
],
),
),
),
);
}).toList()
: [],
),
],
),
),
InkWell(
onTap: () {
setState(() {
isPrivateMeeting = !isPrivateMeeting;
});
},
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 15),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
AppLocalizations.of(context)!
.isprivateprivatemeeting,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontWeight: FontWeight.normal,
fontSize: 13,
color: Colors.black.withOpacity(.8),
),
),
Checkbox(
value: isPrivateMeeting,
onChanged: (f) {
setState(() {
isPrivateMeeting = f ?? false;
});
}),
],
),
),
),

// Final ExpansionTile if required
Visibility(
visible: !isPrivateMeeting,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 10.0),
child: ExpansionTileCustom(
isForm: true,
subTitile:
AppLocalizations.of(context)!.meetingmanager,
title: meetingAddState.selectedManager.text ??
AppLocalizations.of(context)!
.selectmeetingmanager,
widgets: <Widget>[
Column(
children: globalState.meetingsManagerModel!
.map((manager) {
bool isSelected =
meetingAddState.selectedManager.id ==
manager.id;
return CheckBoxInTile(
backColor: isSelected
? Color(0xff06CF64)
: Colors.white,
textColor: isSelected
? Colors.white
: Colors.black,
text: manager.name ?? '',
hasIcon: false,
onTap: () {
setState(() {
meetingAddState.selectedManager =
ItemSelected(
id: manager.id,
text: manager
.name); // Update selected manager
});
},
);
}).toList(),
),
],
),
),
),

// Submit Button

SizedBox(
height: 60,
),
Consumer<PrivateMeetingAddState>(
builder: (context, value, child) {
return submit(context, value);
},
),
],
),
),
),
);
case Status.loading:
return const LoadingWidget();
default:
return Container();
}
},
);
}

CustomButton submit(BuildContext context, PrivateMeetingAddState state) {
switch (state.statusAddMeeting) {
case Status.loading:
return CustomButton(
width: double.infinity,
hieght: 50,
fontSize: 16,
onPressed: null,
text: AppLocalizations.of(context)!.loading);

default:
return CustomButton(
width: double.infinity,
hieght: 50,
fontSize: 16,
onPressed: () async {
final status = await state.addPrivateMeeting(
locationId: state.selectedLocation.id,
subjectId: state.selectedSubject.id,
managerId:
!isPrivateMeeting ? state.selectedManager.id : null,
fromHour: Tools.formatTime(state.selectedStartTime.hour,
state.selectedStartTime.minute),
toHour: Tools.formatTime(
state.selectedEndTime.hour, state.selectedEndTime.minute),
dateMeeting: state.fromDate ?? '',
visitorName: visitorName.text,
visitorCompany: visitorCompanyNameController.text,
visitorMobile: visitorPhoneController.text,
visitorRole: visitorRole.text);
if (status == Status.ready) {
context.pushNamed('navigate', pathParameters: {'tab': '2'});
Tools.showCustomSnackBar(
text: AppLocalizations.of(context)!.addprivatemeetingdone,
isError: false,
context,
);
} else {
Tools.showCustomSnackBar(
text: state.errorsAddMeeting == null
? state.messageAddMeeting ??
AppLocalizations.of(context)!.haserror
: Tools.combineErrorMessages(
state.errorsAddMeeting ?? {}),
isError: true,
context,
);
}
},
text: AppLocalizations.of(context)!.submit);
}
}
}

+ 81
- 0
lib/screens/private_meeting_add/state.dart Просмотреть файл

@@ -0,0 +1,81 @@
import 'package:flutter/material.dart';
import 'package:qadirneyriz/global/global_class/selected_item.dart';
import 'package:qadirneyriz/services/private_meetings/private_meetings.dart';
import 'package:qadirneyriz/utils/enums/status.dart';

class PrivateMeetingAddState extends ChangeNotifier {
PrivateMeetingsApi privateMeetingApi = PrivateMeetingsApi();
// date
String? fromDate;
void setFromDate(String date) {
fromDate = date;
notifyListeners();
}

// subject
ItemSelected selectedSubject = ItemSelected();
// location
ItemSelected selectedLocation = ItemSelected();
// manager
ItemSelected selectedManager = ItemSelected();
//users
List<int> selectedUsersItems = [];
// time
TimeOfDay selectedStartTime =
TimeOfDay(hour: TimeOfDay.now().hour, minute: TimeOfDay.now().minute);
TimeOfDay selectedEndTime =
TimeOfDay(hour: TimeOfDay.now().hour, minute: TimeOfDay.now().minute);

// add meeting

Status statusAddMeeting = Status.empty;
String? messageAddMeeting;
Map? errorsAddMeeting;

Future<Status> addPrivateMeeting({
int? locationId,
int? subjectId,
int? managerId,
required String fromHour,
required String toHour,
required String dateMeeting,
required String visitorName,
required String visitorMobile,
required String visitorRole,
required String visitorCompany,
}) async {
statusAddMeeting = Status.loading;
notifyListeners();
try {
final result = await privateMeetingApi.addPrivateMeetingApi(
locationId: locationId,
subjectId: subjectId,
managerId: managerId,
fromHour: fromHour,
toHour: toHour,
dateMeeting: dateMeeting,
visitorCompany: visitorCompany,
visitorMobile: visitorMobile,
visitorName: visitorName,
visitorRole: visitorRole);
if (result.isOk) {
statusAddMeeting = Status.ready;
messageAddMeeting = result.message;
} else if (result.isOk == false) {
// print(result.isOk);
errorsAddMeeting = result.errors;
messageAddMeeting = result.message;
statusAddMeeting = Status.error;
} else {
statusAddMeeting = Status.error;
}
notifyListeners();
} catch (e) {
statusAddMeeting = Status.error;
// print(e);
}
notifyListeners();
// print(statusAddMeeting);
return statusAddMeeting;
}
}

+ 444
- 0
lib/screens/private_meeting_edit/screen.dart Просмотреть файл

@@ -0,0 +1,444 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
import 'package:qadirneyriz/diologs/diolog_add_location.dart';
import 'package:qadirneyriz/diologs/diolog_add_subject.dart';
import 'package:qadirneyriz/global/global_class/selected_item.dart';

import 'package:qadirneyriz/global/global_state/global_state.dart';
import 'package:qadirneyriz/screens/meeting_edit/state.dart';
import 'package:qadirneyriz/screens/private_meeting_edit/state.dart';
import 'package:qadirneyriz/utils/enums/status.dart';
import 'package:qadirneyriz/utils/tools/tools.dart';
import 'package:qadirneyriz/widgets/ExpansionTileCustom.dart';
import 'package:qadirneyriz/widgets/checkBox_inTile.dart';
import 'package:qadirneyriz/widgets/custom_appbar.dart';
import 'package:qadirneyriz/widgets/custom_button.dart';
import 'package:qadirneyriz/widgets/custom_textfield.dart';
import 'package:qadirneyriz/widgets/empty_widget.dart';
import 'package:qadirneyriz/widgets/error_widget.dart';
import 'package:qadirneyriz/widgets/loading_widget.dart';
import 'package:qadirneyriz/widgets/picker.dart';

class EditPrivateMeetingScreen extends StatefulWidget {
final int id;
const EditPrivateMeetingScreen({
Key? key,
required this.id,
}) : super(key: key);

@override
State<EditPrivateMeetingScreen> createState() =>
_EditPrivateMeetingScreenState();
}

class _EditPrivateMeetingScreenState extends State<EditPrivateMeetingScreen> {
final _formKey = GlobalKey<FormState>(); // Key for form validation
// all states we have
late EditPrivateMeetingState privateMeetingEditState;
late GlobalState globalState;
TextEditingController visitorName = TextEditingController();
TextEditingController visitorPhoneController = TextEditingController();
TextEditingController visitorRole = TextEditingController();
TextEditingController visitorCompanyNameController = TextEditingController();
@override
void initState() {
super.initState();

//set states
privateMeetingEditState =
Provider.of<EditPrivateMeetingState>(context, listen: false);
globalState = Provider.of<GlobalState>(context, listen: false);

Future.delayed(Duration.zero, () async {
// get items
await privateMeetingEditState.getOnePrivateMeeting(id: widget.id);
await globalState.getAllFiltersItems(refresh: true);
// set variables

if (privateMeetingEditState.onePrivateMeetingStatus[widget.id] ==
Status.ready &&
globalState.allFiltersStatus == Status.ready) {
privateMeetingEditState.setAllVariablesAtStart(id: widget.id);
visitorName.text = privateMeetingEditState.createdName;
visitorPhoneController.text =
privateMeetingEditState.createdPhoneNumber;
visitorRole.text = privateMeetingEditState.createdRole;
visitorCompanyNameController.text =
privateMeetingEditState.createdCompanyName;
}
});
}

@override
Widget build(BuildContext context) {
return Scaffold(
body: Consumer2<EditPrivateMeetingState, GlobalState>(
builder: (context, meetingEditState, globalState, child) {
return CustomScrollView(
slivers: <Widget>[
CustomAppbar(
title: AppLocalizations.of(context)!.editprivatemeeting),
SliverFillRemaining(
child: content(context, meetingEditState, globalState)),
],
);
},
),
);
}

Widget content(BuildContext context, EditPrivateMeetingState meetingEditState,
GlobalState globalState) {
final itemOnePrivateMeetingStatus =
meetingEditState.onePrivateMeetingStatus[widget.id];
if (itemOnePrivateMeetingStatus == Status.ready &&
globalState.allFiltersStatus == Status.ready) {
final itemInOneMeeting =
meetingEditState.onePrivateMeetingModel![widget.id]!;
return Padding(
// This is now wrapped inside SliverToBoxAdapter
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CustomTextField(
paddingHarizon: 0,
paddingVertical: 10,
label: AppLocalizations.of(context)!.visitorname,
hintText: '',
textEditingController: visitorName,
textInputType: TextInputType.text),
CustomTextField(
paddingHarizon: 0,
paddingVertical: 10,
label: AppLocalizations.of(context)!.visitorrole,
hintText: '',
textEditingController: visitorRole,
textInputType: TextInputType.text),
CustomTextField(
paddingHarizon: 0,
paddingVertical: 10,
label: AppLocalizations.of(context)!.phonenumber,
hintText: '',
textEditingController: visitorPhoneController,
textInputType: TextInputType.phone),
CustomTextField(
paddingHarizon: 0,
paddingVertical: 10,
label: AppLocalizations.of(context)!.companyname,
hintText: '',
textEditingController: visitorCompanyNameController,
textInputType: TextInputType.text),
// subject ExpansionTile
Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: ExpansionTileCustom(
isForm: true,
subTitile: AppLocalizations.of(context)!.meetingsubject,
title: meetingEditState.selectedSubject.id != null
? meetingEditState.selectedSubject.text ?? ''
: meetingEditState.onePrivateMeetingModel![widget.id]!
.subject!.subject ??
'',
widgets: <Widget>[
CheckBoxInTile(
text: AppLocalizations.of(context)!.newsubject,
onTap: () async {
await showDialog(
context: context, // این باید کانتکست فعلی باشد
builder: (BuildContext context) {
return AddSubjectDiolog();
},
);
},
hasIcon: true,
backColor: Colors.white,
textColor: Colors.black.withOpacity(.5),
),
Column(
children: globalState.subjectsModel!.map((subject) {
bool isSelected =
meetingEditState.selectedSubject.id == subject.id;
return CheckBoxInTile(
backColor:
isSelected ? Color(0xff06CF64) : Colors.white,
textColor: isSelected ? Colors.white : Colors.black,
text: subject.subject ?? '',
hasIcon: false,
onTap: () {
setState(() {
meetingEditState.selectedSubject = ItemSelected(
text: subject.subject ?? '',
id: subject.id ??
0); // Update selected location
});
},
);
}).toList(),
),
],
),
),

// Date Picker
Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: PickerCustom(
showDate: meetingEditState.fromDate != null
? meetingEditState.fromDate ?? ''
: itemInOneMeeting.dateJalali ?? '',
onTap: () {
showDialog(
context: context,
builder: (context) {
return Dialog(
child: Tools.shamsiDateCalendarWidget(
context,
(newDate) {
String fromDateString =
'${newDate.year}/${newDate.month}/${newDate.day}';
meetingEditState.setFromDate(
fromDateString); // Update the selected date
},
),
);
},
);
},
isForm: true,
title: AppLocalizations.of(context)!.date,
),
),

// From and To time Range Pickers
Padding(
padding: const EdgeInsets.symmetric(vertical: 15.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
PickerCustom(
showDate: meetingEditState.selectedStartTime != null
? Tools.formatTime(
meetingEditState.selectedStartTime!.hour,
meetingEditState.selectedStartTime!.minute)
: itemInOneMeeting.azHour ?? '',
onTap: () async {
TimeOfDay? picked = await showTimePicker(
context: context,
initialTime: meetingEditState.selectedStartTime!,
);
if (picked != null &&
picked != meetingEditState.selectedStartTime)
setState(() {
meetingEditState.selectedStartTime = picked;
});
},
isForm: true,
icon: Icons.access_time_outlined,
title: AppLocalizations.of(context)!.clock,
),
Text(AppLocalizations.of(context)!.to),
PickerCustom(
showDate: meetingEditState.selectedEndTime != null
? Tools.formatTime(
meetingEditState.selectedEndTime!.hour,
meetingEditState.selectedEndTime!.minute)
: itemInOneMeeting.taHour ?? '',
isForm: true,
icon: Icons.access_time_outlined,
onTap: () async {
TimeOfDay? picked = await showTimePicker(
context: context,
initialTime: meetingEditState.selectedEndTime!,
);
if (picked != null &&
picked != meetingEditState.selectedEndTime)
setState(() {
meetingEditState.selectedEndTime = picked;
});
},
),
],
),
),

// Location ExpansionTile
Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: ExpansionTileCustom(
isForm: true,
subTitile: AppLocalizations.of(context)!.location,
title: meetingEditState.selectedLocation.id != null
? meetingEditState.selectedLocation.text ?? ''
: itemInOneMeeting.location!.address ?? '',
widgets: <Widget>[
CheckBoxInTile(
text: AppLocalizations.of(context)!.newlocation,
onTap: () async {
await showDialog(
context: context, // این باید کانتکست فعلی باشد
builder: (BuildContext context) {
return AddLocationDiolog();
},
);
},
hasIcon: true,
backColor: Colors.white,
textColor: Colors.black.withOpacity(.5),
),
Column(
children: globalState.locationsModel!.map((location) {
bool isSelected =
meetingEditState.selectedLocation.id ==
location.id;
return CheckBoxInTile(
backColor:
isSelected ? Color(0xff06CF64) : Colors.white,
textColor: isSelected ? Colors.white : Colors.black,
text: location.address ?? '',
hasIcon: false,
onTap: () {
setState(() {
meetingEditState.selectedLocation =
ItemSelected(
text: location.address,
id: location
.id); // Update selected location
});
},
);
}).toList(),
),
],
),
),

// Final ExpansionTile if required
Padding(
padding: const EdgeInsets.symmetric(vertical: 10.0),
child: ExpansionTileCustom(
isForm: true,
subTitile: AppLocalizations.of(context)!.meetingmanager,
title: meetingEditState.selectedManager.id != null
? meetingEditState.selectedManager.text ?? ''
: itemInOneMeeting.manager!.name ?? '',
widgets: <Widget>[
Column(
children:
globalState.meetingsManagerModel!.map((manager) {
bool isSelected =
meetingEditState.selectedManager.id == manager.id;
return CheckBoxInTile(
backColor:
isSelected ? Color(0xff06CF64) : Colors.white,
textColor: isSelected ? Colors.white : Colors.black,
text: manager.name ?? '',
hasIcon: false,
onTap: () {
setState(() {
meetingEditState.selectedManager = ItemSelected(
id: manager.id,
text: manager
.name); // Update selected manager
});
},
);
}).toList(),
),
],
),
),

// Submit Button
SizedBox(
height: 60,
),
submit(context)
],
),
),
),
);
} else if (itemOnePrivateMeetingStatus == Status.loading ||
globalState.allFiltersStatus == Status.loading) {
return const LoadingWidget();
} else if (itemOnePrivateMeetingStatus == Status.error ||
globalState.allFiltersStatus == Status.error) {
return CustomErrorWidget(
onPressed: () async {
await privateMeetingEditState.getOnePrivateMeeting(
id: widget.id, refresh: true);
await globalState.getAllFiltersItems(refresh: true);
},
);
} else if (itemOnePrivateMeetingStatus == Status.empty ||
globalState.allFiltersStatus == Status.empty) {
return EmptyStateWidget();
} else {
return Container();
}
}

CustomButton submit(BuildContext context) {
switch (privateMeetingEditState.statusEditPrivateMeeting) {
case Status.loading:
return CustomButton(
width: double.infinity,
hieght: 50,
fontSize: 16,
onPressed: null,
text: AppLocalizations.of(context)!.loading);

default:
return CustomButton(
width: double.infinity,
hieght: 50,
fontSize: 16,
onPressed: () async {
final status = await privateMeetingEditState.editPrivateMeeting(
id: widget.id,
locationId: privateMeetingEditState.selectedLocation.id ?? -1,
subjectId: privateMeetingEditState.selectedSubject.id ?? -1,
managerId: privateMeetingEditState.selectedManager.id ?? -1,
fromHour: Tools.formatTime(
privateMeetingEditState.selectedStartTime!.hour,
privateMeetingEditState.selectedStartTime!.minute),
toHour: Tools.formatTime(
privateMeetingEditState.selectedEndTime!.hour,
privateMeetingEditState.selectedEndTime!.minute),
dateMeeting: privateMeetingEditState.fromDate ?? '',
visitorRole: visitorName.text,
visitorCompany: visitorCompanyNameController.text,
visitorMobile: visitorPhoneController.text,
visitorName: visitorName.text);
if (status == Status.ready) {
context.pop();
Tools.showCustomSnackBar(
text: AppLocalizations.of(context)!.editdone,
isError: false,
context,
);
} else {
Tools.showCustomSnackBar(
text: privateMeetingEditState.errorsEditPrivateMeeting == null
? privateMeetingEditState.messageEditPrivateMeeting ??
AppLocalizations.of(context)!.haserror
: Tools.combineErrorMessages(
privateMeetingEditState.errorsEditPrivateMeeting ??
{}),
isError: true,
context,
);
}
},
text: AppLocalizations.of(context)!.submit);
}
}
}

+ 165
- 0
lib/screens/private_meeting_edit/state.dart Просмотреть файл

@@ -0,0 +1,165 @@
import 'package:flutter/material.dart';
import 'package:qadirneyriz/global/global_class/selected_item.dart';
import 'package:qadirneyriz/models/private_meeting/one_private_meeting_model.dart';
import 'package:qadirneyriz/services/private_meetings/private_meetings.dart';
import 'package:qadirneyriz/utils/enums/status.dart';

class EditPrivateMeetingState extends ChangeNotifier {
PrivateMeetingsApi privateMeetingApi = PrivateMeetingsApi();

Map<int, Status> onePrivateMeetingStatus = {};
Map<int, OnePrivateMeetingModel>? onePrivateMeetingModel = {};

Future<Status> getOnePrivateMeeting(
{bool refresh = false, required int id}) async {
onePrivateMeetingStatus[id] = Status.loading;
notifyListeners();
// Ensure the status map is initialized
if (onePrivateMeetingStatus[id] == null || refresh) {
onePrivateMeetingStatus[id] = Status.loading;
notifyListeners();
}

// Initialize the model map if it's null
onePrivateMeetingModel ??= {};

// If not refreshing and data exists, return the current state
if (!refresh && onePrivateMeetingModel![id] != null) {
onePrivateMeetingStatus[id] = Status.ready;
notifyListeners();
return onePrivateMeetingStatus[id]!;
}

// Otherwise, fetch new data from API
try {
onePrivateMeetingModel![id] =
await privateMeetingApi.getOnePrivateMeeting(id: id);
if (onePrivateMeetingModel![id] != null) {
onePrivateMeetingStatus[id] = Status.ready;
} else {
onePrivateMeetingStatus[id] = Status.empty;
}
} catch (e) {
onePrivateMeetingStatus[id] = Status.error;
// print(e);
}

notifyListeners();
return onePrivateMeetingStatus[id]!;
}

// date
String? fromDate;
void setFromDate(String date) {
fromDate = date;
notifyListeners();
}

// subject
ItemSelected selectedSubject = ItemSelected();
// location
ItemSelected selectedLocation = ItemSelected();
// manager
ItemSelected selectedManager = ItemSelected();
// time
TimeOfDay? selectedStartTime;
TimeOfDay? selectedEndTime;
// فیلد های مخصوص ملاقات ها
String createdName = '';
String createdRole = '';
String createdPhoneNumber = '';
String createdCompanyName = '';
// function at start
void setAllVariablesAtStart({required int id}) {
if (onePrivateMeetingStatus[id] == Status.ready) {
final item = onePrivateMeetingModel![id]!;

selectedLocation = ItemSelected(
id: item.locationsId ?? -1,
text: item.location != null ? item.location!.address ?? '' : '');

selectedSubject = ItemSelected(
text: item.subject != null ? item.subject!.subject ?? '' : '',
id: item.subject!.id ?? -1);

selectedManager = ItemSelected(
id: item.managerId ?? -1,
text: item.manager != null ? item.manager!.name ?? '' : '');

fromDate = item.dateJalali;

String timeStart = item.azHour ?? ':';
List<String> timeParts = timeStart.split(':');

int hourStart = int.parse(timeParts[0]);
int minuteStart = int.parse(timeParts[1]);
selectedStartTime = TimeOfDay(hour: hourStart, minute: minuteStart);

String timeEnd = item.taHour ?? ':';
List<String> timeEndParts = timeEnd.split(':');

int hourEnd = int.parse(timeEndParts[0]);
int minuteEnd = int.parse(timeEndParts[1]);
selectedEndTime = TimeOfDay(hour: hourEnd, minute: minuteEnd);

createdCompanyName = item.visitCompany ?? '';
createdPhoneNumber = item.visitMobile ?? '';
createdName = item.visitName ?? '';
createdRole = item.visitRole ?? '';
}
}

// send edit private meeting
Status statusEditPrivateMeeting = Status.empty;
String? messageEditPrivateMeeting;
Map? errorsEditPrivateMeeting;

Future<Status> editPrivateMeeting({
required int id,
required int locationId,
required int subjectId,
required int managerId,
required String fromHour,
required String toHour,
required String dateMeeting,
required String visitorName,
required String visitorMobile,
required String visitorRole,
required String visitorCompany,
}) async {
statusEditPrivateMeeting = Status.loading;
notifyListeners();
try {
final result = await privateMeetingApi.editPrivateMeetingApi(
id: id,
locationId: locationId,
subjectId: subjectId,
managerId: managerId,
fromHour: fromHour,
toHour: toHour,
dateMeeting: dateMeeting,
visitorCompany: visitorCompany,
visitorMobile: visitorMobile,
visitorName: visitorName,
visitorRole: visitorRole);
if (result.isOk) {
statusEditPrivateMeeting = Status.ready;
messageEditPrivateMeeting = result.message;
} else if (result.isOk == false) {
// print(result.isOk);
errorsEditPrivateMeeting = result.errors;
messageEditPrivateMeeting = result.message;
statusEditPrivateMeeting = Status.error;
} else {
statusEditPrivateMeeting = Status.error;
}
notifyListeners();
} catch (e) {
statusEditPrivateMeeting = Status.error;
// print(e);
}
notifyListeners();
// print(statusEditPrivateMeeting);
return statusEditPrivateMeeting;
}
}

+ 590
- 0
lib/screens/private_meeting_summary/screen.dart Просмотреть файл

@@ -0,0 +1,590 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
import 'dart:io';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:go_router/go_router.dart';
import 'package:open_file/open_file.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:provider/provider.dart';
import 'package:qadirneyriz/config/config.dart';
import 'package:qadirneyriz/models/private_meeting/private_meetings_model.dart';
import 'package:qadirneyriz/screens/private_meeting_summary/state.dart';
import 'package:qadirneyriz/setting/setting.dart';
import 'package:qadirneyriz/utils/enums/status.dart';
import 'package:qadirneyriz/utils/tools/tools.dart';
import 'package:qadirneyriz/widgets/card_meeting.dart';
import 'package:qadirneyriz/widgets/custom_appbar.dart';
import 'package:qadirneyriz/widgets/custom_button.dart';
import 'package:qadirneyriz/widgets/error_widget.dart';
import 'package:qadirneyriz/widgets/loading_widget.dart';

class PrivateMeetingSummaryScreen extends StatefulWidget {
final DatumInPrivateMeeting itemInPrivateMeeting;
const PrivateMeetingSummaryScreen({
Key? key,
required this.itemInPrivateMeeting,
}) : super(key: key);

@override
State<PrivateMeetingSummaryScreen> createState() =>
_PrivateMeetingSummaryScreenState();
}

class _PrivateMeetingSummaryScreenState
extends State<PrivateMeetingSummaryScreen> {
late TextEditingController _textControllerDescription;
late PrivateMeetingSummaryState state;
@override
void initState() {
super.initState();
_textControllerDescription = TextEditingController();
if (widget.itemInPrivateMeeting.description != null) {
_textControllerDescription.text =
widget.itemInPrivateMeeting.description ?? '';
}
Future.delayed(Duration.zero, () async {
state = Provider.of<PrivateMeetingSummaryState>(context, listen: false);
await state.getStringFiles(id: widget.itemInPrivateMeeting.id ?? 0);
});
}

@override
void dispose() {
_textControllerDescription.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
return Scaffold(
body: Consumer<PrivateMeetingSummaryState>(
builder: (context, value, child) {
switch (value.stringsFilsStatus[widget.itemInPrivateMeeting.id]) {
case Status.ready:
return CustomScrollView(
slivers: <Widget>[
CustomAppbar(
title: AppLocalizations.of(context)!.meetingsummary,
),
SliverToBoxAdapter(
child: CustomCardMeeting(
status: widget.itemInPrivateMeeting.accepted ?? 0,
titel: widget.itemInPrivateMeeting.subject != null
? widget.itemInPrivateMeeting.subject!.subject ?? ''
: '',
date: widget.itemInPrivateMeeting.dateJalali ?? '',
location: widget.itemInPrivateMeeting.location != null
? widget.itemInPrivateMeeting.location!.address ?? ''
: '',
fromTime: widget.itemInPrivateMeeting.azHour ?? '',
toTime: widget.itemInPrivateMeeting.taHour ?? '',
cardId: widget.itemInPrivateMeeting.id ?? -1,
),
),
SliverToBoxAdapter(
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(10.0),
child: Container(
decoration: BoxDecoration(
color: const Color(0xffF4F9F6),
boxShadow: [
BoxShadow(
color: config.ui.mainGray.withOpacity(.1),
spreadRadius: .1,
offset: const Offset(0, 2),
blurRadius: 6,
),
],
borderRadius:
const BorderRadius.all(Radius.circular(12)),
),
child: CustomTextArea(
hintText: AppLocalizations.of(context)!
.descriptionofthemeeting,
controller: _textControllerDescription,
),
),
),
if (state.filesStringModel[
widget.itemInPrivateMeeting.id] !=
null &&
state
.filesStringModel[
widget.itemInPrivateMeeting.id]!
.isNotEmpty)
Padding(
padding: EdgeInsets.all(10),
child: Container(
decoration: BoxDecoration(
color: const Color(0xffF4F9F6),
boxShadow: [
BoxShadow(
color: config.ui.mainGray.withOpacity(.1),
spreadRadius: .1,
offset: const Offset(0, 2),
blurRadius: 6,
),
],
borderRadius:
const BorderRadius.all(Radius.circular(12)),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 10, vertical: 20),
child: Text(
AppLocalizations.of(context)!.files,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: config.ui.mainGreen,
),
),
),
ListView.builder(
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
padding: EdgeInsets.all(0),
itemCount: state
.filesStringModel[
widget.itemInPrivateMeeting.id]!
.length,
itemBuilder:
(BuildContext context, int index) {
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: 20, vertical: 10),
child: deleteFilesButton(
value,
widget.itemInPrivateMeeting.id ??
-1,
state.filesStringModel[widget
.itemInPrivateMeeting
.id]![index]),
);
},
),
],
),
),
),
Padding(
padding: const EdgeInsets.all(10.0),
child: ReceiptUploadDialog(
state: value,
),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: submitSummaryButton(context, value),
),
],
),
),
if (widget.itemInPrivateMeeting.description != null &&
state.filesStringModel[widget.itemInPrivateMeeting.id!] !=
null &&
state.filesStringModel[widget.itemInPrivateMeeting.id!]!
.isNotEmpty)
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.only(
top: 5, bottom: 40, left: 10, right: 10),
child: downloadButton(
value, widget.itemInPrivateMeeting.id ?? -1),
),
)
],
);
case Status.loading:
return const LoadingWidget();
case Status.error:
return CustomErrorWidget(
onPressed: () async {
await state.getStringFiles(
id: widget.itemInPrivateMeeting.id ?? 0);
},
);

default:
return Container();
}
},
),
);
}

Widget deleteFilesButton(
PrivateMeetingSummaryState state, int id, String text) {
switch (state.statusDeleteFile) {
case Status.loading:
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Icon(Icons.cancel_outlined),
Text(text),
],
);

default:
return InkWell(
onTap: () async {
final shouldProceed = await showDialog<bool>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text(
AppLocalizations.of(context)!.acceptoperetion,
),
content: Text(
AppLocalizations.of(context)!.areusuretodeletfile,
),
actions: [
TextButton(
onPressed: () {
// لغو عملیات
Navigator.of(context).pop(false);
},
child: Text(
AppLocalizations.of(context)!.cancel,
),
),
TextButton(
onPressed: () {
// تأیید عملیات
Navigator.of(context).pop(true);
},
child: Text(
AppLocalizations.of(context)!.accept,
),
),
],
);
},
);

// اگر کاربر تأیید کرد، عملیات انجام شود
if (shouldProceed == true) {
final status = await state.deleteFileSummary(id: id, text: text);
if (status == Status.ready) {
await state.getStringFiles(id: id);
// context.pop();
}
}
},
child: state.filesStringModel[id] != null
? Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Icon(Icons.cancel_outlined),
Text(text),
],
)
: Container(),
);
}
}

CustomButton submitSummaryButton(
BuildContext context, PrivateMeetingSummaryState state) {
switch (state.statusMinuteMeeting) {
case Status.loading:
return CustomButton(
hieght: 50, text: AppLocalizations.of(context)!.loading);

default:
return CustomButton(
hieght: 50,
text: AppLocalizations.of(context)!.submitsummarymeeting,
onPressed: () async {
if (_textControllerDescription.text == '') {
// call add new subject
Tools.showCustomSnackBar(
text: AppLocalizations.of(context)!.enterdescription,
isError: true,
context,
);
} else if (state.selectedFiles == null) {
// call add new subject
Tools.showCustomSnackBar(
text: AppLocalizations.of(context)!.enterfile,
isError: true,
context,
);
} else {
final status = await state.addMinuteMeeting(
id: widget.itemInPrivateMeeting.id ?? -1,
description: _textControllerDescription.text,
meetingFiles: state.selectedFiles ?? []);

if (status == Status.ready) {
await state.getStringFiles(
id: widget.itemInPrivateMeeting.id ?? -1);
context.pop();
Tools.showCustomSnackBar(
text: AppLocalizations.of(context)!.donesummary,
isError: false,
context,
);
} else {
Tools.showCustomSnackBar(
text: state.errorsMinuteMeeting == null
? state.messageMinuteMeeting ??
AppLocalizations.of(context)!.haserror
: Tools.combineErrorMessages(
state.errorsMinuteMeeting ?? {}),
isError: true,
context,
);
}
}
},
);
}
}

Future<bool> hasStoragePermission() async {
if (Platform.isAndroid) {
final status = await Permission.storage.status;
if (status != PermissionStatus.granted) {
final result = await Permission.manageExternalStorage.request();
if (result == PermissionStatus.granted) {
return true;
}
} else {
return true;
}
} else {
return true;
}
return false;
}

CustomButton downloadButton(PrivateMeetingSummaryState state, int id) {
switch (state.statusDownload) {
case Status.loading:
return CustomButton(
borderRadius: 15,
hieght: 50,
text: AppLocalizations.of(context)!.loading,
width: double.infinity,
);

default:
return CustomButton(
borderRadius: 15,
hieght: 50,
text: AppLocalizations.of(context)!.downloadreport,
width: double.infinity,
onPressed: () async {
bool hasPermission = await hasStoragePermission();
if (!hasPermission) {
Tools.showCustomSnackBar(context,
text: AppLocalizations.of(context)!.needpermission,
isError: true);
return;
}

// Download the file
await state.downloadSummary(id: id);

if (state.statusDownload == Status.ready) {
try {
await OpenFile.open(state.messageDownload);
} catch (e) {
Tools.showCustomSnackBar(
context,
text: AppLocalizations.of(context)!.needzipapp,
isError: true,
);
}
} else {
Tools.showCustomSnackBar(
context,
text: AppLocalizations.of(context)!.error,
isError: true,
);
}
},
);
}
}
}

class CustomTextArea extends StatelessWidget {
final String hintText;
final TextEditingController controller;
final int maxLines;
final int minLines;

const CustomTextArea({
Key? key,
required this.hintText,
required this.controller,
this.maxLines = 20,
this.minLines = 4,
}) : super(key: key);

@override
Widget build(BuildContext context) {
return TextField(
controller: controller,
maxLines: maxLines,
minLines: minLines,
decoration: InputDecoration(
hintText: hintText,
hintStyle: TextStyle(color: Colors.black.withOpacity(.4), fontSize: 13),
border: InputBorder.none,
contentPadding: const EdgeInsets.all(12.0),
),
);
}
}

class ReceiptUploadDialog extends StatefulWidget {
final PrivateMeetingSummaryState state;
const ReceiptUploadDialog({
Key? key,
required this.state,
}) : super(key: key);

@override
_ReceiptUploadDialogState createState() => _ReceiptUploadDialogState();
}

class _ReceiptUploadDialogState extends State<ReceiptUploadDialog> {
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: const Color(0xffF4F9F6),
boxShadow: [
BoxShadow(
color: config.ui.mainGray.withOpacity(.1),
spreadRadius: .1,
offset: const Offset(0, 2),
blurRadius: 6,
),
],
borderRadius: const BorderRadius.all(Radius.circular(12)),
),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 25, horizontal: 10),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
AppLocalizations.of(context)!.fileupload,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: config.ui.mainGreen,
),
),
const SizedBox(height: 20),

// Upload box
FileBorderBox(
child: GestureDetector(
onTap: widget.state.pickFiles,
child: Column(
children: [
Icon(Icons.cloud_upload_outlined,
size: 30, color: config.ui.mainGreen),
Text(
AppLocalizations.of(context)!.selectfile,
style:
TextStyle(fontSize: 12, color: config.ui.mainGreen),
),
],
),
),
),

// File preview
if (widget.state.selectedFiles != null)
FilePreview(
files: widget.state.selectedFiles!,
onDelete: widget.state.removeFile,
),
],
),
),
);
}
}

class FilePreview extends StatelessWidget {
final List<PlatformFile> files;
final void Function(int) onDelete;

const FilePreview({
super.key,
required this.files,
required this.onDelete,
});

@override
Widget build(BuildContext context) {
return ListView.builder(
shrinkWrap: true,
itemCount: files.length,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (BuildContext context, int index) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
margin: const EdgeInsets.symmetric(vertical: 5, horizontal: 10),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
border: Border(bottom: BorderSide(color: config.ui.secendGreen)),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
const Icon(Icons.file_present_outlined, color: Color(0xffD0D5ED)),
const SizedBox(width: 10),
Expanded(
child: Text(
files[index].name,
style: const TextStyle(fontSize: 12),
),
),
IconButton(
icon: Icon(Icons.delete, color: config.ui.secendGreen),
onPressed: () => onDelete(index),
),
],
),
);
},
);
}
}

class FileBorderBox extends StatelessWidget {
final Widget child;
const FileBorderBox({super.key, required this.child});

@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: config.ui.mainGreen,
style: BorderStyle.solid,
width: .5,
),
),
child: Center(child: child),
);
}
}

+ 161
- 0
lib/screens/private_meeting_summary/state.dart Просмотреть файл

@@ -0,0 +1,161 @@
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:qadirneyriz/services/private_meetings/private_meetings.dart';
import 'package:qadirneyriz/utils/enums/status.dart';

class PrivateMeetingSummaryState extends ChangeNotifier {
// send add meeting minute
PrivateMeetingsApi meetingsApi = PrivateMeetingsApi();
Status statusMinuteMeeting = Status.empty;
String? messageMinuteMeeting;
Map? errorsMinuteMeeting;

Future<Status> addMinuteMeeting(
{required int id,
required String description,
required List<PlatformFile> meetingFiles}) async {
statusMinuteMeeting = Status.loading;
notifyListeners();
try {
final result = await meetingsApi.addMeetingMinuteApi(
id: id, description: description, meetingFiles: meetingFiles);
if (result.isOk) {
statusMinuteMeeting = Status.ready;
messageMinuteMeeting = result.message;
} else if (result.isOk == false) {
// print(result.isOk);
errorsMinuteMeeting = result.errors;
messageMinuteMeeting = result.message;
statusMinuteMeeting = Status.error;
} else {
statusMinuteMeeting = Status.error;
}
notifyListeners();
} catch (e) {
statusMinuteMeeting = Status.error;
// print(e);
}
notifyListeners();
// print(statusMinuteMeeting);
return statusMinuteMeeting;
}

List<PlatformFile>? selectedFiles;

Future<void> pickFiles() async {
final result = await FilePicker.platform.pickFiles(
allowMultiple: true,
);

if (result != null) {
selectedFiles = result.files;

notifyListeners();
}
}

void removeFile(int index) {
selectedFiles!.removeAt(index);
notifyListeners();
}

// download summary

Status statusDownload = Status.empty;
String? messageDownload;

Future<Status> downloadSummary({required int id}) async {
statusDownload = Status.loading;
notifyListeners();
try {
final result = await meetingsApi.downloadSummary(id: id);

if (result == null) {
statusDownload = Status.error;
} else {
if (result.isOk) {
statusDownload = Status.ready;
messageDownload = result.message ?? '';
} else {
statusDownload = Status.error;
}
}
} catch (e) {
statusDownload = Status.error;
}

notifyListeners();
return statusDownload;
}

// get file string
Map<int, Status> stringsFilsStatus = {};
Map<int, List<String>> filesStringModel = {};
Map<int, List<String>?> messageStringFiles = {};
Map? errorsStringFiles;
Future<Map<int, Status>> getStringFiles({required int id}) async {
if (filesStringModel[id] != null && filesStringModel[id]!.isNotEmpty) {
try {
filesStringModel[id] = await meetingsApi.getListStringFils(id: id);
print('${filesStringModel[id]}');

stringsFilsStatus[id] = Status.ready;
print('${filesStringModel} filesStringModel[id]');
} catch (e) {
stringsFilsStatus[id] = Status.error;
print('$e');
}
} else {
stringsFilsStatus[id] = Status.ready;
notifyListeners();
try {
filesStringModel[id] = await meetingsApi.getListStringFils(id: id);
print('${filesStringModel[id]}');

stringsFilsStatus[id] = Status.ready;
print('${filesStringModel} filesStringModel[id]');
} catch (e) {
stringsFilsStatus[id] = Status.error;
print('$e');
}
}

notifyListeners();
print('${stringsFilsStatus} stringsFilsStatus');

return stringsFilsStatus;
}

// delete file of summary
Status statusDeleteFile = Status.empty;
String? messageDeleteFile;
Map? errorsDeleteFile;

Future<Status> deleteFileSummary({
required int id,
required String text,
}) async {
statusDeleteFile = Status.loading;
notifyListeners();
try {
final result = await meetingsApi.deleteFileSummary(id: id, text: text);
if (result.isOk) {
statusDeleteFile = Status.ready;
messageDeleteFile = result.message;
} else if (result.isOk == false) {
print(result.isOk);
errorsDeleteFile = result.errors;
messageDeleteFile = result.message;
statusDeleteFile = Status.error;
}
notifyListeners();
} catch (e) {
statusDeleteFile = Status.error;
print(e);
}
notifyListeners();
print(statusDeleteFile);
return statusDeleteFile;
}

}

+ 422
- 0
lib/screens/report/screen.dart Просмотреть файл

@@ -0,0 +1,422 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:open_file/open_file.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:provider/provider.dart';
import 'package:qadirneyriz/config/config.dart';
import 'package:qadirneyriz/global/global_state/global_state.dart';
import 'package:qadirneyriz/screens/report/state.dart';
import 'package:qadirneyriz/setting/setting.dart';
import 'package:qadirneyriz/utils/enums/status.dart';
import 'package:qadirneyriz/utils/tools/tools.dart';
import 'package:qadirneyriz/widgets/ExpansionTileCustom.dart';
import 'package:qadirneyriz/widgets/custom_appbar.dart';
import 'package:qadirneyriz/widgets/custom_button.dart';
import 'package:qadirneyriz/widgets/error_widget.dart';
import 'package:qadirneyriz/widgets/loading_widget.dart';
import 'package:qadirneyriz/widgets/picker.dart';
import 'package:qadirneyriz/widgets/today_widget.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';

class ReportScreen extends StatefulWidget {
const ReportScreen({super.key});

@override
State<ReportScreen> createState() => _ReportScreenState();
}

class _ReportScreenState extends State<ReportScreen> {
@override
Widget build(BuildContext context) {
DateTime now = DateTime.now();
String dateMiladi = DateFormat('yyyy-MM-dd').format(now);
String dateJalali =
'${setting.timeNow.day} ${Tools.getMonthName(setting.timeNow.month)} ${setting.timeNow.year}'; // فرمت کردن تاریخ
return CustomScrollView(
slivers: <Widget>[
const CustomAppbar(),
SliverToBoxAdapter(
child: TodayWidget(
formattedDate: setting.userLocalDb.getUser().language == 'en'
? dateMiladi
: dateJalali),
),
SliverFillRemaining(
child: FiltersItemInReport(),
)
],
);
}
}

class FiltersItemInReport extends StatefulWidget {
const FiltersItemInReport({
super.key,
});

@override
State<FiltersItemInReport> createState() => _FiltersItemInReportState();
}

class _FiltersItemInReportState extends State<FiltersItemInReport> {
ReportState? reportState;
GlobalState? globalState;
@override
void initState() {
reportState = Provider.of<ReportState>(context, listen: false);
globalState = Provider.of<GlobalState>(context, listen: false);
Future.delayed(Duration.zero, () async {
await globalState!.getAllFiltersItems();
});
super.initState();
}

@override
Widget build(BuildContext context) {
List<MeetingsStatus> meetingStatuses = [
MeetingsStatus(id: 1, title: AppLocalizations.of(context)!.donemeetings),
MeetingsStatus(
id: 2, title: AppLocalizations.of(context)!.adjournedmeetings),
MeetingsStatus(
id: 3, title: AppLocalizations.of(context)!.canceldmeetings),
MeetingsStatus(
id: 4, title: AppLocalizations.of(context)!.meetingswaitingtobeheld),
];
return Consumer2<ReportState, GlobalState>(
builder: (context, reportState, globalState, child) {
switch (globalState.allFiltersStatus) {
case Status.ready:
return Column(
children: [
Expanded(
child: SingleChildScrollView(
child: Column(
children: [
Column(
mainAxisSize: MainAxisSize.max,
children: [
ExpansionTileCustom(
title: AppLocalizations.of(context)!.date,
widgets: <Widget>[
Row(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
PickerCustom(
showDate: reportState.fromDate.isNotEmpty
? reportState.fromDate
: AppLocalizations.of(context)!
.selectdate, // Show selected date or prompt
onTap: () {
showDialog(
context: context,
builder: (context) {
return Dialog(
child: Tools
.shamsiDateCalendarWidget(
context,
(newDate) {
String fromDateString =
'${newDate.year}/${newDate.month}/${newDate.day}';
reportState.setFromDates(
fromDateString); // Update the selected date
},
),
);
},
);
},
),
Text(
AppLocalizations.of(context)!.to,
),
PickerCustom(
showDate: reportState.toDate.isNotEmpty
? reportState.toDate
: AppLocalizations.of(context)!
.selectdate, // Show selected date or prompt
onTap: () {
showDialog(
context: context,
builder: (context) {
return Dialog(
child: Tools
.shamsiDateCalendarWidget(
context,
(newDate) {
String toDateString =
'${newDate.year}/${newDate.month}/${newDate.day}';
reportState.setToDates(
toDateString); // Update the selected date
},
),
);
},
);
},
),
],
)
],
),
ExpansionTileCustom(
title: AppLocalizations.of(context)!.location,
widgets: <Widget>[
ListView.builder(
primary: false,
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: globalState.locationsModel!.length,
itemBuilder:
(BuildContext context, int index) {
final items =
globalState.locationsModel![index];
return RadioListTile<int>(
toggleable: true,
groupValue:
reportState.selectedLocationId,
value: items.id ?? -1,
title: Text(
items.address ?? '',
maxLines: 1,
style: TextStyle(
fontWeight: FontWeight.w100,
fontSize: 14),
overflow: TextOverflow.ellipsis,
),
activeColor: config.ui.secendGreen,
onChanged: (int? newValue) {
reportState
.selectLocation(newValue ?? null);
},
);
},
),
],
),
ExpansionTileCustom(
title:
AppLocalizations.of(context)!.meetingmanager,
widgets: <Widget>[
ListView.builder(
primary: false,
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount:
globalState.meetingsManagerModel!.length,
itemBuilder:
(BuildContext context, int index) {
final items = globalState
.meetingsManagerModel![index];
return RadioListTile<int>(
toggleable: true,
groupValue:
reportState.selectedManagersId,
value: items.id ?? -1,
title: Text(
items.name ?? '',
style: TextStyle(
fontWeight: FontWeight.w100,
fontSize: 14),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
activeColor: config.ui.secendGreen,
onChanged: (int? newValue) {
reportState
.selectManager(newValue ?? null);
},
);
},
),
],
),
ExpansionTileCustom(
title: AppLocalizations.of(context)!.subject,
widgets: <Widget>[
ListView.builder(
primary: false,
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: globalState.subjectsModel!.length,
itemBuilder:
(BuildContext context, int index) {
final items =
globalState.subjectsModel![index];
return RadioListTile<int>(
toggleable: true,
groupValue: reportState.selectedSubjectId,
value: items.id ?? -1,
title: Text(
items.subject ?? '',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontWeight: FontWeight.w100,
fontSize: 14),
),
activeColor: config.ui.secendGreen,
onChanged: (int? newValue) {
reportState
.selectSubject(newValue ?? null);
},
);
},
),
],
),
Divider(),
SizedBox(
height: 250,
child: ListView.builder(
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
primary: false,
itemCount: meetingStatuses.length,
itemBuilder: (BuildContext context, int index) {
final items = meetingStatuses[index];
return RadioListTile<int>(
toggleable: true,
groupValue: reportState.selectedStatusId,
value: items.id,
title: Text(
items.title,
maxLines: 1,
style: TextStyle(
fontWeight: FontWeight.w100,
fontSize: 14),
overflow: TextOverflow.ellipsis,
),
activeColor: config.ui.secendGreen,
onChanged: (int? newValue) {
reportState.selectStatusMeeting(
newValue ?? null);
},
);
},
),
),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 20, vertical: 50),
child: downloadButton(reportState),
)
],
),
],
),
),
),
],
);
case Status.loading:
return const LoadingWidget();
case Status.error:
return CustomErrorWidget(
onPressed: () async {
await globalState.getAllFiltersItems(refresh: true);
},
);
default:
return Container();
}
},
);
}

CustomButton downloadButton(ReportState state) {
switch (state.statusDownload) {
case Status.loading:
return CustomButton(
borderRadius: 15,
hieght: 50,
text: AppLocalizations.of(context)!.loading,
width: double.infinity,
);

default:
return CustomButton(
borderRadius: 15,
hieght: 50,
text: AppLocalizations.of(context)!.downloadreport,
width: double.infinity,
onPressed: () async {
bool hasPermission = await hasStoragePermission();
if (!hasPermission) {
Tools.showCustomSnackBar(context,
text: 'Permission denied. Please allow storage access.',
isError: true);
return;
}

// Download the file
await state.downloadReport(
toDate: reportState!.toDate,
fromDate: reportState!.fromDate,
location: reportState!.selectedLocationId,
subject: reportState!.selectedSubjectId,
meetingManager: reportState!.selectedManagersId,
status: reportState!.selectedStatusId);

if (state.statusDownload == Status.ready) {
await OpenFile.open(state.messageDownload);
// print(status.message);
} else {
Tools.showCustomSnackBar(
context,
text: AppLocalizations.of(context)!.error,
isError: true,
);
}
},
);
}
}

Future<bool> hasStoragePermission() async {
if (Platform.isAndroid) {
final status = await Permission.storage.status;
if (status != PermissionStatus.granted) {
final result = await Permission.manageExternalStorage.request();
if (result == PermissionStatus.granted) {
return true;
}
} else {
return true;
}
} else {
return true;
}
return false;
}
}

class LineButtomSheet extends StatelessWidget {
const LineButtomSheet({
super.key,
});

@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.only(top: 8.0),
width: 30.0,
height: 3.0,
decoration: BoxDecoration(
color: Colors.grey.shade400,
borderRadius: BorderRadius.circular(24.0),
),
);
}
}

class MeetingsStatus {
int id;
String title;
MeetingsStatus({
required this.id,
required this.title,
});
}

+ 113
- 0
lib/screens/report/state.dart Просмотреть файл

@@ -0,0 +1,113 @@
import 'package:flutter/material.dart';
import 'package:qadirneyriz/services/report/report.dart';
import 'package:qadirneyriz/utils/enums/status.dart';

class ReportState extends ChangeNotifier {
ReportApi reportApi = ReportApi();
// set date for filters
String fromDate = '';
String toDate = '';

void setFromDates(String? newFromDate) {
fromDate = newFromDate ?? '';
notifyListeners();
}

void setToDates(String? newToDate) {
toDate = newToDate ?? '';
notifyListeners();
}

// clear filters
void clearFilters() {
selectedLocationId = null;
selectedManagersId = null;
selectedStatusId = null;
selectedSubjectId = null;
fromDate = '';
toDate = '';
notifyListeners();
}

// is filter Not empty
bool hasActiveFilters() {
return selectedLocationId != null ||
selectedManagersId != null ||
selectedStatusId != null ||
selectedSubjectId != null ||
fromDate.isNotEmpty ||
toDate.isNotEmpty;
}

// get filters location meetings

int? selectedLocationId;
void selectLocation(int? locationId) {
selectedLocationId = locationId;
notifyListeners();
}

// get filters subjects meetings

int? selectedSubjectId;
void selectSubject(int? subjectId) {
selectedSubjectId = subjectId;
notifyListeners();
}
// get filters meeting managers

int? selectedManagersId;
void selectManager(int? managerId) {
selectedManagersId = managerId;
notifyListeners();
}

// all meeting status filters

int? selectedStatusId;
void selectStatusMeeting(int? statusId) {
selectedStatusId = statusId;
notifyListeners();
}

// download report

Status statusDownload = Status.empty;
String? messageDownload;

Future<Status> downloadReport(
{String? fromDate,
String? toDate,
int? location,
int? subject,
int? meetingManager,
int? status}) async {
statusDownload = Status.loading;
notifyListeners();
try {
final result = await reportApi.downloadReport(
fromDate: fromDate,
toDate: toDate,
location: location,
subject: subject,
meetingManager: meetingManager,
status: status);

if (result == null) {
statusDownload = Status.error;
} else {
if (result.isOk) {
statusDownload = Status.ready;
messageDownload = result.message ?? '';
} else {
statusDownload = Status.error;
}
}
} catch (e) {
statusDownload = Status.error;
}

notifyListeners();
return statusDownload;
}
}

+ 15
- 7
lib/services/global/global.dart Просмотреть файл

@@ -14,7 +14,8 @@ class GlobalServices {
'Accept': 'application/json',
};

final String link = "${config.network.baseUrl}admin/locations";
final String link =
"${config.network.baseUrl}admin/locations?lang=${setting.userLocalDb.getUser().language}";

final response = await Dio().get(link,
options: Options(
@@ -32,7 +33,8 @@ class GlobalServices {
'Accept': 'application/json',
};

final String link = "${config.network.baseUrl}admin/subjects";
final String link =
"${config.network.baseUrl}admin/subjects?lang=${setting.userLocalDb.getUser().language}";

final response = await Dio().get(link,
options: Options(
@@ -41,7 +43,7 @@ class GlobalServices {
final list = response.data
.map<SubjectsModel>((e) => SubjectsModel.fromJson(e))
.toList();
print('$list subjecthaaaaa');
return list;
}

@@ -55,7 +57,8 @@ class GlobalServices {
headers['Authorization'] = "Bearer $dataToken";
}

final String link = "${config.network.baseUrl}meeting-manager";
final String link =
"${config.network.baseUrl}meeting-manager?lang=${setting.userLocalDb.getUser().language}";

final response = await Dio().get(link,
options: Options(
@@ -77,7 +80,8 @@ class GlobalServices {
headers['Authorization'] = "Bearer $dataToken";
}

final String link = "${config.network.baseUrl}admin/users";
final String link =
"${config.network.baseUrl}admin/users?lang=${setting.userLocalDb.getUser().language}";

final response = await Dio().get(link,
options: Options(
@@ -91,6 +95,7 @@ class GlobalServices {
// add new subject
Future<Result> addNewSubject({
required String subject,
required String enSubject,
}) async {
try {
Map<String, String> headers = {"Accept": "application/json"};
@@ -100,7 +105,8 @@ class GlobalServices {
if (dataToken != '') {
headers['Authorization'] = "Bearer $dataToken";
}
formData = FormData.fromMap({"subject": subject});
formData =
FormData.fromMap({"subject": subject, "subject_en": enSubject});

final res = await Dio().post("${config.network.baseUrl}admin/add-subject",
data: formData, options: Options(headers: headers));
@@ -153,6 +159,7 @@ class GlobalServices {
// add new location
Future<Result> addNewLocation({
required String address,
required String addressEn,
}) async {
try {
Map<String, String> headers = {"Accept": "application/json"};
@@ -162,7 +169,8 @@ class GlobalServices {
if (dataToken != '') {
headers['Authorization'] = "Bearer $dataToken";
}
formData = FormData.fromMap({"address": address});
formData =
FormData.fromMap({"address": address, "address_en": addressEn});

final res = await Dio().post(
"${config.network.baseUrl}admin/add-location",


+ 31
- 1
lib/services/home/home.dart Просмотреть файл

@@ -2,6 +2,7 @@ import 'package:dio/dio.dart';
import 'package:qadirneyriz/config/config.dart';
import 'package:qadirneyriz/models/home/home_models.dart';
import 'package:qadirneyriz/setting/setting.dart';
import 'package:qadirneyriz/utils/result/result.dart';

class HomeApi {
getTodayMeetings() async {
@@ -12,7 +13,8 @@ class HomeApi {
if (dataToken != '') {
headers['Authorization'] = "Bearer $dataToken";
}
final String link = "${config.network.baseUrl}today-meetings";
final String link =
"${config.network.baseUrl}today-meetings?lang=${setting.userLocalDb.getUser().language}";

final response = await Dio().get(link,
options: Options(
@@ -22,4 +24,32 @@ class HomeApi {

return list;
}

// log out
Future<Result?> logOutApi() async {
try {
Map<String, String> headers = {"Accept": "application/json"};

String dataToken = setting.userLocalDb.getUser().token!;
if (dataToken != '') {
headers['Authorization'] = "Bearer $dataToken";
}

final res = await Dio().post("${config.network.baseUrl}admin/logout",
options: Options(headers: headers));

if (res.statusCode == 200 || res.statusCode == 201) {
setting.userLocalDb.logOut();
return const Result(isOk: true);
}
} on DioException catch (e) {
// print(e);
// print(e.response!.data);
return Result(
isOk: false,
errors: e.response!.data['errors'],
message: e.response!.data['message']);
}
return const Result(isOk: false);
}
}

+ 172
- 9
lib/services/meetings/meetings.dart Просмотреть файл

@@ -1,3 +1,5 @@
import 'dart:io';
import 'package:path_provider/path_provider.dart';
import 'package:dio/dio.dart';
import 'package:file_picker/file_picker.dart';
import 'package:qadirneyriz/config/config.dart';
@@ -25,9 +27,10 @@ class MeetingsApi {
headers['Authorization'] = "Bearer $dataToken";
}

final String link = "${config.network.baseUrl}meetings";
print(
'Parameters: count: $count, page: $page, fromDate: $fromDate, toDate: $toDate, location: $location, subject: $subject, meetingManager: $meetingManager, status: $status');
final String link =
"${config.network.baseUrl}meetings?lang=${setting.userLocalDb.getUser().language}";
// print(
// 'Parameters: count: $count, page: $page, fromDate: $fromDate, toDate: $toDate, location: $location, subject: $subject, meetingManager: $meetingManager, status: $status');

final response = await Dio().get(link,
options: Options(
@@ -59,7 +62,8 @@ class MeetingsApi {
headers['Authorization'] = "Bearer $dataToken";
}

final String link = "${config.network.baseUrl}meeting/$id";
final String link =
"${config.network.baseUrl}meeting/$id?lang=${setting.userLocalDb.getUser().language}";

final response = await Dio().get(
link,
@@ -67,12 +71,65 @@ class MeetingsApi {
headers: headers,
),
);
print('${response.data} response.data');
final OneMeetingModel oneMeet = OneMeetingModel.fromJson(response.data);

return oneMeet;
}

// add meeting
Future<Result> addMeetingApi(
{int? locationId,
int? subjectId,
int? managerId,
required String fromHour,
required String toHour,
required String dateMeeting,
required List<int> members}) async {
try {
Map<String, String> headers = {"Accept": "application/json"};
String dataToken = setting.userLocalDb.getUser().token!;
if (dataToken != '') {
headers['Authorization'] = "Bearer $dataToken";
}
FormData? formData;

if (managerId != null) {
formData = FormData.fromMap({
'locations_id': locationId,
'subject_id': subjectId,
'az_hour': fromHour,
'ta_hour': toHour,
'members[]': members,
'date_meeting': dateMeeting,
});
} else {
formData = FormData.fromMap({
'locations_id': locationId,
'subject_id': subjectId,
'manager_id': managerId,
'az_hour': fromHour,
'ta_hour': toHour,
'members[]': members,
'date_meeting': dateMeeting,
});
}
print('${formData.fields} saggggggggg');
final res = await Dio().post("${config.network.baseUrl}admin/add-meeting",
data: formData, options: Options(headers: headers));

if (res.statusCode == 200 || res.statusCode == 201) {
return Result(isOk: true, message: res.data['message']);
}
} on DioException catch (e) {
print('${e.message}');
return Result(
isOk: false,
errors: e.response!.data['errors'],
message: e.response!.data['message']);
}
return const Result(isOk: false);
}

// edit meeting
Future<Result> editMeetingApi(
{required int id,
@@ -101,7 +158,7 @@ class MeetingsApi {
'members[]': members,
'date_meeting': dateMeeting,
});
print('${formData.fields} things to send');
// print('${formData.fields} things to send');
final res = await Dio().post(
"${config.network.baseUrl}admin/edit-meeting",
data: formData,
@@ -111,7 +168,7 @@ class MeetingsApi {
return Result(isOk: true, message: res.data['message']);
}
} on DioException catch (e) {
print(e);
// print(e);
return Result(
isOk: false,
errors: e.response!.data['errors'],
@@ -144,7 +201,6 @@ class MeetingsApi {
return Result(isOk: true, message: res.data['message']);
}
} on DioException catch (e) {
print(e);
return Result(
isOk: false,
errors: e.response!.data['errors'],
@@ -177,7 +233,6 @@ class MeetingsApi {
return Result(isOk: true, message: res.data['message']);
}
} on DioException catch (e) {
print(e);
return Result(
isOk: false,
errors: e.response!.data['errors'],
@@ -226,6 +281,114 @@ class MeetingsApi {
options: Options(headers: headers),
);

// Check response status
if (res.statusCode == 200 || res.statusCode == 201) {
return Result(isOk: true, message: res.data['message']);
}
} on DioException catch (e) {
return Result(
isOk: false,
errors: e.response?.data['errors'],
message: e.response?.data['message'],
);
}
return const Result(isOk: false);
}

// download meeting summary
Future<Result?> downloadSummary(
{required int id, String? format = 'zip'}) async {
try {
final Map<String, String> headers = {"Accept": "application/json"};
String dataToken = setting.userLocalDb.getUser().token!;
if (dataToken != '') {
headers['Authorization'] = "Bearer $dataToken";
}
final Directory tempDir = await getApplicationDocumentsDirectory();

final String tempPath = tempDir.path;
final String savePath = '$tempPath/sammary_$id.$format';

final res = await Dio().download(
'${config.network.baseUrl}download-minutes/$id',
savePath,
options: Options(headers: headers),
);

if (res.statusCode == 200 || res.statusCode == 201) {
return Result(isOk: true, message: savePath);
} else {
return Result(
isOk: false, message: 'Failed with status code: ${res.statusCode}');
}
} on DioException catch (e) {
return Result(
isOk: false,
errors: e.response?.data['errors'],
message:
e.response?.data['message'] ?? 'An error occurred during download.',
);
}
}

// get String file of summary
Future<List<String>> getListStringFils({required int id}) async {
Map<String, String> headers = {
'Accept': 'application/json',
};

String dataToken = setting.userLocalDb.getUser().token!;
if (dataToken.isNotEmpty) {
headers['Authorization'] = "Bearer $dataToken";
}

// ساخت لینک API
final String link =
"${config.network.baseUrl}admin/all-meeting-minutes/$id";

// فراخوانی API
final response = await Dio().get(
link,
options: Options(
headers: headers,
),
);

print('${response.data} response.data');

// بررسی ساختار پاسخ و تبدیل داده‌ها
if (response.data is List) {
return List<String>.from(response.data);
} else {
return [];
}
}

// delete file of summary
Future<Result> deleteFileSummary({
required int id,
required String text,
}) async {
try {
Map<String, String> headers = {"Accept": "application/json"};
String dataToken = setting.userLocalDb.getUser().token!;
if (dataToken.isNotEmpty) {
headers['Authorization'] = "Bearer $dataToken";
}

// Create FormData
FormData formData = FormData();

// Send request
final link =
"${config.network.baseUrl}admin/delete-meeting-minutes/$id/$text";
print('${link}');
final res = await Dio().get(
link,
data: formData,
options: Options(headers: headers),
);

// Check response status
if (res.statusCode == 200 || res.statusCode == 201) {
return Result(isOk: true, message: res.data['message']);


+ 427
- 0
lib/services/private_meetings/private_meetings.dart Просмотреть файл

@@ -0,0 +1,427 @@
import 'dart:io';
import 'package:path_provider/path_provider.dart';
import 'package:dio/dio.dart';
import 'package:file_picker/file_picker.dart';
import 'package:qadirneyriz/config/config.dart';
import 'package:qadirneyriz/models/private_meeting/one_private_meeting_model.dart';
import 'package:qadirneyriz/models/private_meeting/private_meetings_model.dart';
import 'package:qadirneyriz/setting/setting.dart';
import 'package:qadirneyriz/utils/result/result.dart';

class PrivateMeetingsApi {
// get all
Future<PrivateMeetingsModel> getPrivateMeetings({
required int count,
required int page,
String? fromDate,
String? toDate,
int? location,
int? subject,
int? meetingManager,
int? status,
}) async {
Map<String, String> headers = {
'Accept': 'application/json',
};
String dataToken = setting.userLocalDb.getUser().token!;
if (dataToken != '') {
headers['Authorization'] = "Bearer $dataToken";
}

final String link =
"${config.network.baseUrl}private_meetings?lang=${setting.userLocalDb.getUser().language}";
// print(
// 'Parameters: count: $count, page: $page, fromDate: $fromDate, toDate: $toDate, location: $location, subject: $subject, meetingManager: $meetingManager, status: $status');

final response = await Dio().get(link,
options: Options(
headers: headers,
),
queryParameters: {
'count': count,
'page': page,
'date_meeting_az': fromDate,
'date_meeting_ta': toDate,
'location': location,
'subject': subject,
'meeting_manager': meetingManager,
'status': status,
});

final PrivateMeetingsModel privateMeetingsList =
PrivateMeetingsModel.fromJson(response.data);

return privateMeetingsList;
}
// add private meeting

Future<Result> addPrivateMeetingApi({
int? locationId,
int? subjectId,
int? managerId,
required String fromHour,
required String toHour,
required String dateMeeting,
required String visitorName,
required String visitorMobile,
required String visitorRole,
required String visitorCompany,
}) async {
try {
Map<String, String> headers = {"Accept": "application/json"};
String dataToken = setting.userLocalDb.getUser().token!;
if (dataToken != '') {
headers['Authorization'] = "Bearer $dataToken";
}
FormData? formData;

if (managerId != null) {
formData = FormData.fromMap({
'locations_id': locationId,
'subject_id': subjectId,
'manager_id': managerId,
'az_hour': fromHour,
'ta_hour': toHour,
'date_meeting': dateMeeting,
'visit_name': visitorName,
'visit_mobile': visitorMobile,
'visit_role': visitorRole,
'visit_company': visitorCompany
});
} else {
formData = FormData.fromMap({
'locations_id': locationId,
'subject_id': subjectId,
'az_hour': fromHour,
'ta_hour': toHour,
'date_meeting': dateMeeting,
'visit_name': visitorName,
'visit_mobile': visitorMobile,
'visit_role': visitorRole,
'visit_company': visitorCompany
});
}

final res = await Dio().post(
"${config.network.baseUrl}admin/add-private-meeting",
data: formData,
options: Options(headers: headers));

if (res.statusCode == 200 || res.statusCode == 201) {
return Result(isOk: true, message: res.data['message']);
}
} on DioException catch (e) {
return Result(
isOk: false,
errors: e.response!.data['errors'],
message: e.response!.data['message']);
}
return const Result(isOk: false);
}

// cancel private meeting
Future<Result> cancelPrivateMeetingApi({
required int id,
}) async {
try {
Map<String, String> headers = {"Accept": "application/json"};
String dataToken = setting.userLocalDb.getUser().token!;
if (dataToken != '') {
headers['Authorization'] = "Bearer $dataToken";
}
FormData? formData;

formData = FormData.fromMap({
'meeting_id': id,
});
final res = await Dio().post(
"${config.network.baseUrl}cancel-private-meeting",
data: formData,
options: Options(headers: headers));

if (res.statusCode == 200 || res.statusCode == 201) {
return Result(isOk: true, message: res.data['message']);
}
} on DioException catch (e) {
return Result(
isOk: false,
errors: e.response!.data['errors'],
message: e.response!.data['message']);
}
return const Result(isOk: false);
}

// accept private meeting
Future<Result> acceptPrivateMeetingApi({
required int id,
}) async {
try {
Map<String, String> headers = {"Accept": "application/json"};
String dataToken = setting.userLocalDb.getUser().token!;
if (dataToken != '') {
headers['Authorization'] = "Bearer $dataToken";
}
FormData? formData;

formData = FormData.fromMap({
'meeting_id': id,
});
final res = await Dio().post(
"${config.network.baseUrl}accept-private-meeting",
data: formData,
options: Options(headers: headers));

if (res.statusCode == 200 || res.statusCode == 201) {
return Result(isOk: true, message: res.data['message']);
}
} on DioException catch (e) {
return Result(
isOk: false,
errors: e.response!.data['errors'],
message: e.response!.data['message']);
}
return const Result(isOk: false);
}

// get one private meeting
Future<OnePrivateMeetingModel> getOnePrivateMeeting(
{required final int id}) async {
Map<String, String> headers = {
'Accept': 'application/json',
};
String dataToken = setting.userLocalDb.getUser().token!;
if (dataToken != '') {
headers['Authorization'] = "Bearer $dataToken";
}

final String link =
"${config.network.baseUrl}private-meeting/$id?lang=${setting.userLocalDb.getUser().language}";

final response = await Dio().get(
link,
options: Options(
headers: headers,
),
);
final OnePrivateMeetingModel onePrivateMeet =
OnePrivateMeetingModel.fromJson(response.data);

return onePrivateMeet;
}

// edit private meeting
Future<Result> editPrivateMeetingApi({
required int id,
required int locationId,
required int subjectId,
required int managerId,
required String fromHour,
required String toHour,
required String dateMeeting,
required String visitorName,
required String visitorMobile,
required String visitorRole,
required String visitorCompany,
}) async {
try {
Map<String, String> headers = {"Accept": "application/json"};
String dataToken = setting.userLocalDb.getUser().token!;
if (dataToken != '') {
headers['Authorization'] = "Bearer $dataToken";
}
FormData? formData;

formData = FormData.fromMap({
'id': id,
'locations_id': locationId,
'subject_id': subjectId,
'manager_id': managerId,
'az_hour': fromHour,
'ta_hour': toHour,
'date_meeting': dateMeeting,
'visit_name': visitorName,
'visit_mobile': visitorMobile,
'visit_role': visitorRole,
'visit_company': visitorCompany
});

final res = await Dio().post(
"${config.network.baseUrl}admin/edit-private-meeting",
data: formData,
options: Options(headers: headers));

if (res.statusCode == 200 || res.statusCode == 201) {
return Result(isOk: true, message: res.data['message']);
}
} on DioException catch (e) {
return Result(
isOk: false,
errors: e.response!.data['errors'],
message: e.response!.data['message']);
}
return const Result(isOk: false);
}

// add private meeting minutes
Future<Result> addMeetingMinuteApi({
required int id,
required String description,
required List<PlatformFile> meetingFiles, // List of PlatformFile
}) async {
try {
Map<String, String> headers = {"Accept": "application/json"};
String dataToken = setting.userLocalDb.getUser().token!;
if (dataToken.isNotEmpty) {
headers['Authorization'] = "Bearer $dataToken";
}

// Create FormData
FormData formData = FormData();

// Add id and description fields
formData.fields.add(MapEntry('id', id.toString()));
formData.fields.add(MapEntry('description', description));

// Check if meetingFiles is not empty and add files to FormData
if (meetingFiles.isNotEmpty) {
for (var file in meetingFiles) {
if (file.path != null) {
// Ensure that the file path is not null
formData.files.add(
MapEntry(
'meeting_files[]', await MultipartFile.fromFile(file.path!)),
);
}
}
}

// Send request
final res = await Dio().post(
"${config.network.baseUrl}admin/add-private-meeting-minutes",
data: formData,
options: Options(headers: headers),
);

// Check response status
if (res.statusCode == 200 || res.statusCode == 201) {
return Result(isOk: true, message: res.data['message']);
}
} on DioException catch (e) {
return Result(
isOk: false,
errors: e.response?.data['errors'],
message: e.response?.data['message'],
);
}
return const Result(isOk: false);
}

// download meeting summary
Future<Result?> downloadSummary(
{required int id, String? format = 'zip'}) async {
try {
final Map<String, String> headers = {"Accept": "application/json"};
String dataToken = setting.userLocalDb.getUser().token!;
if (dataToken != '') {
headers['Authorization'] = "Bearer $dataToken";
}
final Directory tempDir = await getApplicationDocumentsDirectory();

final String tempPath = tempDir.path;
final String savePath = '$tempPath/sammary_$id.$format';

final res = await Dio().download(
'${config.network.baseUrl}private-download-minutes/$id',
savePath,
options: Options(headers: headers),
);

if (res.statusCode == 200 || res.statusCode == 201) {
return Result(isOk: true, message: savePath);
} else {
return Result(
isOk: false, message: 'Failed with status code: ${res.statusCode}');
}
} on DioException catch (e) {
return Result(
isOk: false,
errors: e.response?.data['errors'],
message:
e.response?.data['message'] ?? 'An error occurred during download.',
);
}
}

// get String file of summary
Future<List<String>> getListStringFils({required int id}) async {
Map<String, String> headers = {
'Accept': 'application/json',
};

String dataToken = setting.userLocalDb.getUser().token!;
if (dataToken.isNotEmpty) {
headers['Authorization'] = "Bearer $dataToken";
}

// ساخت لینک API
final String link =
"${config.network.baseUrl}admin/all-private-meeting-minutes/$id";

// فراخوانی API
final response = await Dio().get(
link,
options: Options(
headers: headers,
),
);

print('${response.data} response.data');

// بررسی ساختار پاسخ و تبدیل داده‌ها
if (response.data is List) {
return List<String>.from(response.data);
} else {
return [];
}
}

// delete file of summary
Future<Result> deleteFileSummary({
required int id,
required String text,
}) async {
try {
Map<String, String> headers = {"Accept": "application/json"};
String dataToken = setting.userLocalDb.getUser().token!;
if (dataToken.isNotEmpty) {
headers['Authorization'] = "Bearer $dataToken";
}

// Create FormData
FormData formData = FormData();

// Send request
final link =
"${config.network.baseUrl}admin/delete-private-meeting-minutes/$id/$text";
print('${link}');
final res = await Dio().get(
link,
data: formData,
options: Options(headers: headers),
);

// Check response status
if (res.statusCode == 200 || res.statusCode == 201) {
return Result(isOk: true, message: res.data['message']);
}
} on DioException catch (e) {
print(e);
return Result(
isOk: false,
errors: e.response?.data['errors'],
message: e.response?.data['message'],
);
}
return const Result(isOk: false);
}
}

+ 57
- 0
lib/services/report/report.dart Просмотреть файл

@@ -0,0 +1,57 @@
import 'dart:io';
import 'package:path_provider/path_provider.dart';
import 'package:dio/dio.dart';
import 'package:qadirneyriz/config/config.dart';
import 'package:qadirneyriz/setting/setting.dart';
import 'package:qadirneyriz/utils/result/result.dart';

class ReportApi {
Future<Result?> downloadReport(
{String? fromDate,
String? toDate,
int? location,
int? subject,
int? meetingManager,
int? status,
String format = 'xlsx'}) async {
try {
final Map<String, String> headers = {"Accept": "application/json"};
String dataToken = setting.userLocalDb.getUser().token!;
if (dataToken != '') {
headers['Authorization'] = "Bearer $dataToken";
}
final Directory tempDir = await getApplicationDocumentsDirectory();

final String tempPath = tempDir.path;
final String savePath = '$tempPath/report.$format';

final res = await Dio().download(
'${config.network.baseUrl}statistic',
savePath,
queryParameters: {
'date_meeting_az': fromDate,
'date_meeting_ta': toDate,
'location': location,
'subject': subject,
'meeting_manager': meetingManager,
'status': status,
},
options: Options(headers: headers),
);

if (res.statusCode == 200 || res.statusCode == 201) {
return Result(isOk: true, message: savePath);
} else {
return Result(
isOk: false, message: 'Failed with status code: ${res.statusCode}');
}
} on DioException catch (e) {
return Result(
isOk: false,
errors: e.response?.data['errors'],
message:
e.response?.data['message'] ?? 'An error occurred during download.',
);
}
}
}

+ 3
- 0
lib/setting/app_setting.dart Просмотреть файл

@@ -1,13 +1,16 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
import 'package:shamsi_date/shamsi_date.dart';
import 'package:qadirneyriz/services/global/global.dart';
import 'package:qadirneyriz/utils/hive/user_local_db.dart';

class AppSetting {
UserLocalDb userLocalDb;
GlobalServices globalServices;
Jalali timeNow;

AppSetting({
required this.userLocalDb,
required this.globalServices,
required this.timeNow,
});
}

+ 5
- 2
lib/setting/setting.dart Просмотреть файл

@@ -1,6 +1,9 @@
import 'package:qadirneyriz/services/global/global.dart';
import 'package:qadirneyriz/setting/app_setting.dart';
import 'package:qadirneyriz/utils/hive/user_local_db.dart';
import 'package:shamsi_date/shamsi_date.dart';

final setting =
AppSetting(userLocalDb: UserLocalDb(), globalServices: GlobalServices());
final setting = AppSetting(
userLocalDb: UserLocalDb(),
globalServices: GlobalServices(),
timeNow: Jalali.now());

+ 13
- 8
lib/splash_screen.dart Просмотреть файл

@@ -1,7 +1,6 @@
import 'dart:developer';

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:qadirneyriz/config/config.dart';
import 'package:qadirneyriz/setting/setting.dart';
import 'package:qadirneyriz/widgets/custom_background.dart';
import 'package:qadirneyriz/widgets/loading_widget.dart';
@@ -22,11 +21,17 @@ class _SplashScreenState extends State<SplashScreen> {

@override
Widget build(BuildContext context) {
return const Scaffold(
return Scaffold(
body: CustomBackground(
child: LoadingWidget(
color: Colors.white,
size: 30,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Spacer(),
LoadingWidget(
color: config.ui.mainGreen,
size: 30,
),
],
),
),
);
@@ -34,8 +39,8 @@ class _SplashScreenState extends State<SplashScreen> {

void checkUser() async {
String token = setting.userLocalDb.getUser().token ?? '';
log(token);
Future.delayed(const Duration(seconds: 1), () {
Future.delayed(const Duration(seconds: 4), () {
if (token != '') {
context.goNamed('navigate', pathParameters: {'tab': '0'});
} else {


+ 5
- 5
lib/utils/tools/tools.dart Просмотреть файл

@@ -44,11 +44,11 @@ class Tools {
),
behavior: SnackBarBehavior.floating, // تغییر مکان نمایش به حالت شناور
margin: const EdgeInsets.all(16), // فاصله از لبه‌های صفحه
action: SnackBarAction(
label: 'UNDO',
textColor: Colors.yellow, // رنگ متن اکشن
onPressed: () {},
),
// action: SnackBarAction(
// label: 'UNDO',
// textColor: Colors.yellow, // رنگ متن اکشن
// onPressed: () {},
// ),
),
);
}


+ 8
- 5
lib/widgets/ExpansionTileCustom.dart Просмотреть файл

@@ -59,11 +59,14 @@ class _ExpansionTileCustomState extends State<ExpansionTileCustom> {
: null, // Adjust padding if needed
iconColor: config.ui.secendGreen,
collapsedIconColor: config.ui.secendGreen,
title: Text(
widget.title,
style: TextStyle(
fontSize: 12,
color: Colors.black.withOpacity(.5),
title: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: Text(
widget.title,
style: TextStyle(
fontSize: 12,
color: Colors.black.withOpacity(.5),
),
),
),
children: [


+ 25
- 16
lib/widgets/card_meeting.dart Просмотреть файл

@@ -4,6 +4,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:provider/provider.dart';
import 'package:qadirneyriz/config/config.dart';
import 'package:qadirneyriz/screens/meeting/state.dart';
import 'package:qadirneyriz/screens/private_meeting/screen.dart';

class CustomCardMeeting extends StatelessWidget {
final String titel;
@@ -11,6 +12,7 @@ class CustomCardMeeting extends StatelessWidget {
final String location;
final String fromTime;
final String toTime;
final int status;
final int cardId;
final void Function(String)? onSelectedMoreButton;
final List<PopupMenuEntry<String>> Function(BuildContext)?
@@ -19,6 +21,7 @@ class CustomCardMeeting extends StatelessWidget {
Key? key,
required this.titel,
required this.date,
required this.status,
required this.location,
required this.fromTime,
required this.toTime,
@@ -30,7 +33,7 @@ class CustomCardMeeting extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
child: Consumer<MeetingsState>(
builder: (context, value, child) {
return Container(
@@ -126,23 +129,29 @@ class CustomCardMeeting extends StatelessWidget {
height: 8,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Icon(
Icons.alarm,
color: config.ui.mainGreen,
),
const SizedBox(
width: 8,
),
Flexible(
fit: FlexFit.loose,
child: Text(
'$fromTime ${AppLocalizations.of(context)!.to} $toTime',
maxLines: 1,
style: const TextStyle(fontSize: 14),
overflow: TextOverflow.ellipsis,
),
Row(
children: [
Icon(
Icons.alarm,
color: config.ui.mainGreen,
),
const SizedBox(
width: 8,
),
Text(
'$fromTime ${AppLocalizations.of(context)!.to} $toTime',
maxLines: 1,
style: const TextStyle(fontSize: 14),
overflow: TextOverflow.ellipsis,
),
],
),
if (this.status != 0)
PrivateMeetingLabel(
status: this.status,
)
],
),
],


+ 58
- 0
lib/widgets/checkBox_inTile.dart Просмотреть файл

@@ -0,0 +1,58 @@
import 'package:flutter/material.dart';
import 'package:qadirneyriz/widgets/ink_warpper.dart';

class CheckBoxInTile extends StatelessWidget {
final void Function()? onTap;
final String text;
final bool hasIcon;
final Color backColor;
final Color textColor;

const CheckBoxInTile({
Key? key,
this.onTap,
required this.text,
required this.hasIcon,
required this.backColor,
required this.textColor,
}) : super(key: key);

@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
child: InkWrapper(
borderRadius: 10,
onTap: onTap,
child: Container(
decoration: BoxDecoration(boxShadow: [
BoxShadow(
color: Colors.black12,
blurRadius: 8,
offset: Offset(0, 4),
),
], color: backColor, borderRadius: BorderRadius.circular(10)),
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
maxLines: 1,
overflow: TextOverflow.ellipsis,
text,
style: TextStyle(color: textColor, fontSize: 12),
),
),
if (hasIcon)
Icon(Icons.add_circle_outline,
color: Colors.black.withOpacity(.3))
],
),
),
),
),
);
}
}

+ 2
- 1
lib/widgets/custom_appbar.dart Просмотреть файл

@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:qadirneyriz/config/config.dart';

class CustomAppbar extends StatelessWidget {
final String? title;
@@ -27,7 +28,7 @@ class CustomAppbar extends StatelessWidget {
),
],
),
backgroundColor: Colors.white,
backgroundColor: config.ui.backGroundColor,
);
}
}

+ 12
- 15
lib/widgets/custom_background.dart Просмотреть файл

@@ -1,7 +1,4 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:qadirneyriz/config/config.dart';

class CustomBackground extends StatelessWidget {
final Widget child;
@@ -13,21 +10,21 @@ class CustomBackground extends StatelessWidget {
return Container(
width: double.infinity,
height: double.infinity,
decoration: BoxDecoration(color: config.ui.mainGreen),
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('assets/images/001.jpg'), // اصلاح شده
fit: BoxFit.cover, // تصویر را به صورت تمام صفحه تطبیق می‌دهد
colorFilter: opacity != null
? ColorFilter.mode(
Colors.black.withOpacity(opacity!),
BlendMode.dstATop,
)
: null,
),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Center(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 50),
child: SizedBox(
width: 500,
child: Image.asset(
'assets/images/logo.png',
),
),
),
),
Expanded(
child: child,
)


+ 4
- 20
lib/widgets/custom_button.dart Просмотреть файл

@@ -11,10 +11,7 @@ class CustomButton extends StatelessWidget {
this.width,
this.isCircle = false,
this.color,
this.topLeftRadius = 20,
this.topRightRadius = 20,
this.bottomLeftRadius = 20,
this.bottomRightRadius = 20,
this.borderRadius = 20,
this.fontWeight,
this.gradient,
this.textColor = Colors.white,
@@ -26,14 +23,11 @@ class CustomButton extends StatelessWidget {
String text;
bool isCircle;
final Gradient? gradient;
final double borderRadius;
double? fontSize;
Color? color;
Color textColor;
FontWeight? fontWeight;
final double topLeftRadius;
final double topRightRadius;
final double bottomLeftRadius;
final double bottomRightRadius;
void Function()? onPressed;

@override
@@ -45,12 +39,7 @@ class CustomButton extends StatelessWidget {
gradient: gradient,
borderRadius: isCircle
? null // اگر دکمه دایره باشد، از borderRadius استفاده نمی‌شود
: BorderRadius.only(
topLeft: Radius.circular(topLeftRadius),
topRight: Radius.circular(topRightRadius),
bottomLeft: Radius.circular(bottomLeftRadius),
bottomRight: Radius.circular(bottomRightRadius),
),
: BorderRadius.circular(borderRadius),
),
child: ElevatedButton(
onPressed: onPressed,
@@ -61,12 +50,7 @@ class CustomButton extends StatelessWidget {
shadowColor: (gradient != null) ? Colors.transparent : null,
shape: isCircle == false
? RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(topLeftRadius),
topRight: Radius.circular(topRightRadius),
bottomLeft: Radius.circular(bottomLeftRadius),
bottomRight: Radius.circular(bottomRightRadius),
),
borderRadius: BorderRadius.circular(borderRadius),
)
: const CircleBorder(),
),


+ 8
- 3
lib/widgets/custom_textfield.dart Просмотреть файл

@@ -69,8 +69,9 @@ class _CustomTextFieldState extends State<CustomTextField> {
child: Text(
widget.label,
style: TextStyle(
color: config.ui.mainGray.withOpacity(.7),
fontSize: 15,
fontWeight: FontWeight.normal,
fontSize: 13,
color: Colors.black.withOpacity(.8),
),
),
)
@@ -97,7 +98,11 @@ class _CustomTextFieldState extends State<CustomTextField> {
keyboardType: widget.textInputType,
textInputAction: widget.textInputAction,
obscureText: (widget.isPass) ? obscureText : false,
style: const TextStyle(color: Colors.black),
style: TextStyle(
fontSize: 12,
color: Colors.black.withOpacity(.5),
),

decoration: InputDecoration(
hintText: widget.hintText,
hintStyle: TextStyle(


+ 2
- 2
lib/widgets/empty_widget.dart Просмотреть файл

@@ -35,7 +35,7 @@ class EmptyStateWidget extends StatelessWidget {
Text(
text ?? AppLocalizations.of(context)!.empty,
style: TextStyle(
fontSize: 14.0,
fontSize: 12.0,
color: color,
),
),
@@ -43,7 +43,7 @@ class EmptyStateWidget extends StatelessWidget {
isBack == true
? CustomButton(
hieght: 50,
width: 150,
fontSize: 12,
text: AppLocalizations.of(context)!.back,
onPressed: onPressed)
: Container()


+ 7
- 3
lib/widgets/error_widget.dart Просмотреть файл

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:qadirneyriz/config/config.dart';
import 'package:qadirneyriz/widgets/custom_button.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';

class CustomErrorWidget extends StatelessWidget {
const CustomErrorWidget({
@@ -16,17 +17,20 @@ class CustomErrorWidget extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Something went wrong!',
AppLocalizations.of(context)!.error,
style: TextStyle(
color: config.ui.mainGray,
fontSize: 16,
fontSize: 12,
fontWeight: FontWeight.bold),
),
const SizedBox(
height: 20,
),
CustomButton(
hieght: 50, width: 183, text: 'Try again!', onPressed: onPressed)
hieght: 50,
fontSize: 12,
text: AppLocalizations.of(context)!.tryagain,
onPressed: onPressed)
],
),
);


+ 4
- 0
linux/flutter/generated_plugin_registrant.cc Просмотреть файл

@@ -6,6 +6,10 @@

#include "generated_plugin_registrant.h"

#include <open_file_linux/open_file_linux_plugin.h>

void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) open_file_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "OpenFileLinuxPlugin");
open_file_linux_plugin_register_with_registrar(open_file_linux_registrar);
}

+ 1
- 0
linux/flutter/generated_plugins.cmake Просмотреть файл

@@ -3,6 +3,7 @@
#

list(APPEND FLUTTER_PLUGIN_LIST
open_file_linux
)

list(APPEND FLUTTER_FFI_PLUGIN_LIST


+ 2
- 0
macos/Flutter/GeneratedPluginRegistrant.swift Просмотреть файл

@@ -5,10 +5,12 @@
import FlutterMacOS
import Foundation

import open_file_mac
import path_provider_foundation
import sqflite

func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
OpenFilePlugin.register(with: registry.registrar(forPlugin: "OpenFilePlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
}

+ 112
- 0
pubspec.lock Просмотреть файл

@@ -538,6 +538,70 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.0"
open_file:
dependency: "direct main"
description:
name: open_file
sha256: "737641e823d568a12b63494855010ceef286bcdf8f88d0a831e53229a5e850e8"
url: "https://pub.dev"
source: hosted
version: "3.5.9"
open_file_android:
dependency: transitive
description:
name: open_file_android
sha256: "58141fcaece2f453a9684509a7275f231ac0e3d6ceb9a5e6de310a7dff9084aa"
url: "https://pub.dev"
source: hosted
version: "1.0.6"
open_file_ios:
dependency: transitive
description:
name: open_file_ios
sha256: "02996f01e5f6863832068e97f8f3a5ef9b613516db6897f373b43b79849e4d07"
url: "https://pub.dev"
source: hosted
version: "1.0.3"
open_file_linux:
dependency: transitive
description:
name: open_file_linux
sha256: d189f799eecbb139c97f8bc7d303f9e720954fa4e0fa1b0b7294767e5f2d7550
url: "https://pub.dev"
source: hosted
version: "0.0.5"
open_file_mac:
dependency: transitive
description:
name: open_file_mac
sha256: dd1570bd12601b4d50fda3609c1662382f17ee403b47f0d74d737de603a39ec6
url: "https://pub.dev"
source: hosted
version: "1.0.2"
open_file_platform_interface:
dependency: transitive
description:
name: open_file_platform_interface
sha256: "101b424ca359632699a7e1213e83d025722ab668b9fd1412338221bf9b0e5757"
url: "https://pub.dev"
source: hosted
version: "1.0.3"
open_file_web:
dependency: transitive
description:
name: open_file_web
sha256: e3dbc9584856283dcb30aef5720558b90f88036360bd078e494ab80a80130c4f
url: "https://pub.dev"
source: hosted
version: "0.0.4"
open_file_windows:
dependency: transitive
description:
name: open_file_windows
sha256: d26c31ddf935a94a1a3aa43a23f4fff8a5ff4eea395fe7a8cb819cf55431c875
url: "https://pub.dev"
source: hosted
version: "0.0.3"
package_config:
dependency: transitive
description:
@@ -602,6 +666,54 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.3.0"
permission_handler:
dependency: "direct main"
description:
name: permission_handler
sha256: "18bf33f7fefbd812f37e72091a15575e72d5318854877e0e4035a24ac1113ecb"
url: "https://pub.dev"
source: hosted
version: "11.3.1"
permission_handler_android:
dependency: transitive
description:
name: permission_handler_android
sha256: "71bbecfee799e65aff7c744761a57e817e73b738fedf62ab7afd5593da21f9f1"
url: "https://pub.dev"
source: hosted
version: "12.0.13"
permission_handler_apple:
dependency: transitive
description:
name: permission_handler_apple
sha256: e6f6d73b12438ef13e648c4ae56bd106ec60d17e90a59c4545db6781229082a0
url: "https://pub.dev"
source: hosted
version: "9.4.5"
permission_handler_html:
dependency: transitive
description:
name: permission_handler_html
sha256: af26edbbb1f2674af65a8f4b56e1a6f526156bc273d0e65dd8075fab51c78851
url: "https://pub.dev"
source: hosted
version: "0.1.3+2"
permission_handler_platform_interface:
dependency: transitive
description:
name: permission_handler_platform_interface
sha256: e9c8eadee926c4532d0305dff94b85bf961f16759c3af791486613152af4b4f9
url: "https://pub.dev"
source: hosted
version: "4.2.3"
permission_handler_windows:
dependency: transitive
description:
name: permission_handler_windows
sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e"
url: "https://pub.dev"
source: hosted
version: "0.2.1"
platform:
dependency: transitive
description:


+ 4
- 1
pubspec.yaml Просмотреть файл

@@ -32,13 +32,16 @@ dependencies:
flutter_staggered_grid_view: ^0.7.0
flutter_persian_calendar: ^0.0.2
file_picker: ^8.1.3
permission_handler: ^11.3.1
open_file: ^3.5.9

dev_dependencies:
flutter_test:
sdk: flutter

# flutter_lints: ^4.0.0
build_runner: ^2.4.9


flutter:
generate: true
uses-material-design: true


+ 3
- 0
windows/flutter/generated_plugin_registrant.cc Просмотреть файл

@@ -6,6 +6,9 @@

#include "generated_plugin_registrant.h"

#include <permission_handler_windows/permission_handler_windows_plugin.h>

void RegisterPlugins(flutter::PluginRegistry* registry) {
PermissionHandlerWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
}

+ 1
- 0
windows/flutter/generated_plugins.cmake Просмотреть файл

@@ -3,6 +3,7 @@
#

list(APPEND FLUTTER_PLUGIN_LIST
permission_handler_windows
)

list(APPEND FLUTTER_FFI_PLUGIN_LIST


Загрузка…
Отмена
Сохранить