278 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
			
		
		
	
	
			278 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
| import 'dart:math';
 | |
| 
 | |
| import 'package:flutter/material.dart';
 | |
| import 'package:font_awesome_flutter/font_awesome_flutter.dart';
 | |
| import 'package:gap/gap.dart';
 | |
| import 'package:get/get.dart';
 | |
| import 'package:intl/intl.dart';
 | |
| import 'package:solian/controllers/chat_events_controller.dart';
 | |
| import 'package:solian/models/channel.dart';
 | |
| import 'package:solian/platform.dart';
 | |
| import 'package:solian/providers/database/database.dart';
 | |
| import 'package:solian/router.dart';
 | |
| import 'package:solian/widgets/account/account_avatar.dart';
 | |
| import 'package:badges/badges.dart' as badges;
 | |
| 
 | |
| class ChannelListWidget extends StatefulWidget {
 | |
|   final List<Channel> channels;
 | |
|   final int selfId;
 | |
|   final bool useReplace;
 | |
|   final Function(Channel)? onSelected;
 | |
| 
 | |
|   const ChannelListWidget({
 | |
|     super.key,
 | |
|     required this.channels,
 | |
|     required this.selfId,
 | |
|     this.useReplace = false,
 | |
|     this.onSelected,
 | |
|   });
 | |
| 
 | |
|   @override
 | |
|   State<ChannelListWidget> createState() => _ChannelListWidgetState();
 | |
| }
 | |
| 
 | |
| class _ChannelListWidgetState extends State<ChannelListWidget> {
 | |
|   Map<int, LocalMessageEventTableData>? _lastMessages;
 | |
| 
 | |
|   final ChatEventController _eventController = ChatEventController();
 | |
| 
 | |
|   Future<void> _loadLastMessages() async {
 | |
|     final messages = await _eventController.src.getLastInAllChannels();
 | |
|     setState(() {
 | |
|       _lastMessages = messages
 | |
|           .map((k, v) => MapEntry(k, v.firstOrNull))
 | |
|           .cast<int, LocalMessageEventTableData>();
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   @override
 | |
|   void initState() {
 | |
|     super.initState();
 | |
|     _eventController.initialize().then((_) {
 | |
|       _loadLastMessages();
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   void _gotoChannel(Channel item) {
 | |
|     if (widget.useReplace) {
 | |
|       AppRouter.instance.pushReplacementNamed(
 | |
|         'channelChat',
 | |
|         pathParameters: {'alias': item.alias},
 | |
|         queryParameters: {
 | |
|           if (item.realmId != null) 'realm': item.realm!.alias,
 | |
|         },
 | |
|       );
 | |
|     } else {
 | |
|       AppRouter.instance.pushNamed(
 | |
|         'channelChat',
 | |
|         pathParameters: {'alias': item.alias},
 | |
|         queryParameters: {
 | |
|           if (item.realmId != null) 'realm': item.realm!.alias,
 | |
|         },
 | |
|       );
 | |
|     }
 | |
| 
 | |
|     if (widget.onSelected != null) {
 | |
|       widget.onSelected!(item);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   Widget _buildTitle(Channel item, ChannelMember? otherside) {
 | |
|     if (otherside != null) {
 | |
|       return Row(
 | |
|         crossAxisAlignment: CrossAxisAlignment.center,
 | |
|         children: [
 | |
|           Expanded(child: Text(otherside.account.nick)),
 | |
|           if (_lastMessages != null && _lastMessages![item.id] != null)
 | |
|             Text(
 | |
|               DateFormat('MM/dd').format(
 | |
|                 _lastMessages![item.id]!.createdAt.toLocal(),
 | |
|               ),
 | |
|               style: TextStyle(
 | |
|                 fontSize: 12,
 | |
|                 color:
 | |
|                     Theme.of(context).colorScheme.onSurface.withOpacity(0.75),
 | |
|               ),
 | |
|             ),
 | |
|         ],
 | |
|       );
 | |
|     }
 | |
|     return Row(
 | |
|       crossAxisAlignment: CrossAxisAlignment.center,
 | |
|       children: [
 | |
|         Expanded(child: Text(item.name)),
 | |
|         if (_lastMessages != null && _lastMessages![item.id] != null)
 | |
|           Text(
 | |
|             DateFormat('MM/dd').format(
 | |
|               _lastMessages![item.id]!.createdAt.toLocal(),
 | |
|             ),
 | |
|             style: TextStyle(
 | |
|               fontSize: 12,
 | |
|               color: Theme.of(context).colorScheme.onSurface.withOpacity(0.75),
 | |
|             ),
 | |
|           ),
 | |
|       ],
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   Widget _buildSubtitle(Channel item, ChannelMember? otherside) {
 | |
|     if (PlatformInfo.isWeb) {
 | |
|       return otherside != null
 | |
|           ? Text(
 | |
|               'channelDirectDescription'.trParams(
 | |
|                 {'username': '@${otherside.account.name}'},
 | |
|               ),
 | |
|               maxLines: 1,
 | |
|               overflow: TextOverflow.ellipsis,
 | |
|             )
 | |
|           : Text(
 | |
|               item.description,
 | |
|               maxLines: 1,
 | |
|               overflow: TextOverflow.ellipsis,
 | |
|             );
 | |
|     }
 | |
| 
 | |
|     return AnimatedSwitcher(
 | |
|       switchInCurve: Curves.easeIn,
 | |
|       switchOutCurve: Curves.easeOut,
 | |
|       transitionBuilder: (child, animation) {
 | |
|         return FadeTransition(opacity: animation, child: child);
 | |
|       },
 | |
|       duration: const Duration(milliseconds: 300),
 | |
|       child: (_lastMessages == null || _lastMessages![item.id] == null)
 | |
|           ? Builder(
 | |
|               builder: (context) {
 | |
|                 return otherside != null
 | |
|                     ? Text(
 | |
|                         'channelDirectDescription'.trParams(
 | |
|                           {'username': '@${otherside.account.name}'},
 | |
|                         ),
 | |
|                         maxLines: 1,
 | |
|                         overflow: TextOverflow.ellipsis,
 | |
|                       )
 | |
|                     : Text(
 | |
|                         item.description,
 | |
|                         maxLines: 1,
 | |
|                         overflow: TextOverflow.ellipsis,
 | |
|                       );
 | |
|               },
 | |
|             )
 | |
|           : Builder(
 | |
|               builder: (context) {
 | |
|                 final data = _lastMessages![item.id]!.data!;
 | |
|                 return Row(
 | |
|                   crossAxisAlignment: CrossAxisAlignment.center,
 | |
|                   children: [
 | |
|                     if (item.type == 0)
 | |
|                       Badge(
 | |
|                         label: Text(data.sender.account.nick),
 | |
|                         backgroundColor:
 | |
|                             Theme.of(context).colorScheme.secondaryContainer,
 | |
|                         textColor:
 | |
|                             Theme.of(context).colorScheme.onSecondaryContainer,
 | |
|                       ),
 | |
|                     if (item.type == 0) const Gap(6),
 | |
|                     if (data.body['text'] != null)
 | |
|                       Expanded(
 | |
|                         child: Text(
 | |
|                           data.body['text'],
 | |
|                           maxLines: 1,
 | |
|                           overflow: TextOverflow.ellipsis,
 | |
|                         ),
 | |
|                       )
 | |
|                     else
 | |
|                       Badge(label: Text('unablePreview'.tr)),
 | |
|                   ],
 | |
|                 );
 | |
|               },
 | |
|             ),
 | |
|       layoutBuilder: (currentChild, previousChildren) {
 | |
|         return Stack(
 | |
|           alignment: Alignment.centerLeft,
 | |
|           children: <Widget>[
 | |
|             ...previousChildren,
 | |
|             if (currentChild != null) currentChild,
 | |
|           ],
 | |
|         );
 | |
|       },
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   Widget _buildEntry(Channel item) {
 | |
|     const padding = EdgeInsets.symmetric(horizontal: 20);
 | |
| 
 | |
|     final otherside =
 | |
|         item.members!.where((e) => e.account.id != widget.selfId).firstOrNull;
 | |
| 
 | |
|     if (item.type == 1 && otherside != null) {
 | |
|       final avatar = AccountAvatar(
 | |
|         content: otherside.account.avatar,
 | |
|         radius: 20,
 | |
|         bgColor: Theme.of(context).colorScheme.primary,
 | |
|         feColor: Theme.of(context).colorScheme.onPrimary,
 | |
|       );
 | |
| 
 | |
|       return ListTile(
 | |
|         leading: avatar,
 | |
|         contentPadding: padding,
 | |
|         title: _buildTitle(item, otherside),
 | |
|         subtitle: _buildSubtitle(item, otherside),
 | |
|         onTap: () => _gotoChannel(item),
 | |
|       );
 | |
|     } else {
 | |
|       final avatar = CircleAvatar(
 | |
|         backgroundColor: Theme.of(context).colorScheme.primary,
 | |
|         radius: 20,
 | |
|         child: FaIcon(
 | |
|           FontAwesomeIcons.hashtag,
 | |
|           color: Theme.of(context).colorScheme.onPrimary,
 | |
|           size: 16,
 | |
|         ),
 | |
|       );
 | |
| 
 | |
|       return ListTile(
 | |
|         minTileHeight: null,
 | |
|         leading: item.realmId == null
 | |
|             ? avatar
 | |
|             : badges.Badge(
 | |
|                 position: badges.BadgePosition.bottomEnd(bottom: -4, end: -6),
 | |
|                 badgeStyle: badges.BadgeStyle(
 | |
|                   badgeColor: Theme.of(context).colorScheme.secondaryContainer,
 | |
|                   padding: const EdgeInsets.all(2),
 | |
|                   elevation: 8,
 | |
|                 ),
 | |
|                 badgeContent: AccountAvatar(
 | |
|                   content: item.realm?.avatar,
 | |
|                   radius: 10,
 | |
|                   fallbackWidget: const Icon(
 | |
|                     Icons.workspaces,
 | |
|                     size: 16,
 | |
|                   ),
 | |
|                 ),
 | |
|                 child: avatar,
 | |
|               ),
 | |
|         contentPadding: padding,
 | |
|         title: _buildTitle(item, null),
 | |
|         subtitle: _buildSubtitle(item, null),
 | |
|         onTap: () => _gotoChannel(item),
 | |
|       );
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   @override
 | |
|   Widget build(BuildContext context) {
 | |
|     return CustomScrollView(
 | |
|       slivers: [
 | |
|         SliverList.builder(
 | |
|           itemCount: widget.channels.length,
 | |
|           itemBuilder: (context, index) {
 | |
|             final element = widget.channels[index];
 | |
|             return _buildEntry(element);
 | |
|           },
 | |
|         ),
 | |
|         SliverGap(max(16, MediaQuery.of(context).padding.bottom)),
 | |
|       ],
 | |
|     );
 | |
|   }
 | |
| }
 |