Skip to content

Commit 7b09cc7

Browse files
authored
feat: better location messages (#17)
1 parent 4c181b9 commit 7b09cc7

File tree

6 files changed

+303
-50
lines changed

6 files changed

+303
-50
lines changed

lib/chat/common/event_x.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,12 @@ extension EventX on Event {
5353
!maybePreviousEvent.isImage &&
5454
maybePreviousEvent.senderId == senderId;
5555
}
56+
57+
Uri? get geoUri {
58+
final maybe = content.tryGet<String>('geo_uri');
59+
if (maybe == null) {
60+
return null;
61+
}
62+
return Uri.tryParse(maybe);
63+
}
5664
}

lib/chat/events/view/chat_map.dart

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
import 'package:collection/collection.dart';
2+
import 'package:flutter/material.dart';
3+
import 'package:flutter_map/flutter_map.dart';
4+
import 'package:latlong2/latlong.dart';
5+
import 'package:matrix/matrix.dart';
6+
import 'package:url_launcher/url_launcher.dart';
7+
import 'package:watch_it/watch_it.dart';
8+
import 'package:yaru/icons.dart';
9+
10+
import '../../../common/uri_x.dart';
11+
import '../../../common/view/ui_constants.dart';
12+
import '../../../l10n/l10n.dart';
13+
import '../../common/chat_model.dart';
14+
import '../../common/event_x.dart';
15+
16+
class ChatMap extends StatelessWidget {
17+
const ChatMap({
18+
super.key,
19+
required this.event,
20+
required this.partOfMessageCohort,
21+
required this.timeline,
22+
required this.onReplyOriginClick,
23+
});
24+
25+
final Event event;
26+
final bool partOfMessageCohort;
27+
final Timeline timeline;
28+
final Future<void> Function(Event) onReplyOriginClick;
29+
30+
@override
31+
Widget build(BuildContext context) {
32+
final geoUri = event.geoUri;
33+
if (geoUri != null && geoUri.scheme == 'geo') {
34+
final latlong = geoUri.latLong;
35+
if (latlong.length == 2 && latlong.none((e) => e == null)) {
36+
return Column(
37+
mainAxisSize: MainAxisSize.min,
38+
spacing: kMediumPadding,
39+
children: [
40+
Map(
41+
isUserMessage: event.senderId == di<ChatModel>().myUserId,
42+
latitude: latlong.first!,
43+
longitude: latlong.last!,
44+
),
45+
SizedBox(
46+
width: double.infinity,
47+
child: TextButton.icon(
48+
icon: const Icon(YaruIcons.location),
49+
onPressed: () {
50+
final maybeUri = Uri.tryParse(event.body);
51+
if (maybeUri != null) {
52+
launchUrl(maybeUri);
53+
}
54+
},
55+
label: Text(
56+
context.l10n.openInMaps,
57+
),
58+
),
59+
),
60+
],
61+
);
62+
}
63+
}
64+
65+
return Text(event.body);
66+
}
67+
}
68+
69+
class Map extends StatelessWidget {
70+
const Map({
71+
required this.latitude,
72+
required this.longitude,
73+
this.zoom = 14.0,
74+
this.width = 400,
75+
this.height = 400,
76+
this.radius = 10.0,
77+
required this.isUserMessage,
78+
super.key,
79+
});
80+
81+
final double latitude;
82+
final double longitude;
83+
final double zoom;
84+
final double width;
85+
final double height;
86+
final double radius;
87+
final bool isUserMessage;
88+
89+
@override
90+
Widget build(BuildContext context) {
91+
final theme = Theme.of(context);
92+
93+
return ClipRRect(
94+
borderRadius: BorderRadius.circular(radius),
95+
child: Container(
96+
constraints: BoxConstraints.loose(Size(width, height)),
97+
child: AspectRatio(
98+
aspectRatio: width / height,
99+
child: Stack(
100+
children: <Widget>[
101+
FlutterMap(
102+
options: MapOptions(
103+
initialCenter: LatLng(latitude, longitude),
104+
initialZoom: zoom,
105+
),
106+
children: [
107+
TileLayer(
108+
maxZoom: 20,
109+
minZoom: 0,
110+
urlTemplate:
111+
'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
112+
subdomains: const ['a', 'b', 'c'],
113+
),
114+
MarkerLayer(
115+
rotate: true,
116+
markers: [
117+
Marker(
118+
point: LatLng(latitude, longitude),
119+
width: 30,
120+
height: 30,
121+
child: Transform.translate(
122+
offset: const Offset(0, -12.5),
123+
child: const Icon(
124+
YaruIcons.location,
125+
color: Colors.red,
126+
size: 30,
127+
),
128+
),
129+
),
130+
],
131+
),
132+
],
133+
),
134+
Container(
135+
alignment: Alignment.bottomRight,
136+
child: Text(
137+
' © OpenStreetMap contributors ',
138+
style: TextStyle(
139+
color: theme.brightness == Brightness.dark
140+
? Colors.white
141+
: Colors.black,
142+
backgroundColor: theme.appBarTheme.backgroundColor,
143+
),
144+
),
145+
),
146+
],
147+
),
148+
),
149+
),
150+
);
151+
}
152+
}

lib/chat/events/view/chat_message_bubble.dart

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import '../../common/view/chat_profile_dialog.dart';
1212
import '../chat_download_model.dart';
1313
import 'chat_event_status_icon.dart';
1414
import 'chat_html_message.dart';
15+
import 'chat_map.dart';
1516
import 'chat_message_attachment_indicator.dart';
1617
import 'chat_message_bubble_shape.dart';
1718
import 'chat_message_media_avatar.dart';
@@ -177,20 +178,27 @@ class _ChatMessageBubbleContent extends StatelessWidget {
177178
decoration: TextDecoration.lineThrough,
178179
),
179180
)
180-
: event.isRichMessage
181-
? HtmlMessage(
182-
html: html,
183-
room: timeline.room,
184-
defaultTextColor:
185-
context.colorScheme.onSurface,
181+
: event.messageType == MessageTypes.Location
182+
? ChatMap(
183+
event: event,
184+
partOfMessageCohort: partOfMessageCohort,
185+
timeline: timeline,
186+
onReplyOriginClick: onReplyOriginClick,
186187
)
187-
: SelectableText.rich(
188-
TextSpan(
189-
style: messageStyle,
190-
text: displayEvent.body,
191-
),
192-
style: messageStyle,
193-
),
188+
: event.isRichMessage
189+
? HtmlMessage(
190+
html: html,
191+
room: timeline.room,
192+
defaultTextColor:
193+
context.colorScheme.onSurface,
194+
)
195+
: SelectableText.rich(
196+
TextSpan(
197+
style: messageStyle,
198+
text: displayEvent.body,
199+
),
200+
style: messageStyle,
201+
),
194202
),
195203
const SizedBox(
196204
height: kBigPadding,
@@ -235,7 +243,8 @@ class ChatMessageBubbleLeading extends StatelessWidget {
235243
if (event.messageType == MessageTypes.BadEncrypted) {
236244
return const SizedBox.square(dimension: kAvatarDefaultSize);
237245
} else if (event.messageType != MessageTypes.Text &&
238-
event.messageType != MessageTypes.Notice) {
246+
event.messageType != MessageTypes.Notice &&
247+
event.messageType != MessageTypes.Location) {
239248
return ChatMessageMediaAvatar(event: event);
240249
}
241250

lib/common/uri_x.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
extension UriX on Uri {
2+
List<double?> get latLong =>
3+
path.split(';').first.split(',').map((s) => double.tryParse(s)).toList();
4+
}

0 commit comments

Comments
 (0)