feat(ui): Render date bubbles in a less stupid way
This commit is contained in:
		
							parent
							
								
									e78dae0950
								
							
						
					
					
						commit
						28591a6787
					
				| @ -3,9 +3,9 @@ import 'package:emoji_picker_flutter/emoji_picker_flutter.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter/services.dart'; | ||||
| import 'package:flutter_bloc/flutter_bloc.dart'; | ||||
| import 'package:flutter_list_view/flutter_list_view.dart'; | ||||
| import 'package:flutter_vibrate/flutter_vibrate.dart'; | ||||
| import 'package:get_it/get_it.dart'; | ||||
| import 'package:grouped_list/grouped_list.dart'; | ||||
| import 'package:moxxyv2/i18n/strings.g.dart'; | ||||
| import 'package:moxxyv2/shared/error_types.dart'; | ||||
| import 'package:moxxyv2/shared/helpers.dart'; | ||||
| @ -40,7 +40,7 @@ class ConversationPage extends StatefulWidget { | ||||
| 
 | ||||
| class ConversationPageState extends State<ConversationPage> with TickerProviderStateMixin { | ||||
|   final TextEditingController _controller = TextEditingController(); | ||||
|   final FlutterListViewController _scrollController = FlutterListViewController(); | ||||
|   final ScrollController _scrollController = ScrollController(); | ||||
|   late final AnimationController _animationController;  | ||||
|   late final AnimationController _overviewAnimationController; | ||||
|   late final TabController _tabController; | ||||
| @ -111,63 +111,8 @@ class ConversationPageState extends State<ConversationPage> with TickerProviderS | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   Widget _renderBubble(ConversationState state, BuildContext context, int _index, double maxWidth, String jid) { | ||||
|     if (_index.isEven) { | ||||
|       // Render a date bubble at the top of the list | ||||
|       if (_index == 2 * state.messages.length - 1) { | ||||
|         return Row( | ||||
|           mainAxisAlignment: MainAxisAlignment.center, | ||||
|           children: [ | ||||
|             DateBubble( | ||||
|               formatDateBubble( | ||||
|                 DateTime.fromMillisecondsSinceEpoch( | ||||
|                   state.messages.last.timestamp, | ||||
|                 ), | ||||
|                 DateTime.now(), | ||||
|               ), | ||||
|             ), | ||||
|           ], | ||||
|         ); | ||||
|       } | ||||
| 
 | ||||
|       final prevIndexRaw = (_index + 2) ~/ 2; | ||||
|       final prevIndex = state.messages.length - prevIndexRaw; | ||||
|       final prevMessageDateTime = prevIndex < 0 || prevIndexRaw == 0 ? | ||||
|         null : | ||||
|         DateTime.fromMillisecondsSinceEpoch( | ||||
|           state.messages[prevIndex].timestamp, | ||||
|         ); | ||||
| 
 | ||||
|       if (prevMessageDateTime == null) return const SizedBox(); | ||||
| 
 | ||||
|       final nextIndexRaw = _index ~/ 2; | ||||
|       final nextIndex = state.messages.length - nextIndexRaw; | ||||
|       final nextMessageDateTime = nextIndex < 0 || nextIndexRaw == 0 ? | ||||
|         null : | ||||
|         DateTime.fromMillisecondsSinceEpoch( | ||||
|           state.messages[nextIndex].timestamp, | ||||
|         ); | ||||
|       if (nextMessageDateTime == null) return const SizedBox(); | ||||
|        | ||||
|       // Check if we have to render a date bubble | ||||
|       if (prevMessageDateTime.day != nextMessageDateTime.day || | ||||
|           prevMessageDateTime.month != nextMessageDateTime.month || | ||||
|           prevMessageDateTime.year != nextMessageDateTime.year) { | ||||
|         return Row( | ||||
|           mainAxisAlignment: MainAxisAlignment.center, | ||||
|           children: [ | ||||
|             DateBubble( | ||||
|               formatDateBubble(nextMessageDateTime, DateTime.now()), | ||||
|             ), | ||||
|           ], | ||||
|         ); | ||||
|       } | ||||
| 
 | ||||
|       return const SizedBox(); | ||||
|     } | ||||
|      | ||||
|     final index = state.messages.length - 1 - (_index - 1) ~/ 2; | ||||
|     final item = state.messages[index]; | ||||
|   Widget _renderBubble(ConversationState state, Message message, int index, double maxWidth) { | ||||
|     final item = message; | ||||
| 
 | ||||
|     if (item.isPseudoMessage) { | ||||
|       return Row( | ||||
| @ -188,12 +133,12 @@ class ConversationPageState extends State<ConversationPage> with TickerProviderS | ||||
| 
 | ||||
|     final start = index - 1 < 0 ? | ||||
|       true : | ||||
|       isSent(state.messages[index - 1], jid) != isSent(item, jid); | ||||
|       isSent(state.messages[index - 1], state.jid) != isSent(item, state.jid); | ||||
|     final end = index + 1 >= state.messages.length ? | ||||
|       true : | ||||
|       isSent(state.messages[index + 1], jid) != isSent(item, jid); | ||||
|       isSent(state.messages[index + 1], state.jid) != isSent(item, state.jid); | ||||
|     final between = !start && !end; | ||||
|     final sentBySelf = isSent(item, jid); | ||||
|     final sentBySelf = isSent(message, state.jid); | ||||
|      | ||||
|     final bubble = RawChatBubble( | ||||
|       item, | ||||
| @ -210,27 +155,29 @@ class ConversationPageState extends State<ConversationPage> with TickerProviderS | ||||
|       message: item, | ||||
|       sentBySelf: sentBySelf, | ||||
|       maxWidth: maxWidth, | ||||
|       onSwipedCallback: (_) => _quoteMessage(context, item), | ||||
|       onSwipedCallback: (_) => _quoteMessage(context, message), | ||||
|       onReactionTap: (reaction) { | ||||
|         final bloc = context.read<ConversationBloc>(); | ||||
|         if (reaction.reactedBySelf) { | ||||
|           bloc.add( | ||||
|             ReactionRemovedEvent( | ||||
|               reaction.emoji, | ||||
|               index, | ||||
|               //index, | ||||
|               0, | ||||
|             ), | ||||
|           ); | ||||
|         } else { | ||||
|           bloc.add( | ||||
|             ReactionAddedEvent( | ||||
|               reaction.emoji, | ||||
|               index, | ||||
|               //index, | ||||
|               0, | ||||
|             ), | ||||
|           ); | ||||
|         } | ||||
|       }, | ||||
|       onLongPressed: (event) async { | ||||
|         if (!item.isLongpressable) { | ||||
|         if (!message.isLongpressable) { | ||||
|           return; | ||||
|         } | ||||
| 
 | ||||
| @ -301,7 +248,8 @@ class ConversationPageState extends State<ConversationPage> with TickerProviderS | ||||
|                     if (emoji != null) { | ||||
|                       // ignore: use_build_context_synchronously | ||||
|                       context.read<ConversationBloc>().add( | ||||
|                         ReactionAddedEvent(emoji, index), | ||||
|                         //ReactionAddedEvent(emoji, index), | ||||
|                         ReactionAddedEvent(emoji, 0), | ||||
|                       ); | ||||
|                     } | ||||
| 
 | ||||
| @ -552,22 +500,36 @@ class ConversationPageState extends State<ConversationPage> with TickerProviderS | ||||
|                     //       be static over the entire lifetime of the BLoC. | ||||
|                     buildWhen: (prev, next) => prev.messages != next.messages || prev.conversation?.encrypted != next.conversation?.encrypted, | ||||
|                     builder: (context, state) => Expanded( | ||||
|                       child: FlutterListView( | ||||
|                         shrinkWrap: true, | ||||
|                         controller: _scrollController, | ||||
|                       // Inspired by https://github.com/SimformSolutionsPvtLtd/flutter_chatview/blob/main/lib/src/widgets/chat_groupedlist_widget.dart | ||||
|                       child: SingleChildScrollView( | ||||
|                         reverse: true, | ||||
|                         delegate: FlutterListViewDelegate( | ||||
|                           (BuildContext context, int index) => _renderBubble( | ||||
|                         controller: _scrollController, | ||||
|                         child: GroupedListView<Message, DateTime>( | ||||
|                           elements: state.messages, | ||||
|                           shrinkWrap: true, | ||||
|                           physics: const NeverScrollableScrollPhysics(), | ||||
|                           groupBy: (message) { | ||||
|                             final dt = DateTime.fromMillisecondsSinceEpoch(message.timestamp); | ||||
|                             return DateTime( | ||||
|                               dt.year, | ||||
|                               dt.month, | ||||
|                               dt.day, | ||||
|                             ); | ||||
|                           }, | ||||
|                           groupSeparatorBuilder: (DateTime dt) => Row( | ||||
|                             mainAxisAlignment: MainAxisAlignment.center, | ||||
|                             children: [ | ||||
|                               DateBubble( | ||||
|                                 formatDateBubble(dt, DateTime.now()), | ||||
|                               ), | ||||
|                             ], | ||||
|                           ), | ||||
|                           indexedItemBuilder: (context, message, index) => _renderBubble( | ||||
|                             state, | ||||
|                             context, | ||||
|                             message, | ||||
|                             index, | ||||
|                             maxWidth, | ||||
|                             state.jid,                            | ||||
|                           ), | ||||
|                           childCount: state.messages.length * 2, | ||||
|                           keepPosition: true, | ||||
|                           keepPositionOffset: 40, | ||||
|                           firstItemAlign: FirstItemAlign.end, | ||||
|                         ), | ||||
|                       ), | ||||
|                     ), | ||||
|  | ||||
							
								
								
									
										14
									
								
								pubspec.lock
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								pubspec.lock
									
									
									
									
									
								
							| @ -489,13 +489,6 @@ packages: | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "2.0.1" | ||||
|   flutter_list_view: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: flutter_list_view | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "1.1.21" | ||||
|   flutter_localizations: | ||||
|     dependency: "direct main" | ||||
|     description: flutter | ||||
| @ -642,6 +635,13 @@ packages: | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "2.1.0" | ||||
|   grouped_list: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: grouped_list | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "5.1.2" | ||||
|   hex: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|  | ||||
| @ -34,7 +34,6 @@ dependencies: | ||||
|   flutter_image_compress: 1.1.0 | ||||
|   flutter_isolate: 2.0.2 | ||||
|   flutter_keyboard_visibility: 5.4.0 | ||||
|   flutter_list_view: 1.1.21 | ||||
|   flutter_localizations: | ||||
|     sdk: flutter | ||||
|   flutter_parsed_text: 2.2.1 | ||||
| @ -45,6 +44,7 @@ dependencies: | ||||
|   fluttertoast: 8.1.1 | ||||
|   freezed_annotation: 2.1.0 | ||||
|   get_it: 7.2.0 | ||||
|   grouped_list: 5.1.2 | ||||
|   hex: 0.2.0 | ||||
|   image: 3.2.0 | ||||
|   json_annotation: 4.6.0 | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user