✨ Desktop chat list
🍱 Update launch sfx
			
			
This commit is contained in:
		
							
								
								
									
										
											BIN
										
									
								
								assets/audio/sfx/launch-done.mp3
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								assets/audio/sfx/launch-done.mp3
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| @@ -939,5 +939,7 @@ | ||||
|   "settingsSoundEffects": "Sound Effects", | ||||
|   "settingsSoundEffectsDescription": "Enable the sound effects around the app.", | ||||
|   "settingsResetMemorizedWindowSize": "Reset Window Size", | ||||
|   "settingsResetMemorizedWindowSizeDescription": "Reset the memorized window size, and set it to the default size." | ||||
|   "settingsResetMemorizedWindowSizeDescription": "Reset the memorized window size, and set it to the default size.", | ||||
|   "chatDirect": "Direct Messages", | ||||
|   "back": "返回" | ||||
| } | ||||
|   | ||||
| @@ -936,5 +936,7 @@ | ||||
|   "settingsSoundEffects": "声音效果", | ||||
|   "settingsSoundEffectsDescription": "在一些场合下启用声音特效。", | ||||
|   "settingsResetMemorizedWindowSize": "重置窗口大小", | ||||
|   "settingsResetMemorizedWindowSizeDescription": "重置记忆的窗口大小,以重新设置为默认大小。" | ||||
|   "settingsResetMemorizedWindowSizeDescription": "重置记忆的窗口大小,以重新设置为默认大小。", | ||||
|   "chatDirect": "私信", | ||||
|   "back": "返回" | ||||
| } | ||||
|   | ||||
| @@ -396,8 +396,8 @@ class _AppSplashScreenState extends State<_AppSplashScreen> with TrayListener { | ||||
|     final cfg = context.read<ConfigProvider>(); | ||||
|     if (!cfg.soundEffects) return; | ||||
|  | ||||
|     final player = AudioPlayer(playerId: 'launch-intro-player'); | ||||
|     await player.play(AssetSource('audio/sfx/launch-intro.mp3'), volume: 0.5); | ||||
|     final player = AudioPlayer(playerId: 'launch-done-player'); | ||||
|     await player.play(AssetSource('audio/sfx/launch-done.mp3'), volume: 0.8); | ||||
|     player.onPlayerComplete.listen((_) { | ||||
|       player.dispose(); | ||||
|     }); | ||||
|   | ||||
| @@ -4,7 +4,6 @@ import 'package:flutter/material.dart'; | ||||
| import 'package:go_router/go_router.dart'; | ||||
| import 'package:material_symbols_icons/symbols.dart'; | ||||
| import 'package:shared_preferences/shared_preferences.dart'; | ||||
| import 'package:surface/types/realm.dart'; | ||||
|  | ||||
| class AppNavListItem { | ||||
|   final String title; | ||||
| @@ -135,11 +134,4 @@ class NavigationProvider extends ChangeNotifier { | ||||
|     _currentIndex = idx; | ||||
|     notifyListeners(); | ||||
|   } | ||||
|  | ||||
|   SnRealm? focusedRealm; | ||||
|  | ||||
|   void setFocusedRealm(SnRealm? realm) { | ||||
|     focusedRealm = realm; | ||||
|     notifyListeners(); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,3 +1,5 @@ | ||||
| import 'package:animations/animations.dart'; | ||||
| import 'package:collection/collection.dart'; | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_expandable_fab/flutter_expandable_fab.dart'; | ||||
| @@ -6,11 +8,14 @@ import 'package:go_router/go_router.dart'; | ||||
| import 'package:google_fonts/google_fonts.dart'; | ||||
| import 'package:material_symbols_icons/symbols.dart'; | ||||
| import 'package:provider/provider.dart'; | ||||
| import 'package:styled_widget/styled_widget.dart'; | ||||
| import 'package:surface/providers/channel.dart'; | ||||
| import 'package:surface/providers/sn_network.dart'; | ||||
| import 'package:surface/providers/sn_realm.dart'; | ||||
| import 'package:surface/providers/user_directory.dart'; | ||||
| import 'package:surface/providers/userinfo.dart'; | ||||
| import 'package:surface/types/chat.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/app_bar_leading.dart'; | ||||
| @@ -18,6 +23,7 @@ import 'package:surface/widgets/dialog.dart'; | ||||
| import 'package:surface/widgets/loading_indicator.dart'; | ||||
| import 'package:surface/widgets/navigation/app_scaffold.dart'; | ||||
| import 'package:surface/widgets/unauthorized_hint.dart'; | ||||
| import 'package:surface/widgets/universal_image.dart'; | ||||
| import 'package:uuid/uuid.dart'; | ||||
|  | ||||
| class ChatScreen extends StatefulWidget { | ||||
| @@ -35,6 +41,7 @@ class _ChatScreenState extends State<ChatScreen> { | ||||
|   List<SnChannel>? _channels; | ||||
|   Map<int, SnChatMessage>? _lastMessages; | ||||
|   Map<int, int>? _unreadCounts; | ||||
|   Map<int, int>? _unreadCountsGrouped; | ||||
|  | ||||
|   Future<void> _fetchWhatsNew() async { | ||||
|     final sn = context.read<SnNetworkProvider>(); | ||||
| @@ -42,19 +49,48 @@ class _ChatScreenState extends State<ChatScreen> { | ||||
|     if (resp.data == null) return; | ||||
|     final List<dynamic> out = resp.data; | ||||
|     setState(() { | ||||
|       _unreadCounts = {for (var v in out) v['channel_id']: v['count']}; | ||||
|       _unreadCounts ??= {}; | ||||
|       _unreadCountsGrouped ??= {}; | ||||
|       for (var v in out) { | ||||
|         _unreadCounts![v['channel_id']] = v['count']; | ||||
|         final channel = | ||||
|             _channels?.firstWhereOrNull((ele) => ele.id == v['channel_id']); | ||||
|         if (channel != null) { | ||||
|           if (channel.realmId != null) { | ||||
|             _unreadCountsGrouped![channel.realmId!] ??= 0; | ||||
|             _unreadCountsGrouped![channel.realmId!] = | ||||
|                 (_unreadCountsGrouped![channel.realmId!]! + v['count']).toInt(); | ||||
|           } | ||||
|           if (channel.type == 1) { | ||||
|             _unreadCountsGrouped![0] ??= 0; | ||||
|             _unreadCountsGrouped![0] = | ||||
|                 (_unreadCountsGrouped![0]! + v['count']).toInt(); | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   void _refreshChannels({bool noRemote = false}) { | ||||
|   void _refreshChannels({bool withBoost = false, bool noRemote = false}) { | ||||
|     final ct = context.read<ChatChannelProvider>(); | ||||
|     final ua = context.read<UserProvider>(); | ||||
|     if (!ua.isAuthorized) { | ||||
|       setState(() => _isBusy = false); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     if (!withBoost) { | ||||
|       if (!noRemote) { | ||||
|         ct.refreshAvailableChannels(); | ||||
|       } | ||||
|     } else { | ||||
|       setState(() { | ||||
|         _channels = ct.availableChannels; | ||||
|       }); | ||||
|     } | ||||
|  | ||||
|     final chan = context.read<ChatChannelProvider>(); | ||||
|     chan.fetchChannels(noRemote: noRemote).listen((channels) async { | ||||
|     chan.fetchChannels(noRemote: true).listen((channels) async { | ||||
|       final lastMessages = await chan.getLastMessages(channels); | ||||
|       _lastMessages = {for (final val in lastMessages) val.channelId: val}; | ||||
|       channels.sort((a, b) { | ||||
| @@ -130,29 +166,49 @@ class _ChatScreenState extends State<ChatScreen> { | ||||
|   @override | ||||
|   void initState() { | ||||
|     super.initState(); | ||||
|     _refreshChannels(); | ||||
|     _refreshChannels(withBoost: true); | ||||
|     _fetchWhatsNew(); | ||||
|   } | ||||
|  | ||||
|   void _onTapChannel(SnChannel channel) { | ||||
|     setState(() => _unreadCounts?[channel.id] = 0); | ||||
|     GoRouter.of(context).pushReplacementNamed( | ||||
|       'chatRoom', | ||||
|       pathParameters: { | ||||
|         'scope': channel.realm?.alias ?? 'global', | ||||
|         'alias': channel.alias, | ||||
|       }, | ||||
|     ).then((value) { | ||||
|       if (mounted) { | ||||
|         setState(() => _unreadCounts?[channel.id] = 0); | ||||
|         _refreshChannels(noRemote: true); | ||||
|       } | ||||
|     }); | ||||
|     if (ResponsiveScaffold.getIsExpand(context)) { | ||||
|       GoRouter.of(context).pushReplacementNamed( | ||||
|         'chatRoom', | ||||
|         pathParameters: { | ||||
|           'scope': channel.realm?.alias ?? 'global', | ||||
|           'alias': channel.alias, | ||||
|         }, | ||||
|       ).then((value) { | ||||
|         if (mounted) { | ||||
|           setState(() => _unreadCounts?[channel.id] = 0); | ||||
|           _refreshChannels(noRemote: true); | ||||
|         } | ||||
|       }); | ||||
|     } else { | ||||
|       GoRouter.of(context).pushNamed( | ||||
|         'chatRoom', | ||||
|         pathParameters: { | ||||
|           'scope': channel.realm?.alias ?? 'global', | ||||
|           'alias': channel.alias, | ||||
|         }, | ||||
|       ).then((value) { | ||||
|         if (mounted) { | ||||
|           setState(() => _unreadCounts?[channel.id] = 0); | ||||
|           _refreshChannels(noRemote: true); | ||||
|         } | ||||
|       }); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   SnRealm? _focusedRealm; | ||||
|   bool _isDirect = false; | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     final ua = context.read<UserProvider>(); | ||||
|     final sn = context.read<SnNetworkProvider>(); | ||||
|     final rel = context.read<SnRealmProvider>(); | ||||
|  | ||||
|     if (!ua.isAuthorized) { | ||||
|       return AppScaffold( | ||||
| @@ -235,34 +291,178 @@ class _ChatScreenState extends State<ChatScreen> { | ||||
|       body: Column( | ||||
|         children: [ | ||||
|           LoadingIndicator(isActive: _isBusy), | ||||
|           Expanded( | ||||
|             child: MediaQuery.removePadding( | ||||
|               context: context, | ||||
|               removeTop: true, | ||||
|           if (_channels != null) | ||||
|             Expanded( | ||||
|               child: RefreshIndicator( | ||||
|                 onRefresh: () => Future.wait([ | ||||
|                   Future.sync(() => _refreshChannels()), | ||||
|                   _fetchWhatsNew(), | ||||
|                 ]), | ||||
|                 child: ListView.builder( | ||||
|                   itemCount: _channels?.length ?? 0, | ||||
|                   itemBuilder: (context, idx) { | ||||
|                     final channel = _channels![idx]; | ||||
|                     final lastMessage = _lastMessages?[channel.id]; | ||||
|                 child: Builder(builder: (context) { | ||||
|                   final scopeList = ListView( | ||||
|                     key: const Key('realm-list-view'), | ||||
|                     padding: EdgeInsets.zero, | ||||
|                     children: [ | ||||
|                       ListTile( | ||||
|                         minTileHeight: 48, | ||||
|                         leading: | ||||
|                             const Icon(Symbols.inbox_text).padding(right: 4), | ||||
|                         contentPadding: EdgeInsets.only(left: 24, right: 24), | ||||
|                         title: Text('chatDirect').tr(), | ||||
|                         trailing: Row( | ||||
|                           mainAxisSize: MainAxisSize.min, | ||||
|                           crossAxisAlignment: CrossAxisAlignment.center, | ||||
|                           children: [ | ||||
|                             if (_unreadCountsGrouped?[0] != null && | ||||
|                                 (_unreadCountsGrouped?[0] ?? 0) > 0) | ||||
|                               Badge( | ||||
|                                 label: Text( | ||||
|                                   _unreadCountsGrouped![0].toString(), | ||||
|                                 ), | ||||
|                               ), | ||||
|                           ], | ||||
|                         ), | ||||
|                         onTap: () { | ||||
|                           setState(() => _isDirect = true); | ||||
|                         }, | ||||
|                       ), | ||||
|                       ...rel.availableRealms.map((ele) { | ||||
|                         return ListTile( | ||||
|                           minTileHeight: 48, | ||||
|                           contentPadding: EdgeInsets.only(left: 20, right: 24), | ||||
|                           leading: AccountImage( | ||||
|                             content: ele.avatar, | ||||
|                             radius: 16, | ||||
|                           ), | ||||
|                           trailing: Row( | ||||
|                             mainAxisSize: MainAxisSize.min, | ||||
|                             crossAxisAlignment: CrossAxisAlignment.center, | ||||
|                             children: [ | ||||
|                               if (_unreadCountsGrouped?[ele.id] != null && | ||||
|                                   (_unreadCountsGrouped?[ele.id] ?? 0) > 0) | ||||
|                                 Badge( | ||||
|                                   label: Text( | ||||
|                                     _unreadCountsGrouped![ele.id].toString(), | ||||
|                                   ), | ||||
|                                 ), | ||||
|                             ], | ||||
|                           ), | ||||
|                           title: Text(ele.name), | ||||
|                           onTap: () { | ||||
|                             setState(() => _focusedRealm = ele); | ||||
|                           }, | ||||
|                         ); | ||||
|                       }), | ||||
|                     ], | ||||
|                   ); | ||||
|  | ||||
|                     return _ChatChannelEntry( | ||||
|                       channel: channel, | ||||
|                       lastMessage: lastMessage, | ||||
|                       unreadCount: _unreadCounts?[channel.id], | ||||
|                       onTap: () { | ||||
|                         _onTapChannel(channel); | ||||
|                       }, | ||||
|                     ); | ||||
|                   }, | ||||
|                 ), | ||||
|                   final directChatList = ListView( | ||||
|                     key: Key('direct-chat-list-view'), | ||||
|                     padding: EdgeInsets.zero, | ||||
|                     children: [ | ||||
|                       ListTile( | ||||
|                         minTileHeight: 48, | ||||
|                         leading: const Icon(Symbols.arrow_left_alt), | ||||
|                         contentPadding: EdgeInsets.only(left: 24), | ||||
|                         title: Text('back').tr(), | ||||
|                         onTap: () { | ||||
|                           setState(() => _isDirect = false); | ||||
|                         }, | ||||
|                       ), | ||||
|                       const Divider(height: 1), | ||||
|                       ..._channels!.where((ele) => ele.type == 1).map( | ||||
|                         (ele) { | ||||
|                           return _ChatChannelEntry( | ||||
|                             channel: ele, | ||||
|                             unreadCount: _unreadCounts?[ele.id], | ||||
|                             lastMessage: _lastMessages?[ele.id], | ||||
|                             isCompact: true, | ||||
|                             onTap: () => _onTapChannel(ele), | ||||
|                           ); | ||||
|                         }, | ||||
|                       ) | ||||
|                     ], | ||||
|                   ); | ||||
|  | ||||
|                   final realmScopedChatList = _focusedRealm == null | ||||
|                       ? const SizedBox.shrink() | ||||
|                       : ListView( | ||||
|                           key: ValueKey(_focusedRealm), | ||||
|                           padding: EdgeInsets.zero, | ||||
|                           children: [ | ||||
|                             if (_focusedRealm!.banner != null) | ||||
|                               AspectRatio( | ||||
|                                 aspectRatio: 16 / 9, | ||||
|                                 child: AutoResizeUniversalImage( | ||||
|                                   sn.getAttachmentUrl( | ||||
|                                     _focusedRealm!.banner!, | ||||
|                                   ), | ||||
|                                   fit: BoxFit.cover, | ||||
|                                 ), | ||||
|                               ), | ||||
|                             ListTile( | ||||
|                               minTileHeight: 48, | ||||
|                               tileColor: Theme.of(context) | ||||
|                                   .colorScheme | ||||
|                                   .surfaceContainer, | ||||
|                               leading: AccountImage( | ||||
|                                 content: _focusedRealm!.avatar, | ||||
|                                 radius: 16, | ||||
|                               ), | ||||
|                               contentPadding: EdgeInsets.only( | ||||
|                                 left: 20, | ||||
|                                 right: 16, | ||||
|                               ), | ||||
|                               trailing: IconButton( | ||||
|                                 icon: const Icon(Symbols.close), | ||||
|                                 padding: EdgeInsets.zero, | ||||
|                                 constraints: const BoxConstraints(), | ||||
|                                 visualDensity: VisualDensity.compact, | ||||
|                                 onPressed: () { | ||||
|                                   setState(() => _focusedRealm = null); | ||||
|                                 }, | ||||
|                               ), | ||||
|                               title: Text(_focusedRealm!.name), | ||||
|                             ), | ||||
|                             ...(_channels! | ||||
|                                 .where( | ||||
|                                     (ele) => ele.realmId == _focusedRealm?.id) | ||||
|                                 .map( | ||||
|                               (ele) { | ||||
|                                 return _ChatChannelEntry( | ||||
|                                   channel: ele, | ||||
|                                   unreadCount: _unreadCounts?[ele.id], | ||||
|                                   lastMessage: _lastMessages?[ele.id], | ||||
|                                   onTap: () => _onTapChannel(ele), | ||||
|                                   isCompact: true, | ||||
|                                 ); | ||||
|                               }, | ||||
|                             )) | ||||
|                           ], | ||||
|                         ); | ||||
|  | ||||
|                   return PageTransitionSwitcher( | ||||
|                     duration: const Duration(milliseconds: 300), | ||||
|                     transitionBuilder: (Widget child, | ||||
|                         Animation<double> primaryAnimation, | ||||
|                         Animation<double> secondaryAnimation) { | ||||
|                       return SharedAxisTransition( | ||||
|                         animation: primaryAnimation, | ||||
|                         secondaryAnimation: secondaryAnimation, | ||||
|                         fillColor: Colors.transparent, | ||||
|                         transitionType: SharedAxisTransitionType.horizontal, | ||||
|                         child: child, | ||||
|                       ); | ||||
|                     }, | ||||
|                     child: (_focusedRealm == null && !_isDirect) | ||||
|                         ? scopeList | ||||
|                         : _isDirect | ||||
|                             ? directChatList | ||||
|                             : realmScopedChatList, | ||||
|                   ); | ||||
|                 }), | ||||
|               ), | ||||
|             ), | ||||
|           ), | ||||
|         ], | ||||
|       ), | ||||
|     ); | ||||
| @@ -274,11 +474,13 @@ class _ChatChannelEntry extends StatelessWidget { | ||||
|   final int? unreadCount; | ||||
|   final SnChatMessage? lastMessage; | ||||
|   final Function? onTap; | ||||
|   final bool isCompact; | ||||
|   const _ChatChannelEntry({ | ||||
|     required this.channel, | ||||
|     this.unreadCount, | ||||
|     this.lastMessage, | ||||
|     this.onTap, | ||||
|     this.isCompact = false, | ||||
|   }); | ||||
|  | ||||
|   @override | ||||
| @@ -297,6 +499,34 @@ class _ChatChannelEntry extends StatelessWidget { | ||||
|         ? ud.getFromCache(otherMember.accountId)?.nick ?? channel.name | ||||
|         : channel.name; | ||||
|  | ||||
|     if (isCompact) { | ||||
|       return ListTile( | ||||
|         minTileHeight: 48, | ||||
|         contentPadding: | ||||
|             EdgeInsets.only(left: otherMember != null ? 20 : 24, right: 24), | ||||
|         leading: otherMember != null | ||||
|             ? AccountImage( | ||||
|                 content: ud.getFromCache(otherMember.accountId)?.avatar, | ||||
|                 radius: 16, | ||||
|               ) | ||||
|             : const Icon(Symbols.tag), | ||||
|         trailing: Row( | ||||
|           mainAxisSize: MainAxisSize.min, | ||||
|           crossAxisAlignment: CrossAxisAlignment.center, | ||||
|           children: [ | ||||
|             if (unreadCount != null && (unreadCount ?? 0) > 0) | ||||
|               Badge( | ||||
|                 label: Text(unreadCount.toString()), | ||||
|               ), | ||||
|           ], | ||||
|         ), | ||||
|         title: Text(title), | ||||
|         onTap: () { | ||||
|           onTap?.call(); | ||||
|         }, | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     return ListTile( | ||||
|       title: Row( | ||||
|         children: [ | ||||
| @@ -359,7 +589,7 @@ class _ChatChannelEntry extends StatelessWidget { | ||||
|         content: otherMember != null | ||||
|             ? ud.getFromCache(otherMember.accountId)?.avatar | ||||
|             : channel.realm?.avatar, | ||||
|         fallbackWidget: const Icon(Symbols.chat, size: 20), | ||||
|         fallbackWidget: const Icon(Symbols.tag, size: 20), | ||||
|       ), | ||||
|       onTap: () => onTap?.call(), | ||||
|     ); | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| import 'dart:io'; | ||||
|  | ||||
| import 'package:animations/animations.dart'; | ||||
| import 'package:bitsdojo_window/bitsdojo_window.dart'; | ||||
| import 'package:collection/collection.dart'; | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| @@ -11,13 +10,9 @@ import 'package:go_router/go_router.dart'; | ||||
| import 'package:material_symbols_icons/symbols.dart'; | ||||
| import 'package:provider/provider.dart'; | ||||
| import 'package:styled_widget/styled_widget.dart'; | ||||
| import 'package:surface/providers/channel.dart'; | ||||
| import 'package:surface/providers/navigation.dart'; | ||||
| import 'package:surface/providers/sn_network.dart'; | ||||
| import 'package:surface/providers/sn_realm.dart'; | ||||
| import 'package:surface/providers/userinfo.dart'; | ||||
| import 'package:surface/widgets/account/account_image.dart'; | ||||
| import 'package:surface/widgets/universal_image.dart'; | ||||
| import 'package:surface/widgets/version_label.dart'; | ||||
|  | ||||
| class AppNavigationDrawer extends StatefulWidget { | ||||
| @@ -151,163 +146,3 @@ class _AppNavigationDrawerState extends State<AppNavigationDrawer> { | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
| class _DrawerContentList extends StatelessWidget { | ||||
|   const _DrawerContentList(); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     final ct = context.read<ChatChannelProvider>(); | ||||
|     final sn = context.read<SnNetworkProvider>(); | ||||
|     final nav = context.watch<NavigationProvider>(); | ||||
|     final rel = context.watch<SnRealmProvider>(); | ||||
|  | ||||
|     return PageTransitionSwitcher( | ||||
|       duration: const Duration(milliseconds: 300), | ||||
|       transitionBuilder: (Widget child, Animation<double> primaryAnimation, | ||||
|           Animation<double> secondaryAnimation) { | ||||
|         return SharedAxisTransition( | ||||
|           animation: primaryAnimation, | ||||
|           secondaryAnimation: secondaryAnimation, | ||||
|           fillColor: Colors.transparent, | ||||
|           transitionType: SharedAxisTransitionType.horizontal, | ||||
|           child: child, | ||||
|         ); | ||||
|       }, | ||||
|       child: nav.focusedRealm == null | ||||
|           ? ListView( | ||||
|               key: const Key('realm-list-view'), | ||||
|               padding: EdgeInsets.zero, | ||||
|               children: [ | ||||
|                 Column( | ||||
|                   mainAxisSize: MainAxisSize.min, | ||||
|                   crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                   children: [ | ||||
|                     Text('Solar Network').bold(), | ||||
|                     AppVersionLabel(), | ||||
|                   ], | ||||
|                 ).padding( | ||||
|                   horizontal: 32, | ||||
|                   vertical: 12, | ||||
|                 ), | ||||
|                 ...rel.availableRealms.map((ele) { | ||||
|                   return ListTile( | ||||
|                     minTileHeight: 48, | ||||
|                     contentPadding: EdgeInsets.symmetric(horizontal: 24), | ||||
|                     leading: AccountImage( | ||||
|                       content: ele.avatar, | ||||
|                       radius: 16, | ||||
|                     ), | ||||
|                     title: Text(ele.name), | ||||
|                     onTap: () { | ||||
|                       nav.setFocusedRealm(ele); | ||||
|                     }, | ||||
|                   ); | ||||
|                 }), | ||||
|                 ListTile( | ||||
|                   minTileHeight: 48, | ||||
|                   contentPadding: EdgeInsets.only(left: 28, right: 16), | ||||
|                   leading: const Icon(Symbols.globe).padding(right: 4), | ||||
|                   title: Text('screenRealmDiscovery').tr(), | ||||
|                   onTap: () { | ||||
|                     GoRouter.of(context).pushNamed('realmDiscovery'); | ||||
|                     Scaffold.of(context).closeDrawer(); | ||||
|                   }, | ||||
|                 ), | ||||
|               ], | ||||
|             ) | ||||
|           : ListView( | ||||
|               key: ValueKey(nav.focusedRealm), | ||||
|               padding: EdgeInsets.zero, | ||||
|               children: [ | ||||
|                 if (nav.focusedRealm!.banner != null) | ||||
|                   AspectRatio( | ||||
|                     aspectRatio: 16 / 9, | ||||
|                     child: AutoResizeUniversalImage( | ||||
|                       sn.getAttachmentUrl( | ||||
|                         nav.focusedRealm!.banner!, | ||||
|                       ), | ||||
|                       fit: BoxFit.cover, | ||||
|                     ), | ||||
|                   ), | ||||
|                 ListTile( | ||||
|                   minTileHeight: 48, | ||||
|                   tileColor: Theme.of(context).colorScheme.surfaceContainer, | ||||
|                   contentPadding: EdgeInsets.only( | ||||
|                     left: 24, | ||||
|                     right: 16, | ||||
|                   ), | ||||
|                   leading: AccountImage( | ||||
|                     content: nav.focusedRealm!.avatar, | ||||
|                     radius: 16, | ||||
|                   ), | ||||
|                   trailing: IconButton( | ||||
|                     icon: const Icon(Symbols.close), | ||||
|                     padding: EdgeInsets.zero, | ||||
|                     constraints: const BoxConstraints(), | ||||
|                     visualDensity: VisualDensity.compact, | ||||
|                     onPressed: () { | ||||
|                       nav.setFocusedRealm(null); | ||||
|                     }, | ||||
|                   ), | ||||
|                   title: Text(nav.focusedRealm!.name), | ||||
|                   onTap: () { | ||||
|                     GoRouter.of(context).goNamed( | ||||
|                       'realmDetail', | ||||
|                       pathParameters: { | ||||
|                         'alias': nav.focusedRealm!.alias, | ||||
|                       }, | ||||
|                     ); | ||||
|                     Scaffold.of(context).closeDrawer(); | ||||
|                   }, | ||||
|                 ), | ||||
|                 ListTile( | ||||
|                   minTileHeight: 48, | ||||
|                   contentPadding: EdgeInsets.only( | ||||
|                     left: 28, | ||||
|                     right: 8, | ||||
|                   ), | ||||
|                   leading: const Icon(Symbols.globe), | ||||
|                   title: Text('community').tr(), | ||||
|                   onTap: () { | ||||
|                     GoRouter.of(context).goNamed( | ||||
|                       'realmCommunity', | ||||
|                       pathParameters: { | ||||
|                         'alias': nav.focusedRealm!.alias, | ||||
|                       }, | ||||
|                     ); | ||||
|                     Scaffold.of(context).closeDrawer(); | ||||
|                   }, | ||||
|                 ), | ||||
|                 if (ct.availableChannels | ||||
|                     .where((ele) => ele.realmId == nav.focusedRealm?.id) | ||||
|                     .isNotEmpty) | ||||
|                   const Divider(height: 1), | ||||
|                 ...(ct.availableChannels | ||||
|                     .where((ele) => ele.realmId == nav.focusedRealm?.id) | ||||
|                     .map((ele) { | ||||
|                   return ListTile( | ||||
|                     minTileHeight: 48, | ||||
|                     contentPadding: EdgeInsets.only( | ||||
|                       left: 28, | ||||
|                       right: 8, | ||||
|                     ), | ||||
|                     leading: const Icon(Symbols.tag), | ||||
|                     title: Text(ele.name), | ||||
|                     onTap: () { | ||||
|                       GoRouter.of(context).goNamed( | ||||
|                         'chatRoom', | ||||
|                         pathParameters: { | ||||
|                           'scope': ele.realm?.alias ?? 'global', | ||||
|                           'alias': ele.alias, | ||||
|                         }, | ||||
|                       ); | ||||
|                       Scaffold.of(context).closeDrawer(); | ||||
|                     }, | ||||
|                   ); | ||||
|                 })) | ||||
|               ], | ||||
|             ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user