Add support for asset-type messages

This commit is contained in:
Nutcake 2023-05-07 12:53:19 +02:00
parent 16305542e3
commit 1ffc171028
6 changed files with 187 additions and 9 deletions

View file

@ -73,6 +73,8 @@ extension Strip on String {
return htmlparser.parse(document.body?.text).documentElement?.text ?? ""; return htmlparser.parse(document.body?.text).documentElement?.text ?? "";
} }
// This won't be accurate since userIds can't contain certain characters that usernames can
// but it's fine for just having a name to display
String stripUid() => startsWith("U-") ? substring(2) : this; String stripUid() => startsWith("U-") ? substring(2) : this;
} }

View file

@ -0,0 +1,34 @@
import 'dart:math';
import 'package:collection/collection.dart';
class PhotoAsset {
final String locationName;
final List<String> userIds;
final DateTime timestamp;
final String imageUri;
PhotoAsset({required this.locationName, required this.userIds, required this.timestamp, required this.imageUri});
factory PhotoAsset.fromTags(List<String> tags) {
final List<String> userIds = [];
Map<String, String> parsedTags = Map.fromEntries(tags.map((e) {
final delimIdx = e.indexOf(":");
if (delimIdx == -1) return null;
final key = e.substring(0, delimIdx);
final value = e.substring(min(delimIdx+1, e.length));
if (key == "user") {
userIds.add(value);
return null;
}
return MapEntry(key, value);
}).whereNotNull());
return PhotoAsset(
locationName: parsedTags["location_name"]!,
userIds: userIds,
timestamp: DateTime.parse(parsedTags["timestamp"]!),
imageUri: parsedTags["texture_asset"]!,
);
}
}

View file

@ -0,0 +1,89 @@
import 'dart:convert';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:contacts_plus_plus/auxiliary.dart';
import 'package:contacts_plus_plus/client_holder.dart';
import 'package:contacts_plus_plus/models/photo_asset.dart';
import 'package:contacts_plus_plus/models/message.dart';
import 'package:contacts_plus_plus/widgets/messages/message_state_indicator.dart';
import 'package:flutter/material.dart';
import 'package:full_screen_image/full_screen_image.dart';
import 'package:intl/intl.dart';
import 'package:photo_view/photo_view.dart';
class MessageAsset extends StatelessWidget {
MessageAsset({required this.message, super.key});
final Message message;
final DateFormat _dateFormat = DateFormat.Hm();
@override
Widget build(BuildContext context) {
final content = jsonDecode(message.content);
PhotoAsset? photoAsset;
try {
photoAsset = PhotoAsset.fromTags((content["tags"] as List).map((e) => "$e").toList());
} catch (_) {}
return Container(
constraints: const BoxConstraints(maxWidth: 300),
padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8),
child: Column(
children: [
CachedNetworkImage(
imageUrl: Aux.neosDbToHttp(content["thumbnailUri"]),
imageBuilder: (context, image) {
return InkWell(
onTap: () async {
await Navigator.push(
context, MaterialPageRoute(builder: (context) =>
PhotoView(
minScale: PhotoViewComputedScale.contained,
imageProvider: photoAsset == null
? image
: CachedNetworkImageProvider(Aux.neosDbToHttp(photoAsset.imageUri)),
heroAttributes: PhotoViewHeroAttributes(tag: message.id),
),
),);
},
child: Hero(
tag: message.id,
child: ClipRRect(borderRadius: BorderRadius.circular(16), child: Image(image: image,)),
),
);
},
placeholder: (context, uri) => const CircularProgressIndicator(),
),
const SizedBox(height: 8,),
Row(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
children: [
Expanded(child: Text("${content["name"]}", maxLines: null, style: Theme.of(context).textTheme.bodySmall?.copyWith(color: Colors.white60),)),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4.0),
child: Text(
_dateFormat.format(message.sendTime),
style: Theme
.of(context)
.textTheme
.labelMedium
?.copyWith(color: Colors.white54),
),
),
if (message.senderId == ClientHolder
.of(context)
.apiClient
.userId) Padding(
padding: const EdgeInsets.only(left: 4.0),
child: MessageStateIndicator(messageState: message.state),
),
],
),
],
),
);
}
}

View file

@ -1,5 +1,5 @@
import 'package:contacts_plus_plus/models/message.dart'; import 'package:contacts_plus_plus/models/message.dart';
import 'package:contacts_plus_plus/widgets/messages/message_asset.dart';
import 'package:contacts_plus_plus/widgets/messages/message_audio_player.dart'; import 'package:contacts_plus_plus/widgets/messages/message_audio_player.dart';
import 'package:contacts_plus_plus/widgets/messages/message_session_invite.dart'; import 'package:contacts_plus_plus/widgets/messages/message_session_invite.dart';
import 'package:contacts_plus_plus/widgets/messages/message_state_indicator.dart'; import 'package:contacts_plus_plus/widgets/messages/message_state_indicator.dart';
@ -14,7 +14,6 @@ class MyMessageBubble extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var content = message.content;
switch (message.type) { switch (message.type) {
case MessageType.sessionInvite: case MessageType.sessionInvite:
return Row( return Row(
@ -38,10 +37,28 @@ class MyMessageBubble extends StatelessWidget {
], ],
); );
case MessageType.object: case MessageType.object:
content = "[Asset]"; return Row(
continue rawText; mainAxisAlignment: MainAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
children: [
Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
color: Theme
.of(context)
.colorScheme
.primaryContainer,
margin: const EdgeInsets.only(left: 32, bottom: 16, right: 12),
child: Container(
constraints: const BoxConstraints(maxWidth: 300),
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 12),
child: MessageAsset(message: message,),
),
),
],
);
case MessageType.unknown: case MessageType.unknown:
rawText:
case MessageType.text: case MessageType.text:
return Row( return Row(
mainAxisAlignment: MainAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.end,
@ -63,7 +80,7 @@ class MyMessageBubble extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end,
children: [ children: [
Text( Text(
content, message.content,
softWrap: true, softWrap: true,
maxLines: null, maxLines: null,
style: Theme style: Theme
@ -155,8 +172,26 @@ class OtherMessageBubble extends StatelessWidget {
], ],
); );
case MessageType.object: case MessageType.object:
content = "[Asset]"; return Row(
continue rawText; mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
color: Theme
.of(context)
.colorScheme
.secondaryContainer,
margin: const EdgeInsets.only(right: 32, bottom: 16, left: 12),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
child: MessageAsset(message: message,),
),
),
],
);
case MessageType.unknown: case MessageType.unknown:
rawText: rawText:
case MessageType.text: case MessageType.text:

View file

@ -256,6 +256,14 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
full_screen_image:
dependency: "direct main"
description:
name: full_screen_image
sha256: "97831b00d07b17c674ba3a4eddcee306dbd0d7c66851f1b67856e12daeb9b3aa"
url: "https://pub.dev"
source: hosted
version: "2.0.0"
html: html:
dependency: "direct main" dependency: "direct main"
description: description:
@ -472,6 +480,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.1.0" version: "5.1.0"
photo_view:
dependency: "direct main"
description:
name: photo_view
sha256: "8036802a00bae2a78fc197af8a158e3e2f7b500561ed23b4c458107685e645bb"
url: "https://pub.dev"
source: hosted
version: "0.14.0"
platform: platform:
dependency: transitive dependency: transitive
description: description:

View file

@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts # In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix. # of the product and file versions while build-number is used as the build suffix.
version: 1.0.2+1 version: 1.1.0+1
environment: environment:
sdk: '>=2.19.6 <3.0.0' sdk: '>=2.19.6 <3.0.0'
@ -53,6 +53,8 @@ dependencies:
collection: ^1.17.0 collection: ^1.17.0
package_info_plus: ^3.1.2 package_info_plus: ^3.1.2
provider: ^6.0.5 provider: ^6.0.5
full_screen_image: ^2.0.0
photo_view: ^0.14.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: