2019-07-05 09:40:43 +00:00
|
|
|
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';
|
2020-04-25 15:16:33 +00:00
|
|
|
import 'package:comunic/ui/dialogs/input_url_dialog.dart';
|
|
|
|
import 'package:comunic/ui/dialogs/input_youtube_link_dialog.dart';
|
2020-04-25 09:58:45 +00:00
|
|
|
import 'package:comunic/ui/dialogs/new_survey_dialog.dart';
|
2021-03-17 16:53:07 +00:00
|
|
|
import 'package:comunic/ui/dialogs/post_visibility_picker_dialog.dart';
|
2020-05-16 07:20:17 +00:00
|
|
|
import 'package:comunic/ui/widgets/post_container_widget.dart';
|
2019-07-05 09:40:43 +00:00
|
|
|
import 'package:comunic/utils/files_utils.dart';
|
|
|
|
import 'package:comunic/utils/intl_utils.dart';
|
|
|
|
import 'package:comunic/utils/ui_utils.dart';
|
2021-03-13 14:14:54 +00:00
|
|
|
import 'package:file_picker/file_picker.dart';
|
2019-07-05 09:40:43 +00:00
|
|
|
import 'package:flutter/material.dart';
|
2021-03-13 17:03:20 +00:00
|
|
|
|
|
|
|
import '../../models/api_request.dart';
|
|
|
|
import '../../utils/log_utils.dart';
|
|
|
|
import '../../utils/ui_utils.dart';
|
2019-07-05 09:40:43 +00:00
|
|
|
|
|
|
|
/// 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({
|
2022-03-10 18:39:57 +00:00
|
|
|
Key? key,
|
|
|
|
required this.postTarget,
|
|
|
|
required this.targetID,
|
|
|
|
required this.onCreated,
|
2022-03-11 15:36:42 +00:00
|
|
|
}) : super(key: key);
|
2019-07-05 09:40:43 +00:00
|
|
|
|
|
|
|
@override
|
|
|
|
_PostCreateFormWidgetState createState() => _PostCreateFormWidgetState();
|
|
|
|
}
|
|
|
|
|
|
|
|
class _PostCreateFormWidgetState extends State<PostCreateFormWidget> {
|
|
|
|
// Helpers
|
|
|
|
final PostsHelper _postHelper = PostsHelper();
|
|
|
|
|
|
|
|
// Class members
|
|
|
|
bool _isCreating = false;
|
|
|
|
final TextEditingController _postTextController = TextEditingController();
|
2022-03-10 18:39:57 +00:00
|
|
|
PostVisibilityLevel? _postVisibilityLevel;
|
|
|
|
BytesFile? _postImage;
|
|
|
|
String? _postURL;
|
|
|
|
List<int>? _postPDF;
|
|
|
|
DateTime? _timeEnd;
|
|
|
|
NewSurvey? _postSurvey;
|
|
|
|
String? _youtubeID;
|
2019-07-05 09:40:43 +00:00
|
|
|
|
|
|
|
bool get hasImage => _postImage != null;
|
|
|
|
|
2020-04-25 12:38:15 +00:00
|
|
|
bool get hasURL => _postURL != null;
|
|
|
|
|
2020-04-24 11:35:05 +00:00
|
|
|
bool get hasPDF => _postPDF != null;
|
|
|
|
|
2020-04-25 06:23:52 +00:00
|
|
|
bool get hasTimeEnd => _timeEnd != null;
|
|
|
|
|
2020-04-25 09:58:45 +00:00
|
|
|
bool get hasSurvey => _postSurvey != null;
|
|
|
|
|
2020-04-25 15:17:11 +00:00
|
|
|
bool get hasYouTubeID => _youtubeID != null;
|
2020-04-25 15:16:33 +00:00
|
|
|
|
2019-07-05 09:40:43 +00:00
|
|
|
bool get canSubmitForm =>
|
2020-04-24 11:37:44 +00:00
|
|
|
!_isCreating && _postTextController.text.length > 5 ||
|
|
|
|
postKind != PostKind.TEXT;
|
2019-07-05 09:40:43 +00:00
|
|
|
|
|
|
|
PostKind get postKind {
|
|
|
|
if (hasImage)
|
|
|
|
return PostKind.IMAGE;
|
2020-04-24 11:35:05 +00:00
|
|
|
else if (hasPDF)
|
|
|
|
return PostKind.PDF;
|
2020-04-25 12:38:15 +00:00
|
|
|
else if (hasURL)
|
|
|
|
return PostKind.WEB_LINK;
|
2020-04-25 06:23:52 +00:00
|
|
|
else if (hasTimeEnd)
|
|
|
|
return PostKind.COUNTDOWN;
|
2020-04-25 09:58:45 +00:00
|
|
|
else if (hasSurvey)
|
|
|
|
return PostKind.SURVEY;
|
2020-04-25 15:17:11 +00:00
|
|
|
else if (hasYouTubeID)
|
2020-04-25 15:16:33 +00:00
|
|
|
return PostKind.YOUTUBE;
|
2019-07-05 09:40:43 +00:00
|
|
|
else
|
|
|
|
return PostKind.TEXT;
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
void initState() {
|
|
|
|
super.initState();
|
|
|
|
|
2020-04-17 08:41:10 +00:00
|
|
|
_resetForm();
|
2019-07-05 09:40:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
2020-05-16 07:20:17 +00:00
|
|
|
return PostContainer(
|
|
|
|
child: Card(
|
|
|
|
child: 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,
|
|
|
|
),
|
|
|
|
],
|
2020-04-25 08:01:45 +00:00
|
|
|
),
|
2020-05-16 07:20:17 +00:00
|
|
|
),
|
|
|
|
),
|
2020-04-25 09:58:45 +00:00
|
|
|
|
2020-05-16 07:20:17 +00:00
|
|
|
Container(width: 20),
|
2020-04-25 15:16:33 +00:00
|
|
|
|
2020-05-16 07:20:17 +00:00
|
|
|
// Post visibility level
|
|
|
|
_PostOptionWidget(
|
2022-03-10 18:39:57 +00:00
|
|
|
icon: PostVisibilityLevelsMapIcons[_postVisibilityLevel!]!,
|
2020-05-16 07:20:17 +00:00
|
|
|
selected: false,
|
|
|
|
customColor: Colors.black,
|
|
|
|
onTap: _changeVisibilityLevel,
|
2020-04-25 08:01:45 +00:00
|
|
|
),
|
2020-04-25 12:38:15 +00:00
|
|
|
|
2020-05-16 07:20:17 +00:00
|
|
|
// Submit post button
|
|
|
|
_isCreating
|
|
|
|
? Container()
|
2021-03-13 14:42:19 +00:00
|
|
|
: ElevatedButton(
|
2022-03-10 18:39:57 +00:00
|
|
|
child: Text(tr("Send")!.toUpperCase()),
|
2021-03-13 14:42:19 +00:00
|
|
|
onPressed: canSubmitForm ? _submitForm : null),
|
2020-05-16 07:20:17 +00:00
|
|
|
],
|
2019-07-05 09:40:43 +00:00
|
|
|
),
|
2020-05-16 07:20:17 +00:00
|
|
|
)
|
|
|
|
],
|
|
|
|
),
|
|
|
|
),
|
2019-07-05 09:40:43 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-04-17 08:41:10 +00:00
|
|
|
/// Reset the form
|
|
|
|
void _resetForm() {
|
|
|
|
setState(() {
|
|
|
|
_postVisibilityLevel = widget.postTarget == PostTarget.GROUP_PAGE
|
|
|
|
? PostVisibilityLevel.GROUP_MEMBERS
|
|
|
|
: PostVisibilityLevel.FRIENDS;
|
|
|
|
|
|
|
|
_postTextController.text = "";
|
|
|
|
_resetPostSelection();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2019-07-05 09:40:43 +00:00
|
|
|
/// Change post visibility level
|
|
|
|
Future<void> _changeVisibilityLevel() async {
|
2021-03-17 16:53:07 +00:00
|
|
|
final newLevel = await showPostVisibilityPickerDialog(
|
2019-07-05 09:40:43 +00:00
|
|
|
context: context,
|
2022-03-10 18:39:57 +00:00
|
|
|
initialLevel: _postVisibilityLevel!,
|
2019-07-05 09:40:43 +00:00
|
|
|
isGroup: widget.postTarget == PostTarget.GROUP_PAGE,
|
|
|
|
);
|
|
|
|
|
|
|
|
setState(() => _postVisibilityLevel = newLevel);
|
|
|
|
}
|
|
|
|
|
2019-07-05 09:50:37 +00:00
|
|
|
/// Remove all data attached to the post (image, etc...)
|
|
|
|
void _resetPostSelection() {
|
|
|
|
setState(() {
|
|
|
|
_postImage = null;
|
2020-04-25 12:38:15 +00:00
|
|
|
_postURL = null;
|
2020-04-24 11:35:05 +00:00
|
|
|
_postPDF = null;
|
2020-04-25 06:23:52 +00:00
|
|
|
_timeEnd = null;
|
2020-04-25 09:58:45 +00:00
|
|
|
_postSurvey = null;
|
2020-04-25 15:16:33 +00:00
|
|
|
_youtubeID = null;
|
2019-07-05 09:50:37 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2019-07-05 09:40:43 +00:00
|
|
|
/// Pick an image for the new post
|
|
|
|
Future<void> _pickImageForPost() async {
|
2021-03-13 17:03:20 +00:00
|
|
|
try {
|
|
|
|
final image = await pickImage(context);
|
2019-07-05 09:40:43 +00:00
|
|
|
|
2021-03-13 17:03:20 +00:00
|
|
|
if (image == null) return;
|
2019-07-05 09:40:43 +00:00
|
|
|
|
2021-03-13 17:03:20 +00:00
|
|
|
_resetPostSelection();
|
2019-07-05 09:50:37 +00:00
|
|
|
|
2021-03-13 17:03:20 +00:00
|
|
|
setState(() {
|
|
|
|
this._postImage = image;
|
|
|
|
});
|
|
|
|
} catch (e, s) {
|
|
|
|
logError(e, s);
|
2022-03-10 18:39:57 +00:00
|
|
|
snack(context, tr("Failed to pick an image for the post!")!);
|
2021-03-13 17:03:20 +00:00
|
|
|
}
|
2019-07-05 09:40:43 +00:00
|
|
|
}
|
|
|
|
|
2020-04-25 12:38:15 +00:00
|
|
|
/// 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;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-04-24 11:35:05 +00:00
|
|
|
/// Pick a PDF for the new post
|
|
|
|
Future<void> _pickPDFForPost() async {
|
|
|
|
try {
|
2021-03-13 14:14:54 +00:00
|
|
|
final file = await FilePicker.platform.pickFiles(
|
|
|
|
type: FileType.custom,
|
|
|
|
allowedExtensions: ["pdf"],
|
|
|
|
withData: true,
|
2020-04-24 11:35:05 +00:00
|
|
|
);
|
|
|
|
|
2021-03-13 14:14:54 +00:00
|
|
|
if (file == null || file.files.isEmpty) return;
|
|
|
|
|
2020-04-24 11:35:05 +00:00
|
|
|
_resetPostSelection();
|
|
|
|
|
|
|
|
setState(() {
|
2021-03-13 14:14:54 +00:00
|
|
|
this._postPDF = file.files.first.bytes;
|
2020-04-24 11:35:05 +00:00
|
|
|
});
|
|
|
|
} catch (e, stack) {
|
|
|
|
print("Pick PDF error: $e\n$stack");
|
2022-03-10 18:39:57 +00:00
|
|
|
showSimpleSnack(context, tr("Could not pick a PDF!")!);
|
2020-04-24 11:35:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-25 06:23:52 +00:00
|
|
|
/// Pick countdown time
|
|
|
|
Future<void> _pickCountdownTime() async {
|
2020-04-25 08:10:52 +00:00
|
|
|
final yesterday = DateTime.now().subtract(Duration(days: 1));
|
|
|
|
|
2022-03-10 18:39:57 +00:00
|
|
|
final initialDate = _timeEnd == null ? DateTime.now() : _timeEnd!;
|
2020-04-25 06:23:52 +00:00
|
|
|
|
|
|
|
// Pick date
|
|
|
|
final newDate = await showDatePicker(
|
|
|
|
context: context,
|
|
|
|
initialDate: initialDate,
|
2020-04-25 08:10:52 +00:00
|
|
|
firstDate: yesterday.isBefore(initialDate) ? yesterday : initialDate,
|
2020-04-25 06:23:52 +00:00
|
|
|
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,
|
2020-04-25 06:25:32 +00:00
|
|
|
minutes: newTime.minute - newDate.minute,
|
|
|
|
seconds: -newDate.second));
|
2020-04-25 06:23:52 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-04-25 09:58:45 +00:00
|
|
|
/// 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;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-04-25 15:16:33 +00:00
|
|
|
/// Pick a new YouTube video
|
|
|
|
Future<void> _pickYouTubeVideo() async {
|
|
|
|
final youtubeID = await showInputYouTubeIDDialog(context, _youtubeID);
|
|
|
|
|
|
|
|
if (youtubeID == null) return;
|
|
|
|
|
|
|
|
_resetPostSelection();
|
|
|
|
setState(() {
|
|
|
|
_youtubeID = youtubeID;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2019-07-05 09:40:43 +00:00
|
|
|
/// Submit new post
|
|
|
|
Future<void> _submitForm() async {
|
|
|
|
if (!canSubmitForm)
|
2022-03-10 18:39:57 +00:00
|
|
|
showSimpleSnack(context, tr("Form can not be submitted at this point!")!);
|
2019-07-05 09:40:43 +00:00
|
|
|
|
|
|
|
setState(() => _isCreating = true);
|
|
|
|
|
|
|
|
try {
|
|
|
|
await _postHelper.createPost(NewPost(
|
2020-04-25 15:16:33 +00:00
|
|
|
target: widget.postTarget,
|
|
|
|
targetID: widget.targetID,
|
2022-03-10 18:39:57 +00:00
|
|
|
visibility: _postVisibilityLevel!,
|
2020-04-25 15:16:33 +00:00
|
|
|
content: _postTextController.text,
|
|
|
|
kind: postKind,
|
|
|
|
image: _postImage,
|
|
|
|
url: _postURL,
|
|
|
|
pdf: _postPDF,
|
|
|
|
timeEnd: _timeEnd,
|
|
|
|
survey: _postSurvey,
|
|
|
|
youtubeId: _youtubeID,
|
|
|
|
));
|
2019-07-05 09:40:43 +00:00
|
|
|
setState(() => _isCreating = false);
|
|
|
|
|
2022-03-10 18:39:57 +00:00
|
|
|
showSimpleSnack(context, tr("The post has been successfully created!")!);
|
2019-07-05 09:40:43 +00:00
|
|
|
|
2020-04-17 08:41:10 +00:00
|
|
|
this._resetForm();
|
2019-07-05 09:40:43 +00:00
|
|
|
widget.onCreated();
|
2021-12-28 16:15:25 +00:00
|
|
|
} catch (e, s) {
|
2019-07-05 09:40:43 +00:00
|
|
|
setState(() => _isCreating = false);
|
2021-12-28 16:15:25 +00:00
|
|
|
print("Error while creating post : $e $s");
|
2022-03-10 18:39:57 +00:00
|
|
|
showSimpleSnack(context, tr("Could not create post !")!);
|
2019-07-05 09:40:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Widget for a single post option
|
|
|
|
class _PostOptionWidget extends StatelessWidget {
|
|
|
|
final IconData icon;
|
|
|
|
final bool selected;
|
2022-03-10 18:39:57 +00:00
|
|
|
final Color? customColor;
|
2019-07-05 09:40:43 +00:00
|
|
|
final void Function() onTap;
|
|
|
|
|
|
|
|
const _PostOptionWidget(
|
2022-03-10 18:39:57 +00:00
|
|
|
{Key? key,
|
|
|
|
required this.icon,
|
|
|
|
required this.selected,
|
|
|
|
required this.onTap,
|
2019-07-05 09:40:43 +00:00
|
|
|
this.customColor})
|
2022-03-11 15:36:42 +00:00
|
|
|
: super(key: key);
|
2019-07-05 09:40:43 +00:00
|
|
|
|
|
|
|
bool get hasCustomColor => customColor != null;
|
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
return IconButton(
|
|
|
|
icon: Icon(icon),
|
|
|
|
onPressed: onTap,
|
|
|
|
color: hasCustomColor
|
|
|
|
? customColor
|
2021-02-07 16:09:08 +00:00
|
|
|
: selected
|
|
|
|
? _ActiveButtonsColor
|
|
|
|
: _InactiveButtonsColor,
|
2019-07-05 09:40:43 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|