✨ Realm channels
This commit is contained in:
		| @@ -21,6 +21,22 @@ class ChannelProvider extends GetxController { | ||||
|     return resp; | ||||
|   } | ||||
|  | ||||
|   Future<Response> listChannel({String scope = 'global'}) async { | ||||
|     final AuthProvider auth = Get.find(); | ||||
|     if (!await auth.isAuthorized) throw Exception('unauthorized'); | ||||
|  | ||||
|     final client = GetConnect(maxAuthRetries: 3); | ||||
|     client.httpClient.baseUrl = ServiceFinder.services['messaging']; | ||||
|     client.httpClient.addAuthenticator(auth.requestAuthenticator); | ||||
|  | ||||
|     final resp = await client.get('/api/channels/$scope'); | ||||
|     if (resp.statusCode != 200) { | ||||
|       throw Exception(resp.bodyString); | ||||
|     } | ||||
|  | ||||
|     return resp; | ||||
|   } | ||||
|  | ||||
|   Future<Response> listAvailableChannel({String realm = 'global'}) async { | ||||
|     final AuthProvider auth = Get.find(); | ||||
|     if (!await auth.isAuthorized) throw Exception('unauthorized'); | ||||
|   | ||||
| @@ -110,7 +110,7 @@ abstract class AppRouter { | ||||
|           final arguments = state.extra as ChannelOrganizeArguments?; | ||||
|           return ChannelOrganizeScreen( | ||||
|             edit: arguments?.edit, | ||||
|             realm: state.uri.queryParameters['realm'], | ||||
|             realm: arguments?.realm, | ||||
|           ); | ||||
|         }, | ||||
|       ), | ||||
|   | ||||
| @@ -3,6 +3,7 @@ import 'package:flutter_animate/flutter_animate.dart'; | ||||
| import 'package:get/get.dart'; | ||||
| import 'package:solian/exts.dart'; | ||||
| import 'package:solian/models/channel.dart'; | ||||
| import 'package:solian/models/realm.dart'; | ||||
| import 'package:solian/providers/auth.dart'; | ||||
| import 'package:solian/providers/content/channel.dart'; | ||||
| import 'package:solian/router.dart'; | ||||
| @@ -12,13 +13,14 @@ import 'package:uuid/uuid.dart'; | ||||
|  | ||||
| class ChannelOrganizeArguments { | ||||
|   final Channel? edit; | ||||
|   final Realm? realm; | ||||
|  | ||||
|   ChannelOrganizeArguments({this.edit}); | ||||
|   ChannelOrganizeArguments({this.edit, this.realm}); | ||||
| } | ||||
|  | ||||
| class ChannelOrganizeScreen extends StatefulWidget { | ||||
|   final Channel? edit; | ||||
|   final String? realm; | ||||
|   final Realm? realm; | ||||
|  | ||||
|   const ChannelOrganizeScreen({super.key, this.edit, this.realm}); | ||||
|  | ||||
| @@ -49,7 +51,7 @@ class _ChannelOrganizeScreenState extends State<ChannelOrganizeScreen> { | ||||
|     client.httpClient.baseUrl = ServiceFinder.services['messaging']; | ||||
|     client.httpClient.addAuthenticator(auth.requestAuthenticator); | ||||
|  | ||||
|     final scope = (widget.realm?.isNotEmpty ?? false) ? widget.realm : 'global'; | ||||
|     final scope = widget.realm != null ? widget.realm!.alias : 'global'; | ||||
|     final payload = { | ||||
|       'alias': _aliasController.value.text.toLowerCase(), | ||||
|       'name': _nameController.value.text, | ||||
| @@ -60,9 +62,9 @@ class _ChannelOrganizeScreenState extends State<ChannelOrganizeScreen> { | ||||
|     Response? resp; | ||||
|     try { | ||||
|       if (widget.edit != null) { | ||||
|         resp = await provider.updateChannel(scope!, widget.edit!.id, payload); | ||||
|         resp = await provider.updateChannel(scope, widget.edit!.id, payload); | ||||
|       } else { | ||||
|         resp = await provider.createChannel(scope!, payload); | ||||
|         resp = await provider.createChannel(scope, payload); | ||||
|       } | ||||
|     } catch (e) { | ||||
|       context.showErrorDialog(e); | ||||
| @@ -99,6 +101,13 @@ class _ChannelOrganizeScreenState extends State<ChannelOrganizeScreen> { | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     final notifyBannerActions = [ | ||||
|       TextButton( | ||||
|         onPressed: cancelAction, | ||||
|         child: Text('cancel'.tr), | ||||
|       ), | ||||
|     ]; | ||||
|  | ||||
|     return Material( | ||||
|       color: Theme.of(context).colorScheme.surface, | ||||
|       child: Scaffold( | ||||
| @@ -126,13 +135,19 @@ class _ChannelOrganizeScreenState extends State<ChannelOrganizeScreen> { | ||||
|                     'channelEditingNotify' | ||||
|                         .trParams({'channel': '#${widget.edit!.alias}'}), | ||||
|                   ), | ||||
|                   actions: [ | ||||
|                     TextButton( | ||||
|                       onPressed: cancelAction, | ||||
|                       child: Text('cancel'.tr), | ||||
|                     ), | ||||
|                   ], | ||||
|                 ), | ||||
|                   actions: notifyBannerActions, | ||||
|                 ).paddingOnly(bottom: 6), | ||||
|               if (widget.realm != null) | ||||
|                 MaterialBanner( | ||||
|                   leading: const Icon(Icons.group), | ||||
|                   leadingPadding: const EdgeInsets.only(left: 10, right: 20), | ||||
|                   dividerColor: Colors.transparent, | ||||
|                   content: Text( | ||||
|                     'channelInRealmNotify' | ||||
|                         .trParams({'realm': '#${widget.realm!.alias}'}), | ||||
|                   ), | ||||
|                   actions: notifyBannerActions, | ||||
|                 ).paddingOnly(bottom: 6), | ||||
|               Row( | ||||
|                 children: [ | ||||
|                   Expanded( | ||||
|   | ||||
| @@ -8,8 +8,8 @@ import 'package:solian/providers/content/channel.dart'; | ||||
| import 'package:solian/router.dart'; | ||||
| import 'package:solian/screens/account/notification.dart'; | ||||
| import 'package:solian/theme.dart'; | ||||
| import 'package:solian/widgets/account/account_avatar.dart'; | ||||
| import 'package:solian/widgets/account/signin_required_overlay.dart'; | ||||
| import 'package:solian/widgets/channel/channel_list.dart'; | ||||
|  | ||||
| class ContactScreen extends StatefulWidget { | ||||
|   const ContactScreen({super.key}); | ||||
| @@ -137,13 +137,7 @@ class _ContactScreenState extends State<ContactScreen> { | ||||
|                   SliverToBoxAdapter( | ||||
|                     child: const LinearProgressIndicator().animate().scaleX(), | ||||
|                   ), | ||||
|                 SliverList.builder( | ||||
|                   itemCount: _channels.length, | ||||
|                   itemBuilder: (context, index) { | ||||
|                     final element = _channels[index]; | ||||
|                     return buildItem(element); | ||||
|                   }, | ||||
|                 ), | ||||
|                 ChannelListWidget(channels: _channels, selfId: _accountId ?? 0), | ||||
|               ], | ||||
|             ), | ||||
|           ); | ||||
| @@ -151,58 +145,4 @@ class _ContactScreenState extends State<ContactScreen> { | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   Widget buildItem(Channel element) { | ||||
|     if (element.type == 1) { | ||||
|       final otherside = element.members! | ||||
|           .where((e) => e.account.externalId != _accountId) | ||||
|           .first; | ||||
|  | ||||
|       return ListTile( | ||||
|         leading: AccountAvatar( | ||||
|           content: otherside.account.avatar, | ||||
|           bgColor: Colors.indigo, | ||||
|           feColor: Colors.white, | ||||
|         ), | ||||
|         contentPadding: const EdgeInsets.symmetric(horizontal: 24), | ||||
|         title: Text(otherside.account.nick), | ||||
|         subtitle: Text( | ||||
|           'channelDirectDescription' | ||||
|               .trParams({'username': '@${otherside.account.name}'}), | ||||
|         ), | ||||
|         onTap: () { | ||||
|           AppRouter.instance.pushNamed( | ||||
|             'channelChat', | ||||
|             pathParameters: {'alias': element.alias}, | ||||
|             queryParameters: { | ||||
|               if (element.realmId != null) 'realm': element.realm!.alias, | ||||
|             }, | ||||
|           ); | ||||
|         }, | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     return ListTile( | ||||
|       leading: const CircleAvatar( | ||||
|         backgroundColor: Colors.indigo, | ||||
|         child: FaIcon( | ||||
|           FontAwesomeIcons.hashtag, | ||||
|           color: Colors.white, | ||||
|           size: 16, | ||||
|         ), | ||||
|       ), | ||||
|       contentPadding: const EdgeInsets.symmetric(horizontal: 24), | ||||
|       title: Text(element.name), | ||||
|       subtitle: Text(element.description), | ||||
|       onTap: () { | ||||
|         AppRouter.instance.pushNamed( | ||||
|           'channelChat', | ||||
|           pathParameters: {'alias': element.alias}, | ||||
|           queryParameters: { | ||||
|             if (element.realmId != null) 'realm': element.realm!.alias, | ||||
|           }, | ||||
|         ); | ||||
|       }, | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -2,14 +2,19 @@ import 'package:flutter/material.dart'; | ||||
| import 'package:get/get.dart'; | ||||
| import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; | ||||
| import 'package:solian/exts.dart'; | ||||
| import 'package:solian/models/channel.dart'; | ||||
| import 'package:solian/models/pagination.dart'; | ||||
| import 'package:solian/models/post.dart'; | ||||
| import 'package:solian/models/realm.dart'; | ||||
| import 'package:solian/providers/auth.dart'; | ||||
| import 'package:solian/providers/content/channel.dart'; | ||||
| import 'package:solian/providers/content/post.dart'; | ||||
| import 'package:solian/providers/content/realm.dart'; | ||||
| import 'package:solian/router.dart'; | ||||
| import 'package:solian/screens/channel/channel_organize.dart'; | ||||
| import 'package:solian/screens/posts/post_publish.dart'; | ||||
| import 'package:solian/theme.dart'; | ||||
| import 'package:solian/widgets/channel/channel_list.dart'; | ||||
| import 'package:solian/widgets/posts/post_list.dart'; | ||||
|  | ||||
| class RealmViewScreen extends StatefulWidget { | ||||
| @@ -26,6 +31,7 @@ class _RealmViewScreenState extends State<RealmViewScreen> { | ||||
|   String? _overrideAlias; | ||||
|  | ||||
|   Realm? _realm; | ||||
|   final List<Channel> _channels = List.empty(growable: true); | ||||
|  | ||||
|   getRealm({String? overrideAlias}) async { | ||||
|     final RealmProvider provider = Get.find(); | ||||
| @@ -46,11 +52,29 @@ class _RealmViewScreenState extends State<RealmViewScreen> { | ||||
|     setState(() => _isBusy = false); | ||||
|   } | ||||
|  | ||||
|   getChannels() async { | ||||
|     setState(() => _isBusy = true); | ||||
|  | ||||
|     final ChannelProvider provider = Get.find(); | ||||
|     final resp = await provider.listChannel(scope: _realm!.alias); | ||||
|  | ||||
|     setState(() { | ||||
|       _channels.clear(); | ||||
|       _channels.addAll( | ||||
|         resp.body.map((e) => Channel.fromJson(e)).toList().cast<Channel>(), | ||||
|       ); | ||||
|     }); | ||||
|  | ||||
|     setState(() => _isBusy = false); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   void initState() { | ||||
|     super.initState(); | ||||
|  | ||||
|     getRealm(); | ||||
|     getRealm().then((_) { | ||||
|       getChannels(); | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
| @@ -113,7 +137,11 @@ class _RealmViewScreenState extends State<RealmViewScreen> { | ||||
|                 return TabBarView( | ||||
|                   children: [ | ||||
|                     RealmPostListWidget(realm: _realm!), | ||||
|                     Icon(Icons.directions_transit), | ||||
|                     RealmChannelListWidget( | ||||
|                       realm: _realm!, | ||||
|                       channels: _channels, | ||||
|                       onRefresh: () => getChannels(), | ||||
|                     ), | ||||
|                   ], | ||||
|                 ); | ||||
|               }, | ||||
| @@ -174,7 +202,7 @@ class _RealmPostListWidgetState extends State<RealmPostListWidget> { | ||||
|           SliverToBoxAdapter( | ||||
|             child: ListTile( | ||||
|               leading: const Icon(Icons.post_add), | ||||
|               contentPadding: const EdgeInsets.symmetric(horizontal: 24), | ||||
|               contentPadding: const EdgeInsets.only(left: 24, right: 8), | ||||
|               tileColor: Theme.of(context).colorScheme.surfaceContainer, | ||||
|               title: Text('postNew'.tr), | ||||
|               subtitle: Text( | ||||
| @@ -199,3 +227,60 @@ class _RealmPostListWidgetState extends State<RealmPostListWidget> { | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
| class RealmChannelListWidget extends StatelessWidget { | ||||
|   final Realm realm; | ||||
|   final List<Channel> channels; | ||||
|   final Future Function() onRefresh; | ||||
|  | ||||
|   const RealmChannelListWidget({ | ||||
|     super.key, | ||||
|     required this.realm, | ||||
|     required this.channels, | ||||
|     required this.onRefresh, | ||||
|   }); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     final AuthProvider auth = Get.find(); | ||||
|  | ||||
|     return FutureBuilder( | ||||
|       future: auth.getProfile(), | ||||
|       builder: (context, snapshot) { | ||||
|         return RefreshIndicator( | ||||
|           onRefresh: onRefresh, | ||||
|           child: CustomScrollView( | ||||
|             slivers: [ | ||||
|               SliverToBoxAdapter( | ||||
|                 child: ListTile( | ||||
|                   leading: const Icon(Icons.add_box), | ||||
|                   contentPadding: const EdgeInsets.only(left: 32, right: 8), | ||||
|                   tileColor: Theme.of(context).colorScheme.surfaceContainer, | ||||
|                   title: Text('channelNew'.tr), | ||||
|                   subtitle: Text( | ||||
|                     'channelNewInRealmHint' | ||||
|                         .trParams({'realm': '#${realm.alias}'}), | ||||
|                   ), | ||||
|                   onTap: () { | ||||
|                     AppRouter.instance | ||||
|                         .pushNamed( | ||||
|                       'channelOrganizing', | ||||
|                       extra: ChannelOrganizeArguments(realm: realm), | ||||
|                     ) | ||||
|                         .then((value) { | ||||
|                       if (value != null) onRefresh(); | ||||
|                     }); | ||||
|                   }, | ||||
|                 ), | ||||
|               ), | ||||
|               ChannelListWidget( | ||||
|                 channels: channels, | ||||
|                 selfId: snapshot.data?.body['id'] ?? 0, | ||||
|               ) | ||||
|             ], | ||||
|           ), | ||||
|         ); | ||||
|       }, | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -108,10 +108,13 @@ class SolianMessages extends Translations { | ||||
|           'realmPublic': 'Public Realm', | ||||
|           'realmCommunity': 'Community Realm', | ||||
|           'realmDetail': 'Realm detail', | ||||
|           'channelNew': 'Create a new channel', | ||||
|           'channelNewInRealmHint': 'Create channel in realm @realm', | ||||
|           'channelOrganizing': 'Organize a channel', | ||||
|           'channelOrganizeCommon': 'Create regular channel', | ||||
|           'channelOrganizeDirect': 'Create DM', | ||||
|           'channelOrganizeDirectHint': 'Choose friend to create DM', | ||||
|           'channelInRealmNotify': 'You\'re creating channel in realm @realm', | ||||
|           'channelEditingNotify': 'You\'re editing channel @channel', | ||||
|           'channelAlias': 'Alias (Identifier)', | ||||
|           'channelName': 'Name', | ||||
| @@ -233,10 +236,13 @@ class SolianMessages extends Translations { | ||||
|           'realmPublic': '公开领域', | ||||
|           'realmCommunity': '社区领域', | ||||
|           'realmDetail': '领域详情', | ||||
|           'channelNew': '创建新频道', | ||||
|           'channelNewInRealmHint': '在领域 @realm 里创建新频道', | ||||
|           'channelOrganizing': '组织频道', | ||||
|           'channelOrganizeCommon': '创建普通频道', | ||||
|           'channelOrganizeDirect': '创建私信频道', | ||||
|           'channelOrganizeDirectHint': '选择好友来创建私信', | ||||
|           'channelInRealmNotify': '你正在领域 @realm 中创建频道', | ||||
|           'channelEditingNotify': '你正在编辑频道 @channel', | ||||
|           'channelAlias': '别称(标识符)', | ||||
|           'channelName': '显示名称', | ||||
|   | ||||
							
								
								
									
										76
									
								
								lib/widgets/channel/channel_list.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								lib/widgets/channel/channel_list.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,76 @@ | ||||
| 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/router.dart'; | ||||
| import 'package:solian/widgets/account/account_avatar.dart'; | ||||
|  | ||||
| class ChannelListWidget extends StatelessWidget { | ||||
|   final List<Channel> channels; | ||||
|   final int selfId; | ||||
|  | ||||
|   const ChannelListWidget( | ||||
|       {super.key, required this.channels, required this.selfId}); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return SliverList.builder( | ||||
|       itemCount: channels.length, | ||||
|       itemBuilder: (context, index) { | ||||
|         final element = channels[index]; | ||||
|  | ||||
|         if (element.type == 1) { | ||||
|           final otherside = element.members! | ||||
|               .where((e) => e.account.externalId != selfId) | ||||
|               .first; | ||||
|  | ||||
|           return ListTile( | ||||
|             leading: AccountAvatar( | ||||
|               content: otherside.account.avatar, | ||||
|               bgColor: Colors.indigo, | ||||
|               feColor: Colors.white, | ||||
|             ), | ||||
|             contentPadding: const EdgeInsets.symmetric(horizontal: 24), | ||||
|             title: Text(otherside.account.nick), | ||||
|             subtitle: Text( | ||||
|               'channelDirectDescription' | ||||
|                   .trParams({'username': '@${otherside.account.name}'}), | ||||
|             ), | ||||
|             onTap: () { | ||||
|               AppRouter.instance.pushNamed( | ||||
|                 'channelChat', | ||||
|                 pathParameters: {'alias': element.alias}, | ||||
|                 queryParameters: { | ||||
|                   if (element.realmId != null) 'realm': element.realm!.alias, | ||||
|                 }, | ||||
|               ); | ||||
|             }, | ||||
|           ); | ||||
|         } | ||||
|  | ||||
|         return ListTile( | ||||
|           leading: const CircleAvatar( | ||||
|             backgroundColor: Colors.indigo, | ||||
|             child: FaIcon( | ||||
|               FontAwesomeIcons.hashtag, | ||||
|               color: Colors.white, | ||||
|               size: 16, | ||||
|             ), | ||||
|           ), | ||||
|           contentPadding: const EdgeInsets.symmetric(horizontal: 24), | ||||
|           title: Text(element.name), | ||||
|           subtitle: Text(element.description), | ||||
|           onTap: () { | ||||
|             AppRouter.instance.pushNamed( | ||||
|               'channelChat', | ||||
|               pathParameters: {'alias': element.alias}, | ||||
|               queryParameters: { | ||||
|                 if (element.realmId != null) 'realm': element.realm!.alias, | ||||
|               }, | ||||
|             ); | ||||
|           }, | ||||
|         ); | ||||
|       }, | ||||
|     ); | ||||
|   } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user