forked from Henry-Hiles/nexus
add sticker and emote support
This commit is contained in:
parent
3e82faeb86
commit
e7890cfe4f
6 changed files with 97 additions and 6 deletions
|
|
@ -65,7 +65,7 @@ A simple and user-friendly Matrix client made with Flutter and the Matrix Dart S
|
||||||
- [ ] Plain text
|
- [ ] Plain text
|
||||||
- [ ] Matrix URIs
|
- [ ] Matrix URIs
|
||||||
- [ ] Matrix.to links
|
- [ ] Matrix.to links
|
||||||
- [ ] Custom emojis/stickers
|
- [x] Custom emojis/stickers
|
||||||
- [ ] Encrypted messages
|
- [ ] Encrypted messages
|
||||||
- [x] History loading
|
- [x] History loading
|
||||||
- [x] Backwards
|
- [x] Backwards
|
||||||
|
|
|
||||||
24
lib/controllers/thumbnail_controller.dart
Normal file
24
lib/controllers/thumbnail_controller.dart
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||||
|
import "package:matrix/matrix.dart";
|
||||||
|
import "package:nexus/controllers/client_controller.dart";
|
||||||
|
import "package:nexus/models/image_data.dart";
|
||||||
|
|
||||||
|
class ThumbnailController extends AsyncNotifier<String?> {
|
||||||
|
ThumbnailController(this.data);
|
||||||
|
final ImageData data;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String?> build({String? from}) async {
|
||||||
|
final client = await ref.watch(ClientController.provider.future);
|
||||||
|
final uri = await Uri.tryParse(data.uri)?.getDownloadUri(
|
||||||
|
client,
|
||||||
|
); // TODO: Should use thumb when c10y fixes animated thumbs
|
||||||
|
|
||||||
|
return uri.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
static final provider = AsyncNotifierProvider.family
|
||||||
|
.autoDispose<ThumbnailController, String?, ImageData>(
|
||||||
|
ThumbnailController.new,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -50,8 +50,8 @@ extension EventToMessage on Event {
|
||||||
text: "Unable to decrypt message.",
|
text: "Unable to decrypt message.",
|
||||||
metadata: {"formatted": "Unable to decrypt message.", ...metadata},
|
metadata: {"formatted": "Unable to decrypt message.", ...metadata},
|
||||||
),
|
),
|
||||||
EventTypes.Message => switch (messageType) {
|
(EventTypes.Sticker || EventTypes.Message) => switch (messageType) {
|
||||||
MessageTypes.Image => Message.image(
|
(MessageTypes.Sticker || MessageTypes.Image) => Message.image(
|
||||||
metadata: metadata,
|
metadata: metadata,
|
||||||
id: eventId,
|
id: eventId,
|
||||||
authorId: senderId,
|
authorId: senderId,
|
||||||
|
|
|
||||||
11
lib/models/image_data.dart
Normal file
11
lib/models/image_data.dart
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
import "package:freezed_annotation/freezed_annotation.dart";
|
||||||
|
part "image_data.freezed.dart";
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class ImageData with _$ImageData {
|
||||||
|
const factory ImageData({
|
||||||
|
required String uri,
|
||||||
|
required int? height,
|
||||||
|
required int? width,
|
||||||
|
}) = _ImageData;
|
||||||
|
}
|
||||||
|
|
@ -2,14 +2,20 @@ import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
import "package:flutter_riverpod/flutter_riverpod.dart";
|
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||||
import "package:flutter_widget_from_html_core/flutter_widget_from_html_core.dart";
|
import "package:flutter_widget_from_html_core/flutter_widget_from_html_core.dart";
|
||||||
|
import "package:matrix/matrix.dart";
|
||||||
|
import "package:nexus/controllers/thumbnail_controller.dart";
|
||||||
|
import "package:nexus/helpers/extensions/get_headers.dart";
|
||||||
import "package:nexus/helpers/launch_helper.dart";
|
import "package:nexus/helpers/launch_helper.dart";
|
||||||
|
import "package:nexus/models/image_data.dart";
|
||||||
import "package:nexus/widgets/chat_page/html/spoiler_text.dart";
|
import "package:nexus/widgets/chat_page/html/spoiler_text.dart";
|
||||||
import "package:nexus/widgets/chat_page/html/code_block.dart";
|
import "package:nexus/widgets/chat_page/html/code_block.dart";
|
||||||
import "package:nexus/widgets/chat_page/quoted.dart";
|
import "package:nexus/widgets/chat_page/quoted.dart";
|
||||||
|
import "package:nexus/widgets/error_dialog.dart";
|
||||||
|
|
||||||
class Html extends ConsumerWidget {
|
class Html extends ConsumerWidget {
|
||||||
final String html;
|
final String html;
|
||||||
const Html(this.html, {super.key});
|
final Client client;
|
||||||
|
const Html(this.html, {required this.client, super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) => HtmlWidget(
|
Widget build(BuildContext context, WidgetRef ref) => HtmlWidget(
|
||||||
|
|
@ -18,13 +24,63 @@ class Html extends ConsumerWidget {
|
||||||
if (element.attributes.keys.contains("data-mx-spoiler")) {
|
if (element.attributes.keys.contains("data-mx-spoiler")) {
|
||||||
return SpoilerText(text: element.text);
|
return SpoilerText(text: element.text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final height = int.tryParse(element.attributes["height"] ?? "") ?? 300;
|
||||||
|
final width = int.tryParse(element.attributes["width"] ?? "");
|
||||||
|
|
||||||
return switch (element.localName) {
|
return switch (element.localName) {
|
||||||
"code" => CodeBlock(
|
"code" => CodeBlock(
|
||||||
element.text,
|
element.text,
|
||||||
lang: element.className.replaceAll("language-", ""),
|
lang: element.className.replaceAll("language-", ""),
|
||||||
),
|
),
|
||||||
|
|
||||||
"blockquote" => Quoted(Html(element.innerHtml)),
|
"blockquote" => Quoted(Html(element.innerHtml, client: client)),
|
||||||
|
|
||||||
|
"img" =>
|
||||||
|
element.attributes["src"] == null
|
||||||
|
? null
|
||||||
|
: Consumer(
|
||||||
|
builder: (_, ref, _) => ref
|
||||||
|
.watch(
|
||||||
|
ThumbnailController.provider(
|
||||||
|
ImageData(
|
||||||
|
uri: element.attributes["src"]!,
|
||||||
|
height: height,
|
||||||
|
width: width,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.when(
|
||||||
|
data: (uri) {
|
||||||
|
if (uri == null) return SizedBox.shrink();
|
||||||
|
|
||||||
|
return InlineCustomWidget(
|
||||||
|
child: Image.network(
|
||||||
|
uri,
|
||||||
|
headers: client.headers,
|
||||||
|
errorBuilder: (_, error, _) => Text(
|
||||||
|
"Image Failed to Load",
|
||||||
|
style: TextStyle(color: Colors.red),
|
||||||
|
),
|
||||||
|
height: height.toDouble(),
|
||||||
|
width: width?.toDouble(),
|
||||||
|
loadingBuilder: (_, child, loadingProgress) =>
|
||||||
|
loadingProgress == null
|
||||||
|
? child
|
||||||
|
: CircularProgressIndicator(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
error: ErrorDialog.new,
|
||||||
|
loading: () => InlineCustomWidget(
|
||||||
|
child: SizedBox(
|
||||||
|
width: width?.toDouble(),
|
||||||
|
height: height.toDouble(),
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
("del" ||
|
("del" ||
|
||||||
"h1" ||
|
"h1" ||
|
||||||
|
|
@ -59,7 +115,6 @@ class Html extends ConsumerWidget {
|
||||||
"caption" ||
|
"caption" ||
|
||||||
"pre" ||
|
"pre" ||
|
||||||
"span" ||
|
"span" ||
|
||||||
"img" ||
|
|
||||||
"details" ||
|
"details" ||
|
||||||
"summary") =>
|
"summary") =>
|
||||||
null,
|
null,
|
||||||
|
|
|
||||||
|
|
@ -200,6 +200,7 @@ class RoomChat extends HookConsumerWidget {
|
||||||
((message.editedAt != null)
|
((message.editedAt != null)
|
||||||
? "<sub edited>(edited)</sub>"
|
? "<sub edited>(edited)</sub>"
|
||||||
: ""),
|
: ""),
|
||||||
|
client: room.roomData.client,
|
||||||
),
|
),
|
||||||
topWidget: TopWidget(
|
topWidget: TopWidget(
|
||||||
message,
|
message,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue