✨ Channel detail
This commit is contained in:
parent
c3bf0a19b8
commit
ff9e1896b4
@ -1,8 +1,10 @@
|
|||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:solian/models/channel.dart';
|
||||||
import 'package:solian/screens/account.dart';
|
import 'package:solian/screens/account.dart';
|
||||||
import 'package:solian/screens/account/friend.dart';
|
import 'package:solian/screens/account/friend.dart';
|
||||||
import 'package:solian/screens/account/personalize.dart';
|
import 'package:solian/screens/account/personalize.dart';
|
||||||
import 'package:solian/screens/channel/channel_chat.dart';
|
import 'package:solian/screens/channel/channel_chat.dart';
|
||||||
|
import 'package:solian/screens/channel/channel_detail.dart';
|
||||||
import 'package:solian/screens/channel/channel_organize.dart';
|
import 'package:solian/screens/channel/channel_organize.dart';
|
||||||
import 'package:solian/screens/contact.dart';
|
import 'package:solian/screens/contact.dart';
|
||||||
import 'package:solian/screens/posts/post_detail.dart';
|
import 'package:solian/screens/posts/post_detail.dart';
|
||||||
@ -64,6 +66,20 @@ abstract class AppRouter {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
ShellRoute(
|
||||||
|
builder: (context, state, child) =>
|
||||||
|
BasicShell(state: state, child: child),
|
||||||
|
routes: [
|
||||||
|
GoRoute(
|
||||||
|
path: '/chat/:alias/detail',
|
||||||
|
name: 'channelDetail',
|
||||||
|
builder: (context, state) => ChannelDetailScreen(
|
||||||
|
channel: state.extra as Channel,
|
||||||
|
realm: state.uri.queryParameters['realm'] ?? 'global',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/posts/publish',
|
path: '/posts/publish',
|
||||||
name: 'postPublishing',
|
name: 'postPublishing',
|
||||||
|
@ -12,6 +12,7 @@ import 'package:solian/models/pagination.dart';
|
|||||||
import 'package:solian/providers/auth.dart';
|
import 'package:solian/providers/auth.dart';
|
||||||
import 'package:solian/providers/chat.dart';
|
import 'package:solian/providers/chat.dart';
|
||||||
import 'package:solian/providers/content/channel.dart';
|
import 'package:solian/providers/content/channel.dart';
|
||||||
|
import 'package:solian/router.dart';
|
||||||
import 'package:solian/services.dart';
|
import 'package:solian/services.dart';
|
||||||
import 'package:solian/theme.dart';
|
import 'package:solian/theme.dart';
|
||||||
import 'package:solian/widgets/chat/chat_message.dart';
|
import 'package:solian/widgets/chat/chat_message.dart';
|
||||||
@ -33,6 +34,7 @@ class ChannelChatScreen extends StatefulWidget {
|
|||||||
|
|
||||||
class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
||||||
bool _isBusy = false;
|
bool _isBusy = false;
|
||||||
|
String? _overrideAlias;
|
||||||
|
|
||||||
Channel? _channel;
|
Channel? _channel;
|
||||||
StreamSubscription<NetworkPackage>? _subscription;
|
StreamSubscription<NetworkPackage>? _subscription;
|
||||||
@ -40,13 +42,20 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
|||||||
final PagingController<int, Message> _pagingController =
|
final PagingController<int, Message> _pagingController =
|
||||||
PagingController(firstPageKey: 0);
|
PagingController(firstPageKey: 0);
|
||||||
|
|
||||||
getChannel() async {
|
getChannel({String? overrideAlias}) async {
|
||||||
final ChannelProvider provider = Get.find();
|
final ChannelProvider provider = Get.find();
|
||||||
|
|
||||||
setState(() => _isBusy = true);
|
setState(() => _isBusy = true);
|
||||||
|
|
||||||
|
if (overrideAlias != null) {
|
||||||
|
_overrideAlias = overrideAlias;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final resp = await provider.getChannel(widget.alias, realm: widget.realm);
|
final resp = await provider.getChannel(
|
||||||
|
_overrideAlias ?? widget.alias,
|
||||||
|
realm: widget.realm,
|
||||||
|
);
|
||||||
setState(() => _channel = Channel.fromJson(resp.body));
|
setState(() => _channel = Channel.fromJson(resp.body));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
context.showErrorDialog(e);
|
context.showErrorDialog(e);
|
||||||
@ -184,8 +193,23 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
|||||||
centerTitle: false,
|
centerTitle: false,
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.settings),
|
icon: const Icon(Icons.more_vert),
|
||||||
onPressed: () {},
|
onPressed: () {
|
||||||
|
AppRouter.instance
|
||||||
|
.pushNamed(
|
||||||
|
'channelDetail',
|
||||||
|
pathParameters: {'alias': widget.alias},
|
||||||
|
queryParameters: {'realm': widget.realm},
|
||||||
|
extra: _channel,
|
||||||
|
)
|
||||||
|
.then((value) {
|
||||||
|
if (value == false) AppRouter.instance.pop();
|
||||||
|
if (value != null) {
|
||||||
|
final resp = Channel.fromJson(value as Map<String, dynamic>);
|
||||||
|
getChannel(overrideAlias: resp.alias);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
),
|
),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: SolianTheme.isLargeScreen(context) ? 8 : 16,
|
width: SolianTheme.isLargeScreen(context) ? 8 : 16,
|
||||||
|
142
lib/screens/channel/channel_detail.dart
Normal file
142
lib/screens/channel/channel_detail.dart
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:solian/models/channel.dart';
|
||||||
|
import 'package:solian/providers/auth.dart';
|
||||||
|
import 'package:solian/router.dart';
|
||||||
|
import 'package:solian/screens/channel/channel_organize.dart';
|
||||||
|
import 'package:solian/widgets/channel/channel_deletion.dart';
|
||||||
|
|
||||||
|
class ChannelDetailScreen extends StatefulWidget {
|
||||||
|
final String realm;
|
||||||
|
final Channel channel;
|
||||||
|
|
||||||
|
const ChannelDetailScreen({
|
||||||
|
super.key,
|
||||||
|
required this.channel,
|
||||||
|
required this.realm,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ChannelDetailScreen> createState() => _ChannelDetailScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ChannelDetailScreenState extends State<ChannelDetailScreen> {
|
||||||
|
bool _isOwned = false;
|
||||||
|
|
||||||
|
void checkOwner() async {
|
||||||
|
final AuthProvider auth = Get.find();
|
||||||
|
final prof = await auth.getProfile();
|
||||||
|
setState(() {
|
||||||
|
_isOwned = prof.body['id'] == widget.channel.account.externalId;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void promptLeaveChannel() async {
|
||||||
|
final did = await showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => ChannelDeletion(
|
||||||
|
channel: widget.channel,
|
||||||
|
realm: widget.realm,
|
||||||
|
isOwned: _isOwned,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (did == true && AppRouter.instance.canPop()) {
|
||||||
|
AppRouter.instance.pop(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
checkOwner();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final ownerActions = [
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.edit),
|
||||||
|
title: Text('channelAdjust'.tr),
|
||||||
|
onTap: () async {
|
||||||
|
AppRouter.instance
|
||||||
|
.pushNamed(
|
||||||
|
'channelOrganizing',
|
||||||
|
extra: ChannelOrganizeArguments(edit: widget.channel),
|
||||||
|
queryParameters:
|
||||||
|
widget.realm != 'global' ? {'realm': widget.realm} : {},
|
||||||
|
)
|
||||||
|
.then((resp) {
|
||||||
|
if (resp != null) {
|
||||||
|
AppRouter.instance.pop(resp);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
CircleAvatar(
|
||||||
|
radius: 28,
|
||||||
|
backgroundColor: Colors.teal,
|
||||||
|
child: FaIcon(
|
||||||
|
widget.channel.icon,
|
||||||
|
color: Colors.white,
|
||||||
|
size: 18,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(widget.channel.name,
|
||||||
|
style: Theme.of(context).textTheme.bodyLarge),
|
||||||
|
Text(widget.channel.description,
|
||||||
|
style: Theme.of(context).textTheme.bodySmall),
|
||||||
|
Text(
|
||||||
|
'#${widget.channel.id.toString().padLeft(8, '0')} · ${widget.channel.alias}',
|
||||||
|
style: const TextStyle(fontSize: 11),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Divider(thickness: 0.3),
|
||||||
|
Expanded(
|
||||||
|
child: ListView(
|
||||||
|
children: [
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.settings),
|
||||||
|
title: Text('channelSettings'.tr),
|
||||||
|
onTap: () {},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.supervisor_account),
|
||||||
|
title: Text('channelMembers'.tr),
|
||||||
|
onTap: () {},
|
||||||
|
),
|
||||||
|
...(_isOwned ? ownerActions : List.empty()),
|
||||||
|
const Divider(thickness: 0.3),
|
||||||
|
ListTile(
|
||||||
|
leading: _isOwned
|
||||||
|
? const Icon(Icons.delete)
|
||||||
|
: const Icon(Icons.exit_to_app),
|
||||||
|
title: Text(_isOwned ? 'delete'.tr : 'leave'.tr),
|
||||||
|
onTap: () => promptLeaveChannel(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -227,7 +227,7 @@ class _ChannelOrganizeScreenState extends State<ChannelOrganizeScreen> {
|
|||||||
).paddingSymmetric(horizontal: 16, vertical: 12),
|
).paddingSymmetric(horizontal: 16, vertical: 12),
|
||||||
),
|
),
|
||||||
const Divider(thickness: 0.3),
|
const Divider(thickness: 0.3),
|
||||||
if (_channelType == 1)
|
if (_channelType == 1 && widget.edit == null)
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.supervisor_account)
|
leading: const Icon(Icons.supervisor_account)
|
||||||
.paddingSymmetric(horizontal: 8),
|
.paddingSymmetric(horizontal: 8),
|
||||||
@ -250,6 +250,8 @@ class _ChannelOrganizeScreenState extends State<ChannelOrganizeScreen> {
|
|||||||
isExpanded: true,
|
isExpanded: true,
|
||||||
items: channelTypes.entries
|
items: channelTypes.entries
|
||||||
.map((item) => DropdownMenuItem<int>(
|
.map((item) => DropdownMenuItem<int>(
|
||||||
|
enabled: widget.edit == null ||
|
||||||
|
item.key == widget.edit?.type,
|
||||||
value: item.key,
|
value: item.key,
|
||||||
child: Text(
|
child: Text(
|
||||||
item.value,
|
item.value,
|
||||||
|
@ -14,6 +14,7 @@ class SolianMessages extends Translations {
|
|||||||
'apply': 'Apply',
|
'apply': 'Apply',
|
||||||
'cancel': 'Cancel',
|
'cancel': 'Cancel',
|
||||||
'confirm': 'Confirm',
|
'confirm': 'Confirm',
|
||||||
|
'leave': 'Leave',
|
||||||
'loading': 'Loading...',
|
'loading': 'Loading...',
|
||||||
'edit': 'Edit',
|
'edit': 'Edit',
|
||||||
'delete': 'Delete',
|
'delete': 'Delete',
|
||||||
@ -79,7 +80,7 @@ class SolianMessages extends Translations {
|
|||||||
'postRepostingNotify': 'You\'re reposting a post from @username.',
|
'postRepostingNotify': 'You\'re reposting a post from @username.',
|
||||||
'postDeletionConfirm': 'Confirm post deletion',
|
'postDeletionConfirm': 'Confirm post deletion',
|
||||||
'postDeletionConfirmCaption':
|
'postDeletionConfirmCaption':
|
||||||
'Are your sure to delete post "@content"? this action cannot be undone!',
|
'Are your sure to delete post "@content"? This action cannot be undone!',
|
||||||
'reactAdd': 'React',
|
'reactAdd': 'React',
|
||||||
'reactCompleted': 'Your reaction has been added',
|
'reactCompleted': 'Your reaction has been added',
|
||||||
'reactUncompleted': 'Your reaction has been removed',
|
'reactUncompleted': 'Your reaction has been removed',
|
||||||
@ -98,9 +99,16 @@ class SolianMessages extends Translations {
|
|||||||
'channelDescription': 'Description',
|
'channelDescription': 'Description',
|
||||||
'channelEncrypted': 'Encrypted Channel',
|
'channelEncrypted': 'Encrypted Channel',
|
||||||
'channelMember': 'Channel member',
|
'channelMember': 'Channel member',
|
||||||
|
'channelMembers': 'Channel members',
|
||||||
'channelType': 'Channel type',
|
'channelType': 'Channel type',
|
||||||
'channelTypeCommon': 'Regular',
|
'channelTypeCommon': 'Regular',
|
||||||
'channelTypeDirect': 'DM',
|
'channelTypeDirect': 'DM',
|
||||||
|
'channelAdjust': 'Channel Adjustment',
|
||||||
|
'channelDetail': 'Channel Detail',
|
||||||
|
'channelSettings': 'Channel settings',
|
||||||
|
'channelDeletionConfirm': 'Confirm channel deletion',
|
||||||
|
'channelDeletionConfirmCaption':
|
||||||
|
'Are you sure to delete channel @channel? This action cannot be undone!',
|
||||||
'messageDecoding': 'Decoding...',
|
'messageDecoding': 'Decoding...',
|
||||||
'messageDecodeFailed': 'Unable to decode: @message',
|
'messageDecodeFailed': 'Unable to decode: @message',
|
||||||
'messageInputPlaceholder': 'Message @channel...',
|
'messageInputPlaceholder': 'Message @channel...',
|
||||||
@ -112,6 +120,7 @@ class SolianMessages extends Translations {
|
|||||||
'reset': '重置',
|
'reset': '重置',
|
||||||
'cancel': '取消',
|
'cancel': '取消',
|
||||||
'confirm': '确认',
|
'confirm': '确认',
|
||||||
|
'leave': '离开',
|
||||||
'loading': '载入中…',
|
'loading': '载入中…',
|
||||||
'edit': '编辑',
|
'edit': '编辑',
|
||||||
'delete': '删除',
|
'delete': '删除',
|
||||||
@ -174,7 +183,7 @@ class SolianMessages extends Translations {
|
|||||||
'postReplyingNotify': '你正在回一个来自 @username 的帖子',
|
'postReplyingNotify': '你正在回一个来自 @username 的帖子',
|
||||||
'postRepostingNotify': '你正在转一个来自 @username 的帖子',
|
'postRepostingNotify': '你正在转一个来自 @username 的帖子',
|
||||||
'postDeletionConfirm': '确认删除帖子',
|
'postDeletionConfirm': '确认删除帖子',
|
||||||
'postDeletionConfirmCaption': '你确定要删除帖子 “@content” 吗?该操作不可不可撤销。',
|
'postDeletionConfirmCaption': '你确定要删除帖子 “@content” 吗?该操作不可撤销。',
|
||||||
'reactAdd': '作出反应',
|
'reactAdd': '作出反应',
|
||||||
'reactCompleted': '你的反应已被添加',
|
'reactCompleted': '你的反应已被添加',
|
||||||
'reactUncompleted': '你的反应已被移除',
|
'reactUncompleted': '你的反应已被移除',
|
||||||
@ -193,9 +202,15 @@ class SolianMessages extends Translations {
|
|||||||
'channelDescription': '频道简介',
|
'channelDescription': '频道简介',
|
||||||
'channelEncrypted': '加密频道',
|
'channelEncrypted': '加密频道',
|
||||||
'channelMember': '频道成员',
|
'channelMember': '频道成员',
|
||||||
|
'channelMembers': '频道成员',
|
||||||
'channelType': '频道类型',
|
'channelType': '频道类型',
|
||||||
'channelTypeCommon': '普通频道',
|
'channelTypeCommon': '普通频道',
|
||||||
'channelTypeDirect': '私信聊天',
|
'channelTypeDirect': '私信聊天',
|
||||||
|
'channelAdjust': '调整频道',
|
||||||
|
'channelDetail': '频道详情',
|
||||||
|
'channelSettings': '频道设置',
|
||||||
|
'channelDeletionConfirm': '确认删除频道',
|
||||||
|
'channelDeletionConfirmCaption': '你确认要删除频道 @channel 吗?该操作不可撤销。',
|
||||||
'messageDecoding': '解码信息中…',
|
'messageDecoding': '解码信息中…',
|
||||||
'messageDecodeFailed': '解码信息失败:@message',
|
'messageDecodeFailed': '解码信息失败:@message',
|
||||||
'messageInputPlaceholder': '在 @channel 发信息…',
|
'messageInputPlaceholder': '在 @channel 发信息…',
|
||||||
|
94
lib/widgets/channel/channel_deletion.dart
Normal file
94
lib/widgets/channel/channel_deletion.dart
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:solian/exts.dart';
|
||||||
|
import 'package:solian/models/channel.dart';
|
||||||
|
import 'package:solian/providers/auth.dart';
|
||||||
|
import 'package:solian/services.dart';
|
||||||
|
|
||||||
|
class ChannelDeletion extends StatefulWidget {
|
||||||
|
final Channel channel;
|
||||||
|
final String realm;
|
||||||
|
final bool isOwned;
|
||||||
|
|
||||||
|
const ChannelDeletion({
|
||||||
|
super.key,
|
||||||
|
required this.channel,
|
||||||
|
required this.realm,
|
||||||
|
required this.isOwned,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ChannelDeletion> createState() => _ChannelDeletionState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ChannelDeletionState extends State<ChannelDeletion> {
|
||||||
|
bool _isBusy = false;
|
||||||
|
|
||||||
|
Future<void> deleteChannel() async {
|
||||||
|
final AuthProvider auth = Get.find();
|
||||||
|
if (!await auth.isAuthorized) return;
|
||||||
|
|
||||||
|
setState(() => _isBusy = true);
|
||||||
|
|
||||||
|
final client = GetConnect();
|
||||||
|
client.httpClient.baseUrl = ServiceFinder.services['messaging'];
|
||||||
|
client.httpClient.addAuthenticator(auth.requestAuthenticator);
|
||||||
|
|
||||||
|
final resp = await client
|
||||||
|
.delete('/api/channels/${widget.realm}/${widget.channel.id}');
|
||||||
|
if (resp.statusCode != 200) {
|
||||||
|
context.showErrorDialog(resp.bodyString);
|
||||||
|
} else if (Navigator.canPop(context)) {
|
||||||
|
Navigator.pop(context, 'OK');
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(() => _isBusy = false);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> leaveChannel() async {
|
||||||
|
final AuthProvider auth = Get.find();
|
||||||
|
if (!await auth.isAuthorized) return;
|
||||||
|
|
||||||
|
setState(() => _isBusy = true);
|
||||||
|
|
||||||
|
final client = GetConnect();
|
||||||
|
client.httpClient.baseUrl = ServiceFinder.services['messaging'];
|
||||||
|
client.httpClient.addAuthenticator(auth.requestAuthenticator);
|
||||||
|
|
||||||
|
final resp = await client.delete(
|
||||||
|
'/api/channels/${widget.realm}/${widget.channel.alias}/members/me',
|
||||||
|
);
|
||||||
|
if (resp.statusCode != 200) {
|
||||||
|
context.showErrorDialog(resp.bodyString);
|
||||||
|
} else if (Navigator.canPop(context)) {
|
||||||
|
Navigator.pop(context, 'OK');
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(() => _isBusy = false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: Text('channelDeletionConfirm'.tr),
|
||||||
|
content: Text(
|
||||||
|
'channelDeletionConfirmCaption'
|
||||||
|
.trParams({'channel': '#${widget.channel.alias}'}),
|
||||||
|
),
|
||||||
|
actions: <Widget>[
|
||||||
|
TextButton(
|
||||||
|
onPressed: _isBusy ? null : () => Navigator.pop(context),
|
||||||
|
child: Text('cancel'.tr),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: _isBusy
|
||||||
|
? null
|
||||||
|
: widget.isOwned
|
||||||
|
? deleteChannel
|
||||||
|
: leaveChannel,
|
||||||
|
child: Text('confirm'.tr),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user