From 9fe7c9530a7d3201b5bb6302d0215bacedf16be6 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Thu, 6 Feb 2025 13:17:17 +0800 Subject: [PATCH] :recycle: Replace duplicate widgets with account select --- api/Wallet/Create Transaction.bru | 10 +- lib/screens/chat/channel_detail.dart | 174 ++++++++------------------- lib/screens/friend.dart | 129 ++++++-------------- lib/screens/realm/realm_detail.dart | 111 +++++------------ pubspec.yaml | 2 +- 5 files changed, 117 insertions(+), 309 deletions(-) diff --git a/api/Wallet/Create Transaction.bru b/api/Wallet/Create Transaction.bru index 58128ba..8a82ee9 100644 --- a/api/Wallet/Create Transaction.bru +++ b/api/Wallet/Create Transaction.bru @@ -12,10 +12,10 @@ post { body:json { { - "client_id": "highland-mc", - "client_secret": "(3^DLAvo3v", - "remark": "秦始皇的赞赏", - "amount": 500, - "payee_id": 1 + "client_id": "alphabot", + "client_secret": "_uR0sVnHTh", + "remark": "新年红包", + "amount": 9705, + "payee_id": 2 } } diff --git a/lib/screens/chat/channel_detail.dart b/lib/screens/chat/channel_detail.dart index 2b768e8..9e8820c 100644 --- a/lib/screens/chat/channel_detail.dart +++ b/lib/screens/chat/channel_detail.dart @@ -10,8 +10,10 @@ import 'package:surface/providers/channel.dart'; import 'package:surface/providers/sn_network.dart'; import 'package:surface/providers/user_directory.dart'; import 'package:surface/providers/userinfo.dart'; +import 'package:surface/types/account.dart'; import 'package:surface/types/chat.dart'; import 'package:surface/widgets/account/account_image.dart'; +import 'package:surface/widgets/account/account_select.dart'; import 'package:surface/widgets/dialog.dart'; import 'package:surface/widgets/loading_indicator.dart'; import 'package:surface/widgets/navigation/app_scaffold.dart'; @@ -20,6 +22,7 @@ import 'package:very_good_infinite_list/very_good_infinite_list.dart'; class ChannelDetailScreen extends StatefulWidget { final String scope; final String alias; + const ChannelDetailScreen({ super.key, required this.scope, @@ -55,8 +58,7 @@ class _ChannelDetailScreenState extends State { try { final sn = context.read(); - final resp = await sn.client - .get('/cgi/im/channels/${_channel!.keyPath}/members/me'); + final resp = await sn.client.get('/cgi/im/channels/${_channel!.keyPath}/members/me'); _profile = SnChannelMember.fromJson(resp.data); _notifyLevel = _profile!.notify; if (!mounted) return; @@ -143,6 +145,25 @@ class _ChannelDetailScreenState extends State { } } + Future _addMember(SnAccount related) async { + setState(() => _isBusy = true); + + try { + final sn = context.read(); + await sn.client.post( + '/cgi/im/channels/${_channel!.keyPath}/members', + data: {'related': related.name}, + ); + if (!mounted) return; + context.showSnackbar('channelMemberAdded'.tr()); + } catch (err) { + if (!mounted) return; + context.showErrorDialog(err); + } finally { + setState(() => _isBusy = false); + } + } + void _showChannelProfileDetail() { showDialog( context: context, @@ -166,13 +187,16 @@ class _ChannelDetailScreenState extends State { ); } - void _showMemberAdd() { - showModalBottomSheet( + void _showMemberAdd() async { + final user = await showModalBottomSheet( context: context, - builder: (context) => _NewChannelMemberWidget( - channel: _channel!, + builder: (context) => AccountSelect( + title: 'channelMemberAdd'.tr(), ), ); + if (!mounted) return; + if (user == null) return; + _addMember(user); } @override @@ -221,11 +245,7 @@ class _ChannelDetailScreenState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('channelDetailPersonalRegion') - .bold() - .fontSize(17) - .tr() - .padding(horizontal: 20, bottom: 4), + Text('channelDetailPersonalRegion').bold().fontSize(17).tr().padding(horizontal: 20, bottom: 4), ListTile( leading: const Icon(Symbols.notifications), trailing: DropdownButtonHideUnderline( @@ -264,8 +284,7 @@ class _ChannelDetailScreenState extends State { ), ListTile( leading: AccountImage( - content: - ud.getAccountFromCache(_profile!.accountId)?.avatar, + content: ud.getAccountFromCache(_profile!.accountId)?.avatar, radius: 18, ), trailing: const Icon(Symbols.chevron_right), @@ -284,8 +303,7 @@ class _ChannelDetailScreenState extends State { trailing: const Icon(Symbols.chevron_right), title: Text('channelActionLeave').tr(), subtitle: Text('channelActionLeaveDescription').tr(), - contentPadding: - const EdgeInsets.symmetric(horizontal: 24), + contentPadding: const EdgeInsets.symmetric(horizontal: 24), onTap: _leaveChannel, ), ], @@ -293,11 +311,7 @@ class _ChannelDetailScreenState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('channelDetailMemberRegion') - .bold() - .fontSize(17) - .tr() - .padding(horizontal: 20, bottom: 4), + Text('channelDetailMemberRegion').bold().fontSize(17).tr().padding(horizontal: 20, bottom: 4), ListTile( leading: const Icon(Symbols.group), trailing: const Icon(Symbols.chevron_right), @@ -319,11 +333,7 @@ class _ChannelDetailScreenState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('channelDetailAdminRegion') - .bold() - .fontSize(17) - .tr() - .padding(horizontal: 20, bottom: 4), + Text('channelDetailAdminRegion').bold().fontSize(17).tr().padding(horizontal: 20, bottom: 4), ListTile( leading: const Icon(Symbols.edit), trailing: const Icon(Symbols.chevron_right), @@ -362,18 +372,17 @@ class _ChannelDetailScreenState extends State { class _ChannelProfileDetailDialog extends StatefulWidget { final SnChannel channel; final SnChannelMember current; + const _ChannelProfileDetailDialog({ required this.channel, required this.current, }); @override - State<_ChannelProfileDetailDialog> createState() => - _ChannelProfileDetailDialogState(); + State<_ChannelProfileDetailDialog> createState() => _ChannelProfileDetailDialogState(); } -class _ChannelProfileDetailDialogState - extends State<_ChannelProfileDetailDialog> { +class _ChannelProfileDetailDialogState extends State<_ChannelProfileDetailDialog> { bool _isBusy = false; final TextEditingController _nickController = TextEditingController(); @@ -444,11 +453,11 @@ class _ChannelProfileDetailDialogState class _ChannelMemberListWidget extends StatefulWidget { final SnChannel channel; + const _ChannelMemberListWidget({required this.channel}); @override - State<_ChannelMemberListWidget> createState() => - _ChannelMemberListWidgetState(); + State<_ChannelMemberListWidget> createState() => _ChannelMemberListWidgetState(); } class _ChannelMemberListWidgetState extends State<_ChannelMemberListWidget> { @@ -463,12 +472,10 @@ class _ChannelMemberListWidgetState extends State<_ChannelMemberListWidget> { try { final ud = context.read(); final sn = context.read(); - final resp = await sn.client.get( - '/cgi/im/channels/${widget.channel.keyPath}/members', - queryParameters: { - 'take': 10, - 'offset': 0, - }); + final resp = await sn.client.get('/cgi/im/channels/${widget.channel.keyPath}/members', queryParameters: { + 'take': 10, + 'offset': 0, + }); final out = List.from( resp.data['data']?.map((e) => SnChannelMember.fromJson(e)) ?? [], ); @@ -526,9 +533,7 @@ class _ChannelMemberListWidgetState extends State<_ChannelMemberListWidget> { children: [ const Icon(Symbols.group, size: 24), const Gap(16), - Text('channelMemberManage') - .tr() - .textStyle(Theme.of(context).textTheme.titleLarge!), + Text('channelMemberManage').tr().textStyle(Theme.of(context).textTheme.titleLarge!), ], ).padding(horizontal: 20, top: 16, bottom: 12), Expanded( @@ -539,8 +544,7 @@ class _ChannelMemberListWidgetState extends State<_ChannelMemberListWidget> { }, child: InfiniteList( itemCount: _members.length, - hasReachedMax: - _totalCount != null && _members.length >= _totalCount!, + hasReachedMax: _totalCount != null && _members.length >= _totalCount!, isLoading: _isBusy, onFetchData: _fetchMembers, itemBuilder: (context, index) { @@ -551,8 +555,7 @@ class _ChannelMemberListWidgetState extends State<_ChannelMemberListWidget> { content: ud.getAccountFromCache(member.accountId)?.avatar, ), title: Text( - ud.getAccountFromCache(member.accountId)?.name ?? - 'unknown'.tr(), + ud.getAccountFromCache(member.accountId)?.name ?? 'unknown'.tr(), ), subtitle: Text(member.nick ?? 'unknown'.tr()), trailing: SizedBox( @@ -562,8 +565,7 @@ class _ChannelMemberListWidgetState extends State<_ChannelMemberListWidget> { mainAxisAlignment: MainAxisAlignment.end, children: [ IconButton( - onPressed: - _isUpdating ? null : () => _deleteMember(member), + onPressed: _isUpdating ? null : () => _deleteMember(member), icon: const Icon(Symbols.person_remove), ), ], @@ -578,83 +580,3 @@ class _ChannelMemberListWidgetState extends State<_ChannelMemberListWidget> { ); } } - -class _NewChannelMemberWidget extends StatefulWidget { - final SnChannel channel; - const _NewChannelMemberWidget({required this.channel}); - - @override - State<_NewChannelMemberWidget> createState() => - _NewChannelMemberWidgetState(); -} - -class _NewChannelMemberWidgetState extends State<_NewChannelMemberWidget> { - bool _isBusy = false; - - final TextEditingController _relatedController = TextEditingController(); - - Future _performAction() async { - if (_relatedController.text.isEmpty) return; - - setState(() => _isBusy = true); - - try { - final sn = context.read(); - await sn.client.post( - '/cgi/im/channels/${widget.channel.keyPath}/members', - data: { - 'related': _relatedController.text, - }, - ); - if (!mounted) return; - Navigator.pop(context, true); - context.showSnackbar('channelMemberAdded'.tr()); - } catch (err) { - if (!mounted) return; - context.showErrorDialog(err); - } finally { - setState(() => _isBusy = false); - } - } - - @override - void dispose() { - super.dispose(); - _relatedController.dispose(); - } - - @override - Widget build(BuildContext context) { - return StyledWidget(Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'channelMemberAdd', - style: Theme.of(context).textTheme.titleLarge, - ).tr(), - const Gap(12), - TextField( - controller: _relatedController, - readOnly: _isBusy, - autocorrect: false, - autofocus: true, - textCapitalization: TextCapitalization.none, - decoration: InputDecoration( - labelText: 'fieldMemberRelatedName'.tr(), - suffix: SizedBox( - height: 24, - child: IconButton( - onPressed: _isBusy ? null : () => _performAction(), - icon: Icon(Symbols.send), - visualDensity: - const VisualDensity(horizontal: -4, vertical: -4), - padding: EdgeInsets.zero, - ), - ), - ), - onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), - ) - ], - )).padding(all: 24); - } -} diff --git a/lib/screens/friend.dart b/lib/screens/friend.dart index 3447dce..912bfa1 100644 --- a/lib/screens/friend.dart +++ b/lib/screens/friend.dart @@ -6,15 +6,15 @@ import 'package:provider/provider.dart'; import 'package:styled_widget/styled_widget.dart'; import 'package:surface/providers/relationship.dart'; import 'package:surface/providers/sn_network.dart'; +import 'package:surface/providers/userinfo.dart'; import 'package:surface/types/account.dart'; import 'package:surface/widgets/account/account_image.dart'; +import 'package:surface/widgets/account/account_select.dart'; import 'package:surface/widgets/app_bar_leading.dart'; import 'package:surface/widgets/dialog.dart'; import 'package:surface/widgets/loading_indicator.dart'; import 'package:surface/widgets/navigation/app_scaffold.dart'; - -import '../providers/userinfo.dart'; -import '../widgets/unauthorized_hint.dart'; +import 'package:surface/widgets/unauthorized_hint.dart'; const kFriendStatus = { 0: 'friendStatusPending', @@ -168,6 +168,24 @@ class _FriendScreenState extends State { }); } + Future _sendRequest(SnAccount user) async { + setState(() => _isBusy = true); + + try { + final sn = context.read(); + await sn.client.post('/cgi/id/users/me/relations', data: { + 'related': user.name, + }); + if (!mounted) return; + context.showSnackbar('friendRequestSent'.tr()); + } catch (err) { + if (!mounted) return; + context.showErrorDialog(err); + } finally { + setState(() => _isBusy = false); + } + } + @override void initState() { super.initState(); @@ -199,11 +217,16 @@ class _FriendScreenState extends State { ), floatingActionButton: FloatingActionButton( child: const Icon(Symbols.add), - onPressed: () { - showModalBottomSheet( + onPressed: () async { + final user = await showModalBottomSheet( context: context, - builder: (context) => _NewFriendWidget(), + builder: (context) => AccountSelect( + title: 'friendNew'.tr(), + ), ); + if (!mounted) return; + if (user == null) return; + _sendRequest(user); }, ), body: Column( @@ -231,8 +254,7 @@ class _FriendScreenState extends State { trailing: const Icon(Symbols.chevron_right), onTap: _showBlocks, ), - if (_requests.isNotEmpty || _blocks.isNotEmpty) - const Divider(height: 1), + if (_requests.isNotEmpty || _blocks.isNotEmpty) const Divider(height: 1), Expanded( child: MediaQuery.removePadding( context: context, @@ -264,16 +286,12 @@ class _FriendScreenState extends State { mainAxisAlignment: MainAxisAlignment.end, children: [ InkWell( - onTap: _isUpdating - ? null - : () => _changeRelation(relation, 2), + onTap: _isUpdating ? null : () => _changeRelation(relation, 2), child: Text('friendBlock').tr(), ), const Gap(8), InkWell( - onTap: _isUpdating - ? null - : () => _deleteRelation(relation), + onTap: _isUpdating ? null : () => _deleteRelation(relation), child: Text('friendDeleteAction').tr(), ), ], @@ -293,83 +311,9 @@ class _FriendScreenState extends State { } } -class _NewFriendWidget extends StatefulWidget { - const _NewFriendWidget(); - - @override - State<_NewFriendWidget> createState() => _NewFriendWidgetState(); -} - -class _NewFriendWidgetState extends State<_NewFriendWidget> { - bool _isBusy = false; - - final TextEditingController _relatedController = TextEditingController(); - - Future _sendRequest() async { - if (_relatedController.text.isEmpty) return; - - setState(() => _isBusy = true); - - try { - final sn = context.read(); - await sn.client.post('/cgi/id/users/me/relations', data: { - 'related': _relatedController.text, - }); - if (!mounted) return; - Navigator.pop(context, true); - context.showSnackbar('friendRequestSent'.tr()); - } catch (err) { - if (!mounted) return; - context.showErrorDialog(err); - } finally { - setState(() => _isBusy = false); - } - } - - @override - void dispose() { - super.dispose(); - _relatedController.dispose(); - } - - @override - Widget build(BuildContext context) { - return StyledWidget(Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'friendNew', - style: Theme.of(context).textTheme.titleLarge, - ).tr(), - const Gap(12), - TextField( - controller: _relatedController, - readOnly: _isBusy, - autocorrect: false, - autofocus: true, - textCapitalization: TextCapitalization.none, - decoration: InputDecoration( - labelText: 'fieldFriendRelatedName'.tr(), - suffix: SizedBox( - height: 24, - child: IconButton( - onPressed: _isBusy ? null : () => _sendRequest(), - icon: Icon(Symbols.send), - visualDensity: - const VisualDensity(horizontal: -4, vertical: -4), - padding: EdgeInsets.zero, - ), - ), - ), - onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), - ) - ], - )).padding(all: 24); - } -} - class _FriendshipListWidget extends StatefulWidget { final List relations; + const _FriendshipListWidget({required this.relations}); @override @@ -476,9 +420,7 @@ class _FriendshipListWidgetState extends State<_FriendshipListWidget> { mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.end, children: [ - Text(kFriendStatus[relation.status] ?? 'unknown') - .tr() - .opacity(0.75), + Text(kFriendStatus[relation.status] ?? 'unknown').tr().opacity(0.75), if (relation.status == 0) Row( mainAxisAlignment: MainAxisAlignment.end, @@ -499,8 +441,7 @@ class _FriendshipListWidgetState extends State<_FriendshipListWidget> { mainAxisAlignment: MainAxisAlignment.end, children: [ InkWell( - onTap: - _isBusy ? null : () => _changeRelation(relation, 1), + onTap: _isBusy ? null : () => _changeRelation(relation, 1), child: Text('friendUnblock').tr(), ), const Gap(8), diff --git a/lib/screens/realm/realm_detail.dart b/lib/screens/realm/realm_detail.dart index 92e699d..102618a 100644 --- a/lib/screens/realm/realm_detail.dart +++ b/lib/screens/realm/realm_detail.dart @@ -8,9 +8,11 @@ import 'package:styled_widget/styled_widget.dart'; import 'package:surface/providers/sn_network.dart'; import 'package:surface/providers/user_directory.dart'; import 'package:surface/providers/userinfo.dart'; +import 'package:surface/types/account.dart'; import 'package:surface/types/post.dart'; import 'package:surface/types/realm.dart'; import 'package:surface/widgets/account/account_image.dart'; +import 'package:surface/widgets/account/account_select.dart'; import 'package:surface/widgets/dialog.dart'; import 'package:surface/widgets/navigation/app_scaffold.dart'; import 'package:very_good_infinite_list/very_good_infinite_list.dart'; @@ -229,13 +231,35 @@ class _RealmMemberListWidgetState extends State<_RealmMemberListWidget> { } } - void _showMemberAdd() { - showModalBottomSheet( + Future _addMember(SnAccount related) async { + setState(() => _isBusy = true); + + try { + final sn = context.read(); + await sn.client.post( + '/cgi/id/realms/${widget.realm!.alias}/members', + data: {'related': related.name}, + ); + if (!mounted) return; + context.showSnackbar('realmMemberAdded'.tr()); + } catch (err) { + if (!mounted) return; + context.showErrorDialog(err); + } finally { + setState(() => _isBusy = false); + } + } + + void _showMemberAdd() async { + final user = await showModalBottomSheet( context: context, - builder: (context) => _NewRealmMemberWidget( - realm: widget.realm!, + builder: (context) => AccountSelect( + title: 'realmMemberAdd'.tr(), ), ); + if (!mounted) return; + if (user == null) return; + _addMember(user); } @override @@ -293,85 +317,6 @@ class _RealmMemberListWidgetState extends State<_RealmMemberListWidget> { } } -class _NewRealmMemberWidget extends StatefulWidget { - final SnRealm realm; - - const _NewRealmMemberWidget({required this.realm}); - - @override - State<_NewRealmMemberWidget> createState() => _NewRealmMemberWidgetState(); -} - -class _NewRealmMemberWidgetState extends State<_NewRealmMemberWidget> { - bool _isBusy = false; - - final TextEditingController _relatedController = TextEditingController(); - - Future _performAction() async { - if (_relatedController.text.isEmpty) return; - - setState(() => _isBusy = true); - - try { - final sn = context.read(); - await sn.client.post( - '/cgi/id/realms/${widget.realm.alias}/members', - data: { - 'related': _relatedController.text, - }, - ); - if (!mounted) return; - Navigator.pop(context, true); - context.showSnackbar('channelMemberAdded'.tr()); - } catch (err) { - if (!mounted) return; - context.showErrorDialog(err); - } finally { - setState(() => _isBusy = false); - } - } - - @override - void dispose() { - super.dispose(); - _relatedController.dispose(); - } - - @override - Widget build(BuildContext context) { - return StyledWidget(Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'realmMemberAdd', - style: Theme.of(context).textTheme.titleLarge, - ).tr(), - const Gap(12), - TextField( - controller: _relatedController, - readOnly: _isBusy, - autocorrect: false, - autofocus: true, - textCapitalization: TextCapitalization.none, - decoration: InputDecoration( - labelText: 'fieldMemberRelatedName'.tr(), - suffix: SizedBox( - height: 24, - child: IconButton( - onPressed: _isBusy ? null : () => _performAction(), - icon: Icon(Symbols.send), - visualDensity: const VisualDensity(horizontal: -4, vertical: -4), - padding: EdgeInsets.zero, - ), - ), - ), - onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), - ) - ], - )).padding(all: 24); - } -} - class _RealmSettingsWidget extends StatefulWidget { final SnRealm? realm; final Function() onUpdate; diff --git a/pubspec.yaml b/pubspec.yaml index 75242d7..dffdda3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 2.2.2+62 +version: 2.3.2+63 environment: sdk: ^3.5.4