Watch
1
0
Fork
You've already forked nexus
0
forked from Nexus/nexus

Compare commits

..
24 changed files with 249 additions and 175 deletions

2
.gitmodules vendored
View file

@ -1,4 +1,4 @@
[submodule "gomuks"] [submodule "gomuks"]
path = gomuks path = gomuks
url = https://github.com/henry-hiles/gomuks url = https://github.com/gomuks/gomuks
branch = main branch = main

View file

@ -69,7 +69,7 @@ A simple and user-friendly Matrix client made with Flutter and a Gomuks backend.
- [x] Edits - [x] Edits
- [x] Attachments - [x] Attachments
- [x] Unencrypted - [x] Unencrypted
- [x] Encrypted - [ ] Encrypted
- [x] Blurhashing - [x] Blurhashing
- [ ] Downloading attachments - [ ] Downloading attachments
- [x] Opening attachments in their own view - [x] Opening attachments in their own view

2
gomuks

@ -1 +1 @@
Subproject commit 1f11743884a64765b1444b250377a09ebcaba93c Subproject commit c06eb1cbda2d49ff199a351e2d55df6ff7ab602b

View file

@ -18,7 +18,6 @@ import "package:nexus/models/content/message.dart";
import "package:nexus/models/event.dart"; import "package:nexus/models/event.dart";
import "package:nexus/models/oauth_auth_code_response.dart"; import "package:nexus/models/oauth_auth_code_response.dart";
import "package:nexus/models/paginate.dart"; import "package:nexus/models/paginate.dart";
import "package:nexus/models/requests/download_media.dart";
import "package:nexus/models/requests/get_event.dart"; import "package:nexus/models/requests/get_event.dart";
import "package:nexus/models/requests/get_related_events.dart"; import "package:nexus/models/requests/get_related_events.dart";
import "package:nexus/models/requests/get_room_state.dart"; import "package:nexus/models/requests/get_room_state.dart";
@ -272,9 +271,6 @@ class ClientController extends AsyncNotifier<int> {
Future<MessageContent> uploadMedia(UploadMediaRequest request) async => Future<MessageContent> uploadMedia(UploadMediaRequest request) async =>
.fromJson(await _sendCommand("upload_media", request.toJson())); .fromJson(await _sendCommand("upload_media", request.toJson()));
Future<File> downloadMedia(DownloadMediaRequest request) async =>
.new((await _sendCommand("download_media", request.toJson()))["path"]);
Future<void> logout() => _sendCommand("logout"); Future<void> logout() => _sendCommand("logout");
Future<void> markRead(Room room) async { Future<void> markRead(Room room) async {

View file

@ -0,0 +1,11 @@
import "package:cross_cache/cross_cache.dart";
import "package:flutter_riverpod/flutter_riverpod.dart";
class CrossCacheController extends Notifier<CrossCache> {
@override
CrossCache build() => .new();
static final provider = NotifierProvider<CrossCacheController, CrossCache>(
CrossCacheController.new,
);
}

View file

@ -121,16 +121,9 @@ class RoomChatController extends AsyncNotifier<IList<Event>?> {
required RelationType relationType, required RelationType relationType,
Event? relation, Event? relation,
}) async { }) async {
Content? baseContent;
if (relationType == .edit) {
baseContent = relation?.content;
} else {
final attachment = ref.watch(AttachmentController.provider(roomId)); final attachment = ref.watch(AttachmentController.provider(roomId));
if (attachment != null && attachment.$2 == null) return;
baseContent = attachment?.$2; if (attachment != null && attachment.$2 == null) return;
ref.invalidate(AttachmentController.provider(roomId));
}
var taggedMessage = text; var taggedMessage = text;
@ -148,7 +141,7 @@ class RoomChatController extends AsyncNotifier<IList<Event>?> {
final event = await client.sendMessage( final event = await client.sendMessage(
SendMessageRequest( SendMessageRequest(
roomId: roomId, roomId: roomId,
baseContent: baseContent, baseContent: attachment?.$2,
mentions: Mentions( mentions: Mentions(
userIds: [ userIds: [
if (shouldMention == true && if (shouldMention == true &&
@ -165,6 +158,8 @@ class RoomChatController extends AsyncNotifier<IList<Event>?> {
), ),
); );
ref.invalidate(AttachmentController.provider(roomId));
ref ref
.watch(RoomsController.provider.notifier) .watch(RoomsController.provider.notifier)
.update( .update(

View file

@ -3,6 +3,7 @@ import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:http/http.dart"; import "package:http/http.dart";
import "package:nexus/controllers/client_state.dart"; import "package:nexus/controllers/client_state.dart";
import "package:nexus/controllers/header.dart"; import "package:nexus/controllers/header.dart";
import "package:nexus/helpers/extensions/mxc_to_https.dart";
import "package:nexus/models/open_graph_data.dart"; import "package:nexus/models/open_graph_data.dart";
class UrlPreviewController extends AsyncNotifier<OpenGraphData?> { class UrlPreviewController extends AsyncNotifier<OpenGraphData?> {
@ -29,9 +30,12 @@ class UrlPreviewController extends AsyncNotifier<OpenGraphData?> {
final decodedValue = json.decode(response.body); final decodedValue = json.decode(response.body);
if (decodedValue is! Map<String, dynamic>) return null; if (decodedValue is! Map<String, dynamic>) return null;
return .fromJson( final mxc = decodedValue["og:image"];
decodedValue, final image = mxc == null
).copyWith(imageUrl: decodedValue["og:image"]); ? null
: Uri.tryParse(mxc)?.mxcToHttps(homeserver);
return .fromJson(decodedValue).copyWith(imageUrl: image);
} }
} }
} }

View file

@ -0,0 +1,4 @@
extension MxcToHttps on Uri {
Uri mxcToHttps(String homeserver) =>
.parse(homeserver).resolve("_matrix/client/v1/media/download/$host$path");
}

View file

@ -1,35 +0,0 @@
import "dart:ui";
import "package:flutter/widgets.dart";
import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:nexus/controllers/client.dart";
import "package:nexus/models/requests/download_media.dart";
class MxcImage extends ImageProvider<MxcImage> {
final WidgetRef ref;
final DownloadMediaRequest request;
const MxcImage(this.ref, this.request);
@override
Future<MxcImage> obtainKey(ImageConfiguration configuration) =>
Future.value(this);
@override
ImageStreamCompleter loadImage(MxcImage key, ImageDecoderCallback decode) =>
MultiFrameImageStreamCompleter(codec: _loadAsync(decode), scale: 1.0);
Future<Codec> _loadAsync(ImageDecoderCallback decode) async {
final file = await ref
.read(ClientController.provider.notifier)
.downloadMedia(request);
final buffer = await ImmutableBuffer.fromFilePath(file.path);
return decode(buffer);
}
@override
bool operator ==(Object other) =>
other is MxcImage && other.request == request;
@override
int get hashCode => request.hashCode;
}

View file

@ -12,9 +12,6 @@ typedef EncryptedFile = Map<String, dynamic>;
@Freezed(unionKey: "msgtype", fallbackUnion: "default") @Freezed(unionKey: "msgtype", fallbackUnion: "default")
abstract class MessageContent extends Content with _$MessageContent { abstract class MessageContent extends Content with _$MessageContent {
MessageContent._(); MessageContent._();
static String? mediaUrlFromJson(Map<dynamic, dynamic> json, String key) =>
json[key] ?? json["file"]?[key];
factory MessageContent({String? body}) = UnknownMessageContent; factory MessageContent({String? body}) = UnknownMessageContent;
@FreezedUnionValue("m.text") @FreezedUnionValue("m.text")
@ -46,7 +43,7 @@ abstract class MessageContent extends Content with _$MessageContent {
EncryptedFile? file, EncryptedFile? file,
String? filename, String? filename,
ImageInfo? info, ImageInfo? info,
@JsonKey(readValue: MessageContent.mediaUrlFromJson) required Uri url, Uri? url,
}) = ImageMessageContent; }) = ImageMessageContent;
@FreezedUnionValue("m.file") @FreezedUnionValue("m.file")
@ -57,7 +54,7 @@ abstract class MessageContent extends Content with _$MessageContent {
EncryptedFile? file, EncryptedFile? file,
String? filename, String? filename,
FileInfo? info, FileInfo? info,
@JsonKey(readValue: MessageContent.mediaUrlFromJson) required Uri url, Uri? url,
}) = FileMessageContent; }) = FileMessageContent;
@FreezedUnionValue("m.audio") @FreezedUnionValue("m.audio")
@ -68,7 +65,7 @@ abstract class MessageContent extends Content with _$MessageContent {
EncryptedFile? file, EncryptedFile? file,
String? filename, String? filename,
AudioInfo? info, AudioInfo? info,
@JsonKey(readValue: MessageContent.mediaUrlFromJson) required Uri url, Uri? url,
}) = AudioMessageContent; }) = AudioMessageContent;
@FreezedUnionValue("m.video") @FreezedUnionValue("m.video")
@ -79,7 +76,7 @@ abstract class MessageContent extends Content with _$MessageContent {
EncryptedFile? file, EncryptedFile? file,
String? filename, String? filename,
VideoInfo? info, VideoInfo? info,
@JsonKey(readValue: MessageContent.mediaUrlFromJson) required Uri url, Uri? url,
}) = VideoMessageContent; }) = VideoMessageContent;
@FreezedUnionValue("m.location") @FreezedUnionValue("m.location")

View file

@ -1,16 +0,0 @@
import "package:freezed_annotation/freezed_annotation.dart";
part "download_media.freezed.dart";
part "download_media.g.dart";
@freezed
abstract class DownloadMediaRequest with _$DownloadMediaRequest {
const factory DownloadMediaRequest({
required Uri mxc,
@Default(false) bool encrypted,
@Default(false) bool isAvatar,
@Default(false) bool thumbnailAvatar,
}) = _DownloadMediaRequest;
factory DownloadMediaRequest.fromJson(Map<String, Object?> json) =>
_$DownloadMediaRequestFromJson(json);
}

View file

@ -16,9 +16,6 @@ abstract class OAuthRegisterClientRequest with _$OAuthRegisterClientRequest {
IList<GrantType>? grantTypes, IList<GrantType>? grantTypes,
IList<Uri>? redirectUris, IList<Uri>? redirectUris,
IList<ResponseType>? responseTypes, IList<ResponseType>? responseTypes,
@Default(AuthMethod.none)
@JsonKey(name: "token_endpoint_auth_method")
AuthMethod? authMethod, AuthMethod? authMethod,
}) = _OAuthRegisterClientRequest; }) = _OAuthRegisterClientRequest;

View file

@ -1,7 +1,11 @@
import "package:color_hash/color_hash.dart"; import "package:color_hash/color_hash.dart";
import "package:cross_cache/cross_cache.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:nexus/helpers/mxc_image.dart"; import "package:nexus/controllers/client_state.dart";
import "package:nexus/controllers/cross_cache.dart";
import "package:nexus/helpers/extensions/get_headers.dart";
import "package:nexus/helpers/extensions/mxc_to_https.dart";
class AvatarOrHash extends ConsumerWidget { class AvatarOrHash extends ConsumerWidget {
final Uri? avatar; final Uri? avatar;
@ -23,6 +27,15 @@ class AvatarOrHash extends ConsumerWidget {
child: Center(child: Icon(Icons.person, size: height / 2)), child: Center(child: Icon(Icons.person, size: height / 2)),
); );
final parsedAvatar = avatar?.mxcToHttps(
ref.watch(
ClientStateController.provider.select(
(value) => value?.homeserverUrl,
),
) ??
"",
);
return SizedBox( return SizedBox(
width: height, width: height,
height: height, height: height,
@ -32,10 +45,14 @@ class AvatarOrHash extends ConsumerWidget {
child: SizedBox( child: SizedBox(
width: height, width: height,
height: height, height: height,
child: avatar == null child: parsedAvatar == null
? fallback ?? box ? fallback ?? box
: Image( : Image(
image: MxcImage(ref, .new(mxc: avatar!, isAvatar: true)), image: CachedNetworkImage(
parsedAvatar.toString(),
ref.watch(CrossCacheController.provider),
headers: ref.headers,
),
fit: .cover, fit: .cover,
loadingBuilder: (_, child, loadingProgress) => loadingBuilder: (_, child, loadingProgress) =>
loadingProgress == null ? child : fallback ?? box, loadingProgress == null ? child : fallback ?? box,

View file

@ -1,18 +1,19 @@
import "package:cross_cache/cross_cache.dart";
import "package:flutter/material.dart"; import "package:flutter/material.dart";
import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:hooks_riverpod/hooks_riverpod.dart";
import "package:m3e_buttons/m3e_buttons.dart"; import "package:m3e_buttons/m3e_buttons.dart";
import "package:nexus/helpers/mxc_image.dart"; import "package:nexus/controllers/cross_cache.dart";
import "package:nexus/models/requests/download_media.dart"; import "package:nexus/helpers/extensions/get_headers.dart";
import "package:nexus/widgets/error_dialog.dart"; import "package:nexus/widgets/error_dialog.dart";
class ExpandableImage extends ConsumerWidget { class ExpandableImage extends ConsumerWidget {
final Widget child; final Widget child;
final DownloadMediaRequest? request; final String? source;
const ExpandableImage(this.request, {required this.child, super.key}); const ExpandableImage(this.source, {required this.child, super.key});
@override @override
Widget build(BuildContext context, WidgetRef ref) => InkWell( Widget build(BuildContext context, WidgetRef ref) => InkWell(
onTap: request == null onTap: source == null
? null ? null
: () => showDialog( : () => showDialog(
context: context, context: context,
@ -26,10 +27,14 @@ class ExpandableImage extends ConsumerWidget {
maxScale: 10, maxScale: 10,
child: Image( child: Image(
errorBuilder: (_, error, stackTrace) => ErrorDialog( errorBuilder: (_, error, stackTrace) => ErrorDialog(
"Loading failed for ${request?.mxc}\nError: $error", "Loading failed for $source\nError: $error",
stackTrace, stackTrace,
), ),
image: MxcImage(ref, request!), image: CachedNetworkImage(
source!,
ref.watch(CrossCacheController.provider),
headers: ref.headers,
),
), ),
), ),
), ),

View file

@ -1,10 +1,14 @@
import "package:cross_cache/cross_cache.dart";
import "package:fast_immutable_collections/fast_immutable_collections.dart"; 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:nexus/controllers/client_state.dart";
import "package:nexus/controllers/cross_cache.dart";
import "package:nexus/helpers/extensions/get_headers.dart";
import "package:nexus/helpers/extensions/link_to_mention.dart"; import "package:nexus/helpers/extensions/link_to_mention.dart";
import "package:nexus/helpers/extensions/mxc_to_https.dart";
import "package:nexus/helpers/launch_helper.dart"; import "package:nexus/helpers/launch_helper.dart";
import "package:nexus/helpers/mxc_image.dart";
import "package:nexus/widgets/expandable_image.dart"; import "package:nexus/widgets/expandable_image.dart";
import "package:nexus/widgets/html/mention_chip.dart"; import "package:nexus/widgets/html/mention_chip.dart";
import "package:nexus/widgets/html/spoiler_text.dart"; import "package:nexus/widgets/html/spoiler_text.dart";
@ -36,7 +40,16 @@ class Html extends ConsumerWidget {
(element.attributes.keys.contains("data-mx-emoticon") ? 32 : null) ?? (element.attributes.keys.contains("data-mx-emoticon") ? 32 : null) ??
300; 300;
final width = int.tryParse(element.attributes["width"] ?? ""); final width = int.tryParse(element.attributes["width"] ?? "");
final src = Uri.tryParse(element.attributes["src"] ?? ""); final src = Uri.tryParse(element.attributes["src"] ?? "")
?.mxcToHttps(
ref.watch(
ClientStateController.provider.select(
(value) => value?.homeserverUrl,
),
) ??
"",
)
.toString();
return switch (element.localName) { return switch (element.localName) {
"code" => "code" =>
@ -64,9 +77,13 @@ class Html extends ConsumerWidget {
: InlineCustomWidget( : InlineCustomWidget(
alignment: PlaceholderAlignment.middle, alignment: PlaceholderAlignment.middle,
child: ExpandableImage( child: ExpandableImage(
.new(mxc: src), src,
child: Image( child: Image(
image: MxcImage(ref, .new(mxc: src)), image: CachedNetworkImage(
src,
ref.watch(CrossCacheController.provider),
headers: ref.headers,
),
errorBuilder: (_, error, _) => Text( errorBuilder: (_, error, _) => Text(
"Image Failed to Load", "Image Failed to Load",
style: .new(color: Theme.of(context).colorScheme.error), style: .new(color: Theme.of(context).colorScheme.error),

View file

@ -1,33 +1,34 @@
import "package:cross_cache/cross_cache.dart";
import "package:flutter/material.dart"; import "package:flutter/material.dart";
import "package:flutter_blurhash/flutter_blurhash.dart"; import "package:flutter_blurhash/flutter_blurhash.dart";
import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:nexus/helpers/mxc_image.dart"; import "package:nexus/controllers/cross_cache.dart";
import "package:nexus/helpers/extensions/get_headers.dart";
import "package:nexus/models/info/image.dart" as i; import "package:nexus/models/info/image.dart" as i;
import "package:nexus/models/requests/download_media.dart";
import "package:nexus/widgets/expandable_image.dart"; import "package:nexus/widgets/expandable_image.dart";
import "package:nexus/widgets/loading.dart"; import "package:nexus/widgets/loading.dart";
class MessageImage extends ConsumerWidget { class MessageImage extends ConsumerWidget {
final Uri url; final Uri url;
final i.ImageInfo? info; final i.ImageInfo? info;
final bool encrypted; const MessageImage(this.url, {this.info, super.key});
const MessageImage(this.url, {this.info, required this.encrypted, super.key});
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) => ExpandableImage(
final request = DownloadMediaRequest(mxc: url, encrypted: encrypted); url.toString(),
return ExpandableImage(
request,
child: ClipRRect( child: ClipRRect(
borderRadius: .all(.circular(8)), borderRadius: .all(.circular(8)),
child: AspectRatio( child: AspectRatio(
aspectRatio: (info?.width ?? 1) / (info?.height ?? 1), aspectRatio: (info?.width ?? 1) / (info?.height ?? 1),
child: Image( child: Image(
image: MxcImage(ref, request), image: CachedNetworkImage(
url.toString(),
ref.watch(CrossCacheController.provider),
headers: ref.headers,
),
width: info?.width, width: info?.width,
fit: BoxFit.fitWidth, fit: BoxFit.fitWidth,
loadingBuilder: (_, child, loadingProgress) => loadingBuilder: (_, child, loadingProgress) => loadingProgress == null
loadingProgress == null
? child ? child
: switch (info?.blurHash) { : switch (info?.blurHash) {
final blurHash? => final blurHash? =>
@ -54,4 +55,3 @@ class MessageImage extends ConsumerWidget {
), ),
); );
} }
}

View file

@ -1,10 +1,13 @@
import "package:cross_cache/cross_cache.dart";
import "package:flutter/material.dart"; import "package:flutter/material.dart";
import "package:flutter_hooks/flutter_hooks.dart"; import "package:flutter_hooks/flutter_hooks.dart";
import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:nexus/controllers/client_state.dart"; import "package:nexus/controllers/client_state.dart";
import "package:nexus/controllers/cross_cache.dart";
import "package:nexus/controllers/reactions.dart"; import "package:nexus/controllers/reactions.dart";
import "package:nexus/controllers/room_chat.dart"; import "package:nexus/controllers/room_chat.dart";
import "package:nexus/helpers/mxc_image.dart"; import "package:nexus/helpers/extensions/get_headers.dart";
import "package:nexus/helpers/extensions/mxc_to_https.dart";
import "package:nexus/models/event.dart"; import "package:nexus/models/event.dart";
import "package:nexus/widgets/error_dialog.dart"; import "package:nexus/widgets/error_dialog.dart";
import "package:nexus/main.dart"; import "package:nexus/main.dart";
@ -52,9 +55,16 @@ class ReactionRow extends ConsumerWidget {
child: reaction.startsWith("mxc://") child: reaction.startsWith("mxc://")
? Image( ? Image(
height: 20, height: 20,
image: MxcImage( image: CachedNetworkImage(
ref, headers: ref.headers,
.new(mxc: Uri.parse(reaction)), Uri.parse(reaction)
.mxcToHttps(
clientState!.homeserverUrl!,
)
.toString(),
ref.watch(
CrossCacheController.provider,
),
), ),
) )
: Text(reaction, overflow: .ellipsis), : Text(reaction, overflow: .ellipsis),

View file

@ -4,6 +4,7 @@ import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:linkify/linkify.dart"; import "package:linkify/linkify.dart";
import "package:nexus/controllers/client_state.dart"; import "package:nexus/controllers/client_state.dart";
import "package:nexus/controllers/event.dart"; import "package:nexus/controllers/event.dart";
import "package:nexus/helpers/extensions/mxc_to_https.dart";
import "package:nexus/models/content/encrypted.dart"; import "package:nexus/models/content/encrypted.dart";
import "package:nexus/models/content/message.dart"; import "package:nexus/models/content/message.dart";
import "package:nexus/models/content/sticker.dart"; import "package:nexus/models/content/sticker.dart";
@ -145,9 +146,14 @@ class MessageRenderer extends ConsumerWidget {
: ConstrainedBox( : ConstrainedBox(
constraints: .loose(.square(200)), constraints: .loose(.square(200)),
child: MessageImage( child: MessageImage(
url, url.mxcToHttps(
ref.watch(
ClientStateController.provider.select(
(value) => value!.homeserverUrl!,
),
),
),
info: info, info: info,
encrypted: false,
), ),
), ),
// TODO: Handle locations // TODO: Handle locations
@ -223,7 +229,14 @@ class MessageRenderer extends ConsumerWidget {
FileMessageContent(:final url) || FileMessageContent(:final url) ||
VideoMessageContent(:final url) || VideoMessageContent(:final url) ||
AudioMessageContent(:final url)) AudioMessageContent(:final url))
ConstrainedBox( switch (url?.mxcToHttps(
ref.watch(
ClientStateController.provider.select(
(value) => value!.homeserverUrl!,
),
),
)) {
final url? => ConstrainedBox(
constraints: .loose(.square(500)), constraints: .loose(.square(500)),
child: switch (event.content) { child: switch (event.content) {
VideoMessageContent(:final info) => VideoMessageContent(:final info) =>
@ -235,18 +248,16 @@ class MessageRenderer extends ConsumerWidget {
:final filename, :final filename,
) => ) =>
FileCard(url, info, filename: filename), FileCard(url, info, filename: filename),
ImageMessageContent( ImageMessageContent(:final info) =>
:final info, MessageImage(url, info: info),
:final file,
) =>
MessageImage(
url,
info: info,
encrypted: file != null,
),
_ => SizedBox.shrink(), _ => SizedBox.shrink(),
}, },
), ),
_ => Text(
"Nexus currently cannot handle encrypted media",
style: errorStyle,
),
},
if (event.lastEditRowId != 0) if (event.lastEditRowId != 0)
Text( Text(

View file

@ -1,9 +1,11 @@
import "package:flutter/material.dart"; import "package:flutter/material.dart";
import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:hooks_riverpod/hooks_riverpod.dart";
import "package:nexus/controllers/rooms.dart"; import "package:nexus/controllers/rooms.dart";
import "package:nexus/helpers/extensions/mxc_to_https.dart";
import "package:nexus/widgets/appbar.dart"; import "package:nexus/widgets/appbar.dart";
import "package:nexus/widgets/avatar_or_hash.dart"; import "package:nexus/widgets/avatar_or_hash.dart";
import "package:nexus/widgets/expandable_image.dart"; import "package:nexus/widgets/expandable_image.dart";
import "package:nexus/controllers/client_state.dart";
import "package:nexus/widgets/linkified_text.dart"; import "package:nexus/widgets/linkified_text.dart";
import "package:nexus/widgets/room_menu.dart"; import "package:nexus/widgets/room_menu.dart";
@ -51,9 +53,15 @@ class RoomAppbar extends ConsumerWidget implements PreferredSizeWidget {
children: [ children: [
if (room.metadata?.avatar != null) if (room.metadata?.avatar != null)
ExpandableImage( ExpandableImage(
room.metadata?.avatar == null room.metadata!.avatar!
? null .mxcToHttps(
: .new(mxc: room.metadata!.avatar!), ref.watch(
ClientStateController.provider.select(
(value) => value!.homeserverUrl!,
),
),
)
.toString(),
child: AvatarOrHash( child: AvatarOrHash(
room.metadata?.avatar, room.metadata?.avatar,
room.metadata?.name ?? "Unnamed Room", room.metadata?.name ?? "Unnamed Room",

View file

@ -1,9 +1,11 @@
import "package:cross_cache/cross_cache.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:nexus/controllers/cross_cache.dart";
import "package:nexus/controllers/url_preview.dart"; import "package:nexus/controllers/url_preview.dart";
import "package:nexus/helpers/extensions/better_when.dart"; import "package:nexus/helpers/extensions/better_when.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/helpers/mxc_image.dart";
class UrlPreview extends ConsumerWidget { class UrlPreview extends ConsumerWidget {
final String link; final String link;
@ -46,9 +48,10 @@ class UrlPreview extends ConsumerWidget {
child: Image( child: Image(
errorBuilder: (_, _, _) => SizedBox.shrink(), errorBuilder: (_, _, _) => SizedBox.shrink(),
width: preview.width, width: preview.width,
image: MxcImage( image: CachedNetworkImage(
ref, preview.imageUrl.toString(),
.new(mxc: preview.imageUrl!), ref.watch(CrossCacheController.provider),
headers: ref.headers,
), ),
fit: .fitWidth, fit: .fitWidth,
), ),

View file

@ -10,6 +10,7 @@ import "package:nexus/controllers/power_level.dart";
import "package:nexus/controllers/profile.dart"; import "package:nexus/controllers/profile.dart";
import "package:nexus/helpers/extensions/better_when.dart"; import "package:nexus/helpers/extensions/better_when.dart";
import "package:nexus/helpers/extensions/get_localpart.dart"; import "package:nexus/helpers/extensions/get_localpart.dart";
import "package:nexus/helpers/extensions/mxc_to_https.dart";
import "package:nexus/models/content/membership.dart"; import "package:nexus/models/content/membership.dart";
import "package:nexus/models/membership_action.dart"; import "package:nexus/models/membership_action.dart";
import "package:nexus/widgets/avatar_or_hash.dart"; import "package:nexus/widgets/avatar_or_hash.dart";
@ -96,7 +97,15 @@ class UserBottomSheet extends ConsumerWidget {
SizedBox(height: 18), SizedBox(height: 18),
ExpandableImage( ExpandableImage(
member.avatarUrl == null ? null : .new(mxc: member.avatarUrl!), member.avatarUrl
?.mxcToHttps(
ref.watch(
ClientStateController.provider.select(
(value) => value!.homeserverUrl!,
),
),
)
.toString(),
child: AvatarOrHash( child: AvatarOrHash(
member.avatarUrl, member.avatarUrl,
member.displayName ?? userId.localpart, member.displayName ?? userId.localpart,

View file

@ -11,7 +11,7 @@ buildGoModule (finalAttrs: {
src = "${src}/gomuks"; src = "${src}/gomuks";
vendorHash = "sha256-4QU51WGiNKYm7z/ajnas2qQaMK5BgyHwPfmNpQvW0qg="; vendorHash = "sha256-mNZEHOGByVqK1kSLC/Cf1VEvkDxRen+TmoV5CXvGrZ4=";
buildPhase = '' buildPhase = ''
runHook preBuild runHook preBuild

View file

@ -249,6 +249,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.15.1" version: "1.15.1"
cross_cache:
dependency: "direct main"
description:
name: cross_cache
sha256: "4983a16603cc99b0a14de6a772fa8ee4533411f46f3c423f1386fea7566049c5"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
cross_file: cross_file:
dependency: transitive dependency: transitive
description: description:
@ -289,6 +297,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.7.14" version: "0.7.14"
dio:
dependency: transitive
description:
name: dio
sha256: ea2bad3c89a27635ce2d85cce4d6b199da49a5a48ec77b03e45b65a3b90922b0
url: "https://pub.dev"
source: hosted
version: "5.10.0"
dio_web_adapter:
dependency: transitive
description:
name: dio_web_adapter
sha256: dd58dc3861eb36edb13b217efc006a1c21e5bbc341de8c229b85634fa5e362e4
url: "https://pub.dev"
source: hosted
version: "2.2.0"
dynamic_color: dynamic_color:
dependency: "direct main" dependency: "direct main"
description: description:
@ -662,6 +686,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.2.1" version: "0.2.1"
idb_shim:
dependency: transitive
description:
name: idb_shim
sha256: "3448298f244bc76a14aca71461eeab6add2b8a877930521c42c2e8b71b644590"
url: "https://pub.dev"
source: hosted
version: "2.9.6+2"
image: image:
dependency: transitive dependency: transitive
description: description:
@ -1216,6 +1248,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.2.2" version: "0.2.2"
sembast:
dependency: transitive
description:
name: sembast
sha256: a58b26925e23071cf0f4754d8449aabe829e9a9930a872fb18e6ae4c2c88e025
url: "https://pub.dev"
source: hosted
version: "3.8.9+1"
shared_preferences: shared_preferences:
dependency: "direct main" dependency: "direct main"
description: description:

View file

@ -50,6 +50,7 @@ dependencies:
fluttertagger: 2.3.2 fluttertagger: 2.3.2
dynamic_polls: 0.0.7 dynamic_polls: 0.0.7
flutter_hooks: 0.21.3+1 flutter_hooks: 0.21.3+1
cross_cache: 1.1.0
ffi: 2.2.0 ffi: 2.2.0
hooks: 2.0.2 hooks: 2.0.2
code_assets: 1.2.1 code_assets: 1.2.1