♻️ Replace duplicate widgets with account select
This commit is contained in:
parent
52f1826e91
commit
9fe7c9530a
@ -12,10 +12,10 @@ post {
|
|||||||
|
|
||||||
body:json {
|
body:json {
|
||||||
{
|
{
|
||||||
"client_id": "highland-mc",
|
"client_id": "alphabot",
|
||||||
"client_secret": "(3^DLAvo3v",
|
"client_secret": "_uR0sVnHTh",
|
||||||
"remark": "秦始皇的赞赏",
|
"remark": "新年红包",
|
||||||
"amount": 500,
|
"amount": 9705,
|
||||||
"payee_id": 1
|
"payee_id": 2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,8 +10,10 @@ import 'package:surface/providers/channel.dart';
|
|||||||
import 'package:surface/providers/sn_network.dart';
|
import 'package:surface/providers/sn_network.dart';
|
||||||
import 'package:surface/providers/user_directory.dart';
|
import 'package:surface/providers/user_directory.dart';
|
||||||
import 'package:surface/providers/userinfo.dart';
|
import 'package:surface/providers/userinfo.dart';
|
||||||
|
import 'package:surface/types/account.dart';
|
||||||
import 'package:surface/types/chat.dart';
|
import 'package:surface/types/chat.dart';
|
||||||
import 'package:surface/widgets/account/account_image.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/dialog.dart';
|
||||||
import 'package:surface/widgets/loading_indicator.dart';
|
import 'package:surface/widgets/loading_indicator.dart';
|
||||||
import 'package:surface/widgets/navigation/app_scaffold.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 {
|
class ChannelDetailScreen extends StatefulWidget {
|
||||||
final String scope;
|
final String scope;
|
||||||
final String alias;
|
final String alias;
|
||||||
|
|
||||||
const ChannelDetailScreen({
|
const ChannelDetailScreen({
|
||||||
super.key,
|
super.key,
|
||||||
required this.scope,
|
required this.scope,
|
||||||
@ -55,8 +58,7 @@ class _ChannelDetailScreenState extends State<ChannelDetailScreen> {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
final resp = await sn.client
|
final resp = await sn.client.get('/cgi/im/channels/${_channel!.keyPath}/members/me');
|
||||||
.get('/cgi/im/channels/${_channel!.keyPath}/members/me');
|
|
||||||
_profile = SnChannelMember.fromJson(resp.data);
|
_profile = SnChannelMember.fromJson(resp.data);
|
||||||
_notifyLevel = _profile!.notify;
|
_notifyLevel = _profile!.notify;
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
@ -143,6 +145,25 @@ class _ChannelDetailScreenState extends State<ChannelDetailScreen> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _addMember(SnAccount related) async {
|
||||||
|
setState(() => _isBusy = true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
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() {
|
void _showChannelProfileDetail() {
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
@ -166,13 +187,16 @@ class _ChannelDetailScreenState extends State<ChannelDetailScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showMemberAdd() {
|
void _showMemberAdd() async {
|
||||||
showModalBottomSheet(
|
final user = await showModalBottomSheet<SnAccount?>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => _NewChannelMemberWidget(
|
builder: (context) => AccountSelect(
|
||||||
channel: _channel!,
|
title: 'channelMemberAdd'.tr(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
if (!mounted) return;
|
||||||
|
if (user == null) return;
|
||||||
|
_addMember(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -221,11 +245,7 @@ class _ChannelDetailScreenState extends State<ChannelDetailScreen> {
|
|||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text('channelDetailPersonalRegion')
|
Text('channelDetailPersonalRegion').bold().fontSize(17).tr().padding(horizontal: 20, bottom: 4),
|
||||||
.bold()
|
|
||||||
.fontSize(17)
|
|
||||||
.tr()
|
|
||||||
.padding(horizontal: 20, bottom: 4),
|
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Symbols.notifications),
|
leading: const Icon(Symbols.notifications),
|
||||||
trailing: DropdownButtonHideUnderline(
|
trailing: DropdownButtonHideUnderline(
|
||||||
@ -264,8 +284,7 @@ class _ChannelDetailScreenState extends State<ChannelDetailScreen> {
|
|||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: AccountImage(
|
leading: AccountImage(
|
||||||
content:
|
content: ud.getAccountFromCache(_profile!.accountId)?.avatar,
|
||||||
ud.getAccountFromCache(_profile!.accountId)?.avatar,
|
|
||||||
radius: 18,
|
radius: 18,
|
||||||
),
|
),
|
||||||
trailing: const Icon(Symbols.chevron_right),
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
@ -284,8 +303,7 @@ class _ChannelDetailScreenState extends State<ChannelDetailScreen> {
|
|||||||
trailing: const Icon(Symbols.chevron_right),
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
title: Text('channelActionLeave').tr(),
|
title: Text('channelActionLeave').tr(),
|
||||||
subtitle: Text('channelActionLeaveDescription').tr(),
|
subtitle: Text('channelActionLeaveDescription').tr(),
|
||||||
contentPadding:
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
const EdgeInsets.symmetric(horizontal: 24),
|
|
||||||
onTap: _leaveChannel,
|
onTap: _leaveChannel,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -293,11 +311,7 @@ class _ChannelDetailScreenState extends State<ChannelDetailScreen> {
|
|||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text('channelDetailMemberRegion')
|
Text('channelDetailMemberRegion').bold().fontSize(17).tr().padding(horizontal: 20, bottom: 4),
|
||||||
.bold()
|
|
||||||
.fontSize(17)
|
|
||||||
.tr()
|
|
||||||
.padding(horizontal: 20, bottom: 4),
|
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Symbols.group),
|
leading: const Icon(Symbols.group),
|
||||||
trailing: const Icon(Symbols.chevron_right),
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
@ -319,11 +333,7 @@ class _ChannelDetailScreenState extends State<ChannelDetailScreen> {
|
|||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text('channelDetailAdminRegion')
|
Text('channelDetailAdminRegion').bold().fontSize(17).tr().padding(horizontal: 20, bottom: 4),
|
||||||
.bold()
|
|
||||||
.fontSize(17)
|
|
||||||
.tr()
|
|
||||||
.padding(horizontal: 20, bottom: 4),
|
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Symbols.edit),
|
leading: const Icon(Symbols.edit),
|
||||||
trailing: const Icon(Symbols.chevron_right),
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
@ -362,18 +372,17 @@ class _ChannelDetailScreenState extends State<ChannelDetailScreen> {
|
|||||||
class _ChannelProfileDetailDialog extends StatefulWidget {
|
class _ChannelProfileDetailDialog extends StatefulWidget {
|
||||||
final SnChannel channel;
|
final SnChannel channel;
|
||||||
final SnChannelMember current;
|
final SnChannelMember current;
|
||||||
|
|
||||||
const _ChannelProfileDetailDialog({
|
const _ChannelProfileDetailDialog({
|
||||||
required this.channel,
|
required this.channel,
|
||||||
required this.current,
|
required this.current,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<_ChannelProfileDetailDialog> createState() =>
|
State<_ChannelProfileDetailDialog> createState() => _ChannelProfileDetailDialogState();
|
||||||
_ChannelProfileDetailDialogState();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ChannelProfileDetailDialogState
|
class _ChannelProfileDetailDialogState extends State<_ChannelProfileDetailDialog> {
|
||||||
extends State<_ChannelProfileDetailDialog> {
|
|
||||||
bool _isBusy = false;
|
bool _isBusy = false;
|
||||||
|
|
||||||
final TextEditingController _nickController = TextEditingController();
|
final TextEditingController _nickController = TextEditingController();
|
||||||
@ -444,11 +453,11 @@ class _ChannelProfileDetailDialogState
|
|||||||
|
|
||||||
class _ChannelMemberListWidget extends StatefulWidget {
|
class _ChannelMemberListWidget extends StatefulWidget {
|
||||||
final SnChannel channel;
|
final SnChannel channel;
|
||||||
|
|
||||||
const _ChannelMemberListWidget({required this.channel});
|
const _ChannelMemberListWidget({required this.channel});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<_ChannelMemberListWidget> createState() =>
|
State<_ChannelMemberListWidget> createState() => _ChannelMemberListWidgetState();
|
||||||
_ChannelMemberListWidgetState();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ChannelMemberListWidgetState extends State<_ChannelMemberListWidget> {
|
class _ChannelMemberListWidgetState extends State<_ChannelMemberListWidget> {
|
||||||
@ -463,12 +472,10 @@ class _ChannelMemberListWidgetState extends State<_ChannelMemberListWidget> {
|
|||||||
try {
|
try {
|
||||||
final ud = context.read<UserDirectoryProvider>();
|
final ud = context.read<UserDirectoryProvider>();
|
||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
final resp = await sn.client.get(
|
final resp = await sn.client.get('/cgi/im/channels/${widget.channel.keyPath}/members', queryParameters: {
|
||||||
'/cgi/im/channels/${widget.channel.keyPath}/members',
|
'take': 10,
|
||||||
queryParameters: {
|
'offset': 0,
|
||||||
'take': 10,
|
});
|
||||||
'offset': 0,
|
|
||||||
});
|
|
||||||
final out = List<SnChannelMember>.from(
|
final out = List<SnChannelMember>.from(
|
||||||
resp.data['data']?.map((e) => SnChannelMember.fromJson(e)) ?? [],
|
resp.data['data']?.map((e) => SnChannelMember.fromJson(e)) ?? [],
|
||||||
);
|
);
|
||||||
@ -526,9 +533,7 @@ class _ChannelMemberListWidgetState extends State<_ChannelMemberListWidget> {
|
|||||||
children: [
|
children: [
|
||||||
const Icon(Symbols.group, size: 24),
|
const Icon(Symbols.group, size: 24),
|
||||||
const Gap(16),
|
const Gap(16),
|
||||||
Text('channelMemberManage')
|
Text('channelMemberManage').tr().textStyle(Theme.of(context).textTheme.titleLarge!),
|
||||||
.tr()
|
|
||||||
.textStyle(Theme.of(context).textTheme.titleLarge!),
|
|
||||||
],
|
],
|
||||||
).padding(horizontal: 20, top: 16, bottom: 12),
|
).padding(horizontal: 20, top: 16, bottom: 12),
|
||||||
Expanded(
|
Expanded(
|
||||||
@ -539,8 +544,7 @@ class _ChannelMemberListWidgetState extends State<_ChannelMemberListWidget> {
|
|||||||
},
|
},
|
||||||
child: InfiniteList(
|
child: InfiniteList(
|
||||||
itemCount: _members.length,
|
itemCount: _members.length,
|
||||||
hasReachedMax:
|
hasReachedMax: _totalCount != null && _members.length >= _totalCount!,
|
||||||
_totalCount != null && _members.length >= _totalCount!,
|
|
||||||
isLoading: _isBusy,
|
isLoading: _isBusy,
|
||||||
onFetchData: _fetchMembers,
|
onFetchData: _fetchMembers,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
@ -551,8 +555,7 @@ class _ChannelMemberListWidgetState extends State<_ChannelMemberListWidget> {
|
|||||||
content: ud.getAccountFromCache(member.accountId)?.avatar,
|
content: ud.getAccountFromCache(member.accountId)?.avatar,
|
||||||
),
|
),
|
||||||
title: Text(
|
title: Text(
|
||||||
ud.getAccountFromCache(member.accountId)?.name ??
|
ud.getAccountFromCache(member.accountId)?.name ?? 'unknown'.tr(),
|
||||||
'unknown'.tr(),
|
|
||||||
),
|
),
|
||||||
subtitle: Text(member.nick ?? 'unknown'.tr()),
|
subtitle: Text(member.nick ?? 'unknown'.tr()),
|
||||||
trailing: SizedBox(
|
trailing: SizedBox(
|
||||||
@ -562,8 +565,7 @@ class _ChannelMemberListWidgetState extends State<_ChannelMemberListWidget> {
|
|||||||
mainAxisAlignment: MainAxisAlignment.end,
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed:
|
onPressed: _isUpdating ? null : () => _deleteMember(member),
|
||||||
_isUpdating ? null : () => _deleteMember(member),
|
|
||||||
icon: const Icon(Symbols.person_remove),
|
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<void> _performAction() async {
|
|
||||||
if (_relatedController.text.isEmpty) return;
|
|
||||||
|
|
||||||
setState(() => _isBusy = true);
|
|
||||||
|
|
||||||
try {
|
|
||||||
final sn = context.read<SnNetworkProvider>();
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -6,15 +6,15 @@ import 'package:provider/provider.dart';
|
|||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
import 'package:surface/providers/relationship.dart';
|
import 'package:surface/providers/relationship.dart';
|
||||||
import 'package:surface/providers/sn_network.dart';
|
import 'package:surface/providers/sn_network.dart';
|
||||||
|
import 'package:surface/providers/userinfo.dart';
|
||||||
import 'package:surface/types/account.dart';
|
import 'package:surface/types/account.dart';
|
||||||
import 'package:surface/widgets/account/account_image.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/app_bar_leading.dart';
|
||||||
import 'package:surface/widgets/dialog.dart';
|
import 'package:surface/widgets/dialog.dart';
|
||||||
import 'package:surface/widgets/loading_indicator.dart';
|
import 'package:surface/widgets/loading_indicator.dart';
|
||||||
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||||
|
import 'package:surface/widgets/unauthorized_hint.dart';
|
||||||
import '../providers/userinfo.dart';
|
|
||||||
import '../widgets/unauthorized_hint.dart';
|
|
||||||
|
|
||||||
const kFriendStatus = {
|
const kFriendStatus = {
|
||||||
0: 'friendStatusPending',
|
0: 'friendStatusPending',
|
||||||
@ -168,6 +168,24 @@ class _FriendScreenState extends State<FriendScreen> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _sendRequest(SnAccount user) async {
|
||||||
|
setState(() => _isBusy = true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
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
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@ -199,11 +217,16 @@ class _FriendScreenState extends State<FriendScreen> {
|
|||||||
),
|
),
|
||||||
floatingActionButton: FloatingActionButton(
|
floatingActionButton: FloatingActionButton(
|
||||||
child: const Icon(Symbols.add),
|
child: const Icon(Symbols.add),
|
||||||
onPressed: () {
|
onPressed: () async {
|
||||||
showModalBottomSheet(
|
final user = await showModalBottomSheet<SnAccount?>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => _NewFriendWidget(),
|
builder: (context) => AccountSelect(
|
||||||
|
title: 'friendNew'.tr(),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
if (!mounted) return;
|
||||||
|
if (user == null) return;
|
||||||
|
_sendRequest(user);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
body: Column(
|
body: Column(
|
||||||
@ -231,8 +254,7 @@ class _FriendScreenState extends State<FriendScreen> {
|
|||||||
trailing: const Icon(Symbols.chevron_right),
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
onTap: _showBlocks,
|
onTap: _showBlocks,
|
||||||
),
|
),
|
||||||
if (_requests.isNotEmpty || _blocks.isNotEmpty)
|
if (_requests.isNotEmpty || _blocks.isNotEmpty) const Divider(height: 1),
|
||||||
const Divider(height: 1),
|
|
||||||
Expanded(
|
Expanded(
|
||||||
child: MediaQuery.removePadding(
|
child: MediaQuery.removePadding(
|
||||||
context: context,
|
context: context,
|
||||||
@ -264,16 +286,12 @@ class _FriendScreenState extends State<FriendScreen> {
|
|||||||
mainAxisAlignment: MainAxisAlignment.end,
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
InkWell(
|
InkWell(
|
||||||
onTap: _isUpdating
|
onTap: _isUpdating ? null : () => _changeRelation(relation, 2),
|
||||||
? null
|
|
||||||
: () => _changeRelation(relation, 2),
|
|
||||||
child: Text('friendBlock').tr(),
|
child: Text('friendBlock').tr(),
|
||||||
),
|
),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
InkWell(
|
InkWell(
|
||||||
onTap: _isUpdating
|
onTap: _isUpdating ? null : () => _deleteRelation(relation),
|
||||||
? null
|
|
||||||
: () => _deleteRelation(relation),
|
|
||||||
child: Text('friendDeleteAction').tr(),
|
child: Text('friendDeleteAction').tr(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -293,83 +311,9 @@ class _FriendScreenState extends State<FriendScreen> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _NewFriendWidget extends StatefulWidget {
|
|
||||||
const _NewFriendWidget();
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<_NewFriendWidget> createState() => _NewFriendWidgetState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _NewFriendWidgetState extends State<_NewFriendWidget> {
|
|
||||||
bool _isBusy = false;
|
|
||||||
|
|
||||||
final TextEditingController _relatedController = TextEditingController();
|
|
||||||
|
|
||||||
Future<void> _sendRequest() async {
|
|
||||||
if (_relatedController.text.isEmpty) return;
|
|
||||||
|
|
||||||
setState(() => _isBusy = true);
|
|
||||||
|
|
||||||
try {
|
|
||||||
final sn = context.read<SnNetworkProvider>();
|
|
||||||
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 {
|
class _FriendshipListWidget extends StatefulWidget {
|
||||||
final List<SnRelationship> relations;
|
final List<SnRelationship> relations;
|
||||||
|
|
||||||
const _FriendshipListWidget({required this.relations});
|
const _FriendshipListWidget({required this.relations});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -476,9 +420,7 @@ class _FriendshipListWidgetState extends State<_FriendshipListWidget> {
|
|||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
Text(kFriendStatus[relation.status] ?? 'unknown')
|
Text(kFriendStatus[relation.status] ?? 'unknown').tr().opacity(0.75),
|
||||||
.tr()
|
|
||||||
.opacity(0.75),
|
|
||||||
if (relation.status == 0)
|
if (relation.status == 0)
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
@ -499,8 +441,7 @@ class _FriendshipListWidgetState extends State<_FriendshipListWidget> {
|
|||||||
mainAxisAlignment: MainAxisAlignment.end,
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
InkWell(
|
InkWell(
|
||||||
onTap:
|
onTap: _isBusy ? null : () => _changeRelation(relation, 1),
|
||||||
_isBusy ? null : () => _changeRelation(relation, 1),
|
|
||||||
child: Text('friendUnblock').tr(),
|
child: Text('friendUnblock').tr(),
|
||||||
),
|
),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
|
@ -8,9 +8,11 @@ import 'package:styled_widget/styled_widget.dart';
|
|||||||
import 'package:surface/providers/sn_network.dart';
|
import 'package:surface/providers/sn_network.dart';
|
||||||
import 'package:surface/providers/user_directory.dart';
|
import 'package:surface/providers/user_directory.dart';
|
||||||
import 'package:surface/providers/userinfo.dart';
|
import 'package:surface/providers/userinfo.dart';
|
||||||
|
import 'package:surface/types/account.dart';
|
||||||
import 'package:surface/types/post.dart';
|
import 'package:surface/types/post.dart';
|
||||||
import 'package:surface/types/realm.dart';
|
import 'package:surface/types/realm.dart';
|
||||||
import 'package:surface/widgets/account/account_image.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/dialog.dart';
|
||||||
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||||
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
|
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
|
||||||
@ -229,13 +231,35 @@ class _RealmMemberListWidgetState extends State<_RealmMemberListWidget> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showMemberAdd() {
|
Future<void> _addMember(SnAccount related) async {
|
||||||
showModalBottomSheet(
|
setState(() => _isBusy = true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
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<SnAccount?>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => _NewRealmMemberWidget(
|
builder: (context) => AccountSelect(
|
||||||
realm: widget.realm!,
|
title: 'realmMemberAdd'.tr(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
if (!mounted) return;
|
||||||
|
if (user == null) return;
|
||||||
|
_addMember(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@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<void> _performAction() async {
|
|
||||||
if (_relatedController.text.isEmpty) return;
|
|
||||||
|
|
||||||
setState(() => _isBusy = true);
|
|
||||||
|
|
||||||
try {
|
|
||||||
final sn = context.read<SnNetworkProvider>();
|
|
||||||
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 {
|
class _RealmSettingsWidget extends StatefulWidget {
|
||||||
final SnRealm? realm;
|
final SnRealm? realm;
|
||||||
final Function() onUpdate;
|
final Function() onUpdate;
|
||||||
|
@ -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
|
# 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
|
# 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.
|
# 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:
|
environment:
|
||||||
sdk: ^3.5.4
|
sdk: ^3.5.4
|
||||||
|
Loading…
x
Reference in New Issue
Block a user