diff --git a/lib/helpers/friends_helper.dart b/lib/helpers/friends_helper.dart new file mode 100644 index 0000000..8dc8ad9 --- /dev/null +++ b/lib/helpers/friends_helper.dart @@ -0,0 +1,38 @@ +import 'package:comunic/lists/friends_list.dart'; +import 'package:comunic/models/api_request.dart'; +import 'package:comunic/models/friend.dart'; + +/// Friends helper +/// +/// @author Pierre HUBERT + +class FriendsHelper { + /// Get the list of friends of the user + /// + /// Returns the list of friends in case of success, or null if an error + /// occurred + Future downloadList() async { + final response = await APIRequest( + uri: "friends/getList", + needLogin: true, + args: { + "complete": "true", + }, + ).exec(); + + if (response.code != 200) return null; + + // Parse and return the list of friends + FriendsList list = FriendsList(); + response.getArray().forEach((f) => + list.add(Friend( + id: f["ID_friend"], + accepted: f["accepted"] == 1, + lastActive: f["time_last_activity"], + following: f["following"] == 1, + canPostTexts: f["canPostTexts"],),), + ); + + return list; + } +} \ No newline at end of file diff --git a/lib/lists/friends_list.dart b/lib/lists/friends_list.dart new file mode 100644 index 0000000..2d514c1 --- /dev/null +++ b/lib/lists/friends_list.dart @@ -0,0 +1,24 @@ +import 'dart:collection'; + +import 'package:comunic/models/friend.dart'; + +/// List of friends of the user +/// +/// @author Pierre HUBERT + +class FriendsList extends ListBase { + List _list = List(); + + int get length => _list.length; + + set length(int length) => _list.length = length; + + @override + Friend operator [](int index) => _list[index]; + + @override + void operator []=(int index, Friend value) => _list[index] = value; + + /// Get the ID of all the friends of the current user + List get usersId => map((f) => f.id).toList(); +} diff --git a/lib/models/friend.dart b/lib/models/friend.dart new file mode 100644 index 0000000..7519215 --- /dev/null +++ b/lib/models/friend.dart @@ -0,0 +1,29 @@ +import 'package:comunic/utils/date_utils.dart'; +import 'package:meta/meta.dart'; + +/// Single user Friend information +/// +/// @author Pierre HUBERT + +class Friend { + final int id; + final bool accepted; + final int lastActive; + final bool following; + final bool canPostTexts; + + Friend({ + @required this.id, + @required this.accepted, + @required this.lastActive, + @required this.following, + @required this.canPostTexts, + }) : assert(id != null), + assert(accepted != null), + assert(lastActive != null), + assert(following != null), + assert(canPostTexts != null); + + /// Check out whether friend is connected or not + bool get isConnected => time() - 30 < lastActive; +} diff --git a/lib/models/user.dart b/lib/models/user.dart index e762169..1d6d26c 100644 --- a/lib/models/user.dart +++ b/lib/models/user.dart @@ -31,6 +31,9 @@ class User extends CacheModel { /// Get user full name String get fullName => firstName + " " + lastName; + /// Get user display name + String get displayName => fullName; //TODO : support HTML characters (eg: É => é) + Map toMap() { return { UserTableContract.C_ID: id, diff --git a/lib/ui/routes/home_route.dart b/lib/ui/routes/home_route.dart index 3ee3c57..43b6987 100644 --- a/lib/ui/routes/home_route.dart +++ b/lib/ui/routes/home_route.dart @@ -1,6 +1,8 @@ import 'package:comunic/ui/screens/conversations_list_screen.dart'; +import 'package:comunic/ui/screens/friends_list_screen.dart'; import 'package:comunic/ui/screens/menus_screen.dart'; import 'package:comunic/ui/tiles/custom_bottom_navigation_bar_item.dart'; +import 'package:comunic/utils/intl_utils.dart'; import 'package:flutter/material.dart'; /// Main route of the application @@ -52,6 +54,9 @@ class _HomeRouteState extends State { return ConversationsListScreen(); case 1: + return FriendsListScreen(); + + case 2: return MenuScreen(); default: @@ -64,11 +69,15 @@ class _HomeRouteState extends State { return [ CustomNavigationBarItem( icon: Icon(Icons.chat), - title: Text("Conversations"), + title: Text(tr("Conversations")), + ), + CustomNavigationBarItem( + icon: Icon(Icons.group), + title: Text(tr("Friends")), ), CustomNavigationBarItem( icon: Icon(Icons.menu), - title: Text("Menu"), + title: Text(tr("Menu")), ), ]; } diff --git a/lib/ui/screens/friends_list_screen.dart b/lib/ui/screens/friends_list_screen.dart new file mode 100644 index 0000000..1749ff1 --- /dev/null +++ b/lib/ui/screens/friends_list_screen.dart @@ -0,0 +1,122 @@ +import 'package:comunic/helpers/friends_helper.dart'; +import 'package:comunic/helpers/users_helper.dart'; +import 'package:comunic/lists/friends_list.dart'; +import 'package:comunic/lists/users_list.dart'; +import 'package:comunic/ui/tiles/accepted_friend_tile.dart'; +import 'package:comunic/ui/tiles/pending_friend_tile.dart'; +import 'package:comunic/ui/widgets/safe_state.dart'; +import 'package:comunic/utils/intl_utils.dart'; +import 'package:comunic/utils/ui_utils.dart'; +import 'package:flutter/material.dart'; + +/// Friends list screen +/// +/// Display the list of friends of the current user +/// +/// @author Pierre HUBERT + +enum _ErrorsLevel { NONE, MINOR, MAJOR } + +class FriendsListScreen extends StatefulWidget { + @override + State createState() => _FriendsListScreenState(); +} + +class _FriendsListScreenState extends SafeState { + /// Helpers + final _friendsHelper = FriendsHelper(); + final _usersHelper = UsersHelper(); + + /// Widget members + _ErrorsLevel _error = _ErrorsLevel.NONE; + FriendsList _friendsList; + UsersList _usersInfo; + bool _loading = true; + + /// Useful setters + set error(_ErrorsLevel err) => setState(() => _error = err); + + set loading(bool loading) => setState(() => _loading = loading); + + void _gotError() => + error = _friendsList == null ? _ErrorsLevel.MAJOR : _ErrorsLevel.MINOR; + + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + _loadList(); + } + + /// Load the list of friends + Future _loadList() async { + error = _ErrorsLevel.NONE; + loading = true; + + // Get the list of friends + final list = await _friendsHelper.downloadList(); + + // Check for errors + if (list == null) return _gotError(); + + // Get information about related users + final users = await _usersHelper.getUsersInfo(list.usersId); + + // Check for errors + if (users == null) return _gotError(); + + // Apply new information + setState(() { + _friendsList = list; + _usersInfo = users; + }); + loading = false; + error = _ErrorsLevel.NONE; + } + + /// Build and return loading error + Widget _buildError() => + buildErrorCard( + tr("Could not load your list of friends!"), + actions: [ + FlatButton( + onPressed: _loadList, + child: Text( + tr("Retry").toUpperCase(), + style: TextStyle(color: Colors.white), + ), + ), + ], + ); + + @override + Widget build(BuildContext context) { + if (_error == _ErrorsLevel.MAJOR) return _buildError(); + if (_friendsList == null) return buildCenteredProgressBar(); + + return Column( + children: [ + + // Check for errors + Container(child: _error != _ErrorsLevel.NONE ? _buildError() : null), + + // Check if loading + Container(child: _loading ? CircularProgressIndicator() : null), + + // List of friends + Expanded( + child: ListView.builder(itemCount: _friendsList.length, itemBuilder: (c, i) => + _friendsList[i].accepted ? AcceptedFriendTile( + friend: _friendsList[i], + user: _usersInfo.getUser(_friendsList[i].id), + ) : PendingFriendTile( + friend: _friendsList[i], + user: _usersInfo.getUser(_friendsList[i].id), + onRespond: (friend, accept){}, + )), + ), + + ], + ); + } +} diff --git a/lib/ui/tiles/accepted_friend_tile.dart b/lib/ui/tiles/accepted_friend_tile.dart new file mode 100644 index 0000000..921325b --- /dev/null +++ b/lib/ui/tiles/accepted_friend_tile.dart @@ -0,0 +1,39 @@ +import 'package:comunic/models/friend.dart'; +import 'package:comunic/models/user.dart'; +import 'package:comunic/ui/widgets/account_image_widget.dart'; +import 'package:comunic/utils/date_utils.dart'; +import 'package:comunic/utils/intl_utils.dart'; +import 'package:flutter/material.dart'; + +/// Accepted friend tile +/// +/// @author Pierre HUBERT + +class AcceptedFriendTile extends StatelessWidget { + final Friend friend; + final User user; + + const AcceptedFriendTile( + {Key key, @required this.friend, @required this.user}) + : assert(friend != null), + assert(user != null), + super(key: key); + + @override + Widget build(BuildContext context) { + return ListTile( + leading: AccountImageWidget(user: user), + title: Text(user.displayName), + subtitle: friend.isConnected + ? Text( + tr( + "Online", + ), + style: TextStyle(color: Colors.green), + ) + : Text( + diffTimeFromNowToStr(friend.lastActive), + ), + ); + } +} diff --git a/lib/ui/tiles/pending_friend_tile.dart b/lib/ui/tiles/pending_friend_tile.dart new file mode 100644 index 0000000..fe9c1f7 --- /dev/null +++ b/lib/ui/tiles/pending_friend_tile.dart @@ -0,0 +1,64 @@ +import 'package:comunic/models/friend.dart'; +import 'package:comunic/models/user.dart'; +import 'package:comunic/ui/widgets/account_image_widget.dart'; +import 'package:comunic/utils/intl_utils.dart'; +import 'package:flutter/material.dart'; + +/// Pending friend tile +/// +/// @author Pierre HUBERT + +typedef RespondFriendshipRequestCallback = void Function(Friend, bool); + +class PendingFriendTile extends StatelessWidget { + final Friend friend; + final User user; + final RespondFriendshipRequestCallback onRespond; + + const PendingFriendTile( + {Key key, + @required this.friend, + @required this.user, + @required this.onRespond}) + : assert(friend != null), + assert(user != null), + assert(onRespond != null), + super(key: key); + + @override + Widget build(BuildContext context) { + return ListTile( + leading: AccountImageWidget( + user: user, + ), + isThreeLine: true, + title: Text(user.fullName), + subtitle: Container( + height: 30.0, + margin: EdgeInsets.only(top: 8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + FlatButton( + child: Text( + tr("Accept").toUpperCase(), + style: TextStyle(color: Colors.white), + ), + color: Colors.green, + onPressed: () => onRespond(friend, true), + ), + Container(width: 8.0,), + FlatButton( + child: Text( + tr("Reject").toUpperCase(), + style: TextStyle(color: Colors.white), + ), + color: Colors.red, + onPressed: () => onRespond(friend, false), + ) + ], + ), + ), + ); + } +} diff --git a/lib/ui/widgets/safe_state.dart b/lib/ui/widgets/safe_state.dart new file mode 100644 index 0000000..5fedbe5 --- /dev/null +++ b/lib/ui/widgets/safe_state.dart @@ -0,0 +1,13 @@ +import 'package:flutter/material.dart'; + +/// Little State hack to avoid issues +/// +/// @author Pierre HUBERT + +abstract class SafeState extends State { + @override + void setState(fn) { + if(mounted) + super.setState(fn); + } +} \ No newline at end of file