mirror of
https://gitlab.com/comunic/comunicmobile
synced 2024-11-25 22:39:22 +00:00
Add user profile route
This commit is contained in:
parent
5ab21bd63e
commit
f92846cb76
175
lib/forez/ui/routes/forez_member_profile_route.dart
Normal file
175
lib/forez/ui/routes/forez_member_profile_route.dart
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
|
import 'package:comunic/forez/helpers/forez_group_helper.dart';
|
||||||
|
import 'package:comunic/helpers/forez_groups_helper.dart';
|
||||||
|
import 'package:comunic/helpers/forez_presence_helper.dart';
|
||||||
|
import 'package:comunic/lists/forez_presences_set.dart';
|
||||||
|
import 'package:comunic/models/advanced_user_info.dart';
|
||||||
|
import 'package:comunic/models/displayed_content.dart';
|
||||||
|
import 'package:comunic/ui/widgets/async_screen_widget.dart';
|
||||||
|
import 'package:comunic/ui/widgets/copy_icon.dart';
|
||||||
|
import 'package:comunic/ui/widgets/forez_presence_calendar_widget.dart';
|
||||||
|
import 'package:comunic/ui/widgets/text_widget.dart';
|
||||||
|
import 'package:comunic/utils/intl_utils.dart';
|
||||||
|
import 'package:comunic/utils/ui_utils.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
|
/// Show information about a Forez member
|
||||||
|
///
|
||||||
|
/// @author Pierre Hubert
|
||||||
|
|
||||||
|
class ForezMemberProfileRoute extends StatefulWidget {
|
||||||
|
final int userID;
|
||||||
|
|
||||||
|
const ForezMemberProfileRoute({
|
||||||
|
Key key,
|
||||||
|
@required this.userID,
|
||||||
|
}) : assert(userID != null),
|
||||||
|
super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_ForezMemberProfileRouteState createState() =>
|
||||||
|
_ForezMemberProfileRouteState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ForezMemberProfileRouteState extends State<ForezMemberProfileRoute> {
|
||||||
|
final double _appBarHeight = 256.0;
|
||||||
|
|
||||||
|
final _key = GlobalKey<AsyncScreenWidgetState>();
|
||||||
|
|
||||||
|
AdvancedUserInfo _user;
|
||||||
|
PresenceSet _presence;
|
||||||
|
|
||||||
|
Future<void> _load() async {
|
||||||
|
_user = await ForezGroupsHelper.getMemberInfo(forezGroup.id, widget.userID);
|
||||||
|
_presence =
|
||||||
|
await ForezPresenceHelper.getForUser(forezGroup.id, widget.userID);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => AsyncScreenWidget(
|
||||||
|
onReload: _load,
|
||||||
|
onBuild: _buildProfile,
|
||||||
|
loadingWidget: _buildLoading(),
|
||||||
|
errorWidget: _buildError(),
|
||||||
|
errorMessage: tr(
|
||||||
|
"Failed to load user information, maybe it is not a Forez member yet?"));
|
||||||
|
|
||||||
|
Widget _buildLoading() => Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(tr("Loading...")),
|
||||||
|
),
|
||||||
|
body: buildCenteredProgressBar(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildError() => Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(tr("Error")),
|
||||||
|
),
|
||||||
|
body: buildErrorCard(
|
||||||
|
tr("Failed to load user information, maybe it is not a Forez member yet?"),
|
||||||
|
actions: [
|
||||||
|
MaterialButton(
|
||||||
|
onPressed: () => _key.currentState.refresh(),
|
||||||
|
child: Text(tr("Try again")),
|
||||||
|
textColor: Colors.white,
|
||||||
|
)
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildProfile() => Scaffold(
|
||||||
|
body: CustomScrollView(
|
||||||
|
slivers: <Widget>[
|
||||||
|
_buildAppBar(),
|
||||||
|
_buildList(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildAppBar() => SliverAppBar(
|
||||||
|
expandedHeight: _appBarHeight,
|
||||||
|
pinned: true,
|
||||||
|
flexibleSpace: FlexibleSpaceBar(
|
||||||
|
title: Text(_user.fullName),
|
||||||
|
background: Stack(
|
||||||
|
fit: StackFit.expand,
|
||||||
|
children: <Widget>[
|
||||||
|
_user.accountImageURL == null
|
||||||
|
? Container()
|
||||||
|
: CachedNetworkImage(
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
imageUrl: _user.accountImageURL,
|
||||||
|
height: _appBarHeight,
|
||||||
|
),
|
||||||
|
// This gradient ensures that the toolbar icons are distinct
|
||||||
|
// against the background image.
|
||||||
|
const DecoratedBox(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
begin: Alignment(0.0, -1.0),
|
||||||
|
end: Alignment(0.0, -0.4),
|
||||||
|
colors: <Color>[Color(0x60000000), Color(0x00000000)],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildList() => SliverList(
|
||||||
|
delegate: SliverChildListDelegate(<Widget>[
|
||||||
|
// Public note
|
||||||
|
!_user.hasPublicNote
|
||||||
|
? Container()
|
||||||
|
: ListTile(
|
||||||
|
leading: Icon(Icons.note),
|
||||||
|
title: TextWidget(content: DisplayedString(_user.publicNote)),
|
||||||
|
subtitle: Text(tr("Note")),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Email address
|
||||||
|
!_user.hasEmailAddress
|
||||||
|
? Container()
|
||||||
|
: ListTile(
|
||||||
|
leading: Icon(Icons.email),
|
||||||
|
title: Text(_user.emailAddress),
|
||||||
|
subtitle: Text(tr("Email address")),
|
||||||
|
trailing: CopyIcon(_user.emailAddress),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Location
|
||||||
|
!_user.hasLocation
|
||||||
|
? Container()
|
||||||
|
: ListTile(
|
||||||
|
leading: Icon(Icons.location_on),
|
||||||
|
title: Text(_user.location),
|
||||||
|
subtitle: Text(tr("Location")),
|
||||||
|
trailing: CopyIcon(_user.location),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Website
|
||||||
|
!_user.hasPersonalWebsite
|
||||||
|
? Container()
|
||||||
|
: ListTile(
|
||||||
|
leading: Icon(Icons.link),
|
||||||
|
title: Text(_user.personalWebsite),
|
||||||
|
subtitle: Text(tr("Website")),
|
||||||
|
trailing: IconButton(
|
||||||
|
icon: Icon(Icons.open_in_new),
|
||||||
|
onPressed: () => launch(_user.personalWebsite),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
Divider(),
|
||||||
|
ListTile(
|
||||||
|
leading: Icon(Icons.calendar_today),
|
||||||
|
title: Text(tr("Presence in Forez")),
|
||||||
|
subtitle: Text(_presence.containsDate(DateTime.now())
|
||||||
|
? tr("Present today")
|
||||||
|
: tr("Absent")),
|
||||||
|
),
|
||||||
|
PresenceCalendarWidget(presenceSet: _presence),
|
||||||
|
Divider(),
|
||||||
|
]));
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
import 'package:comunic/forez/helpers/forez_group_helper.dart';
|
import 'package:comunic/forez/helpers/forez_group_helper.dart';
|
||||||
|
import 'package:comunic/forez/ui/routes/forez_member_profile_route.dart';
|
||||||
import 'package:comunic/forez/ui/screens/forez_directory_screen.dart';
|
import 'package:comunic/forez/ui/screens/forez_directory_screen.dart';
|
||||||
import 'package:comunic/helpers/events_helper.dart';
|
import 'package:comunic/helpers/events_helper.dart';
|
||||||
import 'package:comunic/models/conversation.dart';
|
import 'package:comunic/models/conversation.dart';
|
||||||
@ -72,7 +73,13 @@ class _MainRouteState extends MainController {
|
|||||||
void openGroup(int groupID, {int conversationID}) => _unsupportedFeature();
|
void openGroup(int groupID, {int conversationID}) => _unsupportedFeature();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void openUserPage(int userID) => _unsupportedFeature();
|
void openUserPage(int userID) => pushPage(PageInfo(
|
||||||
|
child: ForezMemberProfileRoute(userID: userID),
|
||||||
|
hideNavBar: true,
|
||||||
|
canShowAsDialog: true,
|
||||||
|
type: PageType.USER_PAGE,
|
||||||
|
id: userID,
|
||||||
|
));
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void openFriendsList() => _unsupportedFeature();
|
void openFriendsList() => _unsupportedFeature();
|
||||||
|
@ -6,6 +6,7 @@ import 'package:comunic/lists/group_members_list.dart';
|
|||||||
import 'package:comunic/lists/users_list.dart';
|
import 'package:comunic/lists/users_list.dart';
|
||||||
import 'package:comunic/models/group_membership.dart';
|
import 'package:comunic/models/group_membership.dart';
|
||||||
import 'package:comunic/models/user.dart';
|
import 'package:comunic/models/user.dart';
|
||||||
|
import 'package:comunic/ui/routes/main_route/main_route.dart';
|
||||||
import 'package:comunic/ui/widgets/account_image_widget.dart';
|
import 'package:comunic/ui/widgets/account_image_widget.dart';
|
||||||
import 'package:comunic/ui/widgets/async_screen_widget.dart';
|
import 'package:comunic/ui/widgets/async_screen_widget.dart';
|
||||||
import 'package:comunic/utils/account_utils.dart';
|
import 'package:comunic/utils/account_utils.dart';
|
||||||
@ -123,9 +124,8 @@ class _ForezDirectoryScreenState extends State<ForezDirectoryScreen> {
|
|||||||
if (user != null) _openUserProfile(user);
|
if (user != null) _openUserProfile(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _openUserProfile(User user) {
|
void _openUserProfile(User user) =>
|
||||||
print("Open user profile ${user.fullName}");
|
MainController.of(context).openUserPage(user.id);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ForezMemberTile extends StatelessWidget {
|
class _ForezMemberTile extends StatelessWidget {
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
import 'package:comunic/helpers/groups_helper.dart';
|
import 'package:comunic/helpers/groups_helper.dart';
|
||||||
|
import 'package:comunic/helpers/users_helper.dart';
|
||||||
|
import 'package:comunic/models/advanced_user_info.dart';
|
||||||
import 'package:comunic/models/api_request.dart';
|
import 'package:comunic/models/api_request.dart';
|
||||||
import 'package:comunic/models/group.dart';
|
import 'package:comunic/models/group.dart';
|
||||||
|
|
||||||
@ -14,4 +16,16 @@ class ForezGroupsHelper {
|
|||||||
.map(GroupsHelper.getGroupFromAPI)
|
.map(GroupsHelper.getGroupFromAPI)
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get advanced information about a Forez member
|
||||||
|
///
|
||||||
|
/// This methods throws an exception in case of failure
|
||||||
|
static Future<AdvancedUserInfo> getMemberInfo(int groupID, int userID) async {
|
||||||
|
final response = await APIRequest.withLogin("forez/get_member_info")
|
||||||
|
.addInt("group", groupID)
|
||||||
|
.addInt("user", userID)
|
||||||
|
.execWithThrowGetObject();
|
||||||
|
|
||||||
|
return UsersHelper.apiToAdvancedUserInfo(response);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -151,9 +151,26 @@ class UsersHelper {
|
|||||||
throw new GetUserAdvancedUserError(cause);
|
throw new GetUserAdvancedUserError(cause);
|
||||||
}
|
}
|
||||||
|
|
||||||
final data = response.getObject();
|
return apiToAdvancedUserInfo(response.getObject());
|
||||||
|
}
|
||||||
|
|
||||||
return AdvancedUserInfo(
|
/// Parse the list of custom emojies
|
||||||
|
static CustomEmojiesList _parseCustomEmojies(List<dynamic> list) {
|
||||||
|
final l = list.cast<Map<String, dynamic>>();
|
||||||
|
|
||||||
|
return CustomEmojiesList()
|
||||||
|
..addAll(l
|
||||||
|
.map((f) => CustomEmoji(
|
||||||
|
id: f["id"],
|
||||||
|
userID: f["userID"],
|
||||||
|
shortcut: f["shortcut"],
|
||||||
|
url: f["url"],
|
||||||
|
))
|
||||||
|
.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
static AdvancedUserInfo apiToAdvancedUserInfo(Map<String, dynamic> data) =>
|
||||||
|
AdvancedUserInfo(
|
||||||
id: data["userID"],
|
id: data["userID"],
|
||||||
firstName: data["firstName"],
|
firstName: data["firstName"],
|
||||||
lastName: data["lastName"],
|
lastName: data["lastName"],
|
||||||
@ -178,19 +195,3 @@ class UsersHelper {
|
|||||||
userLike: data["user_like_page"],
|
userLike: data["user_like_page"],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse the list of custom emojies
|
|
||||||
CustomEmojiesList _parseCustomEmojies(List<dynamic> list) {
|
|
||||||
final l = list.cast<Map<String, dynamic>>();
|
|
||||||
|
|
||||||
return CustomEmojiesList()
|
|
||||||
..addAll(l
|
|
||||||
.map((f) => CustomEmoji(
|
|
||||||
id: f["id"],
|
|
||||||
userID: f["userID"],
|
|
||||||
shortcut: f["shortcut"],
|
|
||||||
url: f["url"],
|
|
||||||
))
|
|
||||||
.toList());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -60,6 +60,10 @@ class AdvancedUserInfo extends User implements LikeElement {
|
|||||||
|
|
||||||
bool get hasPersonalWebsite => personalWebsite.isNotEmpty;
|
bool get hasPersonalWebsite => personalWebsite.isNotEmpty;
|
||||||
|
|
||||||
|
bool get hasEmailAddress => emailAddress != null && emailAddress.isNotEmpty;
|
||||||
|
|
||||||
|
bool get hasLocation => location != null && location.isNotEmpty;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
LikesType get likeType => LikesType.USER;
|
LikesType get likeType => LikesType.USER;
|
||||||
}
|
}
|
||||||
|
@ -28,12 +28,24 @@ class AsyncScreenWidget extends StatefulWidget {
|
|||||||
/// Specify whether old data can be kept or not while updating this widget
|
/// Specify whether old data can be kept or not while updating this widget
|
||||||
final bool showOldDataWhileUpdating;
|
final bool showOldDataWhileUpdating;
|
||||||
|
|
||||||
|
/// Widget to use while we are refreshing
|
||||||
|
///
|
||||||
|
/// This widget is optional
|
||||||
|
final Widget loadingWidget;
|
||||||
|
|
||||||
|
/// Widget to use in case of error
|
||||||
|
///
|
||||||
|
/// This widget is optional
|
||||||
|
final Widget errorWidget;
|
||||||
|
|
||||||
const AsyncScreenWidget({
|
const AsyncScreenWidget({
|
||||||
Key key,
|
Key key,
|
||||||
@required this.onReload,
|
@required this.onReload,
|
||||||
@required this.onBuild,
|
@required this.onBuild,
|
||||||
@required this.errorMessage,
|
@required this.errorMessage,
|
||||||
this.showOldDataWhileUpdating = false,
|
this.showOldDataWhileUpdating = false,
|
||||||
|
this.loadingWidget,
|
||||||
|
this.errorWidget,
|
||||||
}) : assert(onReload != null),
|
}) : assert(onReload != null),
|
||||||
assert(onBuild != null),
|
assert(onBuild != null),
|
||||||
assert(errorMessage != null),
|
assert(errorMessage != null),
|
||||||
@ -59,7 +71,8 @@ class AsyncScreenWidgetState extends SafeState<AsyncScreenWidget> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
// In case of error
|
// In case of error
|
||||||
if (error)
|
if (error)
|
||||||
return buildErrorCard(widget.errorMessage, actions: [
|
return widget.errorWidget ??
|
||||||
|
buildErrorCard(widget.errorMessage, actions: [
|
||||||
MaterialButton(
|
MaterialButton(
|
||||||
textColor: Colors.white,
|
textColor: Colors.white,
|
||||||
onPressed: () => refresh(),
|
onPressed: () => refresh(),
|
||||||
@ -68,7 +81,7 @@ class AsyncScreenWidgetState extends SafeState<AsyncScreenWidget> {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
// Show loading states
|
// Show loading states
|
||||||
if (!ready) return buildCenteredProgressBar();
|
if (!ready) return widget.loadingWidget ?? buildCenteredProgressBar();
|
||||||
|
|
||||||
// The widget is ready, show it
|
// The widget is ready, show it
|
||||||
return RefreshIndicator(
|
return RefreshIndicator(
|
||||||
|
25
lib/ui/widgets/copy_icon.dart
Normal file
25
lib/ui/widgets/copy_icon.dart
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import 'package:clipboard/clipboard.dart';
|
||||||
|
import 'package:comunic/utils/intl_utils.dart';
|
||||||
|
import 'package:comunic/utils/ui_utils.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// Icon used to copy content in clipboard
|
||||||
|
///
|
||||||
|
/// @author Pierre Hubert
|
||||||
|
|
||||||
|
class CopyIcon extends StatelessWidget {
|
||||||
|
final String value;
|
||||||
|
|
||||||
|
const CopyIcon(this.value) : assert(value != null);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return IconButton(
|
||||||
|
icon: Icon(Icons.content_copy),
|
||||||
|
onPressed: () {
|
||||||
|
FlutterClipboard.copy(value);
|
||||||
|
snack(context, tr("'%c%' was copied to clipboard", args: {"c": value}));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user