diff --git a/assets/icons/done_all.svg b/assets/icons/done_all.svg
new file mode 100644
index 0000000..bfeeec0
--- /dev/null
+++ b/assets/icons/done_all.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/lib/screens/channel_chat_screen.dart b/lib/screens/channel_chat_screen.dart
index b59a691..c02425b 100644
--- a/lib/screens/channel_chat_screen.dart
+++ b/lib/screens/channel_chat_screen.dart
@@ -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 {
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 {
),
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 {
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 {
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 {
],
),
),
+ if (trailing != null) ...[
+ const SizedBox(width: 4),
+ trailing,
+ ],
],
);
}
diff --git a/lib/screens/chat_screen.dart b/lib/screens/chat_screen.dart
index 6ee846c..a6d2399 100644
--- a/lib/screens/chat_screen.dart
+++ b/lib/screens/chat_screen.dart
@@ -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';
@@ -1269,7 +1271,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: [
@@ -1286,35 +1305,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,
),
),
),
@@ -1348,23 +1357,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,
),
),
],
@@ -1481,8 +1480,9 @@ class _MessageBubble extends StatelessWidget {
BuildContext context,
_PoiInfo poi,
Color textColor,
- Color metaColor,
- ) {
+ Color metaColor, {
+ Widget? trailing,
+ }) {
return Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
@@ -1519,6 +1519,10 @@ class _MessageBubble extends StatelessWidget {
],
),
),
+ if (trailing != null) ...[
+ const SizedBox(width: 4),
+ trailing,
+ ],
],
);
}
diff --git a/lib/widgets/message_status_icon.dart b/lib/widgets/message_status_icon.dart
new file mode 100644
index 0000000..0689f0b
--- /dev/null
+++ b/lib/widgets/message_status_icon.dart
@@ -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),
+ );
+ }
+}
diff --git a/pubspec.lock b/pubspec.lock
index 756f192..e2254cb 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -363,6 +363,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
@@ -637,6 +645,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:
@@ -1050,6 +1066,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:
diff --git a/pubspec.yaml b/pubspec.yaml
index a7564a9..3330d39 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -62,6 +62,7 @@ dependencies:
share_plus: ^12.0.1
build_pipe: ^0.3.1
web: ^1.1.1
+ flutter_svg: ^2.0.10+1
dev_dependencies:
flutter_test:
@@ -89,6 +90,7 @@ flutter:
assets:
- assets/images/
+ - assets/icons/
flutter_launcher_icons:
android: true