mirror of
				https://gitlab.com/comunic/comunicmobile
				synced 2025-10-31 10:14:50 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			419 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
			
		
		
	
	
			419 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
| import 'dart:io';
 | |
| 
 | |
| import 'package:comunic/enums/post_kind.dart';
 | |
| import 'package:comunic/enums/post_target.dart';
 | |
| import 'package:comunic/enums/post_visibility_level.dart';
 | |
| import 'package:comunic/helpers/posts_helper.dart';
 | |
| import 'package:comunic/models/new_post.dart';
 | |
| import 'package:comunic/ui/dialogs/input_url_dialog.dart';
 | |
| import 'package:comunic/ui/dialogs/input_youtube_link_dialog.dart';
 | |
| import 'package:comunic/ui/dialogs/new_survey_dialog.dart';
 | |
| import 'package:comunic/utils/files_utils.dart';
 | |
| import 'package:comunic/utils/intl_utils.dart';
 | |
| import 'package:comunic/utils/post_utils.dart';
 | |
| import 'package:comunic/utils/ui_utils.dart';
 | |
| import 'package:file_picker_cross/file_picker_cross.dart';
 | |
| import 'package:flutter/material.dart';
 | |
| 
 | |
| /// Widget that allows to create posts
 | |
| ///
 | |
| /// @author Pierre HUBERT
 | |
| 
 | |
| const _ActiveButtonsColor = Colors.blue;
 | |
| const _ActiveButtonsTextColor = Colors.white;
 | |
| const _InactiveButtonsColor = Colors.grey;
 | |
| const _InactiveButtonsTextColor = Colors.black;
 | |
| 
 | |
| class PostCreateFormWidget extends StatefulWidget {
 | |
|   final PostTarget postTarget;
 | |
|   final int targetID;
 | |
|   final void Function() onCreated;
 | |
| 
 | |
|   const PostCreateFormWidget({
 | |
|     Key key,
 | |
|     @required this.postTarget,
 | |
|     @required this.targetID,
 | |
|     @required this.onCreated,
 | |
|   })  : assert(postTarget != null),
 | |
|         assert(targetID != null),
 | |
|         super(key: key);
 | |
| 
 | |
|   @override
 | |
|   _PostCreateFormWidgetState createState() => _PostCreateFormWidgetState();
 | |
| }
 | |
| 
 | |
| class _PostCreateFormWidgetState extends State<PostCreateFormWidget> {
 | |
|   // Helpers
 | |
|   final PostsHelper _postHelper = PostsHelper();
 | |
| 
 | |
|   // Class members
 | |
|   bool _isCreating = false;
 | |
|   final TextEditingController _postTextController = TextEditingController();
 | |
|   PostVisibilityLevel _postVisibilityLevel;
 | |
|   File _postImage;
 | |
|   String _postURL;
 | |
|   List<int> _postPDF;
 | |
|   DateTime _timeEnd;
 | |
|   NewSurvey _postSurvey;
 | |
|   String _youtubeID;
 | |
| 
 | |
|   bool get hasImage => _postImage != null;
 | |
| 
 | |
|   bool get hasURL => _postURL != null;
 | |
| 
 | |
|   bool get hasPDF => _postPDF != null;
 | |
| 
 | |
|   bool get hasTimeEnd => _timeEnd != null;
 | |
| 
 | |
|   bool get hasSurvey => _postSurvey != null;
 | |
| 
 | |
|   bool get hasYouTubeID => _youtubeID != null;
 | |
| 
 | |
|   bool get canSubmitForm =>
 | |
|       !_isCreating && _postTextController.text.length > 5 ||
 | |
|       postKind != PostKind.TEXT;
 | |
| 
 | |
|   PostKind get postKind {
 | |
|     if (hasImage)
 | |
|       return PostKind.IMAGE;
 | |
|     else if (hasPDF)
 | |
|       return PostKind.PDF;
 | |
|     else if (hasURL)
 | |
|       return PostKind.WEB_LINK;
 | |
|     else if (hasTimeEnd)
 | |
|       return PostKind.COUNTDOWN;
 | |
|     else if (hasSurvey)
 | |
|       return PostKind.SURVEY;
 | |
|     else if (hasYouTubeID)
 | |
|       return PostKind.YOUTUBE;
 | |
|     else
 | |
|       return PostKind.TEXT;
 | |
|   }
 | |
| 
 | |
|   @override
 | |
|   void initState() {
 | |
|     super.initState();
 | |
| 
 | |
|     _resetForm();
 | |
|   }
 | |
| 
 | |
|   @override
 | |
|   Widget build(BuildContext context) {
 | |
|     return Column(
 | |
|       children: <Widget>[
 | |
|         // Post text content
 | |
|         Padding(
 | |
|           padding: const EdgeInsets.only(left: 8.0, right: 8.0),
 | |
|           child: TextField(
 | |
|             controller: _postTextController,
 | |
|             minLines: 3,
 | |
|             maxLines: 10,
 | |
|             decoration: InputDecoration(hintText: tr("Create a new post...")),
 | |
|             onChanged: (s) => setState(() {}),
 | |
|           ),
 | |
|         ),
 | |
| 
 | |
|         // Post options
 | |
|         Padding(
 | |
|           padding: const EdgeInsets.only(right: 16.0),
 | |
|           child: Row(
 | |
|             mainAxisAlignment: MainAxisAlignment.spaceBetween,
 | |
|             children: <Widget>[
 | |
|               // Text post button
 | |
|               Expanded(
 | |
|                 child: SingleChildScrollView(
 | |
|                   scrollDirection: Axis.horizontal,
 | |
|                   child: Row(
 | |
|                     children: <Widget>[
 | |
|                       _PostOptionWidget(
 | |
|                           icon: Icons.text_format,
 | |
|                           selected: postKind == PostKind.TEXT,
 | |
|                           onTap: _resetPostSelection),
 | |
| 
 | |
|                       // Include image button
 | |
|                       _PostOptionWidget(
 | |
|                         icon: Icons.image,
 | |
|                         selected: postKind == PostKind.IMAGE,
 | |
|                         onTap: _pickImageForPost,
 | |
|                       ),
 | |
| 
 | |
|                       // Web link
 | |
|                       _PostOptionWidget(
 | |
|                         icon: Icons.link,
 | |
|                         selected: postKind == PostKind.WEB_LINK,
 | |
|                         onTap: _pickURLForPost,
 | |
|                       ),
 | |
| 
 | |
|                       // Include PDF button
 | |
|                       _PostOptionWidget(
 | |
|                         icon: Icons.picture_as_pdf,
 | |
|                         selected: postKind == PostKind.PDF,
 | |
|                         onTap: _pickPDFForPost,
 | |
|                       ),
 | |
| 
 | |
|                       // Add countdown timer
 | |
|                       _PostOptionWidget(
 | |
|                         icon: Icons.timer,
 | |
|                         selected: postKind == PostKind.COUNTDOWN,
 | |
|                         onTap: _pickCountdownTime,
 | |
|                       ),
 | |
| 
 | |
|                       // Add survey
 | |
|                       _PostOptionWidget(
 | |
|                         icon: Icons.insert_chart,
 | |
|                         selected: postKind == PostKind.SURVEY,
 | |
|                         onTap: _pickSurvey,
 | |
|                       ),
 | |
| 
 | |
|                       // Specify YouTube video ID
 | |
|                       _PostOptionWidget(
 | |
|                         icon: Icons.ondemand_video,
 | |
|                         selected: postKind == PostKind.YOUTUBE,
 | |
|                         onTap: _pickYouTubeVideo,
 | |
|                       ),
 | |
|                     ],
 | |
|                   ),
 | |
|                 ),
 | |
|               ),
 | |
| 
 | |
|               Container(width: 20),
 | |
| 
 | |
|               // Post visibility level
 | |
|               _PostOptionWidget(
 | |
|                 icon: PostVisibilityLevelsMapIcons[_postVisibilityLevel],
 | |
|                 selected: false,
 | |
|                 customColor: Colors.black,
 | |
|                 onTap: _changeVisibilityLevel,
 | |
|               ),
 | |
| 
 | |
|               // Submit post button
 | |
|               _isCreating
 | |
|                   ? Container()
 | |
|                   : FlatButton(
 | |
|                       child: Text(tr("Send").toUpperCase()),
 | |
|                       onPressed: canSubmitForm ? _submitForm : null,
 | |
|                       color: _ActiveButtonsColor,
 | |
|                       textColor: _ActiveButtonsTextColor,
 | |
|                       disabledColor: _InactiveButtonsColor,
 | |
|                       disabledTextColor: _InactiveButtonsTextColor,
 | |
|                     ),
 | |
|             ],
 | |
|           ),
 | |
|         )
 | |
|       ],
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   /// Reset the form
 | |
|   void _resetForm() {
 | |
|     setState(() {
 | |
|       _postVisibilityLevel = widget.postTarget == PostTarget.GROUP_PAGE
 | |
|           ? PostVisibilityLevel.GROUP_MEMBERS
 | |
|           : PostVisibilityLevel.FRIENDS;
 | |
| 
 | |
|       _postTextController.text = "";
 | |
|       _resetPostSelection();
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   /// Change post visibility level
 | |
|   Future<void> _changeVisibilityLevel() async {
 | |
|     final newLevel = await showPostVisibilityPicker(
 | |
|       context: context,
 | |
|       initialLevel: _postVisibilityLevel,
 | |
|       isGroup: widget.postTarget == PostTarget.GROUP_PAGE,
 | |
|     );
 | |
| 
 | |
|     setState(() => _postVisibilityLevel = newLevel);
 | |
|   }
 | |
| 
 | |
|   /// Remove all data attached to the post (image, etc...)
 | |
|   void _resetPostSelection() {
 | |
|     setState(() {
 | |
|       _postImage = null;
 | |
|       _postURL = null;
 | |
|       _postPDF = null;
 | |
|       _timeEnd = null;
 | |
|       _postSurvey = null;
 | |
|       _youtubeID = null;
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   /// Pick an image for the new post
 | |
|   Future<void> _pickImageForPost() async {
 | |
|     final image = await pickImage(context);
 | |
| 
 | |
|     if (image == null) return;
 | |
| 
 | |
|     _resetPostSelection();
 | |
| 
 | |
|     setState(() {
 | |
|       this._postImage = image;
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   /// Choose a new URL for the post
 | |
|   Future<void> _pickURLForPost() async {
 | |
|     final url = await showInputURLDialog(
 | |
|       context: context,
 | |
|       title: tr("Specify URL"),
 | |
|       initialURL: _postURL,
 | |
|     );
 | |
| 
 | |
|     if (url == null) return;
 | |
| 
 | |
|     _resetPostSelection();
 | |
| 
 | |
|     setState(() {
 | |
|       _postURL = url;
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   /// Pick a PDF for the new post
 | |
|   Future<void> _pickPDFForPost() async {
 | |
|     try {
 | |
|       final picker = FilePickerCross(
 | |
|         type: FileTypeCross.custom,
 | |
|         fileExtension: "pdf",
 | |
|       );
 | |
| 
 | |
|       if (!await picker.pick()) return;
 | |
| 
 | |
|       _resetPostSelection();
 | |
| 
 | |
|       setState(() {
 | |
|         this._postPDF = picker.toUint8List();
 | |
|       });
 | |
|     } catch (e, stack) {
 | |
|       print("Pick PDF error: $e\n$stack");
 | |
|       showSimpleSnack(context, tr("Could not pick a PDF!"));
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /// Pick countdown time
 | |
|   Future<void> _pickCountdownTime() async {
 | |
|     final yesterday = DateTime.now().subtract(Duration(days: 1));
 | |
| 
 | |
|     final initialDate = _timeEnd == null ? DateTime.now() : _timeEnd;
 | |
| 
 | |
|     // Pick date
 | |
|     final newDate = await showDatePicker(
 | |
|         context: context,
 | |
|         initialDate: initialDate,
 | |
|         firstDate: yesterday.isBefore(initialDate) ? yesterday : initialDate,
 | |
|         lastDate: DateTime.now().add(Duration(days: 5000)));
 | |
| 
 | |
|     if (newDate == null) return;
 | |
| 
 | |
|     // Pick time
 | |
|     final newTime = await showTimePicker(
 | |
|       context: context,
 | |
|       initialTime: TimeOfDay.fromDateTime(initialDate),
 | |
|     );
 | |
| 
 | |
|     if (newTime == null) return;
 | |
| 
 | |
|     // Apply new selection
 | |
|     _resetPostSelection();
 | |
|     setState(() {
 | |
|       _timeEnd = newDate.add(Duration(
 | |
|           hours: newTime.hour - newDate.hour,
 | |
|           minutes: newTime.minute - newDate.minute,
 | |
|           seconds: -newDate.second));
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   /// Pick a new survey for this post
 | |
|   Future<void> _pickSurvey() async {
 | |
|     final newSurvey =
 | |
|         await showNewSurveyDialog(context: context, initialSurvey: _postSurvey);
 | |
| 
 | |
|     if (newSurvey == null) return;
 | |
| 
 | |
|     _resetPostSelection();
 | |
|     setState(() {
 | |
|       _postSurvey = newSurvey;
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   /// Pick a new YouTube video
 | |
|   Future<void> _pickYouTubeVideo() async {
 | |
| 
 | |
|     final youtubeID = await showInputYouTubeIDDialog(context, _youtubeID);
 | |
| 
 | |
|     if (youtubeID == null) return;
 | |
| 
 | |
|     _resetPostSelection();
 | |
|     setState(() {
 | |
|       _youtubeID = youtubeID;
 | |
|     });
 | |
| 
 | |
|   }
 | |
| 
 | |
|   /// Submit new post
 | |
|   Future<void> _submitForm() async {
 | |
|     if (!canSubmitForm)
 | |
|       showSimpleSnack(context, tr("Form can not be submitted at this point!"));
 | |
| 
 | |
|     setState(() => _isCreating = true);
 | |
| 
 | |
|     try {
 | |
|       await _postHelper.createPost(NewPost(
 | |
|         target: widget.postTarget,
 | |
|         targetID: widget.targetID,
 | |
|         visibility: _postVisibilityLevel,
 | |
|         content: _postTextController.text,
 | |
|         kind: postKind,
 | |
|         image: _postImage,
 | |
|         url: _postURL,
 | |
|         pdf: _postPDF,
 | |
|         timeEnd: _timeEnd,
 | |
|         survey: _postSurvey,
 | |
|         youtubeId: _youtubeID,
 | |
|       ));
 | |
|       setState(() => _isCreating = false);
 | |
| 
 | |
|       showSimpleSnack(context, tr("The post has been successfully created!"));
 | |
| 
 | |
|       this._resetForm();
 | |
|       widget.onCreated();
 | |
|     } catch (e) {
 | |
|       setState(() => _isCreating = false);
 | |
|       print("Error while creating post : " + e.toString());
 | |
|       showSimpleSnack(context, tr("Could not create post !"));
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /// Widget for a single post option
 | |
| class _PostOptionWidget extends StatelessWidget {
 | |
|   final IconData icon;
 | |
|   final bool selected;
 | |
|   final Color customColor;
 | |
|   final void Function() onTap;
 | |
| 
 | |
|   const _PostOptionWidget(
 | |
|       {Key key,
 | |
|       @required this.icon,
 | |
|       @required this.selected,
 | |
|       @required this.onTap,
 | |
|       this.customColor})
 | |
|       : assert(icon != null),
 | |
|         assert(selected != null),
 | |
|         assert(onTap != null),
 | |
|         super(key: key);
 | |
| 
 | |
|   bool get hasCustomColor => customColor != null;
 | |
| 
 | |
|   @override
 | |
|   Widget build(BuildContext context) {
 | |
|     return IconButton(
 | |
|       icon: Icon(icon),
 | |
|       onPressed: onTap,
 | |
|       color: hasCustomColor
 | |
|           ? customColor
 | |
|           : selected ? _ActiveButtonsColor : _InactiveButtonsColor,
 | |
|     );
 | |
|   }
 | |
| }
 |