No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.
 
 
 
 
 
 

330 líneas
10 KiB

  1. // ignore_for_file: public_member_api_docs, sort_constructors_first
  2. import 'package:file_picker/file_picker.dart';
  3. import 'package:flutter/material.dart';
  4. import 'package:provider/provider.dart';
  5. import 'package:qadirneyriz/config/config.dart';
  6. import 'package:qadirneyriz/models/meetings/meetings_model.dart';
  7. import 'package:qadirneyriz/screens/meeting_summary/state.dart';
  8. import 'package:qadirneyriz/utils/enums/status.dart';
  9. import 'package:qadirneyriz/utils/tools/tools.dart';
  10. import 'package:qadirneyriz/widgets/card_meeting.dart';
  11. import 'package:qadirneyriz/widgets/custom_appbar.dart';
  12. import 'package:qadirneyriz/widgets/custom_button.dart';
  13. class MeetingSummaryScreen extends StatefulWidget {
  14. final Datum meetingItem;
  15. const MeetingSummaryScreen({
  16. Key? key,
  17. required this.meetingItem,
  18. }) : super(key: key);
  19. @override
  20. State<MeetingSummaryScreen> createState() => _MeetingSummaryScreenState();
  21. }
  22. class _MeetingSummaryScreenState extends State<MeetingSummaryScreen> {
  23. late TextEditingController _textControllerDescription;
  24. @override
  25. void initState() {
  26. super.initState();
  27. _textControllerDescription = TextEditingController();
  28. }
  29. @override
  30. void dispose() {
  31. _textControllerDescription.dispose();
  32. super.dispose();
  33. }
  34. @override
  35. Widget build(BuildContext context) {
  36. return Scaffold(
  37. body: Consumer<MeetingSummaryState>(
  38. builder: (context, value, child) {
  39. return CustomScrollView(
  40. slivers: <Widget>[
  41. CustomAppbar(
  42. title: 'صورت جلسه',
  43. ),
  44. SliverPadding(
  45. padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 2),
  46. sliver: SliverToBoxAdapter(
  47. child: CustomCardMeeting(
  48. titel: widget.meetingItem.subject!.subject ?? '',
  49. date: widget.meetingItem.dateJalali ?? '',
  50. location: widget.meetingItem.location!.address ?? '',
  51. fromTime: widget.meetingItem.azHour ?? '',
  52. toTime: widget.meetingItem.taHour ?? '',
  53. cardId: widget.meetingItem.id ?? -1,
  54. ),
  55. ),
  56. ),
  57. SliverPadding(
  58. padding:
  59. const EdgeInsets.symmetric(vertical: 20, horizontal: 10),
  60. sliver: SliverToBoxAdapter(
  61. child: Container(
  62. decoration: BoxDecoration(
  63. color: const Color(0xffF4F9F6),
  64. boxShadow: [
  65. BoxShadow(
  66. color: config.ui.mainGray.withOpacity(.1),
  67. spreadRadius: .1,
  68. offset: const Offset(0, 2),
  69. blurRadius: 6,
  70. ),
  71. ],
  72. borderRadius: const BorderRadius.all(Radius.circular(12)),
  73. ),
  74. child: CustomTextArea(
  75. hintText: 'شرح جلسه',
  76. controller: _textControllerDescription,
  77. ),
  78. ),
  79. ),
  80. ),
  81. SliverToBoxAdapter(
  82. child: ReceiptUploadDialog(
  83. state: value,
  84. ),
  85. ),
  86. SliverToBoxAdapter(
  87. child: Padding(
  88. padding: const EdgeInsets.all(30.0),
  89. child: submitSammaryButton(context, value),
  90. ),
  91. )
  92. ],
  93. );
  94. },
  95. ),
  96. );
  97. }
  98. Widget submitSammaryButton(BuildContext context, MeetingSummaryState state) {
  99. switch (state.statusMinuteMeeting) {
  100. case Status.loading:
  101. return CustomButton(hieght: 50, text: 'صبر کنید!');
  102. default:
  103. return CustomButton(
  104. hieght: 50,
  105. text: 'ثبت صورت جلسه',
  106. onPressed: () async {
  107. if (_textControllerDescription.text == '') {
  108. // call add new subject
  109. Tools.showCustomSnackBar(
  110. text: 'موضوع وارد کنید!',
  111. isError: true,
  112. context,
  113. );
  114. } else if (state.selectedFiles == null) {
  115. // call add new subject
  116. Tools.showCustomSnackBar(
  117. text: 'فایل وارد کنید!',
  118. isError: true,
  119. context,
  120. );
  121. } else {
  122. final status = await state.addMinuteMeeting(
  123. id: widget.meetingItem.id ?? -1,
  124. description: _textControllerDescription.text,
  125. meetingFiles: state.selectedFiles ?? []);
  126. if (status == Status.ready) {
  127. // call refrresh subjects
  128. } else {
  129. Tools.showCustomSnackBar(
  130. text: state.errorsMinuteMeeting == null
  131. ? state.messageMinuteMeeting ??
  132. ' AppLocalizations.of(context)!.haserror'
  133. : Tools.combineErrorMessages(
  134. state.errorsMinuteMeeting ?? {}),
  135. isError: true,
  136. context,
  137. );
  138. }
  139. }
  140. },
  141. );
  142. }
  143. }
  144. }
  145. class CustomTextArea extends StatelessWidget {
  146. final String hintText;
  147. final TextEditingController controller;
  148. final int maxLines;
  149. final int minLines;
  150. const CustomTextArea({
  151. Key? key,
  152. required this.hintText,
  153. required this.controller,
  154. this.maxLines = 20,
  155. this.minLines = 4,
  156. }) : super(key: key);
  157. @override
  158. Widget build(BuildContext context) {
  159. return TextField(
  160. controller: controller,
  161. maxLines: maxLines,
  162. minLines: minLines,
  163. decoration: InputDecoration(
  164. hintText: hintText,
  165. hintStyle: TextStyle(color: Colors.black.withOpacity(.4), fontSize: 13),
  166. border: InputBorder.none,
  167. contentPadding: const EdgeInsets.all(12.0),
  168. ),
  169. );
  170. }
  171. }
  172. class ReceiptUploadDialog extends StatefulWidget {
  173. final MeetingSummaryState state;
  174. const ReceiptUploadDialog({
  175. Key? key,
  176. required this.state,
  177. }) : super(key: key);
  178. @override
  179. _ReceiptUploadDialogState createState() => _ReceiptUploadDialogState();
  180. }
  181. class _ReceiptUploadDialogState extends State<ReceiptUploadDialog> {
  182. @override
  183. Widget build(BuildContext context) {
  184. return Padding(
  185. padding: const EdgeInsets.symmetric(horizontal: 10),
  186. child: Container(
  187. decoration: BoxDecoration(
  188. color: const Color(0xffF4F9F6),
  189. boxShadow: [
  190. BoxShadow(
  191. color: config.ui.mainGray.withOpacity(.1),
  192. spreadRadius: .1,
  193. offset: const Offset(0, 2),
  194. blurRadius: 6,
  195. ),
  196. ],
  197. borderRadius: const BorderRadius.all(Radius.circular(12)),
  198. ),
  199. child: Padding(
  200. padding: const EdgeInsets.symmetric(vertical: 25, horizontal: 10),
  201. child: Column(
  202. mainAxisSize: MainAxisSize.min,
  203. crossAxisAlignment: CrossAxisAlignment.start,
  204. children: [
  205. Text(
  206. 'آپلود فایل',
  207. style: TextStyle(
  208. fontSize: 16,
  209. fontWeight: FontWeight.bold,
  210. color: config.ui.mainGreen,
  211. ),
  212. ),
  213. const SizedBox(height: 20),
  214. // Upload box
  215. FileBorderBox(
  216. child: GestureDetector(
  217. onTap: widget.state.pickFiles,
  218. child: Column(
  219. children: [
  220. Icon(Icons.cloud_upload_outlined,
  221. size: 30, color: config.ui.mainGreen),
  222. Text(
  223. 'انتخاب فایل',
  224. style:
  225. TextStyle(fontSize: 12, color: config.ui.mainGreen),
  226. ),
  227. ],
  228. ),
  229. ),
  230. ),
  231. // File preview
  232. if (widget.state.selectedFiles != null)
  233. FilePreview(
  234. files: widget.state.selectedFiles!,
  235. onDelete: widget.state.removeFile,
  236. ),
  237. ],
  238. ),
  239. ),
  240. ),
  241. );
  242. }
  243. }
  244. class FilePreview extends StatelessWidget {
  245. final List<PlatformFile> files;
  246. final void Function(int) onDelete;
  247. const FilePreview({
  248. super.key,
  249. required this.files,
  250. required this.onDelete,
  251. });
  252. @override
  253. Widget build(BuildContext context) {
  254. return ListView.builder(
  255. shrinkWrap: true,
  256. itemCount: files.length,
  257. physics: const NeverScrollableScrollPhysics(),
  258. itemBuilder: (BuildContext context, int index) {
  259. return Container(
  260. padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
  261. margin: const EdgeInsets.symmetric(vertical: 5, horizontal: 10),
  262. decoration: BoxDecoration(
  263. borderRadius: BorderRadius.circular(10),
  264. border: Border(bottom: BorderSide(color: config.ui.secendGreen)),
  265. ),
  266. child: Row(
  267. mainAxisAlignment: MainAxisAlignment.start,
  268. children: [
  269. const Icon(Icons.file_present_outlined, color: Color(0xffD0D5ED)),
  270. const SizedBox(width: 10),
  271. Expanded(
  272. child: Text(
  273. files[index].name,
  274. style: const TextStyle(fontSize: 12),
  275. ),
  276. ),
  277. IconButton(
  278. icon: Icon(Icons.delete, color: config.ui.secendGreen),
  279. onPressed: () => onDelete(index),
  280. ),
  281. ],
  282. ),
  283. );
  284. },
  285. );
  286. }
  287. }
  288. class FileBorderBox extends StatelessWidget {
  289. final Widget child;
  290. const FileBorderBox({super.key, required this.child});
  291. @override
  292. Widget build(BuildContext context) {
  293. return Container(
  294. padding: const EdgeInsets.all(20),
  295. decoration: BoxDecoration(
  296. borderRadius: BorderRadius.circular(10),
  297. border: Border.all(
  298. color: config.ui.mainGreen,
  299. style: BorderStyle.solid,
  300. width: .5,
  301. ),
  302. ),
  303. child: Center(child: child),
  304. );
  305. }
  306. }