✨ Channel detail
This commit is contained in:
parent
c3bf0a19b8
commit
ff9e1896b4
@ -1,8 +1,10 @@
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:solian/models/channel.dart';
|
||||
import 'package:solian/screens/account.dart';
|
||||
import 'package:solian/screens/account/friend.dart';
|
||||
import 'package:solian/screens/account/personalize.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/contact.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(
|
||||
path: '/posts/publish',
|
||||
name: 'postPublishing',
|
||||
|
@ -12,6 +12,7 @@ import 'package:solian/models/pagination.dart';
|
||||
import 'package:solian/providers/auth.dart';
|
||||
import 'package:solian/providers/chat.dart';
|
||||
import 'package:solian/providers/content/channel.dart';
|
||||
import 'package:solian/router.dart';
|
||||
import 'package:solian/services.dart';
|
||||
import 'package:solian/theme.dart';
|
||||
import 'package:solian/widgets/chat/chat_message.dart';
|
||||
@ -33,6 +34,7 @@ class ChannelChatScreen extends StatefulWidget {
|
||||
|
||||
class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
||||
bool _isBusy = false;
|
||||
String? _overrideAlias;
|
||||
|
||||
Channel? _channel;
|
||||
StreamSubscription<NetworkPackage>? _subscription;
|
||||
@ -40,13 +42,20 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
||||
final PagingController<int, Message> _pagingController =
|
||||
PagingController(firstPageKey: 0);
|
||||
|
||||
getChannel() async {
|
||||
getChannel({String? overrideAlias}) async {
|
||||
final ChannelProvider provider = Get.find();
|
||||
|
||||
setState(() => _isBusy = true);
|
||||
|
||||
if (overrideAlias != null) {
|
||||
_overrideAlias = overrideAlias;
|
||||
}
|
||||
|
||||
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));
|
||||
} catch (e) {
|
||||
context.showErrorDialog(e);
|
||||
@ -184,8 +193,23 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
||||
centerTitle: false,
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.settings),
|
||||
onPressed: () {},
|
||||
icon: const Icon(Icons.more_vert),
|
||||
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(
|
||||
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),
|
||||
),
|
||||
const Divider(thickness: 0.3),
|
||||
if (_channelType == 1)
|
||||
if (_channelType == 1 && widget.edit == null)
|
||||
ListTile(
|
||||
leading: const Icon(Icons.supervisor_account)
|
||||
.paddingSymmetric(horizontal: 8),
|
||||
@ -250,6 +250,8 @@ class _ChannelOrganizeScreenState extends State<ChannelOrganizeScreen> {
|
||||
isExpanded: true,
|
||||
items: channelTypes.entries
|
||||
.map((item) => DropdownMenuItem<int>(
|
||||
enabled: widget.edit == null ||
|
||||
item.key == widget.edit?.type,
|
||||
value: item.key,
|
||||
child: Text(
|
||||
item.value,
|
||||
|
@ -14,6 +14,7 @@ class SolianMessages extends Translations {
|
||||
'apply': 'Apply',
|
||||
'cancel': 'Cancel',
|
||||
'confirm': 'Confirm',
|
||||
'leave': 'Leave',
|
||||
'loading': 'Loading...',
|
||||
'edit': 'Edit',
|
||||
'delete': 'Delete',
|
||||
@ -79,7 +80,7 @@ class SolianMessages extends Translations {
|
||||
'postRepostingNotify': 'You\'re reposting a post from @username.',
|
||||
'postDeletionConfirm': 'Confirm post deletion',
|
||||
'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',
|
||||
'reactCompleted': 'Your reaction has been added',
|
||||
'reactUncompleted': 'Your reaction has been removed',
|
||||
@ -98,9 +99,16 @@ class SolianMessages extends Translations {
|
||||
'channelDescription': 'Description',
|
||||
'channelEncrypted': 'Encrypted Channel',
|
||||
'channelMember': 'Channel member',
|
||||
'channelMembers': 'Channel members',
|
||||
'channelType': 'Channel type',
|
||||
'channelTypeCommon': 'Regular',
|
||||
'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...',
|
||||
'messageDecodeFailed': 'Unable to decode: @message',
|
||||
'messageInputPlaceholder': 'Message @channel...',
|
||||
@ -112,6 +120,7 @@ class SolianMessages extends Translations {
|
||||
'reset': '重置',
|
||||
'cancel': '取消',
|
||||
'confirm': '确认',
|
||||
'leave': '离开',
|
||||
'loading': '载入中…',
|
||||
'edit': '编辑',
|
||||
'delete': '删除',
|
||||
@ -174,7 +183,7 @@ class SolianMessages extends Translations {
|
||||
'postReplyingNotify': '你正在回一个来自 @username 的帖子',
|
||||
'postRepostingNotify': '你正在转一个来自 @username 的帖子',
|
||||
'postDeletionConfirm': '确认删除帖子',
|
||||
'postDeletionConfirmCaption': '你确定要删除帖子 “@content” 吗?该操作不可不可撤销。',
|
||||
'postDeletionConfirmCaption': '你确定要删除帖子 “@content” 吗?该操作不可撤销。',
|
||||
'reactAdd': '作出反应',
|
||||
'reactCompleted': '你的反应已被添加',
|
||||
'reactUncompleted': '你的反应已被移除',
|
||||
@ -193,9 +202,15 @@ class SolianMessages extends Translations {
|
||||
'channelDescription': '频道简介',
|
||||
'channelEncrypted': '加密频道',
|
||||
'channelMember': '频道成员',
|
||||
'channelMembers': '频道成员',
|
||||
'channelType': '频道类型',
|
||||
'channelTypeCommon': '普通频道',
|
||||
'channelTypeDirect': '私信聊天',
|
||||
'channelAdjust': '调整频道',
|
||||
'channelDetail': '频道详情',
|
||||
'channelSettings': '频道设置',
|
||||
'channelDeletionConfirm': '确认删除频道',
|
||||
'channelDeletionConfirmCaption': '你确认要删除频道 @channel 吗?该操作不可撤销。',
|
||||
'messageDecoding': '解码信息中…',
|
||||
'messageDecodeFailed': '解码信息失败:@message',
|
||||
'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