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/ui/dialogs/post_visibility_picker_dialog.dart'; import 'package:comunic/ui/widgets/post_container_widget.dart'; import 'package:comunic/utils/files_utils.dart'; import 'package:comunic/utils/intl_utils.dart'; import 'package:comunic/utils/ui_utils.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import '../../models/api_request.dart'; import '../../utils/log_utils.dart'; import '../../utils/ui_utils.dart'; /// Widget that allows to create posts /// /// @author Pierre HUBERT const _ActiveButtonsColor = Colors.blue; const _InactiveButtonsColor = Colors.grey; 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 { // Helpers final PostsHelper _postHelper = PostsHelper(); // Class members bool _isCreating = false; final TextEditingController _postTextController = TextEditingController(); PostVisibilityLevel? _postVisibilityLevel; BytesFile? _postImage; String? _postURL; List? _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 PostContainer( child: Card( child: Column( children: [ // 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: [ // Text post button Expanded( child: SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( children: [ _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() : ElevatedButton( child: Text(tr("Send")!.toUpperCase()), onPressed: canSubmitForm ? _submitForm : null), ], ), ) ], ), ), ); } /// 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 _changeVisibilityLevel() async { final newLevel = await showPostVisibilityPickerDialog( 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 _pickImageForPost() async { try { final image = await pickImage(context); if (image == null) return; _resetPostSelection(); setState(() { this._postImage = image; }); } catch (e, s) { logError(e, s); snack(context, tr("Failed to pick an image for the post!")!); } } /// Choose a new URL for the post Future _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 _pickPDFForPost() async { try { final file = await FilePicker.platform.pickFiles( type: FileType.custom, allowedExtensions: ["pdf"], withData: true, ); if (file == null || file.files.isEmpty) return; _resetPostSelection(); setState(() { this._postPDF = file.files.first.bytes; }); } catch (e, stack) { print("Pick PDF error: $e\n$stack"); showSimpleSnack(context, tr("Could not pick a PDF!")!); } } /// Pick countdown time Future _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 _pickSurvey() async { final newSurvey = await showNewSurveyDialog(context: context, initialSurvey: _postSurvey); if (newSurvey == null) return; _resetPostSelection(); setState(() { _postSurvey = newSurvey; }); } /// Pick a new YouTube video Future _pickYouTubeVideo() async { final youtubeID = await showInputYouTubeIDDialog(context, _youtubeID); if (youtubeID == null) return; _resetPostSelection(); setState(() { _youtubeID = youtubeID; }); } /// Submit new post Future _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, s) { setState(() => _isCreating = false); print("Error while creating post : $e $s"); 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, ); } }