diff --git a/lib/helpers/groups_helper.dart b/lib/helpers/groups_helper.dart index 3040e04..42a75f5 100644 --- a/lib/helpers/groups_helper.dart +++ b/lib/helpers/groups_helper.dart @@ -1,6 +1,7 @@ import 'package:comunic/lists/groups_list.dart'; import 'package:comunic/models/api_request.dart'; import 'package:comunic/models/group.dart'; +import 'package:comunic/utils/api_utils.dart'; /// Groups helper /// @@ -91,11 +92,20 @@ class GroupsHelper { return list; } + /// Get the list of groups of a user + Future> getListUser() async => + (await APIRequest(uri: "groups/get_my_list", needLogin: true).exec()) + .assertOk() + .getArray() + .map((f) => cast(f)) + .toSet(); + /// Turn an API entry into a group object Group _getGroupFromAPI(Map map) { return Group( id: map["id"], name: map["name"], + iconURL: map["icon_url"], numberMembers: map["number_members"], membershipLevel: _APIGroupsMembershipLevelsMap[map["membership"]], visibilityLevel: _APIGroupsVisibilityLevelsMap[map["visibility"]], diff --git a/lib/models/api_response.dart b/lib/models/api_response.dart index 1ca8923..422bb62 100644 --- a/lib/models/api_response.dart +++ b/lib/models/api_response.dart @@ -16,4 +16,13 @@ class APIResponse { /// Check if the request is successful or not bool get isOK => code == 200; + + /// Make sure the request succeed, or throw an exception else + APIResponse assertOk() { + + if(!this.isOK) + throw Exception("Request failed with status $code"); + + return this; + } } diff --git a/lib/models/group.dart b/lib/models/group.dart index 979e748..5d43967 100644 --- a/lib/models/group.dart +++ b/lib/models/group.dart @@ -22,6 +22,7 @@ enum GroupPostCreationLevel { MODERATORS, MEMBERS } class Group { final int id; final String name; + final String iconURL; final int numberMembers; final GroupMembershipLevel membershipLevel; final GroupVisibilityLevel visibilityLevel; @@ -33,6 +34,7 @@ class Group { Group({ @required this.id, @required this.name, + @required this.iconURL, @required this.numberMembers, @required this.membershipLevel, @required this.visibilityLevel, @@ -42,6 +44,7 @@ class Group { @required this.following, }) : assert(id != null), assert(name != null), + assert(iconURL != null), assert(numberMembers != null), assert(membershipLevel != null), assert(visibilityLevel != null), diff --git a/lib/ui/routes/home_route.dart b/lib/ui/routes/home_route.dart index cf9da6b..cfc2a3f 100644 --- a/lib/ui/routes/home_route.dart +++ b/lib/ui/routes/home_route.dart @@ -2,6 +2,7 @@ import 'package:comunic/helpers/account_helper.dart'; import 'package:comunic/ui/routes/app_settings_route.dart'; import 'package:comunic/ui/screens/conversations_list_screen.dart'; import 'package:comunic/ui/screens/friends_list_screen.dart'; +import 'package:comunic/ui/screens/groups_list_screen.dart'; import 'package:comunic/ui/screens/newest_posts.dart'; import 'package:comunic/ui/screens/notifications_screen.dart'; import 'package:comunic/ui/widgets/navbar_widget.dart'; @@ -89,6 +90,9 @@ class _HomeRouteState extends State { case BarCallbackActions.OPEN_FRIENDS: return FriendsListScreen(); + case BarCallbackActions.OPEN_GROUPS: + return GroupsListScreen(); + default: throw "Invalid tab : " + _currTab.toString(); } diff --git a/lib/ui/screens/groups_list_screen.dart b/lib/ui/screens/groups_list_screen.dart new file mode 100644 index 0000000..707cdf1 --- /dev/null +++ b/lib/ui/screens/groups_list_screen.dart @@ -0,0 +1,84 @@ +import 'package:comunic/helpers/groups_helper.dart'; +import 'package:comunic/lists/groups_list.dart'; +import 'package:comunic/ui/widgets/group_icon_widget.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'; + +/// Groups list screen +/// +/// @author Pierre Hubert + +class GroupsListScreen extends StatefulWidget { + @override + _GroupsListScreenState createState() => _GroupsListScreenState(); +} + +class _GroupsListScreenState extends SafeState { + /// The list of groups + GroupsList _groups; + + bool _error = false; + + @override + void initState() { + super.initState(); + + this._refreshList(); + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + // Error + buildErrorCard( + tr("Could not load the list of groups!"), + hide: !_error, + actions: [ + MaterialButton( + child: Text(tr("Try again".toUpperCase())), + onPressed: () => _refreshList(), + ), + ], + ), + + // List of groups + Expanded( + child: RefreshIndicator( + onRefresh: () => this._refreshList(), + child: _groups == null + ? Container() + : ListView( + children: _groups.values + .map((g) => ListTile( + leading: GroupIcon(group: g), + title: Text(g.displayName), + )) + .toList(), + ), + )) + ], + ); + } + + /// Refresh the list of groups + Future _refreshList() async { + try { + final list = await GroupsHelper().getListUser(); + final groups = await GroupsHelper().getListOrThrow(list, force: true); + + setState(() { + _groups = groups; + _error = false; + }); + } catch (e) { + print(e); + + setState(() { + _error = true; + }); + } + } +} diff --git a/lib/ui/widgets/group_icon_widget.dart b/lib/ui/widgets/group_icon_widget.dart new file mode 100644 index 0000000..13e35df --- /dev/null +++ b/lib/ui/widgets/group_icon_widget.dart @@ -0,0 +1,50 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:comunic/models/group.dart'; +import 'package:flutter/material.dart'; + +/// Custom group icon +/// +/// @author Pierre Hubert +class GroupIcon extends StatelessWidget { + final Group group; + final double width; + + const GroupIcon({ + Key key, + @required this.group, + this.width = 50, + }) : assert(group != null), + assert(width != null), + super(key: key); + + @override + Widget build(BuildContext context) { + return Material( + child: CachedNetworkImage( + imageUrl: group.iconURL, + width: width, + height: width, + placeholder: (c, s) => Container( + color: Colors.grey, + width: width, + height: width, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: CircularProgressIndicator( + strokeWidth: 4.0, + ), + ), + ), + errorWidget: (c, s, o) => Container( + color: Colors.red, + width: width, + height: width, + child: Icon( + Icons.error, + color: Colors.white, + ), + ), + ), + ); + } +} diff --git a/lib/ui/widgets/navbar_widget.dart b/lib/ui/widgets/navbar_widget.dart index 0467e2d..59437a1 100644 --- a/lib/ui/widgets/navbar_widget.dart +++ b/lib/ui/widgets/navbar_widget.dart @@ -15,6 +15,7 @@ enum BarCallbackActions { OPEN_NEWEST_POSTS, OPEN_FRIENDS, OPEN_MY_PAGE, + OPEN_GROUPS, OPEN_APP_SETTINGS, NONE, ACTION_LOGOUT @@ -81,6 +82,7 @@ final _menuItems = <_MenuItem>[ final _menuActionsItem = <_ActionMenuItem>[ _ActionMenuItem( label: tr("My Page"), action: BarCallbackActions.OPEN_MY_PAGE), + _ActionMenuItem(label: tr("Groups"), action: BarCallbackActions.OPEN_GROUPS), _ActionMenuItem( label: tr("App settings"), action: BarCallbackActions.OPEN_APP_SETTINGS), _ActionMenuItem( diff --git a/lib/utils/api_utils.dart b/lib/utils/api_utils.dart new file mode 100644 index 0000000..66255a5 --- /dev/null +++ b/lib/utils/api_utils.dart @@ -0,0 +1,6 @@ +/// API utilities +/// +/// @author Pierre Hubert + +/// Casting helper +T cast(dynamic val) => val is T ? val : null; diff --git a/lib/utils/ui_utils.dart b/lib/utils/ui_utils.dart index 467013d..ffeaaf2 100644 --- a/lib/utils/ui_utils.dart +++ b/lib/utils/ui_utils.dart @@ -31,7 +31,11 @@ Widget buildLoadingPage({ } /// Build and return an error card -Widget buildErrorCard(String message, {List actions}) { +Widget buildErrorCard(String message, {List actions, bool hide = false}) { + + if(hide) + return Container(); + return Card( elevation: 2.0, color: Colors.red,