feat: Implement nice profile bottom sheet
This commit is contained in:
parent
dc1f1e6623
commit
fb4337272e
|
@ -9,18 +9,15 @@ import 'package:future_loading_dialog/future_loading_dialog.dart';
|
||||||
import 'package:vrouter/vrouter.dart';
|
import 'package:vrouter/vrouter.dart';
|
||||||
|
|
||||||
import 'views/user_bottom_sheet_view.dart';
|
import 'views/user_bottom_sheet_view.dart';
|
||||||
import '../widgets/matrix.dart';
|
|
||||||
|
|
||||||
class UserBottomSheet extends StatefulWidget {
|
class UserBottomSheet extends StatefulWidget {
|
||||||
final User user;
|
final User user;
|
||||||
final Profile profile;
|
|
||||||
final Function onMention;
|
final Function onMention;
|
||||||
final BuildContext outerContext;
|
final BuildContext outerContext;
|
||||||
|
|
||||||
const UserBottomSheet({
|
const UserBottomSheet({
|
||||||
Key key,
|
Key key,
|
||||||
this.user,
|
@required this.user,
|
||||||
this.profile,
|
|
||||||
@required this.outerContext,
|
@required this.outerContext,
|
||||||
this.onMention,
|
this.onMention,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
@ -86,7 +83,6 @@ class UserBottomSheetController extends State<UserBottomSheet> {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'message':
|
case 'message':
|
||||||
if (widget.user != null) {
|
|
||||||
final roomIdResult = await showFutureLoadingDialog(
|
final roomIdResult = await showFutureLoadingDialog(
|
||||||
context: context,
|
context: context,
|
||||||
future: () => widget.user.startDirectChat(),
|
future: () => widget.user.startDirectChat(),
|
||||||
|
@ -95,19 +91,6 @@ class UserBottomSheetController extends State<UserBottomSheet> {
|
||||||
VRouter.of(widget.outerContext)
|
VRouter.of(widget.outerContext)
|
||||||
.toSegments(['rooms', roomIdResult.result]);
|
.toSegments(['rooms', roomIdResult.result]);
|
||||||
Navigator.of(context, rootNavigator: false).pop();
|
Navigator.of(context, rootNavigator: false).pop();
|
||||||
} else {
|
|
||||||
final result = await showFutureLoadingDialog<String>(
|
|
||||||
context: context,
|
|
||||||
future: () => Matrix.of(context).client.startDirectChat(
|
|
||||||
widget.profile.userId,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
if (result.error == null) {
|
|
||||||
VRouter.of(context).toSegments(['rooms', result.result]);
|
|
||||||
Navigator.of(context, rootNavigator: false).pop();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,12 +18,8 @@ class UserBottomSheetView extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final user = controller.widget.user;
|
final user = controller.widget.user;
|
||||||
final client = user?.room?.client ?? Matrix.of(context).client;
|
final client = Matrix.of(context).client;
|
||||||
final mxid = user?.id ?? controller.widget.profile?.userId ?? '';
|
final presence = client.presences[user.id];
|
||||||
final presence = client.presences[mxid];
|
|
||||||
final displayname =
|
|
||||||
user?.calcDisplayname() ?? controller.widget.profile?.displayName ?? '';
|
|
||||||
final avatarUrl = user?.avatarUrl ?? controller.widget.profile?.avatarUrl;
|
|
||||||
return Center(
|
return Center(
|
||||||
child: Container(
|
child: Container(
|
||||||
width: min(
|
width: min(
|
||||||
|
@ -42,12 +38,12 @@ class UserBottomSheetView extends StatelessWidget {
|
||||||
onPressed: Navigator.of(context, rootNavigator: false).pop,
|
onPressed: Navigator.of(context, rootNavigator: false).pop,
|
||||||
tooltip: L10n.of(context).close,
|
tooltip: L10n.of(context).close,
|
||||||
),
|
),
|
||||||
title: Text(displayname),
|
title: Text(user.calcDisplayname()),
|
||||||
actions: [
|
actions: [
|
||||||
if (mxid != client.userID)
|
if (user.id != client.userID)
|
||||||
PopupMenuButton(
|
PopupMenuButton(
|
||||||
itemBuilder: (_) => [
|
itemBuilder: (_) => [
|
||||||
if (user != null && controller.widget.onMention != null)
|
if (controller.widget.onMention != null)
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
value: 'mention',
|
value: 'mention',
|
||||||
child: _TextWithIcon(
|
child: _TextWithIcon(
|
||||||
|
@ -55,8 +51,7 @@ class UserBottomSheetView extends StatelessWidget {
|
||||||
Icons.alternate_email_outlined,
|
Icons.alternate_email_outlined,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (mxid != client.userID &&
|
if (user.id != client.userID && !user.room.isDirectChat)
|
||||||
(user == null || !user.room.isDirectChat))
|
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
value: 'message',
|
value: 'message',
|
||||||
child: _TextWithIcon(
|
child: _TextWithIcon(
|
||||||
|
@ -64,7 +59,7 @@ class UserBottomSheetView extends StatelessWidget {
|
||||||
Icons.send_outlined,
|
Icons.send_outlined,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (user != null && user.canChangePowerLevel)
|
if (user.canChangePowerLevel)
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
value: 'permission',
|
value: 'permission',
|
||||||
child: _TextWithIcon(
|
child: _TextWithIcon(
|
||||||
|
@ -72,7 +67,7 @@ class UserBottomSheetView extends StatelessWidget {
|
||||||
Icons.edit_attributes_outlined,
|
Icons.edit_attributes_outlined,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (user != null && user.canKick)
|
if (user.canKick)
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
value: 'kick',
|
value: 'kick',
|
||||||
child: _TextWithIcon(
|
child: _TextWithIcon(
|
||||||
|
@ -80,9 +75,7 @@ class UserBottomSheetView extends StatelessWidget {
|
||||||
Icons.exit_to_app_outlined,
|
Icons.exit_to_app_outlined,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (user != null &&
|
if (user.canBan && user.membership != Membership.ban)
|
||||||
user.canBan &&
|
|
||||||
user.membership != Membership.ban)
|
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
value: 'ban',
|
value: 'ban',
|
||||||
child: _TextWithIcon(
|
child: _TextWithIcon(
|
||||||
|
@ -90,8 +83,7 @@ class UserBottomSheetView extends StatelessWidget {
|
||||||
Icons.warning_sharp,
|
Icons.warning_sharp,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
else if (user != null &&
|
else if (user.canBan &&
|
||||||
user.canBan &&
|
|
||||||
user.membership == Membership.ban)
|
user.membership == Membership.ban)
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
value: 'unban',
|
value: 'unban',
|
||||||
|
@ -109,17 +101,17 @@ class UserBottomSheetView extends StatelessWidget {
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ContentBanner(
|
child: ContentBanner(
|
||||||
avatarUrl,
|
user.avatarUrl,
|
||||||
defaultIcon: Icons.person_outline,
|
defaultIcon: Icons.person_outline,
|
||||||
client: client,
|
client: client,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(L10n.of(context).username),
|
title: Text(L10n.of(context).username),
|
||||||
subtitle: Text(mxid),
|
subtitle: Text(user.id),
|
||||||
trailing: Icon(Icons.share_outlined),
|
trailing: Icon(Icons.share_outlined),
|
||||||
onTap: () =>
|
onTap: () => FluffyShare.share(
|
||||||
FluffyShare.share(mxid, controller.widget.outerContext),
|
user.id, controller.widget.outerContext),
|
||||||
),
|
),
|
||||||
if (presence != null)
|
if (presence != null)
|
||||||
ListTile(
|
ListTile(
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
|
import 'package:adaptive_dialog/adaptive_dialog.dart';
|
||||||
|
import 'package:fluffychat/widgets/profile_bottom_sheet.dart';
|
||||||
|
|
||||||
import 'package:matrix/matrix.dart';
|
import 'package:matrix/matrix.dart';
|
||||||
import 'package:future_loading_dialog/future_loading_dialog.dart';
|
import 'package:future_loading_dialog/future_loading_dialog.dart';
|
||||||
|
@ -11,7 +12,6 @@ import 'package:punycode/punycode.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||||
|
|
||||||
import 'platform_infos.dart';
|
import 'platform_infos.dart';
|
||||||
import '../pages/user_bottom_sheet.dart';
|
|
||||||
|
|
||||||
class UrlLauncher {
|
class UrlLauncher {
|
||||||
final String url;
|
final String url;
|
||||||
|
@ -83,7 +83,7 @@ class UrlLauncher {
|
||||||
launch(uri.replace(host: newHost).toString());
|
launch(uri.replace(host: newHost).toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
void openMatrixToUrl() async {
|
void openMatrixToUrl([bool startDirectChat = false]) async {
|
||||||
final matrix = Matrix.of(context);
|
final matrix = Matrix.of(context);
|
||||||
// The identifier might be a matrix.to url and needs escaping. Or, it might have multiple
|
// The identifier might be a matrix.to url and needs escaping. Or, it might have multiple
|
||||||
// identifiers (room id & event id), or it might also have a query part.
|
// identifiers (room id & event id), or it might also have a query part.
|
||||||
|
@ -167,12 +167,10 @@ class UrlLauncher {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if (identityParts.primaryIdentifier.sigil == '@') {
|
} else if (identityParts.primaryIdentifier.sigil == '@') {
|
||||||
final profile = await matrix.client
|
|
||||||
.getProfileFromUserId(identityParts.primaryIdentifier);
|
|
||||||
await showModalBottomSheet(
|
await showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (c) => UserBottomSheet(
|
builder: (c) => ProfileBottomSheet(
|
||||||
profile: profile,
|
userId: identityParts.primaryIdentifier,
|
||||||
outerContext: context,
|
outerContext: context,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,101 @@
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:fluffychat/config/themes.dart';
|
||||||
|
import 'package:fluffychat/widgets/matrix.dart';
|
||||||
|
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||||
|
import 'package:fluffychat/widgets/content_banner.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:future_loading_dialog/future_loading_dialog.dart';
|
||||||
|
import 'package:matrix/matrix.dart';
|
||||||
|
import 'package:vrouter/vrouter.dart';
|
||||||
|
import '../utils/localized_exception_extension.dart';
|
||||||
|
|
||||||
|
class ProfileBottomSheet extends StatelessWidget {
|
||||||
|
final String userId;
|
||||||
|
final BuildContext outerContext;
|
||||||
|
const ProfileBottomSheet({
|
||||||
|
@required this.userId,
|
||||||
|
@required this.outerContext,
|
||||||
|
Key key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
void _startDirectChat(BuildContext context) async {
|
||||||
|
final result = await showFutureLoadingDialog<String>(
|
||||||
|
context: context,
|
||||||
|
future: () => Matrix.of(context).client.startDirectChat(userId),
|
||||||
|
);
|
||||||
|
if (result.error == null) {
|
||||||
|
VRouter.of(context).toSegments(['rooms', result.result]);
|
||||||
|
Navigator.of(context, rootNavigator: false).pop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Center(
|
||||||
|
child: Container(
|
||||||
|
width: min(
|
||||||
|
MediaQuery.of(context).size.width, FluffyThemes.columnWidth * 1.5),
|
||||||
|
child: Material(
|
||||||
|
elevation: 4,
|
||||||
|
child: SafeArea(
|
||||||
|
child: Scaffold(
|
||||||
|
extendBodyBehindAppBar: true,
|
||||||
|
appBar: AppBar(
|
||||||
|
elevation: 0,
|
||||||
|
backgroundColor:
|
||||||
|
Theme.of(context).scaffoldBackgroundColor.withOpacity(0.5),
|
||||||
|
leading: IconButton(
|
||||||
|
icon: Icon(Icons.arrow_downward_outlined),
|
||||||
|
onPressed: Navigator.of(context, rootNavigator: false).pop,
|
||||||
|
tooltip: L10n.of(context).close,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
body: FutureBuilder<Profile>(
|
||||||
|
future:
|
||||||
|
Matrix.of(context).client.getProfileFromUserId(userId),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
final profile = snapshot.data;
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: profile == null
|
||||||
|
? Container(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
color: Theme.of(context).secondaryHeaderColor,
|
||||||
|
child: snapshot.hasError
|
||||||
|
? Text(snapshot.error
|
||||||
|
.toLocalizedString(context))
|
||||||
|
: CircularProgressIndicator(),
|
||||||
|
)
|
||||||
|
: ContentBanner(
|
||||||
|
profile.avatarUrl,
|
||||||
|
defaultIcon: Icons.person_outline,
|
||||||
|
client: Matrix.of(context).client,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
title: Text(profile?.displayName ?? userId.localpart),
|
||||||
|
subtitle: Text(userId),
|
||||||
|
trailing: Icon(Icons.account_box_outlined),
|
||||||
|
),
|
||||||
|
Center(
|
||||||
|
child: FloatingActionButton.extended(
|
||||||
|
onPressed: () => _startDirectChat(context),
|
||||||
|
label: Text(L10n.of(context).newChat),
|
||||||
|
icon: Icon(Icons.send_outlined),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 8),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue