diff --git a/lib/helpers/friends_helper.dart b/lib/helpers/friends_helper.dart index 4acac67..9051854 100644 --- a/lib/helpers/friends_helper.dart +++ b/lib/helpers/friends_helper.dart @@ -2,6 +2,7 @@ import 'package:comunic/helpers/database/friends_database_helper.dart'; import 'package:comunic/lists/friends_list.dart'; import 'package:comunic/models/api_request.dart'; import 'package:comunic/models/friend.dart'; +import 'package:comunic/models/friend_status.dart'; import 'package:meta/meta.dart'; /// Friends helper @@ -65,7 +66,7 @@ class FriendsHelper { "accept": accept.toString() }).exec(); - return response.code == 200; + return response.isOK; } /// Update following status for a friend @@ -91,6 +92,28 @@ class FriendsHelper { return response.code == 200; } + /// Get friendship status. Throw an exception in case of failure + Future getFriendshipStatus(int userID) async { + final response = await APIRequest( + uri: "friends/getStatus", + needLogin: true, + args: {"friendID": userID.toString()}, + ).exec(); + + if (response.code != 200) + throw Exception("Could not get friendship status!"); + + final obj = response.getObject(); + + return FriendStatus( + userID: userID, + areFriend: obj["are_friend"], + sentRequest: obj["sent_request"], + receivedRequest: obj["received_request"], + following: obj["following"], + ); + } + /// Get and return the list of friends of an other user /// /// Throws an Exception if could not get the list of friends @@ -106,4 +129,24 @@ class FriendsHelper { return Set.from(response.getArray()); } + + /// Send a friendship request to a specified user + Future sendRequest(int userID) async { + return (await APIRequest( + uri: "friends/sendRequest", + needLogin: true, + args: {"friendID": userID.toString()}, + ).exec()) + .isOK; + } + + /// Cancel a friendship request + Future cancelRequest(int userID) async { + return (await APIRequest( + uri: "friends/removeRequest", + needLogin: true, + args: {"friendID": userID.toString()}, + ).exec()) + .isOK; + } } diff --git a/lib/helpers/users_helper.dart b/lib/helpers/users_helper.dart index 1e9e517..ad2eca7 100644 --- a/lib/helpers/users_helper.dart +++ b/lib/helpers/users_helper.dart @@ -12,7 +12,11 @@ import 'package:comunic/models/user.dart'; /// @author Pierre HUBERT /// Handle advanced information error -enum GetUserAdvancedInformationErrorCause { NOT_FOUND, NETWORK_ERROR } +enum GetUserAdvancedInformationErrorCause { + NOT_FOUND, + NETWORK_ERROR, + NOT_AUTHORIZED +} class GetUserAdvancedUserError extends Error { final GetUserAdvancedInformationErrorCause cause; @@ -76,6 +80,11 @@ class UsersHelper { return list; } + /// Get information about a single user. Throws in case of failure + Future getSingleWithThrow(int user) async { + return (await getListWithThrow(Set()..add(user)))[0]; + } + /// Get users information from a given [Set] Future getList(Set users, {bool forceDownload = false}) async { @@ -125,6 +134,9 @@ class UsersHelper { if (response.code == 404) cause = GetUserAdvancedInformationErrorCause.NOT_FOUND; + if (response.code == 401) + cause = GetUserAdvancedInformationErrorCause.NOT_AUTHORIZED; + throw new GetUserAdvancedUserError(cause); } diff --git a/lib/models/friend_status.dart b/lib/models/friend_status.dart new file mode 100644 index 0000000..49e57a0 --- /dev/null +++ b/lib/models/friend_status.dart @@ -0,0 +1,27 @@ +import 'package:meta/meta.dart'; + +/// Simple friendship status +/// +/// @author Pierre HUBERT + +class FriendStatus { + final int userID; + final bool areFriend; + final bool sentRequest; + final bool receivedRequest; + final bool following; + + const FriendStatus({ + @required this.userID, + @required this.areFriend, + @required this.sentRequest, + @required this.receivedRequest, + @required this.following, + }) : assert(userID != null), + assert(areFriend != null), + assert(sentRequest != null), + assert(receivedRequest != null), + assert(following != null); + + bool get noRequestExchanged => !areFriend && !sentRequest && !receivedRequest; +} diff --git a/lib/ui/routes/user_access_denied_route.dart b/lib/ui/routes/user_access_denied_route.dart new file mode 100644 index 0000000..5b0fd63 --- /dev/null +++ b/lib/ui/routes/user_access_denied_route.dart @@ -0,0 +1,120 @@ +import 'package:comunic/helpers/friends_helper.dart'; +import 'package:comunic/helpers/users_helper.dart'; +import 'package:comunic/models/friend_status.dart'; +import 'package:comunic/models/user.dart'; +import 'package:comunic/ui/routes/user_page_route.dart'; +import 'package:comunic/ui/widgets/FrienshipStatusWidget.dart'; +import 'package:comunic/ui/widgets/account_image_widget.dart'; +import 'package:comunic/utils/intl_utils.dart'; +import 'package:comunic/utils/ui_utils.dart'; +import 'package:flutter/material.dart'; + +/// User access denied route +/// +/// @author Pierre HUBERT + +class UserAccessDeniedRoute extends StatefulWidget { + final int userID; + + const UserAccessDeniedRoute({Key key, @required this.userID}) + : assert(userID != null), + super(key: key); + + @override + _UserAccessDeniedRouteState createState() => _UserAccessDeniedRouteState(); +} + +class _UserAccessDeniedRouteState extends State { + final UsersHelper usersHelper = UsersHelper(); + final FriendsHelper friendsHelper = FriendsHelper(); + + GlobalKey _refreshIndicatorKey = + GlobalKey(); + + FriendStatus _status; + User _user; + bool _error = false; + + void setError(bool e) => setState(() => _error = e); + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + refresh(); + } + + Future refresh() async { + try { + final status = await friendsHelper.getFriendshipStatus(widget.userID); + final user = await usersHelper.getSingleWithThrow(widget.userID); + + // Check if the two users are friend now + if (status.areFriend) + Navigator.of(context).pushReplacement( + MaterialPageRoute( + builder: (c) => UserPageRoute( + userID: widget.userID, + ), + ), + ); + + setState(() { + _status = status; + _user = user; + }); + } catch (e) { + setError(true); + } + } + + @override + Widget build(BuildContext context) { + if (!_error && _status == null) return buildLoadingPage(showAppBar: true); + + return Scaffold( + appBar: AppBar( + title: Text(_error ? "Error" : _user.displayName), + ), + body: RefreshIndicator( + key: _refreshIndicatorKey, + onRefresh: refresh, + child: ListView.builder( + itemBuilder: (c, i) => _scrollBuild(), + itemCount: 1, + ), + ), + ); + } + + Widget _scrollBuild() { + if (_error) return _buildError(); + + return _buildPage(); + } + + Widget _buildError() { + return buildErrorCard("Could get information about your friendship!"); + } + + Widget _buildPage() { + return Center( + child: Column( + children: [ + AccountImageWidget( + user: _user, + width: 75, + ), + Text( + _user.displayName, + style: TextStyle(fontSize: 25.0), + ), + Text(tr("This account is private.")), + FriendshipStatusWidget( + status: _status, + onFriendshipUpdated: () => _refreshIndicatorKey.currentState.show(), + ) + ], + ), + ); + } +} diff --git a/lib/ui/routes/user_page_route.dart b/lib/ui/routes/user_page_route.dart index ac80f74..095e0dc 100644 --- a/lib/ui/routes/user_page_route.dart +++ b/lib/ui/routes/user_page_route.dart @@ -2,6 +2,7 @@ import 'package:comunic/helpers/posts_helper.dart'; import 'package:comunic/helpers/users_helper.dart'; import 'package:comunic/models/advanced_user_info.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/widgets/network_image_widget.dart'; import 'package:comunic/ui/widgets/posts_list_widget.dart'; import 'package:comunic/utils/conversations_utils.dart'; @@ -57,8 +58,17 @@ class _UserPageRouteState extends State { }); _setStatus(_PageStatus.READY); - } on GetUserAdvancedUserError { + } on GetUserAdvancedUserError catch (e) { _setStatus(_PageStatus.ERROR); + + if (e.cause == GetUserAdvancedInformationErrorCause.NOT_AUTHORIZED) + Navigator.of(context).pushReplacement( + MaterialPageRoute( + builder: (c) => UserAccessDeniedRoute( + userID: widget.userID, + ), + ), + ); } } diff --git a/lib/ui/widgets/FrienshipStatusWidget.dart b/lib/ui/widgets/FrienshipStatusWidget.dart new file mode 100644 index 0000000..8181712 --- /dev/null +++ b/lib/ui/widgets/FrienshipStatusWidget.dart @@ -0,0 +1,114 @@ +import 'package:comunic/helpers/friends_helper.dart'; +import 'package:comunic/models/friend_status.dart'; +import 'package:comunic/utils/intl_utils.dart'; +import 'package:comunic/utils/ui_utils.dart'; +import 'package:flutter/material.dart'; + +/// Friendship status widget +/// +/// @author Pierre HUBERT + +const WhiteTextColorStyle = TextStyle(color: Colors.white); + +class FriendshipStatusWidget extends StatefulWidget { + final FriendStatus status; + final void Function() onFriendshipUpdated; + + const FriendshipStatusWidget({ + Key key, + @required this.status, + @required this.onFriendshipUpdated, + }) : assert(status != null), + assert(onFriendshipUpdated != null), + super(key: key); + + @override + _FriendshipStatusWidgetState createState() => _FriendshipStatusWidgetState(); +} + +class _FriendshipStatusWidgetState extends State { + final FriendsHelper _friendsHelper = FriendsHelper(); + + bool _sentRequest = false; + + int get friendID => widget.status.userID; + + void setSentRequest(bool sent) => setState(() => _sentRequest = sent); + + @override + void didUpdateWidget(FriendshipStatusWidget oldWidget) { + super.didUpdateWidget(oldWidget); + setSentRequest(false); + } + + @override + Widget build(BuildContext context) { + if (_sentRequest) return Container(); + + // No request sent yet + if (widget.status.noRequestExchanged) { + return RaisedButton( + child: Text(tr("Send request").toUpperCase()), + onPressed: () => + executeRequest(() => _friendsHelper.sendRequest(friendID)), + ); + } + + // Already sent a friendship request + if (widget.status.sentRequest) { + return RaisedButton( + child: Text( + tr("Cancel request").toUpperCase(), + style: WhiteTextColorStyle, + ), + color: Colors.red, + onPressed: () => + executeRequest(() => _friendsHelper.cancelRequest(friendID)), + ); + } + + // Received a friendship request + if (widget.status.receivedRequest) { + return Column( + children: [ + RaisedButton( + child: Text( + tr("Accept request").toUpperCase(), + style: WhiteTextColorStyle, + ), + color: Colors.green, + onPressed: () => executeRequest( + () => _friendsHelper.respondRequest(friendID, true)), + ), + RaisedButton( + child: Text( + tr("Reject request").toUpperCase(), + style: WhiteTextColorStyle, + ), + color: Colors.red, + onPressed: () => executeRequest( + () => _friendsHelper.respondRequest(friendID, false)), + ) + ], + ); + } + + // The two users are friends, offers to follow him + return RaisedButton( + child: Text((widget.status.following ? tr("Following") : tr("Follow")) + .toUpperCase()), + onPressed: () => executeRequest(() => + _friendsHelper.setFollowing(friendID, !widget.status.following)), + ); + } + + /// Send friendship request + Future executeRequest(Future Function() func) async { + setSentRequest(true); + + if (!await func()) + showSimpleSnack(context, tr("Could not update your membership!")); + + widget.onFriendshipUpdated(); + } +} diff --git a/lib/utils/ui_utils.dart b/lib/utils/ui_utils.dart index 31e3095..fb62682 100644 --- a/lib/utils/ui_utils.dart +++ b/lib/utils/ui_utils.dart @@ -21,7 +21,7 @@ Widget buildLoadingPage({ return Scaffold( appBar: showAppBar ? AppBar( - title: Text(routeTitle), + title: routeTitle == null ? null : Text(routeTitle), ) : null, body: buildCenteredProgressBar(),