feat(ui): Render date bubbles in a less stupid way

This commit is contained in:
PapaTutuWawa 2023-02-18 16:43:24 +01:00
parent e78dae0950
commit 28591a6787
3 changed files with 48 additions and 86 deletions

View File

@ -3,9 +3,9 @@ import 'package:emoji_picker_flutter/emoji_picker_flutter.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.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:flutter_vibrate/flutter_vibrate.dart';
import 'package:get_it/get_it.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/i18n/strings.g.dart';
import 'package:moxxyv2/shared/error_types.dart'; import 'package:moxxyv2/shared/error_types.dart';
import 'package:moxxyv2/shared/helpers.dart'; import 'package:moxxyv2/shared/helpers.dart';
@ -40,7 +40,7 @@ class ConversationPage extends StatefulWidget {
class ConversationPageState extends State<ConversationPage> with TickerProviderStateMixin { class ConversationPageState extends State<ConversationPage> with TickerProviderStateMixin {
final TextEditingController _controller = TextEditingController(); final TextEditingController _controller = TextEditingController();
final FlutterListViewController _scrollController = FlutterListViewController(); final ScrollController _scrollController = ScrollController();
late final AnimationController _animationController; late final AnimationController _animationController;
late final AnimationController _overviewAnimationController; late final AnimationController _overviewAnimationController;
late final TabController _tabController; 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) { Widget _renderBubble(ConversationState state, Message message, int index, double maxWidth) {
if (_index.isEven) { final item = message;
// 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];
if (item.isPseudoMessage) { if (item.isPseudoMessage) {
return Row( return Row(
@ -188,12 +133,12 @@ class ConversationPageState extends State<ConversationPage> with TickerProviderS
final start = index - 1 < 0 ? final start = index - 1 < 0 ?
true : 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 ? final end = index + 1 >= state.messages.length ?
true : 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 between = !start && !end;
final sentBySelf = isSent(item, jid); final sentBySelf = isSent(message, state.jid);
final bubble = RawChatBubble( final bubble = RawChatBubble(
item, item,
@ -210,27 +155,29 @@ class ConversationPageState extends State<ConversationPage> with TickerProviderS
message: item, message: item,
sentBySelf: sentBySelf, sentBySelf: sentBySelf,
maxWidth: maxWidth, maxWidth: maxWidth,
onSwipedCallback: (_) => _quoteMessage(context, item), onSwipedCallback: (_) => _quoteMessage(context, message),
onReactionTap: (reaction) { onReactionTap: (reaction) {
final bloc = context.read<ConversationBloc>(); final bloc = context.read<ConversationBloc>();
if (reaction.reactedBySelf) { if (reaction.reactedBySelf) {
bloc.add( bloc.add(
ReactionRemovedEvent( ReactionRemovedEvent(
reaction.emoji, reaction.emoji,
index, //index,
0,
), ),
); );
} else { } else {
bloc.add( bloc.add(
ReactionAddedEvent( ReactionAddedEvent(
reaction.emoji, reaction.emoji,
index, //index,
0,
), ),
); );
} }
}, },
onLongPressed: (event) async { onLongPressed: (event) async {
if (!item.isLongpressable) { if (!message.isLongpressable) {
return; return;
} }
@ -301,7 +248,8 @@ class ConversationPageState extends State<ConversationPage> with TickerProviderS
if (emoji != null) { if (emoji != null) {
// ignore: use_build_context_synchronously // ignore: use_build_context_synchronously
context.read<ConversationBloc>().add( 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. // be static over the entire lifetime of the BLoC.
buildWhen: (prev, next) => prev.messages != next.messages || prev.conversation?.encrypted != next.conversation?.encrypted, buildWhen: (prev, next) => prev.messages != next.messages || prev.conversation?.encrypted != next.conversation?.encrypted,
builder: (context, state) => Expanded( builder: (context, state) => Expanded(
child: FlutterListView( // Inspired by https://github.com/SimformSolutionsPvtLtd/flutter_chatview/blob/main/lib/src/widgets/chat_groupedlist_widget.dart
shrinkWrap: true, child: SingleChildScrollView(
controller: _scrollController,
reverse: true, reverse: true,
delegate: FlutterListViewDelegate( controller: _scrollController,
(BuildContext context, int index) => _renderBubble( 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, state,
context, message,
index, index,
maxWidth, maxWidth,
state.jid,
), ),
childCount: state.messages.length * 2,
keepPosition: true,
keepPositionOffset: 40,
firstItemAlign: FirstItemAlign.end,
), ),
), ),
), ),

View File

@ -489,13 +489,6 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.1" 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: flutter_localizations:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
@ -642,6 +635,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.0" 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: hex:
dependency: "direct main" dependency: "direct main"
description: description:

View File

@ -34,7 +34,6 @@ dependencies:
flutter_image_compress: 1.1.0 flutter_image_compress: 1.1.0
flutter_isolate: 2.0.2 flutter_isolate: 2.0.2
flutter_keyboard_visibility: 5.4.0 flutter_keyboard_visibility: 5.4.0
flutter_list_view: 1.1.21
flutter_localizations: flutter_localizations:
sdk: flutter sdk: flutter
flutter_parsed_text: 2.2.1 flutter_parsed_text: 2.2.1
@ -45,6 +44,7 @@ dependencies:
fluttertoast: 8.1.1 fluttertoast: 8.1.1
freezed_annotation: 2.1.0 freezed_annotation: 2.1.0
get_it: 7.2.0 get_it: 7.2.0
grouped_list: 5.1.2
hex: 0.2.0 hex: 0.2.0
image: 3.2.0 image: 3.2.0
json_annotation: 4.6.0 json_annotation: 4.6.0