Bladeren bron

some changes

master
amin 11 maanden geleden
bovenliggende
commit
d6280dcc22
30 gewijzigde bestanden met toevoegingen van 1534 en 247 verwijderingen
  1. +6
    -6
      ios/Runner.xcodeproj/project.pbxproj
  2. +2
    -2
      ios/Runner/Info.plist
  3. +1
    -0
      lib/config/network_config.dart
  4. +25
    -11
      lib/drawer_navigation_bar.dart
  5. +45
    -1
      lib/global/global_state/global_state.dart
  6. +13
    -0
      lib/l10n/app_en.arb
  7. +18
    -1
      lib/l10n/app_fa.arb
  8. +4
    -38
      lib/main.dart
  9. +49
    -0
      lib/models/logo_images_model.dart
  10. +117
    -41
      lib/screens/aboutUs/screen.dart
  11. +45
    -5
      lib/screens/auth/state/state.dart
  12. +19
    -17
      lib/screens/home/screen.dart
  13. +1
    -1
      lib/screens/home/state.dart
  14. +42
    -2
      lib/screens/meeting/screen.dart
  15. +35
    -0
      lib/screens/meeting/state.dart
  16. +59
    -1
      lib/screens/private_meeting/screen.dart
  17. +37
    -2
      lib/screens/private_meeting/state.dart
  18. +10
    -10
      lib/screens/private_meeting_summary/state.dart
  19. +537
    -31
      lib/screens/report/screen.dart
  20. +110
    -20
      lib/screens/report/state.dart
  21. +49
    -5
      lib/services/auth/auth.dart
  22. +42
    -1
      lib/services/global/global.dart
  23. +41
    -6
      lib/services/meetings/meetings.dart
  24. +4
    -4
      lib/services/notification/notification_service.dart
  25. +27
    -0
      lib/services/private_meetings/private_meetings.dart
  26. +71
    -2
      lib/services/report/report.dart
  27. +70
    -16
      lib/splash_screen.dart
  28. +51
    -21
      lib/widgets/custom_appbar.dart
  29. +3
    -2
      lib/widgets/custom_netimage.dart
  30. +1
    -1
      pubspec.yaml

+ 6
- 6
ios/Runner.xcodeproj/project.pbxproj Bestand weergeven

@@ -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 = (


+ 2
- 2
ios/Runner/Info.plist Bestand weergeven

@@ -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
- 0
lib/config/network_config.dart Bestand weergeven

@@ -1,4 +1,5 @@
class NetworkConfig {
final baseUrl = 'https://api.nghsco.com/api/';
final imageUrl = 'https://api.nghsco.com/storage/statics/';
const NetworkConfig();
}

+ 25
- 11
lib/drawer_navigation_bar.dart Bestand weergeven

@@ -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');
}),
],
),


+ 45
- 1
lib/global/global_state/global_state.dart Bestand weergeven

@@ -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;


+ 13
- 0
lib/l10n/app_en.arb Bestand weergeven

@@ -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."
}

+ 18
- 1
lib/l10n/app_fa.arb Bestand weergeven

@@ -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":"ساعت",


+ 4
- 38
lib/main.dart Bestand weergeven

@@ -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> {
);
}
}



+ 49
- 0
lib/models/logo_images_model.dart Bestand weergeven

@@ -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,
};
}

+ 117
- 41
lib/screens/aboutUs/screen.dart Bestand weergeven

@@ -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),
],
);
}


+ 45
- 5
lib/screens/auth/state/state.dart Bestand weergeven

@@ -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;
}

}

+ 19
- 17
lib/screens/home/screen.dart Bestand weergeven

@@ -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:


+ 1
- 1
lib/screens/home/state.dart Bestand weergeven

@@ -144,7 +144,7 @@ class HomeState extends ChangeNotifier {
// print(e);
}
notifyListeners();
print(statusEditProfile);
// print(statusEditProfile);
return statusEditProfile;
}
}

+ 42
- 2
lib/screens/meeting/screen.dart Bestand weergeven

@@ -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);


+ 35
- 0
lib/screens/meeting/state.dart Bestand weergeven

@@ -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;


+ 59
- 1
lib/screens/private_meeting/screen.dart Bestand weergeven

@@ -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);


+ 37
- 2
lib/screens/private_meeting/state.dart Bestand weergeven

@@ -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;


+ 10
- 10
lib/screens/private_meeting_summary/state.dart Bestand weergeven

@@ -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;
}



+ 537
- 31
lib/screens/report/screen.dart Bestand weergeven

@@ -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,


+ 110
- 20
lib/screens/report/state.dart Bestand weergeven

@@ -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;
}
}

+ 49
- 5
lib/services/auth/auth.dart Bestand weergeven

@@ -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);
}
}

+ 42
- 1
lib/services/global/global.dart Bestand weergeven

@@ -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;
}
}
}

+ 41
- 6
lib/services/meetings/meetings.dart Bestand weergeven

@@ -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'],


+ 4
- 4
lib/services/notification/notification_service.dart Bestand weergeven

@@ -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 برای نمایش پیام استفاده کنید
});
}


+ 27
- 0
lib/services/private_meetings/private_meetings.dart Bestand weergeven

@@ -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,


+ 71
- 2
lib/services/report/report.dart Bestand weergeven

@@ -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'],


+ 70
- 16
lib/splash_screen.dart Bestand weergeven

@@ -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
});
}
}

+ 51
- 21
lib/widgets/custom_appbar.dart Bestand weergeven

@@ -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,
);
},
);
}
}

+ 3
- 2
lib/widgets/custom_netimage.dart Bestand weergeven

@@ -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,


+ 1
- 1
pubspec.yaml Bestand weergeven

@@ -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


Laden…
Annuleren
Opslaan