chat fixes

This commit is contained in:
Ben Allfree 2026-02-23 04:09:27 -08:00
parent 7465e81996
commit 173fdf7168
5 changed files with 187 additions and 90 deletions

View file

@ -4,6 +4,7 @@ import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:flutter_linkify/flutter_linkify.dart';
import 'package:latlong2/latlong.dart';
import 'package:provider/provider.dart';
@ -23,6 +24,7 @@ import '../widgets/emoji_picker.dart';
import '../widgets/gif_message.dart';
import '../widgets/jump_to_bottom_button.dart';
import '../widgets/gif_picker.dart';
import '../widgets/message_status_icon.dart';
import 'channel_message_path_screen.dart';
import 'map_screen.dart';
@ -337,7 +339,23 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
const SizedBox(height: 8),
],
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)
Stack(
children: [
@ -358,33 +376,31 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
),
if (!enableTracing && isOutgoing)
Positioned(
top: 4,
right: 4,
top: 0,
right: 0,
child: Container(
padding: const EdgeInsets.all(2),
padding: const EdgeInsets.all(3),
decoration: BoxDecoration(
color: Colors.black.withValues(alpha: 0.3),
shape: BoxShape.circle,
color: isOutgoing
? 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 ==
ChannelMessageStatus.sent &&
displayPath.isNotEmpty)
? Icons.check_circle
: message.status ==
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,
child: MessageStatusIcon(
isAcked:
message.status ==
ChannelMessageStatus.sent &&
displayPath.isNotEmpty,
isFailed:
message.status ==
ChannelMessageStatus.failed,
),
),
),
@ -419,25 +435,14 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
const SizedBox(width: 4),
Padding(
padding: const EdgeInsets.only(bottom: 2),
child: Icon(
(message.status ==
ChannelMessageStatus.sent &&
displayPath.isNotEmpty)
? Icons.check_circle
: message.status ==
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,
child: MessageStatusIcon(
isAcked:
message.status ==
ChannelMessageStatus.sent &&
displayPath.isNotEmpty,
isFailed:
message.status ==
ChannelMessageStatus.failed,
),
),
],
@ -727,7 +732,12 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
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 textColor = isOutgoing
? colorScheme.onPrimaryContainer
@ -773,6 +783,10 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
],
),
),
if (trailing != null) ...[
const SizedBox(width: 4),
trailing,
],
],
);
}

View file

@ -5,6 +5,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:flutter_linkify/flutter_linkify.dart';
import 'package:meshcore_open/screens/path_trace_map.dart';
import 'package:provider/provider.dart';
@ -13,6 +14,7 @@ import 'package:latlong2/latlong.dart';
import '../connector/meshcore_connector.dart';
import '../connector/meshcore_protocol.dart';
import '../helpers/reaction_helper.dart';
import '../widgets/message_status_icon.dart';
import '../helpers/chat_scroll_controller.dart';
import '../helpers/link_handler.dart';
import '../helpers/utf8_length_limiter.dart';
@ -1252,7 +1254,24 @@ class _MessageBubble extends StatelessWidget {
if (gifId == null) const SizedBox(height: 4),
],
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)
Stack(
children: [
@ -1269,35 +1288,25 @@ class _MessageBubble extends StatelessWidget {
),
if (!enableTracing && isOutgoing)
Positioned(
top: 4,
right: 4,
top: 0,
right: 0,
child: Container(
padding: const EdgeInsets.all(2),
padding: const EdgeInsets.all(3),
decoration: BoxDecoration(
color: Colors.black.withValues(
alpha: 0.3,
color: bubbleColor,
borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(10),
topRight: Radius.circular(12),
),
shape: BoxShape.circle,
),
child: Icon(
(message.status ==
MessageStatus.delivered &&
message.pathBytes.isNotEmpty)
? Icons.check_circle
: 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.white70,
child: MessageStatusIcon(
isAcked:
message.status ==
MessageStatus.delivered &&
message.pathBytes.isNotEmpty,
isFailed:
message.status ==
MessageStatus.failed,
),
),
),
@ -1331,23 +1340,13 @@ class _MessageBubble extends StatelessWidget {
const SizedBox(width: 4),
Padding(
padding: const EdgeInsets.only(bottom: 2),
child: Icon(
(message.status ==
MessageStatus.delivered &&
message.pathBytes.isNotEmpty)
? Icons.check_circle
: 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,
child: MessageStatusIcon(
isAcked:
message.status ==
MessageStatus.delivered &&
message.pathBytes.isNotEmpty,
isFailed:
message.status == MessageStatus.failed,
),
),
],
@ -1464,8 +1463,9 @@ class _MessageBubble extends StatelessWidget {
BuildContext context,
_PoiInfo poi,
Color textColor,
Color metaColor,
) {
Color metaColor, {
Widget? trailing,
}) {
return Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
@ -1502,6 +1502,10 @@ class _MessageBubble extends StatelessWidget {
],
),
),
if (trailing != null) ...[
const SizedBox(width: 4),
trailing,
],
],
);
}

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),
);
}
}

View file

@ -347,6 +347,14 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: "direct dev"
description: flutter
@ -597,6 +605,14 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: "direct main"
description:
@ -1010,6 +1026,30 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: transitive
description:

View file

@ -60,6 +60,8 @@ dependencies:
gpx: ^2.3.0
path_provider: ^2.1.5
share_plus: ^12.0.1
web: ^1.1.1
flutter_svg: ^2.0.10+1
dev_dependencies:
flutter_test:
@ -87,6 +89,7 @@ flutter:
assets:
- assets/images/
- assets/icons/
flutter_launcher_icons:
android: true