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

Start to build groups page

This commit is contained in:
Pierre HUBERT 2020-04-15 18:06:20 +02:00
parent c7d8843f06
commit b10163575f
9 changed files with 347 additions and 12 deletions

View File

@ -1,4 +1,5 @@
import 'package:comunic/lists/groups_list.dart';
import 'package:comunic/models/advanced_group_info.dart';
import 'package:comunic/models/api_request.dart';
import 'package:comunic/models/group.dart';
import 'package:comunic/utils/api_utils.dart';
@ -35,6 +36,17 @@ const _APIGroupsPostsCreationLevelsMap = {
final _groupsListCache = GroupsList();
/// Callback for getting advanced user information
enum GetAdvancedInfoStatus { SUCCESS, ACCESS_DENIED }
class GetAdvancedInfoResult {
final GetAdvancedInfoStatus status;
final AdvancedGroupInfo info;
GetAdvancedInfoResult(this.status, this.info) : assert(status != null);
}
/// Groups helper
class GroupsHelper {
/// Download a list of groups information from the server
Future<GroupsList> _downloadList(Set<int> groups) async {
@ -92,6 +104,15 @@ class GroupsHelper {
return list;
}
/// Get information about a single group
///
/// Throws in case of failure
Future<Group> getSingle(int groupID, {bool force = false}) async {
return (await getListOrThrow(Set<int>()..add(groupID), force: force))
.values
.first;
}
/// Get the list of groups of a user
Future<Set<int>> getListUser() async =>
(await APIRequest(uri: "groups/get_my_list", needLogin: true).exec())
@ -127,6 +148,27 @@ class GroupsHelper {
"accept": accept ? "true" : "false",
});
/// Get advanced information about the user
Future<GetAdvancedInfoResult> getAdvancedInfo(int groupID) async {
// Get advanced information about the user
final result =
await (APIRequest(uri: "groups/get_advanced_info", needLogin: true)
..addInt("id", groupID))
.exec();
switch (result.code) {
case 401:
return GetAdvancedInfoResult(GetAdvancedInfoStatus.ACCESS_DENIED, null);
case 200:
return GetAdvancedInfoResult(GetAdvancedInfoStatus.SUCCESS,
_getAdvancedGroupInfoFromAPI(result.getObject()));
default:
throw Exception("Could not get advanced group information!");
}
}
/// Turn an API entry into a group object
Group _getGroupFromAPI(Map<String, dynamic> map) {
return Group(
@ -142,4 +184,25 @@ class GroupsHelper {
virtualDirectory: map["virtual_directory"],
following: map["following"]);
}
/// Get advanced group information
AdvancedGroupInfo _getAdvancedGroupInfoFromAPI(Map<String, dynamic> map) =>
AdvancedGroupInfo(
id: map["id"],
name: map["name"],
iconURL: map["icon_url"],
numberMembers: map["number_members"],
membershipLevel: _APIGroupsMembershipLevelsMap[map["membership"]],
visibilityLevel: _APIGroupsVisibilityLevelsMap[map["visibility"]],
registrationLevel:
_APIGroupsRegistrationLevelsMap[map["registration_level"]],
postCreationLevel: _APIGroupsPostsCreationLevelsMap[map["posts_level"]],
virtualDirectory: map["virtual_directory"],
following: map["following"],
timeCreate: map["time_create"],
description: map["description"],
url: map["url"],
numberLikes: map["number_likes"],
isLiking: map["is_liking"],
);
}

View File

@ -0,0 +1,43 @@
import 'package:flutter/material.dart';
import 'group.dart';
/// Advanced group information
///
/// @author Pierre Hubert
class AdvancedGroupInfo extends Group {
final int timeCreate;
final String description;
final String url;
final int numberLikes;
final bool isLiking;
AdvancedGroupInfo({
@required int id,
@required String name,
@required String iconURL,
@required int numberMembers,
@required GroupMembershipLevel membershipLevel,
@required GroupVisibilityLevel visibilityLevel,
@required GroupRegistrationLevel registrationLevel,
@required GroupPostCreationLevel postCreationLevel,
@required String virtualDirectory,
@required bool following,
@required this.timeCreate,
@required this.description,
@required this.url,
@required this.numberLikes,
@required this.isLiking,
}) : super(
id: id,
name: name,
iconURL: iconURL,
numberMembers: numberMembers,
membershipLevel: membershipLevel,
visibilityLevel: visibilityLevel,
registrationLevel: registrationLevel,
postCreationLevel: postCreationLevel,
virtualDirectory: virtualDirectory,
following: following);
}

View File

@ -53,4 +53,9 @@ class Group {
assert(following != null);
get displayName => this.name;
bool get getIsAtLeastMember =>
membershipLevel == GroupMembershipLevel.ADMINISTRATOR ||
membershipLevel == GroupMembershipLevel.MODERATOR ||
membershipLevel == GroupMembershipLevel.MEMBER;
}

View File

@ -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/group_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';
@ -21,6 +22,10 @@ import 'login_route.dart';
class HomeRoute extends StatefulWidget {
@override
State<StatefulWidget> createState() => _HomeRouteState();
/// Get current instance of Home controller
static HomeController of(BuildContext context) =>
context.findAncestorStateOfType<HomeController>();
}
class CurrPage {
@ -28,9 +33,24 @@ class CurrPage {
final Map<String, dynamic> args;
const CurrPage(this.action, {this.args}) : assert(action != null);
@override
String toString() =>
"CurrPage {\n\taction: " +
this.action.toString() +
"\n\targs: " +
this.args.toString() +
"\n}";
}
class _HomeRouteState extends State<HomeRoute> {
/// Public interface of home controller
abstract class HomeController extends State<HomeRoute> {
/// Open a specific group page specified by its [groupID]
void openGroup(int groupID);
}
/// Private implementation of HomeController
class _HomeRouteState extends HomeController {
CurrPage get _currTab => history.last;
List<CurrPage> history = List();
@ -106,6 +126,9 @@ class _HomeRouteState extends State<HomeRoute> {
case BarCallbackActions.OPEN_GROUPS:
return GroupsListScreen();
case BarCallbackActions.OPEN_GROUP_PAGE:
return GroupPageScreen(groupID: _currTab.args["groupID"]);
default:
throw "Invalid tab : " + _currTab.toString();
}
@ -156,4 +179,10 @@ class _HomeRouteState extends State<HomeRoute> {
return LoginRoute();
}));
}
@override
void openGroup(int groupID) {
_pushPage(CurrPage(BarCallbackActions.OPEN_GROUP_PAGE,
args: {"groupID": groupID}));
}
}

View File

@ -0,0 +1,106 @@
import 'package:comunic/helpers/groups_helper.dart';
import 'package:comunic/models/group.dart';
import 'package:comunic/ui/widgets/group_icon_widget.dart';
import 'package:comunic/ui/widgets/group_membership_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';
/// Group access denied screen
///
/// @author Pierre Hubert
class GroupAccessDeniedScreen extends StatefulWidget {
final int groupID;
final Function() onMembershipAcquired;
const GroupAccessDeniedScreen({
Key key,
@required this.groupID,
@required this.onMembershipAcquired,
}) : assert(groupID != null),
assert(onMembershipAcquired != null),
super(key: key);
@override
_GroupAccessDeniedScreenState createState() =>
_GroupAccessDeniedScreenState();
}
class _GroupAccessDeniedScreenState extends SafeState<GroupAccessDeniedScreen> {
Group _group;
bool error = false;
@override
void initState() {
_refresh();
super.initState();
}
@override
Widget build(BuildContext context) {
if (error)
return buildErrorCard(tr("Could not get basic group information!"),
actions: [
MaterialButton(
child: Text(tr("Try again").toUpperCase()),
onPressed: () => this._refresh(),
)
]);
if (_group == null) return buildCenteredProgressBar();
return Padding(
padding: const EdgeInsets.all(8.0),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Spacer(
flex: 5,
),
GroupIcon(group: _group),
Spacer(),
Text(
_group.displayName,
style: TextStyle(fontSize: 20),
),
Spacer(),
Text(tr("A registration is required to access this group page.")),
Spacer(),
GroupMembershipWidget(
group: _group,
onUpdated: () => this._refresh(),
onError: () => this._refresh(),
),
Spacer(
flex: 5,
)
],
),
),
);
}
/// Get basic information about the groups
Future<void> _refresh() async {
try {
setState(() {
error = false;
_group = null;
});
// Get information about a single group
final group = await GroupsHelper().getSingle(widget.groupID, force: true);
if (group.getIsAtLeastMember) widget.onMembershipAcquired();
setState(() => _group = group);
} catch (e) {
print(e);
setState(() => error = true);
}
}
}

View File

@ -0,0 +1,80 @@
import 'package:comunic/helpers/groups_helper.dart';
import 'package:comunic/ui/screens/group_access_denied_screen.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';
/// Group screen
///
/// @author Pierre Hubert
class GroupPageScreen extends StatefulWidget {
final int groupID;
const GroupPageScreen({Key key, @required this.groupID})
: assert(groupID != null),
super(key: key);
@override
_GroupPageScreenState createState() => _GroupPageScreenState();
}
class _GroupPageScreenState extends SafeState<GroupPageScreen> {
int get groupID => widget.groupID;
GetAdvancedInfoResult _getGroupResult;
var error = false;
@override
void initState() {
_refreshPage();
super.initState();
}
@override
Widget build(BuildContext context) {
if (error)
return buildErrorCard(tr("Could not load information about the group!"),
actions: [
MaterialButton(
onPressed: () => _refreshPage(),
child: Text(tr("Try again").toUpperCase()),
)
]);
// If we are still loading the page
if (_getGroupResult == null) return buildCenteredProgressBar();
// Check if access to the group was denied
if (_getGroupResult.status == GetAdvancedInfoStatus.ACCESS_DENIED)
return GroupAccessDeniedScreen(
groupID: groupID,
onMembershipAcquired: () => _refreshPage(),
);
// Create another screen for the group page
}
/// Refresh the page
Future<void> _refreshPage() async {
try {
setState(() {
error = false;
_getGroupResult = null;
});
// Get information about the group
final result = await GroupsHelper().getAdvancedInfo(groupID);
setState(() {
_getGroupResult = result;
});
} catch (e) {
print(e);
setState(() {
error = true;
});
}
}
}

View File

@ -1,6 +1,7 @@
import 'package:comunic/helpers/groups_helper.dart';
import 'package:comunic/lists/groups_list.dart';
import 'package:comunic/models/group.dart';
import 'package:comunic/ui/routes/home_route.dart';
import 'package:comunic/ui/widgets/group_icon_widget.dart';
import 'package:comunic/ui/widgets/group_membership_widget.dart';
import 'package:comunic/ui/widgets/safe_state.dart';
@ -40,7 +41,7 @@ class _GroupsListScreenState extends SafeState<GroupsListScreen> {
hide: !_error,
actions: [
MaterialButton(
child: Text(tr("Try again".toUpperCase())),
child: Text(tr("Try again").toUpperCase()),
onPressed: () => _refreshList(),
),
],
@ -66,6 +67,7 @@ class _GroupsListScreenState extends SafeState<GroupsListScreen> {
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: () => _deleteGroup(g)),
onTap: () => HomeRoute.of(context).openGroup(g.id),
))
.toList(),
),

View File

@ -13,11 +13,11 @@ import 'package:flutter/material.dart';
class GroupMembershipWidget extends StatefulWidget {
final Group group;
final Function() onUpdated;
final Function() onError;
const GroupMembershipWidget({
@required this.group,
this.onUpdated,
}) : assert(group != null);
const GroupMembershipWidget(
{@required this.group, this.onUpdated, this.onError})
: assert(group != null);
@override
_GroupMembershipWidgetState createState() => _GroupMembershipWidgetState();
@ -50,6 +50,9 @@ class _GroupMembershipWidgetState extends SafeState<GroupMembershipWidget> {
case GroupMembershipLevel.VISITOR:
return _buildVisitorState();
default:
throw Exception("Unkonwn grou pmembership level state: $_level");
}
}
@ -84,9 +87,10 @@ class _GroupMembershipWidgetState extends SafeState<GroupMembershipWidget> {
message: tr("Do you really want to reject this invitation?")))
return;
if (!await GroupsHelper().respondInvitation(_id, accept))
if (!await GroupsHelper().respondInvitation(_id, accept)) {
showSimpleSnack(context, tr("Could not respond to your invitation!"));
else {
if (this.widget.onError != null) this.widget.onError();
} else {
// Refresh state
group.membershipLevel =
accept ? GroupMembershipLevel.MEMBER : GroupMembershipLevel.VISITOR;
@ -114,9 +118,10 @@ class _GroupMembershipWidgetState extends SafeState<GroupMembershipWidget> {
/// Cancel group membership request
void _cancelRequest() async {
if (!await GroupsHelper().cancelRequest(_id))
if (!await GroupsHelper().cancelRequest(_id)) {
showSimpleSnack(context, tr("Could not cancel your membership request!"));
else {
if (this.widget.onError != null) this.widget.onError();
} else {
// Refresh state
group.membershipLevel = GroupMembershipLevel.VISITOR;
this.setState(() {});
@ -138,9 +143,10 @@ class _GroupMembershipWidgetState extends SafeState<GroupMembershipWidget> {
/// Create new membership request
void _requestMembership() async {
if (!await GroupsHelper().sendRequest(_id))
if (!await GroupsHelper().sendRequest(_id)) {
showSimpleSnack(context, tr("Could not send your membership request!"));
else {
if (this.widget.onError != null) this.widget.onError();
} else {
// Refresh state
group.membershipLevel = GroupMembershipLevel.PENDING;
this.setState(() {});

View File

@ -16,6 +16,7 @@ enum BarCallbackActions {
OPEN_FRIENDS,
OPEN_MY_PAGE,
OPEN_GROUPS,
OPEN_GROUP_PAGE,
OPEN_APP_SETTINGS,
NONE,
ACTION_LOGOUT