chat fixes

This commit is contained in:
Ben Allfree
2026-02-23 04:09:27 -08:00
parent 53d073d8f2
commit 549fc62632
6 changed files with 187 additions and 90 deletions
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e3e3e3"><path d="M268-240 42-466l57-56 170 170 56 56-57 56Zm226 0L268-466l56-57 170 170 368-368 56 57-424 424Zm0-226-57-56 198-198 57 56-198 198Z"/></svg>

After

Width:  |  Height:  |  Size: 253 B

+57 -43
View File
@@ -4,6 +4,7 @@ import 'dart:math' as math;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart'; import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:flutter_linkify/flutter_linkify.dart'; import 'package:flutter_linkify/flutter_linkify.dart';
import 'package:latlong2/latlong.dart'; import 'package:latlong2/latlong.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@@ -23,6 +24,7 @@ import '../widgets/emoji_picker.dart';
import '../widgets/gif_message.dart'; import '../widgets/gif_message.dart';
import '../widgets/jump_to_bottom_button.dart'; import '../widgets/jump_to_bottom_button.dart';
import '../widgets/gif_picker.dart'; import '../widgets/gif_picker.dart';
import '../widgets/message_status_icon.dart';
import 'channel_message_path_screen.dart'; import 'channel_message_path_screen.dart';
import 'map_screen.dart'; import 'map_screen.dart';
@@ -337,7 +339,23 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
const SizedBox(height: 8), const SizedBox(height: 8),
], ],
if (poi != null) if (poi != null)
_buildPoiMessage(context, poi, isOutgoing) _buildPoiMessage(
context,
poi,
isOutgoing,
trailing: (!enableTracing && isOutgoing)
? Padding(
padding: const EdgeInsets.only(bottom: 2),
child: MessageStatusIcon(
isAcked: message.status ==
ChannelMessageStatus.sent &&
displayPath.isNotEmpty,
isFailed: message.status ==
ChannelMessageStatus.failed,
),
)
: null,
)
else if (gifId != null) else if (gifId != null)
Stack( Stack(
children: [ children: [
@@ -358,33 +376,31 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
), ),
if (!enableTracing && isOutgoing) if (!enableTracing && isOutgoing)
Positioned( Positioned(
top: 4, top: 0,
right: 4, right: 0,
child: Container( child: Container(
padding: const EdgeInsets.all(2), padding: const EdgeInsets.all(3),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.black.withValues(alpha: 0.3), color: isOutgoing
shape: BoxShape.circle, ? Theme.of(
context,
).colorScheme.primaryContainer
: Theme.of(
context,
).colorScheme.surfaceContainerHighest,
borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(10),
topRight: Radius.circular(8),
), ),
child: Icon( ),
(message.status == child: MessageStatusIcon(
isAcked:
message.status ==
ChannelMessageStatus.sent && ChannelMessageStatus.sent &&
displayPath.isNotEmpty) displayPath.isNotEmpty,
? Icons.check_circle isFailed:
: message.status == message.status ==
ChannelMessageStatus.failed ChannelMessageStatus.failed,
? Icons.cancel
: Icons.cloud,
size: 14,
color:
(message.status ==
ChannelMessageStatus.sent &&
displayPath.isNotEmpty)
? Colors.green
: message.status ==
ChannelMessageStatus.failed
? Colors.red
: Colors.white70,
), ),
), ),
), ),
@@ -419,25 +435,14 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
const SizedBox(width: 4), const SizedBox(width: 4),
Padding( Padding(
padding: const EdgeInsets.only(bottom: 2), padding: const EdgeInsets.only(bottom: 2),
child: Icon( child: MessageStatusIcon(
(message.status == isAcked:
message.status ==
ChannelMessageStatus.sent && ChannelMessageStatus.sent &&
displayPath.isNotEmpty) displayPath.isNotEmpty,
? Icons.check_circle isFailed:
: message.status == message.status ==
ChannelMessageStatus.failed ChannelMessageStatus.failed,
? Icons.cancel
: Icons.cloud,
size: 14,
color:
(message.status ==
ChannelMessageStatus.sent &&
displayPath.isNotEmpty)
? Colors.green
: message.status ==
ChannelMessageStatus.failed
? Colors.red
: Colors.grey,
), ),
), ),
], ],
@@ -727,7 +732,12 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
return _PoiInfo(lat: lat, lon: lon, label: label); return _PoiInfo(lat: lat, lon: lon, label: label);
} }
Widget _buildPoiMessage(BuildContext context, _PoiInfo poi, bool isOutgoing) { Widget _buildPoiMessage(
BuildContext context,
_PoiInfo poi,
bool isOutgoing, {
Widget? trailing,
}) {
final colorScheme = Theme.of(context).colorScheme; final colorScheme = Theme.of(context).colorScheme;
final textColor = isOutgoing final textColor = isOutgoing
? colorScheme.onPrimaryContainer ? colorScheme.onPrimaryContainer
@@ -773,6 +783,10 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
], ],
), ),
), ),
if (trailing != null) ...[
const SizedBox(width: 4),
trailing,
],
], ],
); );
} }
+47 -43
View File
@@ -5,6 +5,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart'; import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:flutter_linkify/flutter_linkify.dart'; import 'package:flutter_linkify/flutter_linkify.dart';
import 'package:meshcore_open/screens/path_trace_map.dart'; import 'package:meshcore_open/screens/path_trace_map.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@@ -13,6 +14,7 @@ import 'package:latlong2/latlong.dart';
import '../connector/meshcore_connector.dart'; import '../connector/meshcore_connector.dart';
import '../connector/meshcore_protocol.dart'; import '../connector/meshcore_protocol.dart';
import '../helpers/reaction_helper.dart'; import '../helpers/reaction_helper.dart';
import '../widgets/message_status_icon.dart';
import '../helpers/chat_scroll_controller.dart'; import '../helpers/chat_scroll_controller.dart';
import '../helpers/link_handler.dart'; import '../helpers/link_handler.dart';
import '../helpers/utf8_length_limiter.dart'; import '../helpers/utf8_length_limiter.dart';
@@ -1269,7 +1271,24 @@ class _MessageBubble extends StatelessWidget {
if (gifId == null) const SizedBox(height: 4), if (gifId == null) const SizedBox(height: 4),
], ],
if (poi != null) if (poi != null)
_buildPoiMessage(context, poi, textColor, metaColor) _buildPoiMessage(
context,
poi,
textColor,
metaColor,
trailing: (!enableTracing && isOutgoing)
? Padding(
padding: const EdgeInsets.only(bottom: 2),
child: MessageStatusIcon(
isAcked: message.status ==
MessageStatus.delivered &&
message.pathBytes.isNotEmpty,
isFailed: message.status ==
MessageStatus.failed,
),
)
: null,
)
else if (gifId != null) else if (gifId != null)
Stack( Stack(
children: [ children: [
@@ -1286,35 +1305,25 @@ class _MessageBubble extends StatelessWidget {
), ),
if (!enableTracing && isOutgoing) if (!enableTracing && isOutgoing)
Positioned( Positioned(
top: 4, top: 0,
right: 4, right: 0,
child: Container( child: Container(
padding: const EdgeInsets.all(2), padding: const EdgeInsets.all(3),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.black.withValues( color: bubbleColor,
alpha: 0.3, borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(10),
topRight: Radius.circular(12),
), ),
shape: BoxShape.circle,
), ),
child: Icon( child: MessageStatusIcon(
(message.status == isAcked:
message.status ==
MessageStatus.delivered && MessageStatus.delivered &&
message.pathBytes.isNotEmpty) message.pathBytes.isNotEmpty,
? Icons.check_circle isFailed:
: message.status == message.status ==
MessageStatus.failed MessageStatus.failed,
? Icons.cancel
: Icons.cloud,
size: 14,
color:
(message.status ==
MessageStatus.delivered &&
message.pathBytes.isNotEmpty)
? Colors.green
: message.status ==
MessageStatus.failed
? Colors.red
: Colors.white70,
), ),
), ),
), ),
@@ -1348,23 +1357,13 @@ class _MessageBubble extends StatelessWidget {
const SizedBox(width: 4), const SizedBox(width: 4),
Padding( Padding(
padding: const EdgeInsets.only(bottom: 2), padding: const EdgeInsets.only(bottom: 2),
child: Icon( child: MessageStatusIcon(
(message.status == isAcked:
message.status ==
MessageStatus.delivered && MessageStatus.delivered &&
message.pathBytes.isNotEmpty) message.pathBytes.isNotEmpty,
? Icons.check_circle isFailed:
: message.status == MessageStatus.failed message.status == MessageStatus.failed,
? Icons.cancel
: Icons.cloud,
size: 14,
color:
(message.status ==
MessageStatus.delivered &&
message.pathBytes.isNotEmpty)
? Colors.green
: message.status == MessageStatus.failed
? Colors.red
: Colors.grey,
), ),
), ),
], ],
@@ -1481,8 +1480,9 @@ class _MessageBubble extends StatelessWidget {
BuildContext context, BuildContext context,
_PoiInfo poi, _PoiInfo poi,
Color textColor, Color textColor,
Color metaColor, Color metaColor, {
) { Widget? trailing,
}) {
return Row( return Row(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
@@ -1519,6 +1519,10 @@ class _MessageBubble extends StatelessWidget {
], ],
), ),
), ),
if (trailing != null) ...[
const SizedBox(width: 4),
trailing,
],
], ],
); );
} }
+36
View File
@@ -0,0 +1,36 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
class MessageStatusIcon extends StatelessWidget {
final bool isAcked;
final bool isFailed;
final double size;
const MessageStatusIcon({
super.key,
required this.isAcked,
this.isFailed = false,
this.size = 14,
});
@override
Widget build(BuildContext context) {
if (isFailed) {
return Icon(Icons.cancel, size: size, color: Colors.red);
}
final Color color;
if (isAcked) {
color = Colors.green;
} else {
color = Colors.grey;
}
return SvgPicture.asset(
'assets/icons/done_all.svg',
width: size,
height: size,
colorFilter: ColorFilter.mode(color, BlendMode.srcIn),
);
}
}
+40
View File
@@ -363,6 +363,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "8.2.2" version: "8.2.2"
flutter_svg:
dependency: "direct main"
description:
name: flutter_svg
sha256: "87fbd7c534435b6c5d9d98b01e1fd527812b82e68ddd8bd35fc45ed0fa8f0a95"
url: "https://pub.dev"
source: hosted
version: "2.2.3"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
@@ -637,6 +645,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.9.1" version: "1.9.1"
path_parsing:
dependency: transitive
description:
name: path_parsing
sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
path_provider: path_provider:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -1050,6 +1066,30 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.5.2" version: "4.5.2"
vector_graphics:
dependency: transitive
description:
name: vector_graphics
sha256: a4f059dc26fc8295b5921376600a194c4ec7d55e72f2fe4c7d2831e103d461e6
url: "https://pub.dev"
source: hosted
version: "1.1.19"
vector_graphics_codec:
dependency: transitive
description:
name: vector_graphics_codec
sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146"
url: "https://pub.dev"
source: hosted
version: "1.1.13"
vector_graphics_compiler:
dependency: transitive
description:
name: vector_graphics_compiler
sha256: "5a88dd14c0954a5398af544651c7fb51b457a2a556949bfb25369b210ef73a74"
url: "https://pub.dev"
source: hosted
version: "1.2.0"
vector_math: vector_math:
dependency: transitive dependency: transitive
description: description:
+2
View File
@@ -62,6 +62,7 @@ dependencies:
share_plus: ^12.0.1 share_plus: ^12.0.1
build_pipe: ^0.3.1 build_pipe: ^0.3.1
web: ^1.1.1 web: ^1.1.1
flutter_svg: ^2.0.10+1
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
@@ -89,6 +90,7 @@ flutter:
assets: assets:
- assets/images/ - assets/images/
- assets/icons/
flutter_launcher_icons: flutter_launcher_icons:
android: true android: true