| @@ -500,8 +500,8 @@ | |||
| CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; | |||
| CODE_SIGN_IDENTITY = "Apple Development"; | |||
| CODE_SIGN_STYLE = Automatic; | |||
| CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; | |||
| DEVELOPMENT_TEAM = FE8CDVE4RJ; | |||
| CURRENT_PROJECT_VERSION = 4; | |||
| DEVELOPMENT_TEAM = GQA553X9YL; | |||
| ENABLE_BITCODE = NO; | |||
| INFOPLIST_FILE = Runner/Info.plist; | |||
| LD_RUNPATH_SEARCH_PATHS = ( | |||
| @@ -687,8 +687,8 @@ | |||
| CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; | |||
| CODE_SIGN_IDENTITY = "Apple Development"; | |||
| CODE_SIGN_STYLE = Automatic; | |||
| CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; | |||
| DEVELOPMENT_TEAM = FE8CDVE4RJ; | |||
| CURRENT_PROJECT_VERSION = 4; | |||
| DEVELOPMENT_TEAM = GQA553X9YL; | |||
| ENABLE_BITCODE = NO; | |||
| INFOPLIST_FILE = Runner/Info.plist; | |||
| LD_RUNPATH_SEARCH_PATHS = ( | |||
| @@ -714,8 +714,8 @@ | |||
| CODE_SIGN_ENTITLEMENTS = Runner/RunnerRelease.entitlements; | |||
| CODE_SIGN_IDENTITY = "Apple Development"; | |||
| CODE_SIGN_STYLE = Automatic; | |||
| CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; | |||
| DEVELOPMENT_TEAM = FE8CDVE4RJ; | |||
| CURRENT_PROJECT_VERSION = 4; | |||
| DEVELOPMENT_TEAM = GQA553X9YL; | |||
| ENABLE_BITCODE = NO; | |||
| INFOPLIST_FILE = Runner/Info.plist; | |||
| LD_RUNPATH_SEARCH_PATHS = ( | |||
| @@ -19,11 +19,11 @@ | |||
| <key>CFBundlePackageType</key> | |||
| <string>APPL</string> | |||
| <key>CFBundleShortVersionString</key> | |||
| <string>$(FLUTTER_BUILD_NAME)</string> | |||
| <string>2.0.0</string> | |||
| <key>CFBundleSignature</key> | |||
| <string>????</string> | |||
| <key>CFBundleVersion</key> | |||
| <string>$(FLUTTER_BUILD_NUMBER)</string> | |||
| <string>1</string> | |||
| <key>LSRequiresIPhoneOS</key> | |||
| <true/> | |||
| <key>NSAppTransportSecurity</key> | |||
| @@ -1,4 +1,5 @@ | |||
| class NetworkConfig { | |||
| final baseUrl = 'https://api.nghsco.com/api/'; | |||
| final imageUrl = 'https://api.nghsco.com/storage/statics/'; | |||
| const NetworkConfig(); | |||
| } | |||
| @@ -6,6 +6,7 @@ import 'package:go_router/go_router.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/aboutUs/screen.dart'; | |||
| import 'package:qadirneyriz/screens/auth/state/state.dart'; | |||
| import 'package:qadirneyriz/screens/change_pass/screen.dart'; | |||
| @@ -17,6 +18,8 @@ import 'package:qadirneyriz/screens/private_meeting/screen.dart'; | |||
| import 'package:qadirneyriz/screens/report/screen.dart'; | |||
| import 'package:qadirneyriz/setting/setting.dart'; | |||
| import 'package:qadirneyriz/utils/enums/status.dart'; | |||
| import 'package:qadirneyriz/widgets/custom_netimage.dart'; | |||
| class CustomDrawerNavigation extends StatefulWidget { | |||
| final int activeTab; | |||
| @@ -33,6 +36,7 @@ class _CustomDrawerNavigationState extends State<CustomDrawerNavigation> { | |||
| final String language = setting.userLocalDb.getUser().language; | |||
| String? selectedLanguage; // زبان پیشفرض فارسی | |||
| late AuthState state; | |||
| @override | |||
| void initState() { | |||
| super.initState(); | |||
| @@ -66,8 +70,17 @@ class _CustomDrawerNavigationState extends State<CustomDrawerNavigation> { | |||
| final userRole = setting.userLocalDb.getUser().role; | |||
| return Scaffold( | |||
| // backgroundColor: Colors.green.withOpacity(.2), | |||
| drawer: Consumer<AuthState>( | |||
| builder: (context, value, child) { | |||
| drawer: Consumer2<AuthState, GlobalState>( | |||
| builder: (context, authState, globalState, child) { | |||
| final String image = globalState.logoImagesStatus == Status.ready | |||
| ? globalState.logoImagesModel!.data! | |||
| .firstWhere( | |||
| (element) => element.key == 'logo', | |||
| ) | |||
| .value ?? | |||
| '' | |||
| : ''; | |||
| final String ImageUrl = '${config.network.imageUrl}${image}'; | |||
| return Drawer( | |||
| backgroundColor: config.ui.backGroundColor, | |||
| child: SingleChildScrollView( | |||
| @@ -75,13 +88,14 @@ class _CustomDrawerNavigationState extends State<CustomDrawerNavigation> { | |||
| crossAxisAlignment: CrossAxisAlignment.start, | |||
| children: <Widget>[ | |||
| Padding( | |||
| padding: const EdgeInsets.symmetric( | |||
| horizontal: 10, vertical: 35), | |||
| child: Image.asset( | |||
| 'assets/images/D2.png', // مسیر لوگوی شما | |||
| height: 60, | |||
| ), | |||
| ), | |||
| padding: const EdgeInsets.symmetric( | |||
| horizontal: 10, vertical: 35), | |||
| child: CustomImage( | |||
| logo: true, | |||
| width: 80, | |||
| height: 80, | |||
| image: ImageUrl, | |||
| )), | |||
| if (userRole == 0 || userRole == 2) | |||
| Padding( | |||
| padding: const EdgeInsets.symmetric(horizontal: 5), | |||
| @@ -174,10 +188,10 @@ class _CustomDrawerNavigationState extends State<CustomDrawerNavigation> { | |||
| mainAxisAlignment: MainAxisAlignment.spaceEvenly, | |||
| children: [ | |||
| _buildLanguageButton('fa', 'فارسی', () { | |||
| value.setLocale('fa'); | |||
| authState.setLocale('fa'); | |||
| }), | |||
| _buildLanguageButton('en', 'English', () { | |||
| value.setLocale('en'); | |||
| authState.setLocale('en'); | |||
| }), | |||
| ], | |||
| ), | |||
| @@ -1,13 +1,57 @@ | |||
| import 'package:flutter/material.dart'; | |||
| import 'package:qadirneyriz/models/logo_images_model.dart'; | |||
| import 'package:qadirneyriz/models/meetings/meetings_location_model.dart'; | |||
| import 'package:qadirneyriz/models/meetings/meetings_managers_model.dart'; | |||
| import 'package:qadirneyriz/models/meetings/meetings_subjects_model.dart'; | |||
| import 'package:qadirneyriz/models/meetings/meetings_users_model.dart'; | |||
| import 'package:qadirneyriz/screens/meeting/diolog_meetings_filters.dart'; | |||
| import 'package:qadirneyriz/setting/setting.dart'; | |||
| import 'package:qadirneyriz/utils/enums/status.dart'; | |||
| class GlobalState extends ChangeNotifier { | |||
| // users meetings | |||
| Status logoImagesStatus = Status.empty; | |||
| LogoImagesModel? logoImagesModel; | |||
| Future<Status> getLogoImages({bool refresh = false}) async { | |||
| logoImagesStatus = Status.loading; | |||
| notifyListeners(); | |||
| if (refresh) { | |||
| logoImagesStatus = Status.loading; | |||
| notifyListeners(); | |||
| } | |||
| if (logoImagesModel != null ) { | |||
| logoImagesStatus = Status.ready; | |||
| try { | |||
| logoImagesModel = await setting.globalServices.getLogoImagesApi(); | |||
| if (logoImagesModel != null) { | |||
| logoImagesStatus = Status.ready; | |||
| } else { | |||
| logoImagesStatus = Status.empty; | |||
| } | |||
| } catch (e) { | |||
| logoImagesStatus = Status.error; | |||
| // print('$e error usersModel'); | |||
| } | |||
| notifyListeners(); | |||
| } else { | |||
| try { | |||
| logoImagesModel = await setting.globalServices.getLogoImagesApi(); | |||
| if (logoImagesModel != null) { | |||
| logoImagesStatus = Status.ready; | |||
| } else { | |||
| logoImagesStatus = Status.empty; | |||
| } | |||
| notifyListeners(); | |||
| } catch (e) { | |||
| logoImagesStatus = Status.error; | |||
| print('$e error '); | |||
| } | |||
| } | |||
| notifyListeners(); | |||
| print('$logoImagesStatus logoImagesStatus'); | |||
| return logoImagesStatus; | |||
| } | |||
| // users meetings | |||
| Status usersStatus = Status.empty; | |||
| List<UsersModel>? usersModel; | |||
| @@ -116,6 +116,18 @@ | |||
| "accepted":"Accepted", | |||
| "files":"Files", | |||
| "acceptoperetion":"Accept Operetion", | |||
| "doneprivatemeetings": "Completed private meetings", | |||
| "adjournedprivatemeetings": "Postponed private meetings", | |||
| "canceldprivatemeetings": "Canceled private meetings", | |||
| "privatemeetingswaitingtobeheld": "Private meetings waiting to be held", | |||
| "privatemeetingmanager": "Private meeting manager", | |||
| "download": "Download", | |||
| "downloadPdf":"Download pdf", | |||
| "downloadxlsx":"Download xlsx", | |||
| "deletemeeting":"Delete meeting", | |||
| "meetingdeleted":"Meeting Deleted!", | |||
| "deleteprivatemeeting":"Delete privatemeeting", | |||
| "privatemeetingdeleted":"Privatemeeting Deleted", | |||
| "areusuretodeletfile": "Are you sure you want to delete this file?", | |||
| "changepass":"Change Password", | |||
| "profile":"User Account", "doyouredit":"You must make at least one change", | |||
| @@ -124,5 +136,6 @@ | |||
| "deleteaccount": "Delete Account", | |||
| "suretodelelteaccount": "Are you sure you want to delete your account?", | |||
| "actioncantundo": "This action cannot be undone!", | |||
| "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":"خروج", | |||
| "nomeetingfortoday":"برای امروز جلسه ایی تعریف نشده است.", | |||
| "nomeetingfortoday":"برای امروز جلسه ای تعریف نشده است.", | |||
| "todaymeetings":"جلسه های امروز", | |||
| "empty":"داده ایی وجود ندارد.", | |||
| "back":"بازگشست", | |||
| @@ -29,11 +29,28 @@ | |||
| "location":"مکان", | |||
| "meetingmanager":"مدیر جلسه", | |||
| "subject":"موضوع", | |||
| "donemeetings":"جلسات برگذار شده", | |||
| "adjournedmeetings":"جلسات موکول شده", | |||
| "canceldmeetings":"جلسات لغو شده", | |||
| "meetingswaitingtobeheld":"جلسات منتظر برگذاری", | |||
| "doneprivatemeetings":"ملاقاتهای برگذار شده", | |||
| "adjournedprivatemeetings":"ملاقاتهای موکول شده", | |||
| "canceldprivatemeetings":"ملاقاتهای لغو شده", | |||
| "privatemeetingswaitingtobeheld":"ملاقاتهای منتظر برگذاری", | |||
| "privatemeetingmanager":"مدیر جلسه", | |||
| "download":"دانلود", | |||
| "downloadPdf":"دانلود pdf", | |||
| "downloadxlsx":"دانلود اکسل", | |||
| "deletemeeting":"حذف جلسه", | |||
| "meetingdeleted":"جلسه حذف شد!", | |||
| "deleteprivatemeeting":"حذف ملاقات", | |||
| "privatemeetingdeleted":"ملاقات حذف شد!", | |||
| "selectdate":"انتخاب تاریخ", | |||
| "editmeeting":"ویرایش جلسه", | |||
| "meetingsubject":"موضوع جلسه", | |||
| "clock":"ساعت", | |||
| @@ -24,42 +24,6 @@ Future<void> initializeApp() async { | |||
| options: DefaultFirebaseOptions.currentPlatform, | |||
| ); | |||
| await setting.userLocalDb.openBox(); | |||
| // اجرای Firebase و تنظیمات نوتیفیکیشن فقط در اندروید | |||
| await requestNotificationPermission(); | |||
| await getToken(); | |||
| setupMessageListener(); | |||
| } | |||
| Future<void> requestNotificationPermission() async { | |||
| NotificationSettings settings = await messaging.requestPermission( | |||
| alert: true, | |||
| announcement: false, | |||
| badge: true, | |||
| carPlay: false, | |||
| criticalAlert: false, | |||
| provisional: false, | |||
| sound: true, | |||
| ); | |||
| if (settings.authorizationStatus == AuthorizationStatus.authorized) { | |||
| print('User granted permission'); | |||
| } else { | |||
| print('User declined or has not granted permission'); | |||
| } | |||
| } | |||
| Future<void> getToken() async { | |||
| await messaging.getToken(); | |||
| } | |||
| void setupMessageListener() { | |||
| FirebaseMessaging.onMessage.listen((RemoteMessage message) { | |||
| // print('Message received: ${message.notification?.title}'); | |||
| // print('Message body: ${message.notification?.body}'); | |||
| // You can use a Dialog or Toast to display the message here | |||
| }); | |||
| } | |||
| void main() async { | |||
| @@ -84,10 +48,10 @@ class MyApp extends StatefulWidget { | |||
| const MyApp({super.key}); | |||
| @override | |||
| State<MyApp> createState() => _MyAppState(); | |||
| State<MyApp> createState() => FiltersItemInPrivateMeetingsReport(); | |||
| } | |||
| class _MyAppState extends State<MyApp> { | |||
| class FiltersItemInPrivateMeetingsReport extends State<MyApp> { | |||
| late AuthState state; | |||
| String language = setting.userLocalDb.getUser().language; | |||
| @@ -147,3 +111,5 @@ class _MyAppState extends State<MyApp> { | |||
| ); | |||
| } | |||
| } | |||
| @@ -0,0 +1,49 @@ | |||
| // To parse this JSON data, do | |||
| // | |||
| // final logoImagesModel = logoImagesModelFromJson(jsonString); | |||
| import 'dart:convert'; | |||
| LogoImagesModel logoImagesModelFromJson(String str) => LogoImagesModel.fromJson(json.decode(str)); | |||
| String logoImagesModelToJson(LogoImagesModel data) => json.encode(data.toJson()); | |||
| class LogoImagesModel { | |||
| final List<Datum>? data; | |||
| LogoImagesModel({ | |||
| this.data, | |||
| }); | |||
| factory LogoImagesModel.fromJson(Map<String, dynamic> json) => LogoImagesModel( | |||
| data: json["data"] == null ? [] : List<Datum>.from(json["data"]!.map((x) => Datum.fromJson(x))), | |||
| ); | |||
| Map<String, dynamic> toJson() => { | |||
| "data": data == null ? [] : List<dynamic>.from(data!.map((x) => x.toJson())), | |||
| }; | |||
| } | |||
| class Datum { | |||
| final int? id; | |||
| final String? key; | |||
| final String? value; | |||
| Datum({ | |||
| this.id, | |||
| this.key, | |||
| this.value, | |||
| }); | |||
| factory Datum.fromJson(Map<String, dynamic> json) => Datum( | |||
| id: json["id"], | |||
| key: json["key"], | |||
| value: json["value"], | |||
| ); | |||
| Map<String, dynamic> toJson() => { | |||
| "id": id, | |||
| "key": key, | |||
| "value": value, | |||
| }; | |||
| } | |||
| @@ -1,53 +1,129 @@ | |||
| import 'package:flutter/material.dart'; | |||
| import 'package:provider/provider.dart'; | |||
| import 'package:qadirneyriz/config/config.dart'; | |||
| import 'package:qadirneyriz/global/global_state/global_state.dart'; | |||
| import 'package:qadirneyriz/setting/setting.dart'; | |||
| import 'package:qadirneyriz/utils/enums/status.dart'; | |||
| import 'package:qadirneyriz/widgets/custom_appbar.dart'; | |||
| import 'package:flutter_gen/gen_l10n/app_localizations.dart'; | |||
| import 'package:qadirneyriz/widgets/custom_netimage.dart'; | |||
| class AboutUsScreen extends StatelessWidget { | |||
| class AboutUsScreen extends StatefulWidget { | |||
| const AboutUsScreen({super.key}); | |||
| @override | |||
| State<AboutUsScreen> createState() => _AboutUsScreenState(); | |||
| } | |||
| class _AboutUsScreenState extends State<AboutUsScreen> { | |||
| // تابع کمکی برای گرفتن مقدار کلید از دادهها | |||
| String _getByKey(GlobalState globalState, String key) { | |||
| if (globalState.logoImagesStatus == Status.ready) { | |||
| return globalState.logoImagesModel?.data | |||
| ?.firstWhere((element) => element.key == key) | |||
| .value ?? | |||
| ''; | |||
| } | |||
| return ''; | |||
| } | |||
| // تابع کمکی برای ساخت URL | |||
| String _buildImageUrl(String imagePath) { | |||
| return '${config.network.imageUrl}$imagePath'; | |||
| } | |||
| @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/D2.png', | |||
| width: 80, | |||
| height: 80, | |||
| ), | |||
| SizedBox( | |||
| height: 8, | |||
| ), | |||
| Text( | |||
| 'نسخه 1.0.0', | |||
| style: TextStyle(fontSize: 12), | |||
| return Consumer<GlobalState>( | |||
| builder: (context, globalState, child) { | |||
| // گرفتن مقادیر مربوطه | |||
| final String textAboutUsFarsi = | |||
| _getByKey(globalState, 'about_us_description_fa'); | |||
| final String textAboutUsEn = | |||
| _getByKey(globalState, 'about_us_description_en'); | |||
| final String textAppVersrionFa = | |||
| _getByKey(globalState, 'app_version_fa'); | |||
| final String textAppVersrionEn = | |||
| _getByKey(globalState, 'app_version_en'); | |||
| final String aboutUsImage = _getByKey(globalState, 'about_us_image'); | |||
| final String logoImage = _getByKey(globalState, 'logo'); | |||
| final String aboutUsImageUrl = _buildImageUrl(aboutUsImage); | |||
| final String logoUrl = _buildImageUrl(logoImage); | |||
| // ساخت ویو بر اساس وضعیت | |||
| switch (globalState.logoImagesStatus) { | |||
| case Status.ready: | |||
| return CustomScrollView( | |||
| slivers: [ | |||
| const CustomAppbar(), | |||
| SliverToBoxAdapter( | |||
| child: Column( | |||
| children: [ | |||
| _buildImageSection(aboutUsImageUrl, 300, 300), | |||
| _buildAboutUsText( | |||
| context, textAboutUsFarsi, textAboutUsEn), | |||
| _buildFooter( | |||
| logoUrl, textAppVersrionEn, textAppVersrionFa), | |||
| ], | |||
| ), | |||
| SizedBox( | |||
| height: 10, | |||
| ) | |||
| ], | |||
| ) | |||
| ], | |||
| ), | |||
| ], | |||
| ); | |||
| default: | |||
| return const Center(child: CircularProgressIndicator()); | |||
| } | |||
| }, | |||
| ); | |||
| } | |||
| // ویجت برای بخش تصویر | |||
| Widget _buildImageSection(String imageUrl, double width, double height) { | |||
| return Padding( | |||
| padding: const EdgeInsets.symmetric(vertical: 50), | |||
| child: CustomImage( | |||
| image: imageUrl, | |||
| logo: true, | |||
| width: width, | |||
| height: height, | |||
| boxFit: BoxFit.contain, | |||
| ), | |||
| ); | |||
| } | |||
| // ویجت برای متن درباره ما | |||
| Widget _buildAboutUsText(BuildContext context, String textFa, textEn) { | |||
| final String lang = setting.userLocalDb.getUser().language; | |||
| return Padding( | |||
| padding: const EdgeInsets.symmetric(horizontal: 20), | |||
| child: Text( | |||
| lang == 'en' ? textEn : textFa, | |||
| textAlign: TextAlign.justify, | |||
| ), | |||
| ); | |||
| } | |||
| // ویجت برای بخش فوتر (لوگو و نسخه اپلیکیشن) | |||
| Widget _buildFooter( | |||
| String logoUrl, String textVersionEn, String textVersionFa) { | |||
| final String lang = setting.userLocalDb.getUser().language; | |||
| return Column( | |||
| children: [ | |||
| Padding( | |||
| padding: const EdgeInsets.symmetric(vertical: 10), | |||
| child: CustomImage( | |||
| image: logoUrl, | |||
| logo: true, | |||
| width: 80, | |||
| height: 80, | |||
| boxFit: BoxFit.contain, | |||
| ), | |||
| ) | |||
| ), | |||
| const SizedBox(height: 8), | |||
| Text( | |||
| lang == 'en' ? textVersionEn : textVersionFa, | |||
| style: TextStyle(fontSize: 12), | |||
| ), | |||
| const SizedBox(height: 10), | |||
| ], | |||
| ); | |||
| } | |||
| @@ -21,17 +21,20 @@ class AuthState extends ChangeNotifier { | |||
| String? messageLogin; | |||
| Map? errorsLogin; | |||
| Future<Status> login( | |||
| {required String mobile, | |||
| String? password, | |||
| String? otp, | |||
| Future<Status> login({ | |||
| required String mobile, | |||
| String? password, | |||
| String? otp, | |||
| }) async { | |||
| assert(password != null || otp != null); | |||
| statusLogin = Status.loading; | |||
| notifyListeners(); | |||
| try { | |||
| final result = await authServises.loginApi( | |||
| mobile: mobile, password: password, otp: otp, ); | |||
| mobile: mobile, | |||
| password: password, | |||
| otp: otp, | |||
| ); | |||
| if (result == null) { | |||
| statusLogin = Status.error; | |||
| } else { | |||
| @@ -91,4 +94,41 @@ class AuthState extends ChangeNotifier { | |||
| // print(statusSendotp); | |||
| return statusSendotp; | |||
| } | |||
| // check login | |||
| Status statusCheckLogin = Status.empty; | |||
| String? messageCheckLogin; | |||
| Map? errorsCheckLogin; | |||
| Future<Status> CheckLogin() async { | |||
| statusCheckLogin = Status.loading; | |||
| notifyListeners(); | |||
| try { | |||
| final result = await authServises.checkLoginApi(); | |||
| if (result == null) { | |||
| statusCheckLogin = Status.error; | |||
| } else { | |||
| // print(result); | |||
| if (result.isOk) { | |||
| statusCheckLogin = Status.ready; | |||
| messageCheckLogin = result.message; | |||
| } else if (result.isOk == false) { | |||
| errorsCheckLogin = result.errors; | |||
| messageCheckLogin = result.message; | |||
| statusCheckLogin = Status.error; | |||
| } else { | |||
| statusCheckLogin = Status.error; | |||
| } | |||
| notifyListeners(); | |||
| } | |||
| notifyListeners(); | |||
| } catch (e) { | |||
| statusCheckLogin = Status.error; | |||
| // print(e); | |||
| } | |||
| notifyListeners(); | |||
| // print(statusCheckLogin); | |||
| return statusCheckLogin; | |||
| } | |||
| } | |||
| @@ -4,6 +4,7 @@ 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/global/global_state/global_state.dart'; | |||
| import 'package:qadirneyriz/setting/setting.dart'; | |||
| import 'package:qadirneyriz/utils/tools/tools.dart'; | |||
| import 'package:qadirneyriz/widgets/card_meeting.dart'; | |||
| @@ -44,17 +45,17 @@ class _HomeScreenState extends State<HomeScreen> { | |||
| String dateJalali = Tools.convertToPersianDigits( | |||
| '${setting.timeNow.day} ${Tools.getMonthName(setting.timeNow.month)} ${setting.timeNow.year}'); | |||
| return Consumer<HomeState>( | |||
| builder: (context, value, child) { | |||
| switch (value.todayMettingsStatus) { | |||
| return Consumer2<HomeState, GlobalState>( | |||
| builder: (context, homeState, globalState, child) { | |||
| switch (homeState.todayMettingsStatus) { | |||
| case Status.ready: | |||
| return RefreshIndicator( | |||
| onRefresh: () async { | |||
| await value.getTodayMeetings(); | |||
| await homeState.getTodayMeetings(); | |||
| }, | |||
| child: CustomScrollView( | |||
| slivers: [ | |||
| const CustomAppbar(), | |||
| CustomAppbar(), | |||
| SliverToBoxAdapter( | |||
| child: Padding( | |||
| padding: const EdgeInsets.symmetric( | |||
| @@ -86,7 +87,7 @@ class _HomeScreenState extends State<HomeScreen> { | |||
| Expanded( | |||
| child: Text( | |||
| style: const TextStyle(fontSize: 13), | |||
| value.todayMeetingsModel!.note ?? ''), | |||
| homeState.todayMeetingsModel!.note ?? ''), | |||
| ), | |||
| ], | |||
| ), | |||
| @@ -104,24 +105,25 @@ class _HomeScreenState extends State<HomeScreen> { | |||
| SliverToBoxAdapter( | |||
| child: SizedBox( | |||
| height: 170, | |||
| child: value.todayMeetingsModel!.meetings!.isNotEmpty || | |||
| value.todayMeetingsModel!.privateMeetings! | |||
| child: homeState | |||
| .todayMeetingsModel!.meetings!.isNotEmpty || | |||
| homeState.todayMeetingsModel!.privateMeetings! | |||
| .isNotEmpty | |||
| ? ListView.builder( | |||
| scrollDirection: Axis.horizontal, | |||
| shrinkWrap: true, | |||
| itemCount: | |||
| value.todayMeetingsModel!.meetings!.length + | |||
| value.todayMeetingsModel!.privateMeetings! | |||
| .length, | |||
| itemCount: homeState | |||
| .todayMeetingsModel!.meetings!.length + | |||
| homeState.todayMeetingsModel!.privateMeetings! | |||
| .length, | |||
| itemBuilder: (BuildContext context, int index) { | |||
| // ترکیب دو لیست | |||
| final meetingsLength = | |||
| value.todayMeetingsModel!.meetings!.length; | |||
| final meetingsLength = homeState | |||
| .todayMeetingsModel!.meetings!.length; | |||
| if (index < meetingsLength) { | |||
| // آیتم از لیست `meetings` | |||
| final meeting = value | |||
| final meeting = homeState | |||
| .todayMeetingsModel!.meetings![index]; | |||
| return Padding( | |||
| padding: const EdgeInsets.only( | |||
| @@ -173,7 +175,7 @@ class _HomeScreenState extends State<HomeScreen> { | |||
| )); | |||
| } else { | |||
| // آیتم از لیست `privateMeetings` | |||
| final privateMeeting = value | |||
| final privateMeeting = homeState | |||
| .todayMeetingsModel! | |||
| .privateMeetings![index - meetingsLength]; | |||
| return Padding( | |||
| @@ -381,7 +383,7 @@ class _HomeScreenState extends State<HomeScreen> { | |||
| case Status.error: | |||
| return CustomErrorWidget( | |||
| onPressed: () async { | |||
| await value.getTodayMeetings(refresh: true); | |||
| await homeState.getTodayMeetings(refresh: true); | |||
| }, | |||
| ); | |||
| case Status.empty: | |||
| @@ -144,7 +144,7 @@ class HomeState extends ChangeNotifier { | |||
| // print(e); | |||
| } | |||
| notifyListeners(); | |||
| print(statusEditProfile); | |||
| // print(statusEditProfile); | |||
| return statusEditProfile; | |||
| } | |||
| } | |||
| @@ -176,6 +176,8 @@ class _MeetingsScreenState extends State<MeetingsScreen> { | |||
| acceptMeeting(state, context, items.id ?? -1); | |||
| case 'cancel': | |||
| cancelMeeting(state, context, items.id ?? -1); | |||
| case 'delete': | |||
| deleteMeeting(state, context, items.id ?? -1); | |||
| case 'report': | |||
| if (userRole == 1 && items.description != null) { | |||
| await context.pushNamed( | |||
| @@ -241,8 +243,7 @@ class _MeetingsScreenState extends State<MeetingsScreen> { | |||
| ], | |||
| ), | |||
| ), | |||
| if ((userRole == 0 || userRole == 2) && | |||
| items.accepted == 0) | |||
| if (userRole == 0 || userRole == 2) | |||
| PopupMenuItem( | |||
| enabled: | |||
| state.statusCancelMeeting != Status.loading, | |||
| @@ -279,6 +280,26 @@ class _MeetingsScreenState extends State<MeetingsScreen> { | |||
| ], | |||
| ), | |||
| ), | |||
| if (userRole == 0 || userRole == 2) | |||
| PopupMenuItem( | |||
| enabled: | |||
| state.statusCancelMeeting != Status.loading, | |||
| value: 'delete', | |||
| child: Row( | |||
| children: [ | |||
| Icon( | |||
| Icons.delete, | |||
| color: Colors.green, | |||
| size: 17, | |||
| ), | |||
| SizedBox(width: 8), | |||
| Text( | |||
| AppLocalizations.of(context)!.deletemeeting, | |||
| style: TextStyle(fontSize: 12), | |||
| ), | |||
| ], | |||
| ), | |||
| ), | |||
| ]), | |||
| ); | |||
| }, | |||
| @@ -320,6 +341,25 @@ class _MeetingsScreenState extends State<MeetingsScreen> { | |||
| } | |||
| } | |||
| void deleteMeeting( | |||
| MeetingsState state, BuildContext context, int cardId) async { | |||
| final status = await state.deleteMeeting(id: cardId); | |||
| if (status == Status.ready) { | |||
| Tools.showCustomSnackBar( | |||
| text: AppLocalizations.of(context)!.meetingdeleted, | |||
| isError: false, | |||
| context, | |||
| ); | |||
| await meetingsState.getMeetings(); | |||
| } else { | |||
| Tools.showCustomSnackBar( | |||
| text: AppLocalizations.of(context)!.error, | |||
| isError: true, | |||
| context, | |||
| ); | |||
| } | |||
| } | |||
| void acceptMeeting( | |||
| MeetingsState state, BuildContext context, int cardId) async { | |||
| final status = await state.acceptMeeting(id: cardId); | |||
| @@ -258,6 +258,41 @@ class MeetingsState extends ChangeNotifier { | |||
| return statusCancelMeeting; | |||
| } | |||
| // delete meeting | |||
| Status statusDeleteMeeting = Status.empty; | |||
| String? messageDeleteMeeting; | |||
| Map? errorsDeleteMeeting; | |||
| Future<Status> deleteMeeting({ | |||
| required int id, | |||
| }) async { | |||
| statusDeleteMeeting = Status.loading; | |||
| notifyListeners(); | |||
| try { | |||
| final result = await meetingsApi.deleteMeetingApi( | |||
| id: id, | |||
| ); | |||
| if (result.isOk) { | |||
| statusDeleteMeeting = Status.ready; | |||
| messageDeleteMeeting = result.message; | |||
| } else if (result.isOk == false) { | |||
| print(result.isOk); | |||
| errorsDeleteMeeting = result.errors; | |||
| messageDeleteMeeting = result.message; | |||
| statusDeleteMeeting = Status.error; | |||
| } else { | |||
| statusDeleteMeeting = Status.error; | |||
| } | |||
| notifyListeners(); | |||
| } catch (e) { | |||
| statusDeleteMeeting = Status.error; | |||
| // print(e); | |||
| } | |||
| notifyListeners(); | |||
| // print(statusDeleteMeeting); | |||
| return statusDeleteMeeting; | |||
| } | |||
| // accept meeting | |||
| Status statusAcceptMeeting = Status.empty; | |||
| String? messageAcceptMeeting; | |||
| @@ -181,7 +181,10 @@ class _PrivateMeetingsScreenState extends State<PrivateMeetingsScreen> { | |||
| 'privatemeetinsammary', | |||
| extra: items, // `items` should be a Datum instance | |||
| ); | |||
| case 'cancel': | |||
| cancelPrivateMeeting(state, context, items.id ?? -1); | |||
| case 'delete': | |||
| deletePrivateMeeting(state, context, items.id ?? -1); | |||
| default: | |||
| } | |||
| }, | |||
| @@ -221,6 +224,42 @@ class _PrivateMeetingsScreenState extends State<PrivateMeetingsScreen> { | |||
| ], | |||
| ), | |||
| ), | |||
| 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), | |||
| ), | |||
| ], | |||
| ), | |||
| ), | |||
| if (userRole == 0 || userRole == 2) | |||
| PopupMenuItem( | |||
| value: 'delete', | |||
| child: Row( | |||
| children: [ | |||
| Icon( | |||
| Icons.delete, | |||
| color: Colors.green, | |||
| size: 17, | |||
| ), | |||
| SizedBox(width: 8), | |||
| Text( | |||
| AppLocalizations.of(context)!.deleteprivatemeeting, | |||
| style: TextStyle(fontSize: 12), | |||
| ), | |||
| ], | |||
| ), | |||
| ), | |||
| ], | |||
| ); | |||
| }, | |||
| @@ -262,6 +301,25 @@ class _PrivateMeetingsScreenState extends State<PrivateMeetingsScreen> { | |||
| } | |||
| } | |||
| void deletePrivateMeeting( | |||
| PrivateMeetingsState state, BuildContext context, int cardId) async { | |||
| final status = await state.deleteMeeting(id: cardId); | |||
| if (status == Status.ready) { | |||
| Tools.showCustomSnackBar( | |||
| text: AppLocalizations.of(context)!.privatemeetingdeleted, | |||
| 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); | |||
| @@ -108,12 +108,12 @@ class PrivateMeetingsState extends ChangeNotifier { | |||
| } | |||
| } catch (e) { | |||
| privateStatusMeetings = Status.error; | |||
| print('$e'); | |||
| // print('$e'); | |||
| } | |||
| notifyListeners(); | |||
| } | |||
| notifyListeners(); | |||
| print(privateStatusMeetings); | |||
| // print(privateStatusMeetings); | |||
| return privateStatusMeetings; | |||
| } | |||
| @@ -264,6 +264,41 @@ class PrivateMeetingsState extends ChangeNotifier { | |||
| return statusCancelMeeting[id]!; | |||
| } | |||
| // delete meeting | |||
| Map<int, Status> statusDeleteMeeting = {}; | |||
| String? messageDeleteMeeting; | |||
| Map? errorsDeleteMeeting; | |||
| Future<Status> deleteMeeting({ | |||
| required int id, | |||
| }) async { | |||
| statusDeleteMeeting[id] = Status.loading; | |||
| notifyListeners(); | |||
| try { | |||
| final result = await privateMeetingsApi.deletePrivateMeetingApi( | |||
| id: id, | |||
| ); | |||
| if (result.isOk) { | |||
| statusDeleteMeeting[id] = Status.ready; | |||
| messageDeleteMeeting = result.message; | |||
| } else if (result.isOk == false) { | |||
| // print(result.isOk); | |||
| errorsDeleteMeeting = result.errors; | |||
| messageDeleteMeeting = result.message; | |||
| statusDeleteMeeting[id] = Status.error; | |||
| } else { | |||
| statusDeleteMeeting[id] = Status.error; | |||
| } | |||
| notifyListeners(); | |||
| } catch (e) { | |||
| statusDeleteMeeting[id] = Status.error; | |||
| // print(e); | |||
| } | |||
| notifyListeners(); | |||
| // print(statusDeleteMeeting); | |||
| return statusDeleteMeeting[id]!; | |||
| } | |||
| // accept meeting | |||
| Map<int, Status> statusAcceptMeeting = {}; | |||
| String? messageAcceptMeeting; | |||
| @@ -97,31 +97,31 @@ class PrivateMeetingSummaryState extends ChangeNotifier { | |||
| if (filesStringModel[id] != null && filesStringModel[id]!.isNotEmpty) { | |||
| try { | |||
| filesStringModel[id] = await meetingsApi.getListStringFils(id: id); | |||
| print('${filesStringModel[id]}'); | |||
| // print('${filesStringModel[id]}'); | |||
| stringsFilsStatus[id] = Status.ready; | |||
| print('${filesStringModel} filesStringModel[id]'); | |||
| // print('${filesStringModel} filesStringModel[id]'); | |||
| } catch (e) { | |||
| stringsFilsStatus[id] = Status.error; | |||
| print('$e'); | |||
| // print('$e'); | |||
| } | |||
| } else { | |||
| stringsFilsStatus[id] = Status.ready; | |||
| notifyListeners(); | |||
| try { | |||
| filesStringModel[id] = await meetingsApi.getListStringFils(id: id); | |||
| print('${filesStringModel[id]}'); | |||
| // print('${filesStringModel[id]}'); | |||
| stringsFilsStatus[id] = Status.ready; | |||
| print('${filesStringModel} filesStringModel[id]'); | |||
| // print('${filesStringModel} filesStringModel[id]'); | |||
| } catch (e) { | |||
| stringsFilsStatus[id] = Status.error; | |||
| print('$e'); | |||
| // print('$e'); | |||
| } | |||
| } | |||
| notifyListeners(); | |||
| print('${stringsFilsStatus} stringsFilsStatus'); | |||
| // print('${stringsFilsStatus} stringsFilsStatus'); | |||
| return stringsFilsStatus; | |||
| } | |||
| @@ -143,7 +143,7 @@ class PrivateMeetingSummaryState extends ChangeNotifier { | |||
| statusDeleteFile = Status.ready; | |||
| messageDeleteFile = result.message; | |||
| } else if (result.isOk == false) { | |||
| print(result.isOk); | |||
| // print(result.isOk); | |||
| errorsDeleteFile = result.errors; | |||
| messageDeleteFile = result.message; | |||
| statusDeleteFile = Status.error; | |||
| @@ -151,10 +151,10 @@ class PrivateMeetingSummaryState extends ChangeNotifier { | |||
| notifyListeners(); | |||
| } catch (e) { | |||
| statusDeleteFile = Status.error; | |||
| print(e); | |||
| // print(e); | |||
| } | |||
| notifyListeners(); | |||
| print(statusDeleteFile); | |||
| // print(statusDeleteFile); | |||
| return statusDeleteFile; | |||
| } | |||
| @@ -36,33 +36,62 @@ class _ReportScreenState extends State<ReportScreen> { | |||
| String dateJalali = Tools.convertToPersianDigits( | |||
| '${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(), | |||
| ) | |||
| ], | |||
| return DefaultTabController( | |||
| length: 2, | |||
| child: CustomScrollView( | |||
| slivers: <Widget>[ | |||
| const CustomAppbar(), | |||
| SliverToBoxAdapter( | |||
| child: TodayWidget( | |||
| formattedDate: setting.userLocalDb.getUser().language == 'en' | |||
| ? dateMiladi | |||
| : dateJalali), | |||
| ), | |||
| SliverToBoxAdapter( | |||
| child: TabBar( | |||
| indicatorColor: config.ui.mainGreen, | |||
| labelColor: config.ui.mainGreen, | |||
| unselectedLabelColor: config.ui.mainGreen, | |||
| tabs: [ | |||
| Tab( | |||
| child: Text( | |||
| AppLocalizations.of(context)!.meetings, | |||
| style: TextStyle(fontSize: 14), | |||
| ), | |||
| ), | |||
| Tab( | |||
| child: Text( | |||
| AppLocalizations.of(context)!.privatemeeting, | |||
| style: TextStyle(fontSize: 14), | |||
| ), | |||
| ), | |||
| ], | |||
| ), | |||
| ), | |||
| SliverFillRemaining( | |||
| child: TabBarView(children: [ | |||
| FiltersItemInMeetingsReport(), | |||
| PrivateMeetingsReportsItems(), | |||
| ]), | |||
| ) | |||
| ], | |||
| ), | |||
| ); | |||
| } | |||
| } | |||
| class FiltersItemInReport extends StatefulWidget { | |||
| const FiltersItemInReport({ | |||
| class FiltersItemInMeetingsReport extends StatefulWidget { | |||
| const FiltersItemInMeetingsReport({ | |||
| super.key, | |||
| }); | |||
| @override | |||
| State<FiltersItemInReport> createState() => _FiltersItemInReportState(); | |||
| State<FiltersItemInMeetingsReport> createState() => | |||
| _FiltersItemInMeetingsReportState(); | |||
| } | |||
| class _FiltersItemInReportState extends State<FiltersItemInReport> { | |||
| class _FiltersItemInMeetingsReportState | |||
| extends State<FiltersItemInMeetingsReport> { | |||
| ReportState? reportState; | |||
| GlobalState? globalState; | |||
| @override | |||
| @@ -304,10 +333,15 @@ class _FiltersItemInReportState extends State<FiltersItemInReport> { | |||
| }, | |||
| ), | |||
| ), | |||
| Padding( | |||
| padding: const EdgeInsets.symmetric( | |||
| horizontal: 20, vertical: 50), | |||
| child: downloadButton(reportState), | |||
| Row( | |||
| mainAxisAlignment: MainAxisAlignment.center, | |||
| children: [ | |||
| downloadButtonXlsx(reportState), | |||
| const SizedBox( | |||
| width: 20, | |||
| ), | |||
| downloadButtonPdf(reportState), | |||
| ], | |||
| ) | |||
| ], | |||
| ), | |||
| @@ -332,22 +366,75 @@ class _FiltersItemInReportState extends State<FiltersItemInReport> { | |||
| ); | |||
| } | |||
| CustomButton downloadButton(ReportState state) { | |||
| switch (state.statusDownload) { | |||
| CustomButton downloadButtonXlsx(ReportState state) { | |||
| switch (state.statusDownload['xlsx']) { | |||
| case Status.loading: | |||
| return CustomButton( | |||
| fontSize: 14, | |||
| borderRadius: 10, | |||
| hieght: 40, | |||
| text: AppLocalizations.of(context)!.loading, | |||
| width: 150, | |||
| ); | |||
| default: | |||
| return CustomButton( | |||
| fontSize: 14, | |||
| borderRadius: 10, | |||
| hieght: 40, | |||
| text: AppLocalizations.of(context)!.downloadxlsx, | |||
| width: 150, | |||
| onPressed: () async { | |||
| bool hasPermission = await hasStoragePermission(); | |||
| if (!hasPermission) { | |||
| Tools.showCustomSnackBar(context, | |||
| text: 'Permission denied. Please allow storage access.', | |||
| isError: true); | |||
| return; | |||
| } | |||
| // Download the file | |||
| final status = await state.downloadReport( | |||
| format: 'xlsx', | |||
| toDate: reportState!.toDate, | |||
| fromDate: reportState!.fromDate, | |||
| location: reportState!.selectedLocationId, | |||
| subject: reportState!.selectedSubjectId, | |||
| meetingManager: reportState!.selectedManagersId, | |||
| status: reportState!.selectedStatusId); | |||
| // print(status); | |||
| if (state.statusDownload['xlsx'] == Status.ready) { | |||
| await OpenFile.open(state.messageDownload); | |||
| } else { | |||
| Tools.showCustomSnackBar( | |||
| context, | |||
| text: AppLocalizations.of(context)!.error, | |||
| isError: true, | |||
| ); | |||
| } | |||
| }, | |||
| ); | |||
| } | |||
| } | |||
| CustomButton downloadButtonPdf(ReportState state) { | |||
| switch (state.statusDownload['pdf']) { | |||
| case Status.loading: | |||
| return CustomButton( | |||
| borderRadius: 15, | |||
| hieght: 50, | |||
| fontSize: 14, | |||
| borderRadius: 10, | |||
| hieght: 40, | |||
| text: AppLocalizations.of(context)!.loading, | |||
| width: double.infinity, | |||
| width: 150, | |||
| ); | |||
| default: | |||
| return CustomButton( | |||
| borderRadius: 15, | |||
| hieght: 50, | |||
| text: AppLocalizations.of(context)!.downloadreport, | |||
| width: double.infinity, | |||
| borderRadius: 10, | |||
| fontSize: 14, | |||
| hieght: 40, | |||
| text: AppLocalizations.of(context)!.downloadPdf, | |||
| width: 150, | |||
| onPressed: () async { | |||
| bool hasPermission = await hasStoragePermission(); | |||
| if (!hasPermission) { | |||
| @@ -359,6 +446,7 @@ class _FiltersItemInReportState extends State<FiltersItemInReport> { | |||
| // Download the file | |||
| await state.downloadReport( | |||
| format: 'pdf', | |||
| toDate: reportState!.toDate, | |||
| fromDate: reportState!.fromDate, | |||
| location: reportState!.selectedLocationId, | |||
| @@ -366,7 +454,7 @@ class _FiltersItemInReportState extends State<FiltersItemInReport> { | |||
| meetingManager: reportState!.selectedManagersId, | |||
| status: reportState!.selectedStatusId); | |||
| if (state.statusDownload == Status.ready) { | |||
| if (state.statusDownload['pdf'] == Status.ready) { | |||
| await OpenFile.open(state.messageDownload); | |||
| // print(status.message); | |||
| } else { | |||
| @@ -399,6 +487,424 @@ class _FiltersItemInReportState extends State<FiltersItemInReport> { | |||
| } | |||
| } | |||
| class PrivateMeetingsReportsItems extends StatefulWidget { | |||
| const PrivateMeetingsReportsItems({super.key}); | |||
| @override | |||
| State<PrivateMeetingsReportsItems> createState() => | |||
| _PrivateMeetingsReportsItemsState(); | |||
| } | |||
| class _PrivateMeetingsReportsItemsState | |||
| extends State<PrivateMeetingsReportsItems> { | |||
| 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)!.doneprivatemeetings), | |||
| MeetingsStatus( | |||
| id: 2, title: AppLocalizations.of(context)!.adjournedprivatemeetings), | |||
| MeetingsStatus( | |||
| id: 3, title: AppLocalizations.of(context)!.canceldprivatemeetings), | |||
| MeetingsStatus( | |||
| id: 4, | |||
| title: AppLocalizations.of(context)!.privatemeetingswaitingtobeheld), | |||
| ]; | |||
| 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 | |||
| .fromDatePrivateMeeting.isNotEmpty | |||
| ? reportState.fromDatePrivateMeeting | |||
| : AppLocalizations.of(context)! | |||
| .selectdate, // Show selected date or prompt | |||
| onTap: () { | |||
| showDialog( | |||
| context: context, | |||
| builder: (context) { | |||
| return Dialog( | |||
| child: Tools | |||
| .shamsiDateCalendarWidget( | |||
| context, | |||
| (newDate) { | |||
| String | |||
| fromDateStringPrivateMeeting = | |||
| '${newDate.year}/${newDate.month}/${newDate.day}'; | |||
| reportState | |||
| .setFromDatesPrivateMeeting( | |||
| fromDateStringPrivateMeeting); // Update the selected date | |||
| }, | |||
| ), | |||
| ); | |||
| }, | |||
| ); | |||
| }, | |||
| ), | |||
| Text( | |||
| AppLocalizations.of(context)!.to, | |||
| ), | |||
| PickerCustom( | |||
| showDate: reportState | |||
| .toDatePrivateMeeting.isNotEmpty | |||
| ? reportState.toDatePrivateMeeting | |||
| : AppLocalizations.of(context)! | |||
| .selectdate, // Show selected date or prompt | |||
| onTap: () { | |||
| showDialog( | |||
| context: context, | |||
| builder: (context) { | |||
| return Dialog( | |||
| child: Tools | |||
| .shamsiDateCalendarWidget( | |||
| context, | |||
| (newDate) { | |||
| String | |||
| toDateStringPrivateMeeting = | |||
| '${newDate.year}/${newDate.month}/${newDate.day}'; | |||
| reportState | |||
| .setToDatesPrivateMeeting( | |||
| toDateStringPrivateMeeting); // 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 | |||
| .selectedLocationIdPrivateMeeting, | |||
| 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 | |||
| .selectLocationPrivateMeeting( | |||
| newValue ?? null); | |||
| }, | |||
| ); | |||
| }, | |||
| ), | |||
| ], | |||
| ), | |||
| if (setting.userLocalDb.getUser().role != 1) | |||
| 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 | |||
| .selectedManagersIdPrivateMeeting, | |||
| 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 | |||
| .selectManagerPrivateMeeting( | |||
| 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 | |||
| .selectedSubjectIdPrivateMeeting, | |||
| 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.selectSubjectPrivateMeeting( | |||
| newValue ?? null); | |||
| }, | |||
| ); | |||
| }, | |||
| ), | |||
| ], | |||
| ), | |||
| Divider(), | |||
| if (setting.userLocalDb.getUser().role != 1) | |||
| SizedBox( | |||
| height: 300, | |||
| 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 | |||
| .selectedStatusIdPrivateMeeting, | |||
| 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 | |||
| .selectStatusMeetingPrivateMeeting( | |||
| newValue ?? null); | |||
| }, | |||
| ); | |||
| }, | |||
| ), | |||
| ), | |||
| Row( | |||
| mainAxisAlignment: MainAxisAlignment.center, | |||
| children: [ | |||
| downloadButtonXlsx(reportState), | |||
| SizedBox( | |||
| width: 20, | |||
| ), | |||
| downloadButtonPdf(reportState), | |||
| ], | |||
| ) | |||
| ], | |||
| ), | |||
| ], | |||
| ), | |||
| ), | |||
| ), | |||
| ], | |||
| ); | |||
| case Status.loading: | |||
| return const LoadingWidget(); | |||
| case Status.error: | |||
| return CustomErrorWidget( | |||
| onPressed: () async { | |||
| await globalState.getAllFiltersItems(refresh: true); | |||
| }, | |||
| ); | |||
| default: | |||
| return Container(); | |||
| } | |||
| }, | |||
| ); | |||
| } | |||
| CustomButton downloadButtonXlsx(ReportState state) { | |||
| switch (state.statusDownloadPrivateMeeting['xlsx']) { | |||
| case Status.loading: | |||
| return CustomButton( | |||
| fontSize: 14, | |||
| borderRadius: 10, | |||
| hieght: 40, | |||
| text: AppLocalizations.of(context)!.loading, | |||
| width: 150, | |||
| ); | |||
| default: | |||
| return CustomButton( | |||
| fontSize: 14, | |||
| borderRadius: 10, | |||
| hieght: 40, | |||
| text: AppLocalizations.of(context)!.downloadxlsx, | |||
| width: 150, | |||
| onPressed: () async { | |||
| bool hasPermission = await hasStoragePermission(); | |||
| if (!hasPermission) { | |||
| Tools.showCustomSnackBar(context, | |||
| text: 'Permission denied. Please allow storage access.', | |||
| isError: true); | |||
| return; | |||
| } | |||
| // Download the file | |||
| final status = await state.downloadReportPrivateMeeting( | |||
| format: 'xlsx', | |||
| toDate: reportState!.toDatePrivateMeeting, | |||
| fromDate: reportState!.fromDatePrivateMeeting, | |||
| location: reportState!.selectedLocationIdPrivateMeeting, | |||
| subject: reportState!.selectedSubjectIdPrivateMeeting, | |||
| meetingManager: reportState!.selectedManagersIdPrivateMeeting, | |||
| status: reportState!.selectedStatusIdPrivateMeeting); | |||
| print(status); | |||
| if (state.statusDownloadPrivateMeeting['xlsx'] == Status.ready) { | |||
| await OpenFile.open(state.messageDownloadPrivateMeeting); | |||
| } else { | |||
| Tools.showCustomSnackBar( | |||
| context, | |||
| text: AppLocalizations.of(context)!.error, | |||
| isError: true, | |||
| ); | |||
| } | |||
| }, | |||
| ); | |||
| } | |||
| } | |||
| CustomButton downloadButtonPdf(ReportState state) { | |||
| switch (state.statusDownloadPrivateMeeting['pdf']) { | |||
| case Status.loading: | |||
| return CustomButton( | |||
| fontSize: 14, | |||
| borderRadius: 10, | |||
| hieght: 40, | |||
| text: AppLocalizations.of(context)!.loading, | |||
| width: 150, | |||
| ); | |||
| default: | |||
| return CustomButton( | |||
| fontSize: 14, | |||
| borderRadius: 10, | |||
| hieght: 40, | |||
| text: AppLocalizations.of(context)!.downloadPdf, | |||
| width: 150, | |||
| 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.downloadReportPrivateMeeting( | |||
| format: 'pdf', | |||
| toDate: reportState!.toDatePrivateMeeting, | |||
| fromDate: reportState!.fromDatePrivateMeeting, | |||
| location: reportState!.selectedLocationIdPrivateMeeting, | |||
| subject: reportState!.selectedSubjectIdPrivateMeeting, | |||
| meetingManager: reportState!.selectedManagersIdPrivateMeeting, | |||
| status: reportState!.selectedStatusIdPrivateMeeting); | |||
| if (state.statusDownloadPrivateMeeting['pdf'] == Status.ready) { | |||
| await OpenFile.open(state.messageDownloadPrivateMeeting); | |||
| // 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, | |||
| @@ -18,17 +18,6 @@ class ReportState extends ChangeNotifier { | |||
| 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 || | |||
| @@ -70,9 +59,9 @@ class ReportState extends ChangeNotifier { | |||
| notifyListeners(); | |||
| } | |||
| // download report | |||
| // download report meeting | |||
| Status statusDownload = Status.empty; | |||
| Map<String, Status> statusDownload = {}; | |||
| String? messageDownload; | |||
| Future<Status> downloadReport( | |||
| @@ -81,33 +70,134 @@ class ReportState extends ChangeNotifier { | |||
| int? location, | |||
| int? subject, | |||
| int? meetingManager, | |||
| required String format, | |||
| int? status}) async { | |||
| statusDownload = Status.loading; | |||
| statusDownload[format] = Status.loading; | |||
| notifyListeners(); | |||
| try { | |||
| final result = await reportApi.downloadReport( | |||
| final result = await reportApi.downloadReportMeetings( | |||
| fromDate: fromDate, | |||
| toDate: toDate, | |||
| location: location, | |||
| subject: subject, | |||
| meetingManager: meetingManager, | |||
| format: format, | |||
| status: status); | |||
| if (result == null) { | |||
| statusDownload = Status.error; | |||
| statusDownload[format] = Status.error; | |||
| } else { | |||
| if (result.isOk) { | |||
| statusDownload = Status.ready; | |||
| statusDownload[format] = Status.ready; | |||
| messageDownload = result.message ?? ''; | |||
| } else { | |||
| statusDownload = Status.error; | |||
| statusDownload[format] = Status.error; | |||
| } | |||
| } | |||
| } catch (e) { | |||
| statusDownload[format] = Status.error; | |||
| // print(e); | |||
| } | |||
| // print(statusDownload[format]); | |||
| notifyListeners(); | |||
| return statusDownload[format] ?? Status.error; | |||
| } | |||
| // private meetings report items | |||
| String fromDatePrivateMeeting = ''; | |||
| String toDatePrivateMeeting = ''; | |||
| void setFromDatesPrivateMeeting(String? newFromDate) { | |||
| fromDatePrivateMeeting = newFromDate ?? ''; | |||
| notifyListeners(); | |||
| } | |||
| void setToDatesPrivateMeeting(String? newToDate) { | |||
| toDatePrivateMeeting = newToDate ?? ''; | |||
| notifyListeners(); | |||
| } | |||
| // is filter Not empty | |||
| bool hasActiveFiltersPrivateMeeting() { | |||
| return selectedLocationIdPrivateMeeting != null || | |||
| selectedManagersIdPrivateMeeting != null || | |||
| selectedStatusIdPrivateMeeting != null || | |||
| selectedSubjectIdPrivateMeeting != null || | |||
| fromDatePrivateMeeting.isNotEmpty || | |||
| toDatePrivateMeeting.isNotEmpty; | |||
| } | |||
| // get filters location PrivateMeeting | |||
| int? selectedLocationIdPrivateMeeting; | |||
| void selectLocationPrivateMeeting(int? locationId) { | |||
| selectedLocationIdPrivateMeeting = locationId; | |||
| notifyListeners(); | |||
| } | |||
| // get filters subjects PrivateMeeting | |||
| int? selectedSubjectIdPrivateMeeting; | |||
| void selectSubjectPrivateMeeting(int? subjectId) { | |||
| selectedSubjectIdPrivateMeeting = subjectId; | |||
| notifyListeners(); | |||
| } | |||
| // get filters PrivateMeeting | |||
| int? selectedManagersIdPrivateMeeting; | |||
| void selectManagerPrivateMeeting(int? managerId) { | |||
| selectedManagersIdPrivateMeeting = managerId; | |||
| notifyListeners(); | |||
| } | |||
| // all PrivateMeeting status filters | |||
| int? selectedStatusIdPrivateMeeting; | |||
| void selectStatusMeetingPrivateMeeting(int? statusId) { | |||
| selectedStatusIdPrivateMeeting = statusId; | |||
| notifyListeners(); | |||
| } | |||
| // download report PrivateMeeting | |||
| Map<String, Status> statusDownloadPrivateMeeting = {}; | |||
| String? messageDownloadPrivateMeeting; | |||
| Future<Status> downloadReportPrivateMeeting( | |||
| {String? fromDate, | |||
| String? toDate, | |||
| int? location, | |||
| int? subject, | |||
| int? meetingManager, | |||
| required String format, | |||
| int? status}) async { | |||
| statusDownloadPrivateMeeting[format] = Status.loading; | |||
| notifyListeners(); | |||
| try { | |||
| final result = await reportApi.downloadReportPrivateMeetings( | |||
| format: format, | |||
| fromDate: fromDate, | |||
| toDate: toDate, | |||
| location: location, | |||
| subject: subject, | |||
| meetingManager: meetingManager, | |||
| status: status); | |||
| if (result == null) { | |||
| statusDownloadPrivateMeeting[format] = Status.error; | |||
| } else { | |||
| if (result.isOk) { | |||
| statusDownloadPrivateMeeting[format] = Status.ready; | |||
| messageDownloadPrivateMeeting = result.message ?? ''; | |||
| } else { | |||
| statusDownloadPrivateMeeting[format] = Status.error; | |||
| } | |||
| } | |||
| } catch (e) { | |||
| statusDownload = Status.error; | |||
| statusDownloadPrivateMeeting[format] = Status.error; | |||
| } | |||
| notifyListeners(); | |||
| return statusDownload; | |||
| return statusDownloadPrivateMeeting[format] ?? Status.error; | |||
| } | |||
| } | |||
| @@ -1,11 +1,13 @@ | |||
| import 'package:flutter/foundation.dart'; // برای شناسایی پلتفرم | |||
| import 'package:dio/dio.dart'; | |||
| import 'package:qadirneyriz/config/config.dart'; | |||
| import 'package:qadirneyriz/main.dart'; | |||
| import 'package:qadirneyriz/setting/setting.dart'; | |||
| import 'package:qadirneyriz/utils/result/result.dart'; | |||
| import 'dart:io'; | |||
| class AuthServices { | |||
| String userAgent = | |||
| Platform.isAndroid ? 'application/android' : 'application/ios'; | |||
| Future<Result?> loginApi({ | |||
| required String mobile, | |||
| String? password, | |||
| @@ -18,7 +20,10 @@ class AuthServices { | |||
| assert(password != null || otp != null); | |||
| try { | |||
| Map<String, String> headers = {"Accept": "application/json"}; | |||
| Map<String, String> headers = { | |||
| "Accept": "application/json", | |||
| "user-agent": userAgent | |||
| }; | |||
| FormData formData; | |||
| formData = password != null | |||
| ? FormData.fromMap( | |||
| @@ -26,7 +31,7 @@ class AuthServices { | |||
| : FormData.fromMap( | |||
| {"mobile": mobile, "otp": otp, "device_id": token}); | |||
| print('${formData.fields} resData'); | |||
| // print('${formData.fields} resData'); | |||
| final res = await Dio().post( | |||
| "${config.network.baseUrl}login?lang=${setting.userLocalDb.getUser().language}", | |||
| data: formData, | |||
| @@ -41,7 +46,7 @@ class AuthServices { | |||
| return Result(isOk: true, message: res.data['msg']); | |||
| } | |||
| } on DioException catch (e) { | |||
| print(e); | |||
| // print(e); | |||
| return Result( | |||
| isOk: false, | |||
| errors: e.response?.data['errors'], | |||
| @@ -52,7 +57,10 @@ class AuthServices { | |||
| Future<Result?> sendOtpApi({required String mobile}) async { | |||
| try { | |||
| Map<String, String> headers = {"Accept": "application/json"}; | |||
| Map<String, String> headers = { | |||
| "Accept": "application/json", | |||
| "user-agent": userAgent, | |||
| }; | |||
| FormData formData = FormData.fromMap({"mobile": mobile}); | |||
| final res = await Dio().post( | |||
| @@ -71,4 +79,40 @@ class AuthServices { | |||
| } | |||
| return const Result(isOk: false); | |||
| } | |||
| Future<Result?> checkLoginApi() async { | |||
| final userRole = setting.userLocalDb.getUser().role; | |||
| try { | |||
| Map<String, String> headers = {"Accept": "application/json"}; | |||
| String dataToken = setting.userLocalDb.getUser().token!; | |||
| if (dataToken != '') { | |||
| headers['Authorization'] = "Bearer $dataToken"; | |||
| } | |||
| // print('$dataToken datatokoen'); | |||
| // لینک API | |||
| final apiUrl = userRole != 1 | |||
| ? "${config.network.baseUrl}admin/checkLogin" | |||
| : "${config.network.baseUrl}user/checkLogin"; | |||
| final res = await Dio().get( | |||
| apiUrl, | |||
| options: Options(headers: headers), | |||
| ); | |||
| if (res.statusCode == 200 || res.statusCode == 201) { | |||
| return Result(isOk: true, message: res.data['msg']); | |||
| } | |||
| } on DioException catch (e) { | |||
| return Result( | |||
| isOk: false, | |||
| errors: e.response?.data['errors'], | |||
| message: e.response?.data['msg'], | |||
| ); | |||
| } | |||
| return const Result(isOk: false); | |||
| } | |||
| } | |||
| @@ -1,5 +1,6 @@ | |||
| import 'package:dio/dio.dart'; | |||
| import 'package:qadirneyriz/config/config.dart'; | |||
| import 'package:qadirneyriz/models/logo_images_model.dart'; | |||
| import 'package:qadirneyriz/models/meetings/meetings_location_model.dart'; | |||
| import 'package:qadirneyriz/models/meetings/meetings_managers_model.dart'; | |||
| import 'package:qadirneyriz/models/meetings/meetings_subjects_model.dart'; | |||
| @@ -81,7 +82,7 @@ class GlobalServices { | |||
| } | |||
| final String link = | |||
| "${config.network.baseUrl}admin/users?lang=${setting.userLocalDb.getUser().language}"; | |||
| "${config.network.baseUrl}admin/users?is_active=1?lang=${setting.userLocalDb.getUser().language}"; | |||
| final response = await Dio().get(link, | |||
| options: Options( | |||
| @@ -188,4 +189,44 @@ class GlobalServices { | |||
| } | |||
| return const Result(isOk: false); | |||
| } | |||
| // get logo images | |||
| Future<LogoImagesModel> getLogoImagesApi() async { | |||
| Map<String, String> headers = { | |||
| 'Accept': 'application/json', | |||
| }; | |||
| String dataToken = setting.userLocalDb.getUser().token!; | |||
| int role = setting.userLocalDb.getUser().role!; | |||
| // print("DEBUG: Token => $dataToken"); | |||
| // print("DEBUG: Role => $role"); | |||
| if (dataToken != '') { | |||
| headers['Authorization'] = "Bearer $dataToken"; | |||
| } | |||
| // print("DEBUG: Headers => $headers"); | |||
| final String link = role != 1 | |||
| ? "${config.network.baseUrl}admin/settings" | |||
| : "${config.network.baseUrl}user/settings"; | |||
| // print("DEBUG: API Link => $link"); | |||
| try { | |||
| final response = await Dio().get(link, | |||
| options: Options( | |||
| headers: headers, | |||
| )); | |||
| // print("DEBUG: Response Data => ${response.data}"); | |||
| LogoImagesModel list = LogoImagesModel.fromJson(response.data); | |||
| return list; | |||
| } catch (e) { | |||
| // print("DEBUG: Error => $e"); | |||
| rethrow; | |||
| } | |||
| } | |||
| } | |||
| @@ -93,7 +93,7 @@ class MeetingsApi { | |||
| } | |||
| FormData? formData; | |||
| if (managerId != null) { | |||
| if (managerId == null) { | |||
| formData = FormData.fromMap({ | |||
| 'locations_id': locationId, | |||
| 'subject_id': subjectId, | |||
| @@ -113,7 +113,7 @@ class MeetingsApi { | |||
| 'date_meeting': dateMeeting, | |||
| }); | |||
| } | |||
| print('${formData.fields} saggggggggg'); | |||
| // print('${formData.fields} saggggggggg'); | |||
| final res = await Dio().post("${config.network.baseUrl}admin/add-meeting", | |||
| data: formData, options: Options(headers: headers)); | |||
| @@ -121,7 +121,7 @@ class MeetingsApi { | |||
| return Result(isOk: true, message: res.data['message']); | |||
| } | |||
| } on DioException catch (e) { | |||
| print('${e.message}'); | |||
| // print('${e.message}'); | |||
| return Result( | |||
| isOk: false, | |||
| errors: e.response!.data['errors'], | |||
| @@ -209,6 +209,41 @@ class MeetingsApi { | |||
| return const Result(isOk: false); | |||
| } | |||
| // delete meeting | |||
| Future<Result> deleteMeetingApi({ | |||
| required int id, | |||
| }) async { | |||
| try { | |||
| Map<String, String> headers = {"Accept": "application/json"}; | |||
| String? dataToken = setting.userLocalDb.getUser().token; | |||
| if (dataToken != null && dataToken.isNotEmpty) { | |||
| headers['Authorization'] = "Bearer $dataToken"; | |||
| } | |||
| String url = "${config.network.baseUrl}admin/meetings/$id"; | |||
| final res = await Dio().delete( | |||
| url, | |||
| options: Options(headers: headers), | |||
| ); | |||
| if (res.statusCode == 200 || res.statusCode == 201) { | |||
| return Result(isOk: true, message: res.data['message']); | |||
| } | |||
| } on DioException catch (e) { | |||
| if (e.response != null) { | |||
| return Result( | |||
| isOk: false, | |||
| errors: e.response?.data['errors'], | |||
| message: e.response?.data['message'], | |||
| ); | |||
| } | |||
| } | |||
| return const Result(isOk: false); | |||
| } | |||
| // accept meeting | |||
| Future<Result> acceptMeetingApi({ | |||
| required int id, | |||
| @@ -354,7 +389,7 @@ class MeetingsApi { | |||
| ), | |||
| ); | |||
| print('${response.data} response.data'); | |||
| // print('${response.data} response.data'); | |||
| // بررسی ساختار پاسخ و تبدیل دادهها | |||
| if (response.data is List) { | |||
| @@ -382,7 +417,7 @@ class MeetingsApi { | |||
| // Send request | |||
| final link = | |||
| "${config.network.baseUrl}admin/delete-meeting-minutes/$id/$text"; | |||
| print('${link}'); | |||
| // print('${link}'); | |||
| final res = await Dio().get( | |||
| link, | |||
| data: formData, | |||
| @@ -394,7 +429,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'], | |||
| @@ -16,9 +16,9 @@ class NotificationService { | |||
| ); | |||
| if (settings.authorizationStatus == AuthorizationStatus.authorized) { | |||
| print('User granted permission'); | |||
| // print('User granted permission'); | |||
| } else { | |||
| print('User declined or has not granted permission'); | |||
| // print('User declined or has not granted permission'); | |||
| } | |||
| } | |||
| @@ -32,8 +32,8 @@ class NotificationService { | |||
| /// تنظیم Listener برای دریافت نوتیفیکیشنها | |||
| void setupMessageListener() { | |||
| FirebaseMessaging.onMessage.listen((RemoteMessage message) { | |||
| print('Message received: ${message.notification?.title}'); | |||
| print('Message body: ${message.notification?.body}'); | |||
| // print('Message received: ${message.notification?.title}'); | |||
| // print('Message body: ${message.notification?.body}'); | |||
| // اینجا میتوانید یک Dialog یا Toast برای نمایش پیام استفاده کنید | |||
| }); | |||
| } | |||
| @@ -151,6 +151,33 @@ class PrivateMeetingsApi { | |||
| return const Result(isOk: false); | |||
| } | |||
| // delete private meeting | |||
| Future<Result> deletePrivateMeetingApi({ | |||
| required int id, | |||
| }) 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/delete-private-meeting/${id}", | |||
| 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, | |||
| @@ -6,14 +6,14 @@ import 'package:qadirneyriz/setting/setting.dart'; | |||
| import 'package:qadirneyriz/utils/result/result.dart'; | |||
| class ReportApi { | |||
| Future<Result?> downloadReport( | |||
| Future<Result?> downloadReportMeetings( | |||
| {String? fromDate, | |||
| String? toDate, | |||
| int? location, | |||
| int? subject, | |||
| int? meetingManager, | |||
| int? status, | |||
| String format = 'xlsx'}) async { | |||
| required String format}) async { | |||
| try { | |||
| final Map<String, String> headers = {"Accept": "application/json"}; | |||
| String dataToken = setting.userLocalDb.getUser().token!; | |||
| @@ -35,6 +35,7 @@ class ReportApi { | |||
| 'subject': subject, | |||
| 'meeting_manager': meetingManager, | |||
| 'status': status, | |||
| 'format': format | |||
| }, | |||
| options: Options(headers: headers), | |||
| ); | |||
| @@ -46,6 +47,74 @@ class ReportApi { | |||
| isOk: false, message: 'Failed with status code: ${res.statusCode}'); | |||
| } | |||
| } on DioException catch (e) { | |||
| // print(e); | |||
| return Result( | |||
| isOk: false, | |||
| errors: e.response?.data['errors'], | |||
| message: | |||
| e.response?.data['message'] ?? 'An error occurred during download.', | |||
| ); | |||
| } | |||
| } | |||
| Future<Result?> downloadReportPrivateMeetings( | |||
| {String? fromDate, | |||
| String? toDate, | |||
| int? location, | |||
| int? subject, | |||
| int? meetingManager, | |||
| int? status, | |||
| required String format}) 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/reportprivatemeeting.$format'; | |||
| // print("Download path: $savePath"); | |||
| final String url = '${config.network.baseUrl}private_meetings/export'; | |||
| // print("Download URL: $url"); | |||
| final Map<String, dynamic> params = { | |||
| 'date_meeting_az': fromDate, | |||
| 'date_meeting_ta': toDate, | |||
| 'location': location, | |||
| 'subject': subject, | |||
| 'meeting_manager': meetingManager, | |||
| 'status': status, | |||
| 'format': format | |||
| }; | |||
| // print("Request parameters: $params"); | |||
| final res = await Dio().download( | |||
| url, | |||
| savePath, | |||
| queryParameters: params, | |||
| options: Options(headers: headers), | |||
| ); | |||
| // print("Response status: ${res.statusCode}"); | |||
| // print("Response headers: ${res.headers}"); | |||
| if (res.statusCode == 200 || res.statusCode == 201) { | |||
| // print("File downloaded successfully: $savePath"); | |||
| return Result(isOk: true, message: savePath); | |||
| } else { | |||
| print("Failed with status code: ${res.statusCode}"); | |||
| return Result( | |||
| isOk: false, message: 'Failed with status code: ${res.statusCode}'); | |||
| } | |||
| } on DioException catch (e) { | |||
| print("DioException: $e"); | |||
| print("Error response data: ${e.response?.data}"); | |||
| return Result( | |||
| isOk: false, | |||
| errors: e.response?.data['errors'], | |||
| @@ -1,7 +1,13 @@ | |||
| import 'package:firebase_messaging/firebase_messaging.dart'; | |||
| 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/global/global_state/global_state.dart'; | |||
| import 'package:qadirneyriz/main.dart'; | |||
| import 'package:qadirneyriz/screens/auth/state/state.dart'; | |||
| import 'package:qadirneyriz/setting/setting.dart'; | |||
| import 'package:qadirneyriz/utils/enums/status.dart'; | |||
| import 'package:qadirneyriz/widgets/custom_background.dart'; | |||
| import 'package:qadirneyriz/widgets/loading_widget.dart'; | |||
| @@ -13,39 +19,87 @@ class SplashScreen extends StatefulWidget { | |||
| } | |||
| class _SplashScreenState extends State<SplashScreen> { | |||
| late AuthState authState; | |||
| late GlobalState globalState; | |||
| @override | |||
| void initState() { | |||
| authState = Provider.of<AuthState>(context, listen: false); | |||
| globalState = Provider.of<GlobalState>(context, listen: false); | |||
| super.initState(); | |||
| checkUser(); | |||
| checkUser(authState: authState, globalState: globalState); | |||
| } | |||
| @override | |||
| Widget build(BuildContext context) { | |||
| return Scaffold( | |||
| body: CustomBackground( | |||
| child: Column( | |||
| mainAxisAlignment: MainAxisAlignment.spaceBetween, | |||
| children: [ | |||
| Spacer(), | |||
| LoadingWidget( | |||
| color: config.ui.mainGreen, | |||
| size: 30, | |||
| return Consumer<AuthState>( | |||
| builder: (context, value, child) { | |||
| return Scaffold( | |||
| body: CustomBackground( | |||
| child: Column( | |||
| mainAxisAlignment: MainAxisAlignment.spaceBetween, | |||
| children: [ | |||
| Spacer(), | |||
| LoadingWidget( | |||
| color: config.ui.mainGreen, | |||
| size: 30, | |||
| ), | |||
| ], | |||
| ), | |||
| ], | |||
| ), | |||
| ), | |||
| ), | |||
| ); | |||
| }, | |||
| ); | |||
| } | |||
| void checkUser() async { | |||
| void checkUser( | |||
| {required AuthState authState, required GlobalState globalState}) async { | |||
| await requestNotificationPermission(); | |||
| getToken(); | |||
| setupMessageListener(); | |||
| String token = setting.userLocalDb.getUser().token ?? ''; | |||
| Future.delayed(const Duration(seconds: 4), () { | |||
| Future.delayed(const Duration(seconds: 4), () async { | |||
| final Status logoImagesStatus = await globalState.getLogoImages(); | |||
| if (token != '') { | |||
| context.goNamed('navigate', pathParameters: {'tab': '0'}); | |||
| final Status checkLogin = await authState.CheckLogin(); | |||
| if (checkLogin == Status.ready) { | |||
| context.goNamed('navigate', pathParameters: {'tab': '0'}); | |||
| } else { | |||
| context.goNamed('login'); | |||
| } | |||
| } else { | |||
| context.goNamed('login'); | |||
| } | |||
| }); | |||
| } | |||
| Future<void> requestNotificationPermission() async { | |||
| NotificationSettings settings = await messaging.requestPermission( | |||
| alert: true, | |||
| announcement: false, | |||
| badge: true, | |||
| carPlay: false, | |||
| criticalAlert: false, | |||
| provisional: false, | |||
| sound: true, | |||
| ); | |||
| if (settings.authorizationStatus == AuthorizationStatus.authorized) { | |||
| // print('User granted permission'); | |||
| } else { | |||
| // print('User declined or has not granted permission'); | |||
| } | |||
| } | |||
| Future<void> getToken() async { | |||
| await messaging.getToken(); | |||
| } | |||
| void setupMessageListener() { | |||
| FirebaseMessaging.onMessage.listen((RemoteMessage message) { | |||
| // print('Message received: ${message.notification?.title}'); | |||
| // print('Message body: ${message.notification?.body}'); | |||
| // You can use a Dialog or Toast to display the message here | |||
| }); | |||
| } | |||
| } | |||
| @@ -1,32 +1,62 @@ | |||
| // 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:provider/provider.dart'; | |||
| import 'package:qadirneyriz/config/config.dart'; | |||
| import 'package:qadirneyriz/global/global_state/global_state.dart'; | |||
| import 'package:qadirneyriz/utils/enums/status.dart'; | |||
| import 'package:qadirneyriz/widgets/custom_netimage.dart'; | |||
| class CustomAppbar extends StatelessWidget { | |||
| class CustomAppbar extends StatefulWidget { | |||
| final String? title; | |||
| const CustomAppbar({super.key, this.title}); | |||
| const CustomAppbar({ | |||
| Key? key, | |||
| this.title, | |||
| }) : super(key: key); | |||
| @override | |||
| State<CustomAppbar> createState() => _CustomAppbarState(); | |||
| } | |||
| class _CustomAppbarState extends State<CustomAppbar> { | |||
| @override | |||
| Widget build(BuildContext context) { | |||
| return SliverAppBar( | |||
| title: Row( | |||
| mainAxisAlignment: MainAxisAlignment.spaceBetween, | |||
| children: [ | |||
| const SizedBox(), | |||
| Text( | |||
| this.title == null ? '' : this.title ?? '', | |||
| style: const TextStyle( | |||
| fontSize: 12, | |||
| color: Colors.black, | |||
| ), | |||
| ), | |||
| Image.asset( | |||
| 'assets/images/D2.png', // مسیر لوگو رو اینجا قرار بده | |||
| height: 40, | |||
| return Consumer<GlobalState>( | |||
| builder: (context, globalState, child) { | |||
| final String image = globalState.logoImagesStatus == Status.ready | |||
| ? globalState.logoImagesModel!.data! | |||
| .firstWhere( | |||
| (element) => element.key == 'logo', | |||
| ) | |||
| .value ?? | |||
| '' | |||
| : ''; | |||
| final String ImageUrl = '${config.network.imageUrl}${image}'; | |||
| return SliverAppBar( | |||
| title: Row( | |||
| mainAxisAlignment: MainAxisAlignment.spaceBetween, | |||
| children: [ | |||
| const SizedBox(), | |||
| Text( | |||
| this.widget.title == null ? '' : this.widget.title ?? '', | |||
| style: const TextStyle( | |||
| fontSize: 12, | |||
| color: Colors.black, | |||
| ), | |||
| ), | |||
| globalState.logoImagesStatus == Status.ready | |||
| ? CustomImage( | |||
| logo: true, | |||
| image: ImageUrl, | |||
| width: 50, | |||
| height: 50, | |||
| ) | |||
| : Container() | |||
| ], | |||
| ), | |||
| ], | |||
| ), | |||
| backgroundColor: config.ui.backGroundColor, | |||
| backgroundColor: config.ui.backGroundColor, | |||
| ); | |||
| }, | |||
| ); | |||
| } | |||
| } | |||
| @@ -2,12 +2,12 @@ import 'package:cached_network_image/cached_network_image.dart'; | |||
| import 'package:flutter/material.dart'; | |||
| import 'package:qadirneyriz/config/config.dart'; | |||
| class CustomImage extends StatelessWidget { | |||
| final String? image; | |||
| final double? height; | |||
| final double? width; | |||
| final BoxFit? boxFit; | |||
| final bool logo; | |||
| final double borderRadius; // Border radius | |||
| const CustomImage({ | |||
| @@ -15,6 +15,7 @@ class CustomImage extends StatelessWidget { | |||
| required this.image, | |||
| this.height, | |||
| this.width, | |||
| this.logo = false, | |||
| this.boxFit = BoxFit.cover, | |||
| this.borderRadius = 10.0, // Default border radius | |||
| }); | |||
| @@ -25,7 +26,7 @@ class CustomImage extends StatelessWidget { | |||
| if (image != null && image != '') { | |||
| imageWidget = CachedNetworkImage( | |||
| imageUrl: '${config.network.baseUrl}$image', | |||
| imageUrl: logo ? '$image' : '${config.network.baseUrl}$image', | |||
| width: width, | |||
| height: height, | |||
| fit: boxFit, | |||
| @@ -3,7 +3,7 @@ description: "A new Flutter project." | |||
| publish_to: 'none' | |||
| version: 1.0.3+3 | |||
| version: 2.0.0+1 | |||
| environment: | |||
| sdk: ^3.5.3 | |||