From a02831644c61bf1912d69b328afdfff5443397d3 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Fri, 26 Apr 2024 21:38:31 +0800 Subject: [PATCH] :sparkles: Channel invite friends --- lib/models/friendship.dart | 8 +++ lib/screens/account/friend.dart | 19 ++----- lib/screens/chat/channel/member.dart | 52 +++++++++++++++++++ lib/widgets/account/friend_picker.dart | 65 ++++++++++++++++++++++++ lib/widgets/posts/attachment_editor.dart | 2 +- 5 files changed, 131 insertions(+), 15 deletions(-) create mode 100644 lib/widgets/account/friend_picker.dart diff --git a/lib/models/friendship.dart b/lib/models/friendship.dart index 56ebc34..c42d418 100644 --- a/lib/models/friendship.dart +++ b/lib/models/friendship.dart @@ -50,4 +50,12 @@ class Friendship { "related": related.toJson(), "status": status, }; + + Account getOtherside(int selfId) { + if (accountId != selfId) { + return account; + } else { + return related; + } + } } \ No newline at end of file diff --git a/lib/screens/account/friend.dart b/lib/screens/account/friend.dart index 04735c6..aee9523 100644 --- a/lib/screens/account/friend.dart +++ b/lib/screens/account/friend.dart @@ -3,7 +3,6 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter_animate/flutter_animate.dart'; import 'package:provider/provider.dart'; -import 'package:solian/models/account.dart'; import 'package:solian/models/friendship.dart'; import 'package:solian/providers/auth.dart'; import 'package:solian/utils/service_url.dart'; @@ -21,7 +20,7 @@ class FriendScreen extends StatefulWidget { class _FriendScreenState extends State { bool _isSubmitting = false; - int _currentSideId = 0; + int _selfId = 0; List _friendships = List.empty(); Future fetchFriendships() async { @@ -29,7 +28,7 @@ class _FriendScreenState extends State { final prof = await auth.getProfiles(); if (!await auth.isAuthorized()) return; - _currentSideId = prof['id']; + _selfId = prof['id']; var uri = getRequestUri('passport', '/api/users/me/friends'); @@ -77,7 +76,7 @@ class _FriendScreenState extends State { Future updateFriendship(Friendship relation, int status) async { setState(() => _isSubmitting = true); - final otherside = getOtherside(relation); + final otherside = relation.getOtherside(_selfId); final auth = context.read(); if (!await auth.isAuthorized()) { @@ -161,18 +160,10 @@ class _FriendScreenState extends State { DismissDirection getDismissDirection(Friendship relation) { if (relation.status == 2) return DismissDirection.endToStart; if (relation.status == 1) return DismissDirection.startToEnd; - if (relation.status == 0 && relation.relatedId != _currentSideId) return DismissDirection.startToEnd; + if (relation.status == 0 && relation.relatedId != _selfId) return DismissDirection.startToEnd; return DismissDirection.horizontal; } - Account getOtherside(Friendship relation) { - if (relation.accountId != _currentSideId) { - return relation.account; - } else { - return relation.related; - } - } - @override void initState() { super.initState(); @@ -186,7 +177,7 @@ class _FriendScreenState extends State { Widget build(BuildContext context) { Widget friendshipTileBuilder(context, index, status) { final element = filterWithStatus(status)[index]; - final otherside = getOtherside(element); + final otherside = element.getOtherside(_selfId); final randomId = DateTime.now().microsecondsSinceEpoch >> 10; diff --git a/lib/screens/chat/channel/member.dart b/lib/screens/chat/channel/member.dart index c46cee7..fc6c07a 100644 --- a/lib/screens/chat/channel/member.dart +++ b/lib/screens/chat/channel/member.dart @@ -3,10 +3,12 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter_animate/flutter_animate.dart'; import 'package:provider/provider.dart'; +import 'package:solian/models/account.dart'; import 'package:solian/models/channel.dart'; import 'package:solian/providers/auth.dart'; import 'package:solian/utils/service_url.dart'; import 'package:solian/widgets/account/avatar.dart'; +import 'package:solian/widgets/account/friend_picker.dart'; import 'package:solian/widgets/indent_wrapper.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -81,6 +83,50 @@ class _ChatMemberScreenState extends State { setState(() => _isSubmitting = false); } + Future inviteMember(String username) async { + setState(() => _isSubmitting = true); + + final auth = context.read(); + if (!await auth.isAuthorized()) { + setState(() => _isSubmitting = false); + return; + } + + var uri = getRequestUri('messaging', '/api/channels/${widget.channel.alias}/invite'); + + var res = await auth.client!.post( + uri, + headers: { + 'Content-Type': 'application/json', + }, + body: jsonEncode({ + 'account_name': username, + }), + ); + if (res.statusCode == 200) { + await fetchMemberships(); + } else { + var message = utf8.decode(res.bodyBytes); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text("Something went wrong... $message")), + ); + } + + setState(() => _isSubmitting = false); + } + + void promptInviteMember() async { + final input = await showModalBottomSheet( + context: context, + builder: (context) { + return const FriendPicker(); + }, + ); + if (input == null) return; + + await inviteMember((input as Account).name); + } + bool getKickable(ChannelMember item) { if (_selfId != widget.channel.account.externalId) return false; if (item.accountId == widget.channel.accountId) return false; @@ -101,6 +147,12 @@ class _ChatMemberScreenState extends State { title: AppLocalizations.of(context)!.chatMember, noSafeArea: true, hideDrawer: true, + appBarActions: [ + IconButton( + icon: const Icon(Icons.add), + onPressed: () => promptInviteMember(), + ), + ], child: RefreshIndicator( onRefresh: () => fetchMemberships(), child: CustomScrollView( diff --git a/lib/widgets/account/friend_picker.dart b/lib/widgets/account/friend_picker.dart new file mode 100644 index 0000000..5deda28 --- /dev/null +++ b/lib/widgets/account/friend_picker.dart @@ -0,0 +1,65 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:solian/providers/auth.dart'; +import 'package:solian/providers/friend.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:solian/widgets/account/avatar.dart'; + +class FriendPicker extends StatefulWidget { + const FriendPicker({super.key}); + + @override + State createState() => _FriendPickerState(); +} + +class _FriendPickerState extends State { + int _selfId = 0; + + @override + void initState() { + super.initState(); + + Future.delayed(Duration.zero, () async { + final auth = context.read(); + final friends = context.read(); + friends.fetch(auth); + + final prof = await auth.getProfiles(); + _selfId = prof['id']; + }); + } + + @override + Widget build(BuildContext context) { + final dict = context.watch(); + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: const EdgeInsets.only(left: 16, right: 16, top: 32, bottom: 12), + child: Text( + AppLocalizations.of(context)!.friend, + style: Theme.of(context).textTheme.headlineSmall, + ), + ), + Expanded( + child: ListView.builder( + itemCount: dict.friends.length, + itemBuilder: (context, index) { + var element = dict.friends[index].getOtherside(_selfId); + return ListTile( + title: Text(element.nick), + subtitle: Text(element.name), + leading: AccountAvatar(source: element.avatar), + onTap: () { + Navigator.pop(context, element); + }, + ); + }, + ), + ), + ], + ); + } +} diff --git a/lib/widgets/posts/attachment_editor.dart b/lib/widgets/posts/attachment_editor.dart index 5df5733..c3b565a 100755 --- a/lib/widgets/posts/attachment_editor.dart +++ b/lib/widgets/posts/attachment_editor.dart @@ -254,7 +254,7 @@ class _AttachmentEditorState extends State { ), ); }, - separatorBuilder: (context, index) => const Divider(), + separatorBuilder: (context, index) => const Divider(thickness: 0.3), ), ), ],