| @@ -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 | |||
| @@ -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 +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"}} | |||
| @@ -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> | |||
| @@ -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(); | |||
| } | |||
| @@ -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, | |||
| ); | |||
| } | |||
| } | |||
| @@ -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, | |||
| ); | |||
| } | |||
| } | |||
| @@ -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, | |||
| ); | |||
| } | |||
| } | |||
| @@ -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(); | |||
| } | |||
| } | |||
| @@ -0,0 +1,8 @@ | |||
| class ItemSelected { | |||
| final String? text; | |||
| final int? id; | |||
| ItemSelected({ | |||
| this.text, | |||
| this.id, | |||
| }); | |||
| } | |||
| @@ -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; | |||
| @@ -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." | |||
| } | |||
| @@ -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":"نرمافزار مدیریت جلسات و ملاقاتهای “میزبان” با هدف تسهیل و بهینهسازی فرآیندهای برگزاری جلسات سازمانی و شخصی جناب آقای دکتر محسن مصطفی پور طراحی و توسعه یافته تا ابزاری کارآمد و نوآورانه برای تحقق بیشتر اسم رمز همدلی در مجموعه معظم فولاد غدیر نی ریز باشد . این نرم افزار با حمایت ، همت و پشتیبانی بیدریغ مدیریت محترم عامل ( دکتر محسن مصطفی پور ) ایجاد ، توسعه و راه اندازی شده است." | |||
| } | |||
| @@ -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), | |||
| @@ -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; | |||
| @@ -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(), | |||
| }; | |||
| } | |||
| @@ -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; | |||
| } | |||
| } | |||
| @@ -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(), | |||
| ); | |||
| }, | |||
| ), | |||
| ], | |||
| ); | |||
| @@ -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), | |||
| ), | |||
| ], | |||
| ) | |||
| ], | |||
| ), | |||
| ) | |||
| ], | |||
| ); | |||
| } | |||
| } | |||
| @@ -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 | |||
| @@ -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; | |||
| } | |||
| @@ -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), | |||
| ), | |||
| ], | |||
| ), | |||
| @@ -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; | |||
| } | |||
| } | |||
| @@ -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: | |||
| @@ -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, | |||
| ); | |||
| @@ -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; | |||
| } | |||
| } | |||
| @@ -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); | |||
| } | |||
| } | |||
| } | |||
| @@ -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; | |||
| } | |||
| } | |||
| @@ -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, | |||
| }); | |||
| } | |||
| @@ -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; | |||
| } | |||
| } | |||
| @@ -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, | |||
| ), | |||
| ], | |||
| ), | |||
| ), | |||
| ); | |||
| @@ -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; | |||
| } | |||
| } | |||
| @@ -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, | |||
| }); | |||
| } | |||
| @@ -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)), | |||
| ); | |||
| } | |||
| } | |||
| @@ -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]!; | |||
| } | |||
| } | |||
| @@ -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); | |||
| } | |||
| } | |||
| } | |||
| @@ -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; | |||
| } | |||
| } | |||
| @@ -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); | |||
| } | |||
| } | |||
| } | |||
| @@ -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; | |||
| } | |||
| } | |||
| @@ -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), | |||
| ); | |||
| } | |||
| } | |||
| @@ -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; | |||
| } | |||
| } | |||
| @@ -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, | |||
| }); | |||
| } | |||
| @@ -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; | |||
| } | |||
| } | |||
| @@ -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", | |||
| @@ -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); | |||
| } | |||
| } | |||
| @@ -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']); | |||
| @@ -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); | |||
| } | |||
| } | |||
| @@ -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.', | |||
| ); | |||
| } | |||
| } | |||
| } | |||
| @@ -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, | |||
| }); | |||
| } | |||
| @@ -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()); | |||
| @@ -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 { | |||
| @@ -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: () {}, | |||
| // ), | |||
| ), | |||
| ); | |||
| } | |||
| @@ -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: [ | |||
| @@ -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, | |||
| ) | |||
| ], | |||
| ), | |||
| ], | |||
| @@ -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)) | |||
| ], | |||
| ), | |||
| ), | |||
| ), | |||
| ), | |||
| ); | |||
| } | |||
| } | |||
| @@ -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, | |||
| ); | |||
| } | |||
| } | |||
| @@ -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, | |||
| ) | |||
| @@ -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(), | |||
| ), | |||
| @@ -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( | |||
| @@ -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() | |||
| @@ -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) | |||
| ], | |||
| ), | |||
| ); | |||
| @@ -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); | |||
| } | |||
| @@ -3,6 +3,7 @@ | |||
| # | |||
| list(APPEND FLUTTER_PLUGIN_LIST | |||
| open_file_linux | |||
| ) | |||
| list(APPEND FLUTTER_FFI_PLUGIN_LIST | |||
| @@ -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")) | |||
| } | |||
| @@ -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: | |||
| @@ -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 | |||
| @@ -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")); | |||
| } | |||
| @@ -3,6 +3,7 @@ | |||
| # | |||
| list(APPEND FLUTTER_PLUGIN_LIST | |||
| permission_handler_windows | |||
| ) | |||
| list(APPEND FLUTTER_FFI_PLUGIN_LIST | |||