1
0
mirror of https://gitlab.com/comunic/comunicmobile synced 2024-11-22 12:59:21 +00:00

Can create posts

This commit is contained in:
Pierre HUBERT 2019-07-05 11:40:43 +02:00
parent 44d47e7f5e
commit 3a5a395f79
7 changed files with 310 additions and 0 deletions

View File

@ -0,0 +1,8 @@
/// Posts targets types enum
///
/// @author Pierre HUBERT
enum PostTarget {
USER_PAGE,
GROUP_PAGE
}

View File

@ -1,4 +1,5 @@
import 'package:comunic/enums/post_kind.dart'; 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/enums/post_visibility_level.dart';
import 'package:comunic/enums/user_access_levels.dart'; import 'package:comunic/enums/user_access_levels.dart';
import 'package:comunic/helpers/comments_helper.dart'; import 'package:comunic/helpers/comments_helper.dart';
@ -6,6 +7,7 @@ import 'package:comunic/helpers/survey_helper.dart';
import 'package:comunic/lists/comments_list.dart'; import 'package:comunic/lists/comments_list.dart';
import 'package:comunic/lists/posts_list.dart'; import 'package:comunic/lists/posts_list.dart';
import 'package:comunic/models/api_request.dart'; import 'package:comunic/models/api_request.dart';
import 'package:comunic/models/new_post.dart';
import 'package:comunic/models/post.dart'; import 'package:comunic/models/post.dart';
/// Posts helper /// Posts helper
@ -37,6 +39,11 @@ const _APIUserAccessMap = {
"full": UserAccessLevels.FULL "full": UserAccessLevels.FULL
}; };
const _APIPostsTargetKindsMap = {
PostTarget.USER_PAGE: "user",
PostTarget.GROUP_PAGE: "group"
};
class PostsHelper { class PostsHelper {
/// Get the list of latest posts. Return the list of posts or null in case of /// Get the list of latest posts. Return the list of posts or null in case of
/// failure /// failure
@ -77,6 +84,38 @@ class PostsHelper {
} }
} }
/// Create a new post
///
/// This function crash in case of error
Future<void> createPost(NewPost post) async {
APIRequest request =
APIRequest(uri: "posts/create", needLogin: true, args: {
"kind-page": _APIPostsTargetKindsMap[post.target],
"kind-id": post.targetID.toString(),
"visibility": _APIPostsVisibilityLevelMap.map(
(s, v) => MapEntry(v, s))[post.visibility],
"kind": _APIPostsKindsMap.map((s, k) => MapEntry(k, s))[post.kind],
"content": post.content
});
switch (post.kind) {
case PostKind.TEXT:
break;
case PostKind.IMAGE:
request.addFile("image", post.image);
break;
default:
throw Exception("Unsupported post type :" + post.kind.toString());
break;
}
final response = await request.execWithFiles();
if (!response.isOK) throw Exception("Could not create the post !");
}
/// Update a post content /// Update a post content
Future<bool> updateContent(int id, String newContent) async { Future<bool> updateContent(int id, String newContent) async {
return (await APIRequest( return (await APIRequest(

View File

@ -155,6 +155,7 @@ class UsersHelper {
data["virtualDirectory"] == "" ? null : data["virtualDirectory"], data["virtualDirectory"] == "" ? null : data["virtualDirectory"],
accountImageURL: data["accountImage"], accountImageURL: data["accountImage"],
publicNote: data["publicNote"], publicNote: data["publicNote"],
canPostTexts: data["can_post_texts"],
); );
} }
} }

View File

@ -8,6 +8,7 @@ import 'package:meta/meta.dart';
class AdvancedUserInfo extends User { class AdvancedUserInfo extends User {
final String publicNote; final String publicNote;
final bool canPostTexts;
const AdvancedUserInfo({ const AdvancedUserInfo({
@required int id, @required int id,
@ -17,7 +18,9 @@ class AdvancedUserInfo extends User {
@required String virtualDirectory, @required String virtualDirectory,
@required String accountImageURL, @required String accountImageURL,
@required this.publicNote, @required this.publicNote,
@required this.canPostTexts,
}) : assert(publicNote != null), }) : assert(publicNote != null),
assert(canPostTexts != null),
super( super(
id: id, id: id,
firstName: firstName, firstName: firstName,

33
lib/models/new_post.dart Normal file
View File

@ -0,0 +1,33 @@
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:meta/meta.dart';
/// New post information
///
/// @author Pierre HUBERT
class NewPost {
final PostTarget target;
final int targetID;
final PostVisibilityLevel visibility;
final String content;
final File image;
final PostKind kind;
NewPost({
@required this.target,
@required this.targetID,
@required this.visibility,
@required this.content,
@required this.kind,
@required this.image,
}) : assert(target != null),
assert(targetID != null),
assert(visibility != null),
assert(content != null),
assert(kind != PostKind.TEXT || content.length > 3),
assert(kind != PostKind.IMAGE || image != null);
}

View File

@ -1,9 +1,11 @@
import 'package:comunic/enums/post_target.dart';
import 'package:comunic/helpers/posts_helper.dart'; import 'package:comunic/helpers/posts_helper.dart';
import 'package:comunic/helpers/users_helper.dart'; import 'package:comunic/helpers/users_helper.dart';
import 'package:comunic/models/advanced_user_info.dart'; import 'package:comunic/models/advanced_user_info.dart';
import 'package:comunic/ui/routes/other_friends_lists_route.dart'; import 'package:comunic/ui/routes/other_friends_lists_route.dart';
import 'package:comunic/ui/routes/user_access_denied_route.dart'; import 'package:comunic/ui/routes/user_access_denied_route.dart';
import 'package:comunic/ui/widgets/network_image_widget.dart'; import 'package:comunic/ui/widgets/network_image_widget.dart';
import 'package:comunic/ui/widgets/post_create_form_widget.dart';
import 'package:comunic/ui/widgets/posts_list_widget.dart'; import 'package:comunic/ui/widgets/posts_list_widget.dart';
import 'package:comunic/utils/conversations_utils.dart'; import 'package:comunic/utils/conversations_utils.dart';
import 'package:comunic/utils/intl_utils.dart'; import 'package:comunic/utils/intl_utils.dart';
@ -38,6 +40,8 @@ class _UserPageRouteState extends State<UserPageRoute> {
final double _appBarHeight = 256.0; final double _appBarHeight = 256.0;
_PageStatus _status = _PageStatus.LOADING; _PageStatus _status = _PageStatus.LOADING;
AdvancedUserInfo _userInfo; AdvancedUserInfo _userInfo;
GlobalKey<RefreshIndicatorState> _refreshIndicatorKey =
GlobalKey<RefreshIndicatorState>();
_setStatus(_PageStatus s) => setState(() => _status = s); _setStatus(_PageStatus s) => setState(() => _status = s);
@ -80,8 +84,10 @@ class _UserPageRouteState extends State<UserPageRoute> {
return Scaffold( return Scaffold(
body: RefreshIndicator( body: RefreshIndicator(
key: _refreshIndicatorKey,
child: CustomScrollView( child: CustomScrollView(
slivers: <Widget>[_buildHeader(), _buildBody()], slivers: <Widget>[_buildHeader(), _buildBody()],
physics: AlwaysScrollableScrollPhysics(),
), ),
onRefresh: _getUserInfo, onRefresh: _getUserInfo,
), ),
@ -175,6 +181,16 @@ class _UserPageRouteState extends State<UserPageRoute> {
return SliverList( return SliverList(
delegate: SliverChildListDelegate( delegate: SliverChildListDelegate(
<Widget>[ <Widget>[
// Posts create form
_userInfo.canPostTexts
? PostCreateFormWidget(
postTarget: PostTarget.USER_PAGE,
targetID: _userInfo.id,
onCreated: _postCreated,
)
: Container(),
// Posts list
PostsListWidget( PostsListWidget(
getPostsList: () => _postsHelper.getUserPosts(widget.userID), getPostsList: () => _postsHelper.getUserPosts(widget.userID),
showPostsTarget: false, showPostsTarget: false,
@ -199,4 +215,9 @@ class _UserPageRouteState extends State<UserPageRoute> {
break; break;
} }
} }
/// Method called once a post has been created
void _postCreated() {
_refreshIndicatorKey.currentState.show();
}
} }

View File

@ -0,0 +1,205 @@
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/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: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;
bool get hasImage => _postImage != null;
bool get canSubmitForm =>
!_isCreating && _postTextController.text.length > 5 || hasImage;
PostKind get postKind {
if (hasImage)
return PostKind.IMAGE;
else
return PostKind.TEXT;
}
@override
void initState() {
super.initState();
_postVisibilityLevel = widget.postTarget == PostTarget.GROUP_PAGE
? PostVisibilityLevel.GROUP_MEMBERS
: PostVisibilityLevel.FRIENDS;
}
@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
// Post text content
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>[
// Include image button
_PostOptionWidget(
icon: Icons.image,
selected: hasImage,
onTap: _pickImageForPost,
),
// 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,
),
],
),
)
],
);
}
/// 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);
}
/// Pick an image for the new post
Future<void> _pickImageForPost() async {
final image = await pickImage(context);
if (image == null) return;
setState(() {
this._postImage = image;
});
}
/// 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,
));
setState(() => _isCreating = false);
showSimpleSnack(context, tr("The post has been successfully created!"));
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,
);
}
}