2019-07-05 09:40:43 +00:00
|
|
|
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';
|
2020-04-25 09:58:45 +00:00
|
|
|
import 'package:comunic/ui/dialogs/new_survey_dialog.dart';
|
2020-04-25 12:38:15 +00:00
|
|
|
import 'package:comunic/ui/dialogs/url_dialog.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/post_utils.dart';
|
|
|
|
import 'package:comunic/utils/ui_utils.dart';
|
2020-04-24 11:35:05 +00:00
|
|
|
import 'package:file_picker_cross/file_picker_cross.dart';
|
2019-07-05 09:40:43 +00:00
|
|
|
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;
|
2020-04-25 12:38:15 +00:00
|
|
|
String _postURL;
|
2020-04-24 11:35:05 +00:00
|
|
|
List<int> _postPDF;
|
2020-04-25 06:23:52 +00:00
|
|
|
DateTime _timeEnd;
|
2020-04-25 09:58:45 +00:00
|
|
|
NewSurvey _postSurvey;
|
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;
|
|
|
|
|
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;
|
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) {
|
|
|
|
return Column(
|
|
|
|
children: <Widget>[
|
|
|
|
// Post text content
|
2019-07-05 09:50:37 +00:00
|
|
|
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(() {}),
|
|
|
|
),
|
2019-07-05 09:40:43 +00:00
|
|
|
),
|
|
|
|
|
|
|
|
// Post options
|
|
|
|
Padding(
|
|
|
|
padding: const EdgeInsets.only(right: 16.0),
|
|
|
|
child: Row(
|
|
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
|
|
children: <Widget>[
|
2019-07-05 09:50:37 +00:00
|
|
|
// Text post button
|
|
|
|
Expanded(
|
2020-04-25 08:01:45 +00:00
|
|
|
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,
|
|
|
|
),
|
|
|
|
|
2020-04-25 12:38:15 +00:00
|
|
|
// Web link
|
|
|
|
_PostOptionWidget(
|
|
|
|
icon: Icons.link,
|
|
|
|
selected: postKind == PostKind.WEB_LINK,
|
|
|
|
onTap: _pickURLForPost,
|
|
|
|
),
|
|
|
|
|
2020-04-25 08:01:45 +00:00
|
|
|
// 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,
|
|
|
|
),
|
2020-04-25 09:58:45 +00:00
|
|
|
|
|
|
|
// Add survey
|
|
|
|
_PostOptionWidget(
|
|
|
|
icon: Icons.insert_chart,
|
|
|
|
selected: postKind == PostKind.SURVEY,
|
|
|
|
onTap: _pickSurvey,
|
|
|
|
),
|
2020-04-25 08:01:45 +00:00
|
|
|
],
|
|
|
|
),
|
|
|
|
),
|
2019-07-05 09:50:37 +00:00
|
|
|
),
|
|
|
|
|
2020-04-25 12:38:15 +00:00
|
|
|
Container(width: 20),
|
|
|
|
|
2019-07-05 09:40:43 +00:00
|
|
|
// 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,
|
|
|
|
),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
)
|
|
|
|
],
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
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 {
|
|
|
|
final newLevel = await showPostVisibilityPicker(
|
|
|
|
context: context,
|
|
|
|
initialLevel: _postVisibilityLevel,
|
|
|
|
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;
|
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 {
|
|
|
|
final image = await pickImage(context);
|
|
|
|
|
|
|
|
if (image == null) return;
|
|
|
|
|
2019-07-05 09:50:37 +00:00
|
|
|
_resetPostSelection();
|
|
|
|
|
2019-07-05 09:40:43 +00:00
|
|
|
setState(() {
|
|
|
|
this._postImage = image;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
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 {
|
|
|
|
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!"));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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));
|
|
|
|
|
|
|
|
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;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2019-07-05 09:40:43 +00:00
|
|
|
/// 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(
|
2020-04-25 09:58:45 +00:00
|
|
|
target: widget.postTarget,
|
|
|
|
targetID: widget.targetID,
|
|
|
|
visibility: _postVisibilityLevel,
|
|
|
|
content: _postTextController.text,
|
|
|
|
kind: postKind,
|
|
|
|
image: _postImage,
|
2020-04-25 12:38:15 +00:00
|
|
|
url: _postURL,
|
2020-04-25 09:58:45 +00:00
|
|
|
pdf: _postPDF,
|
|
|
|
timeEnd: _timeEnd,
|
|
|
|
survey: _postSurvey));
|
2019-07-05 09:40:43 +00:00
|
|
|
setState(() => _isCreating = false);
|
|
|
|
|
|
|
|
showSimpleSnack(context, tr("The post has been successfully created!"));
|
|
|
|
|
2020-04-17 08:41:10 +00:00
|
|
|
this._resetForm();
|
2019-07-05 09:40:43 +00:00
|
|
|
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,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|