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

Add posts with surveys support

This commit is contained in:
Pierre HUBERT 2019-06-28 11:32:36 +02:00
parent 66b4d19004
commit fdf8cd08ef
9 changed files with 314 additions and 39 deletions

View File

@ -2,6 +2,7 @@ import 'package:comunic/enums/post_kind.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';
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';
@ -40,14 +41,11 @@ 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
Future<PostsList> getLatest({int from = 0}) async { Future<PostsList> getLatest({int from = 0}) async {
final response = await APIRequest( final response =
uri: "posts/get_latest", await APIRequest(uri: "posts/get_latest", needLogin: true, args: {
needLogin: true, "include_groups": true.toString(),
args: { "startFrom": from.toString(),
"include_groups": true.toString(), }).exec();
"startFrom": from.toString(),
}
).exec();
if (response.code != 200) return null; if (response.code != 200) return null;
@ -63,13 +61,10 @@ class PostsHelper {
/// Get the list of posts of a user /// Get the list of posts of a user
Future<PostsList> getUserPosts(int userID, {int from = 0}) async { Future<PostsList> getUserPosts(int userID, {int from = 0}) async {
final response = await APIRequest( final response = await APIRequest(
uri: "posts/get_user", uri: "posts/get_user",
needLogin: true, needLogin: true,
args: { args: {"userID": userID.toString(), "startFrom": from.toString()})
"userID": userID.toString(), .exec();
"startFrom": from.toString()
}
).exec();
if (response.code != 200) return null; if (response.code != 200) return null;
@ -102,7 +97,8 @@ class PostsHelper {
needLogin: true, needLogin: true,
args: { args: {
"postID": id.toString(), "postID": id.toString(),
"new_level": _APIPostsVisibilityLevelMap.map((k, v) => MapEntry(v, k))[level] "new_level":
_APIPostsVisibilityLevelMap.map((k, v) => MapEntry(v, k))[level]
}, },
).exec()) ).exec())
.isOK; .isOK;
@ -120,6 +116,8 @@ class PostsHelper {
/// Turn an API entry into a [Post] object /// Turn an API entry into a [Post] object
Post _apiToPost(Map<String, dynamic> map) { Post _apiToPost(Map<String, dynamic> map) {
final postKind = _APIPostsKindsMap[map["kind"]];
// Parse comments // Parse comments
CommentsList comments; CommentsList comments;
if (map["comments"] != null) { if (map["comments"] != null) {
@ -128,28 +126,32 @@ class PostsHelper {
.forEach((v) => comments.add(CommentsHelper.apiToComment(v))); .forEach((v) => comments.add(CommentsHelper.apiToComment(v)));
} }
final survey = postKind == PostKind.SURVEY
? SurveyHelper.apiToSurvey(map["data_survey"])
: null;
return Post( return Post(
id: map["ID"], id: map["ID"],
userID: map["userID"], userID: map["userID"],
userPageID: map["user_page_id"], userPageID: map["user_page_id"],
groupID: map["group_id"], groupID: map["group_id"],
timeSent: map["post_time"], timeSent: map["post_time"],
content: map["content"], content: map["content"],
visibilityLevel: _APIPostsVisibilityLevelMap[map["visibility_level"]], visibilityLevel: _APIPostsVisibilityLevelMap[map["visibility_level"]],
kind: _APIPostsKindsMap[map["kind"]], kind: postKind,
fileSize: map["file_size"], fileSize: map["file_size"],
fileType: map["file_type"], fileType: map["file_type"],
filePath: map["file_path"], filePath: map["file_path"],
fileURL: map["file_path_url"], fileURL: map["file_path_url"],
timeEnd: map["time_end"], timeEnd: map["time_end"],
linkURL: map["link_url"], linkURL: map["link_url"],
linkTitle: map["link_title"], linkTitle: map["link_title"],
linkDescription: map["link_description"], linkDescription: map["link_description"],
linkImage: map["link_image"], linkImage: map["link_image"],
likes: map["likes"], likes: map["likes"],
userLike: map["userlike"], userLike: map["userlike"],
access: _APIUserAccessMap[map["user_access"]], access: _APIUserAccessMap[map["user_access"]],
comments: comments, comments: comments,
); survey: survey);
} }
} }

View File

@ -0,0 +1,55 @@
import 'package:comunic/models/api_request.dart';
import 'package:comunic/models/survey.dart';
import 'package:comunic/models/survey_choice.dart';
import 'package:meta/meta.dart';
/// Survey helper
///
/// @author Pierre HUBERT
class SurveyHelper {
/// Cancel the response of a user to a survey
Future<bool> cancelResponse(Survey survey) async {
return (await APIRequest(
uri: "surveys/cancel_response",
needLogin: true,
args: {"postID": survey.postID.toString()}).exec())
.isOK;
}
/// Send the response of a user to a survey
Future<bool> respondToSurvey(
{@required Survey survey, @required SurveyChoice choice}) async {
assert(survey != null);
assert(choice != null);
return (await APIRequest(
uri: "surveys/send_response",
needLogin: true,
args: {
"postID": survey.postID.toString(),
"choiceID": choice.id.toString(),
},
).exec())
.isOK;
}
/// Turn an API entry into a [Survey] object
static Survey apiToSurvey(Map<String, dynamic> map) {
// Parse survey responses
Set<SurveyChoice> choices = Set();
map["choices"].forEach((k, e) => choices.add(SurveyChoice(
id: e["choiceID"], name: e["name"], responses: e["responses"])));
return Survey(
id: map["ID"],
userID: map["userID"],
postID: map["postID"],
creationTime: map["creation_time"],
question: map["question"],
userChoice: map["user_choice"],
choices: choices,
);
}
}

View File

@ -3,6 +3,7 @@ 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/lists/comments_list.dart'; import 'package:comunic/lists/comments_list.dart';
import 'package:comunic/models/like_element.dart'; import 'package:comunic/models/like_element.dart';
import 'package:comunic/models/survey.dart';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
/// Single post information /// Single post information
@ -31,6 +32,7 @@ class Post implements LikeElement {
bool userLike; bool userLike;
final UserAccessLevels access; final UserAccessLevels access;
final CommentsList comments; final CommentsList comments;
final Survey survey;
Post( Post(
{@required this.id, {@required this.id,
@ -53,7 +55,8 @@ class Post implements LikeElement {
@required this.likes, @required this.likes,
@required this.userLike, @required this.userLike,
@required this.access, @required this.access,
@required this.comments}) @required this.comments,
@required this.survey})
: assert(id != null), : assert(id != null),
assert(userID != null), assert(userID != null),
assert(userPageID != 0 || groupID != 0), assert(userPageID != 0 || groupID != 0),
@ -62,6 +65,7 @@ class Post implements LikeElement {
assert(visibilityLevel != null), assert(visibilityLevel != null),
assert(kind != null), assert(kind != null),
assert(kind != PostKind.COUNTDOWN || timeEnd != null), assert(kind != PostKind.COUNTDOWN || timeEnd != null),
assert(kind != PostKind.SURVEY || survey != null),
assert(likes != null), assert(likes != null),
assert(userLike != null), assert(userLike != null),
assert(access != null); assert(access != null);

52
lib/models/survey.dart Normal file
View File

@ -0,0 +1,52 @@
import 'package:comunic/models/survey_choice.dart';
import 'package:meta/meta.dart';
/// Survey information
///
/// @author Pierre HUBERT
class Survey {
final int id;
final int userID;
final int postID;
final int creationTime;
final String question;
int userChoice;
final Set<SurveyChoice> choices;
Survey({
@required this.id,
@required this.userID,
@required this.postID,
@required this.creationTime,
@required this.question,
@required this.userChoice,
@required this.choices,
}) : assert(id != null),
assert(userID != null),
assert(postID != null),
assert(creationTime != null),
assert(question != null),
assert(userChoice != null),
assert(choices != null),
assert(choices.length > 0);
bool get hasResponded => this.userChoice != null && this.userChoice > 0;
SurveyChoice get userResponse {
if (!hasResponded) return null;
return choices.firstWhere((e) => e.id == userChoice);
}
void cancelUserResponse() {
if (hasResponded) userResponse.responses--;
userChoice = 0;
}
void setUserResponse(SurveyChoice choice) {
cancelUserResponse();
userChoice = choice.id;
choice.responses++;
}
}

View File

@ -0,0 +1,19 @@
import 'package:meta/meta.dart';
/// Single survey choice
///
/// @author Pierre HUBERT
class SurveyChoice {
final int id;
final String name;
int responses;
SurveyChoice({
@required this.id,
@required this.name,
@required this.responses,
}) : assert(id != null),
assert(name != null),
assert(responses != null);
}

View File

@ -16,6 +16,7 @@ import 'package:comunic/models/user.dart';
import 'package:comunic/ui/tiles/comment_tile.dart'; import 'package:comunic/ui/tiles/comment_tile.dart';
import 'package:comunic/ui/widgets/account_image_widget.dart'; import 'package:comunic/ui/widgets/account_image_widget.dart';
import 'package:comunic/ui/widgets/network_image_widget.dart'; import 'package:comunic/ui/widgets/network_image_widget.dart';
import 'package:comunic/ui/widgets/survey_widget.dart';
import 'package:comunic/utils/date_utils.dart'; import 'package:comunic/utils/date_utils.dart';
import 'package:comunic/utils/files_utils.dart'; import 'package:comunic/utils/files_utils.dart';
import 'package:comunic/utils/intl_utils.dart'; import 'package:comunic/utils/intl_utils.dart';
@ -171,6 +172,10 @@ class _PostTileState extends State<PostTile> {
postContent = _buildPostWebLink(); postContent = _buildPostWebLink();
break; break;
case PostKind.SURVEY:
postContent = _buildPostSurvey();
break;
default: default:
} }
@ -294,6 +299,13 @@ class _PostTileState extends State<PostTile> {
); );
} }
/// Build post survey
Widget _buildPostSurvey() {
return SurveyWidget(
survey: widget.post.survey,
);
}
/// Build the list of comments /// Build the list of comments
Widget _buildComments() { Widget _buildComments() {
assert(widget.post.hasComments); assert(widget.post.hasComments);

View File

@ -0,0 +1,121 @@
import 'package:comunic/helpers/survey_helper.dart';
import 'package:comunic/models/survey.dart';
import 'package:comunic/models/survey_choice.dart';
import 'package:comunic/utils/intl_utils.dart';
import 'package:comunic/utils/ui_utils.dart';
import 'package:flutter/material.dart';
import 'package:pie_chart/pie_chart.dart';
/// Survey widget
///
/// @author Pierre HUBERT
class SurveyWidget extends StatefulWidget {
final Survey survey;
const SurveyWidget({Key key, @required this.survey})
: assert(survey != null),
super(key: key);
@override
_SurveyWidgetState createState() => _SurveyWidgetState();
}
class _SurveyWidgetState extends State<SurveyWidget> {
final SurveyHelper _helper = SurveyHelper();
Survey get survey => widget.survey;
Map<String, double> _buildDataMap() {
Map<String, double> data = Map();
widget.survey.choices.forEach((e) => data.putIfAbsent(
e.name + " (" + e.responses.toString() + ")", () => 1.0 * e.responses));
return data;
}
@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Text(
widget.survey.question,
style: TextStyle(fontWeight: FontWeight.bold),
),
_buildUserResponse(),
PieChart(
dataMap: _buildDataMap(),
),
],
);
}
Widget _buildUserResponse() {
if (survey.hasResponded) return _buildUserRespondedWidget();
return _buildUserResponseFormWidget();
}
Widget _buildUserRespondedWidget() {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Flexible(
child: Text(
tr("Your response: %response%",
args: {"response": survey.userResponse.name}),
textAlign: TextAlign.center,
),
),
MaterialButton(
child: Text(tr("Cancel").toUpperCase()),
onPressed: _cancelUserResponse,
)
],
);
}
Future<void> _cancelUserResponse() async {
if (!await showConfirmDialog(
context: context,
title: tr("Cancel response to survey"),
message:
tr("Do you really want to cancel your response to this survey ?"),
)) return;
if (!await _helper.cancelResponse(survey)) {
showSimpleSnack(
context, tr("Could not cancel your response to the survey !"));
return;
}
setState(() {
survey.cancelUserResponse();
});
}
Widget _buildUserResponseFormWidget() {
return DropdownButton<SurveyChoice>(
hint: Text(tr("Respond to survey")),
items: survey.choices
.map(
(f) => DropdownMenuItem<SurveyChoice>(
value: f,
child: Text(f.name),
),
)
.toList(),
onChanged: _respondToSurvey,
);
}
/// Respond to survey
Future<void> _respondToSurvey(SurveyChoice choice) async {
// Send the response to the server
if (!await _helper.respondToSurvey(survey: survey, choice: choice))
return showSimpleSnack(
context, tr("Could not send your response to the survey!"));
setState(() {
survey.setUserResponse(choice);
});
}
}

View File

@ -158,6 +158,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.5.0" version: "1.5.0"
pie_chart:
dependency: "direct main"
description:
name: pie_chart
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
quiver: quiver:
dependency: transitive dependency: transitive
description: description:

View File

@ -45,6 +45,9 @@ dependencies:
# Allows to parse HTML special characters # Allows to parse HTML special characters
html: ^0.14.0+2 html: ^0.14.0+2
# Module that display the charts for the surveys
pie_chart: ^1.0.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter