Add room details dialog

Fixes #20
This commit is contained in:
Henry Hiles 2026-05-26 15:31:37 -04:00
commit e69f04f6e7
Signed by: Henry-Hiles
SSH key fingerprint: SHA256:VKQUdS31Q90KvX7EkKMHMBpUspcmItAh86a+v7PGiIs
7 changed files with 105 additions and 41 deletions

View file

@ -5,6 +5,7 @@
"fluttertagger", "fluttertagger",
"Gomuks", "Gomuks",
"Homeserver", "Homeserver",
"Linkified",
"localpart", "localpart",
"msgtype", "msgtype",
"muks", "muks",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

View file

@ -9,10 +9,12 @@ class Appbar extends StatelessWidget implements PreferredSizeWidget {
final Color? backgroundColor; final Color? backgroundColor;
final double? scrolledUnderElevation; final double? scrolledUnderElevation;
final IList<Widget> actions; final IList<Widget> actions;
final VoidCallback? onTap;
const Appbar({ const Appbar({
super.key, super.key,
this.title, this.title,
this.onTap,
this.backgroundColor, this.backgroundColor,
this.scrolledUnderElevation, this.scrolledUnderElevation,
this.leading, this.leading,
@ -37,11 +39,14 @@ class Appbar extends StatelessWidget implements PreferredSizeWidget {
return GestureDetector( return GestureDetector(
onPanStart: (_) => windowManager.startDragging(), onPanStart: (_) => windowManager.startDragging(),
child: AppBar( child: AppBar(
leading: leading, leading: InkWell(onTap: onTap, child: leading),
backgroundColor: backgroundColor, backgroundColor: backgroundColor,
scrolledUnderElevation: scrolledUnderElevation, scrolledUnderElevation: scrolledUnderElevation,
actionsPadding: const EdgeInsets.symmetric(horizontal: 8), actionsPadding: const EdgeInsets.symmetric(horizontal: 8),
title: IgnorePointer(child: title), title: InkWell(
onTap: onTap,
child: IgnorePointer(child: title),
),
flexibleSpace: GestureDetector(onDoubleTap: maximize), flexibleSpace: GestureDetector(onDoubleTap: maximize),
actions: [ actions: [
...actions, ...actions,

View file

@ -140,7 +140,7 @@ class Composer extends HookConsumerWidget {
query.value = newQuery; query.value = newQuery;
}, },
triggerCharacterAndStyles: {"@": style, "#": style}, triggerCharacterAndStyles: {"@": style, "#": style},
builder: (context, key) => TextFormField( builder: (context, key) => TextField(
maxLines: 12, maxLines: 12,
minLines: 1, minLines: 1,
autofocus: true, autofocus: true,
@ -150,7 +150,7 @@ class Composer extends HookConsumerWidget {
), ),
controller: controller.value, controller: controller.value,
key: key, key: key,
onFieldSubmitted: (_) => send(), onSubmitted: (_) => send(),
// Don't defocus on submit // Don't defocus on submit
onEditingComplete: () {}, onEditingComplete: () {},
textInputAction: TextInputAction.done, textInputAction: TextInputAction.done,

View file

@ -0,0 +1,23 @@
import "package:flutter/material.dart";
import "package:flutter_linkify/flutter_linkify.dart";
import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:nexus/helpers/launch_helper.dart";
class LinkifiedText extends ConsumerWidget {
final String text;
final int? maxLines;
final TextStyle? style;
const LinkifiedText(this.text, {this.maxLines, this.style, super.key});
@override
Widget build(BuildContext context, WidgetRef ref) => Linkify(
text: text,
maxLines: maxLines,
style: style,
options: LinkifyOptions(humanize: false),
onOpen: (link) =>
ref.watch(LaunchHelper.provider).launchUrl(Uri.parse(link.url)),
linkStyle: TextStyle(color: Theme.of(context).colorScheme.primary),
overflow: maxLines == null ? null : TextOverflow.ellipsis,
);
}

View file

@ -11,7 +11,6 @@ import "package:nexus/controllers/event_controller.dart";
import "package:nexus/helpers/extensions/get_headers.dart"; import "package:nexus/helpers/extensions/get_headers.dart";
import "package:nexus/helpers/extensions/mxc_to_https.dart"; import "package:nexus/helpers/extensions/mxc_to_https.dart";
import "package:nexus/helpers/extensions/show_context_menu.dart"; import "package:nexus/helpers/extensions/show_context_menu.dart";
import "package:nexus/helpers/launch_helper.dart";
import "package:nexus/models/content/avatar.dart"; import "package:nexus/models/content/avatar.dart";
import "package:nexus/models/content/content.dart"; import "package:nexus/models/content/content.dart";
import "package:nexus/models/content/encrypted.dart"; import "package:nexus/models/content/encrypted.dart";
@ -24,6 +23,7 @@ import "package:nexus/widgets/expandable_image.dart";
import "package:nexus/widgets/html/html.dart"; import "package:nexus/widgets/html/html.dart";
import "package:nexus/widgets/lazy_loading/message_avatar.dart"; import "package:nexus/widgets/lazy_loading/message_avatar.dart";
import "package:nexus/widgets/lazy_loading/message_displayname.dart"; import "package:nexus/widgets/lazy_loading/message_displayname.dart";
import "package:nexus/widgets/linkified_text.dart";
import "package:nexus/widgets/url_preview.dart"; import "package:nexus/widgets/url_preview.dart";
import "package:nexus/widgets/loading.dart"; import "package:nexus/widgets/loading.dart";
import "package:nexus/widgets/players/video.dart"; import "package:nexus/widgets/players/video.dart";
@ -33,7 +33,6 @@ import "package:nexus/widgets/renderers/membership.dart";
import "package:nexus/widgets/renderers/generic_event.dart"; import "package:nexus/widgets/renderers/generic_event.dart";
import "package:nexus/widgets/file_card.dart"; import "package:nexus/widgets/file_card.dart";
import "package:timeago/timeago.dart"; import "package:timeago/timeago.dart";
import "package:flutter_linkify/flutter_linkify.dart";
class EventRenderer extends ConsumerWidget { class EventRenderer extends ConsumerWidget {
final Event event; final Event event;
@ -232,24 +231,10 @@ class EventRenderer extends ConsumerWidget {
}, },
), ),
) )
: Linkify( : LinkifiedText(
body,
style: textStyle, style: textStyle,
text: body,
maxLines: maxLines, maxLines: maxLines,
overflow: maxLines == null
? null
: TextOverflow.ellipsis,
options: LinkifyOptions(
humanize: false,
),
onOpen: (link) => ref
.watch(LaunchHelper.provider)
.launchUrl(Uri.parse(link.url)),
linkStyle: TextStyle(
color: Theme.of(
context,
).colorScheme.primary,
),
), ),
if (!textOnly) ...[ if (!textOnly) ...[

View file

@ -1,12 +1,13 @@
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:hooks_riverpod/hooks_riverpod.dart"; import "package:hooks_riverpod/hooks_riverpod.dart";
import "package:nexus/controllers/client_state_controller.dart";
import "package:nexus/controllers/rooms_controller.dart"; import "package:nexus/controllers/rooms_controller.dart";
import "package:nexus/helpers/extensions/mxc_to_https.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_controller.dart";
import "package:nexus/widgets/linkified_text.dart";
import "package:nexus/widgets/room_menu.dart"; import "package:nexus/widgets/room_menu.dart";
class RoomAppbar extends ConsumerWidget implements PreferredSizeWidget { class RoomAppbar extends ConsumerWidget implements PreferredSizeWidget {
@ -30,13 +31,29 @@ class RoomAppbar extends ConsumerWidget implements PreferredSizeWidget {
final room = roomId == null final room = roomId == null
? null ? null
: ref.watch(RoomsController.provider.select((value) => value[roomId!])); : ref.watch(RoomsController.provider.select((value) => value[roomId!]));
return Appbar( return Appbar(
leading: isDesktop onTap: room == null
? room == null
? null ? null
: ExpandableImage( : () => showDialog(
room.metadata?.avatar context: context,
?.mxcToHttps( builder: (context) => Dialog(
constraints: BoxConstraints.loose(Size.fromWidth(400)),
child: Padding(
padding: EdgeInsetsGeometry.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 8,
children: [
Row(
spacing: 12,
mainAxisSize: MainAxisSize.min,
children: [
if (room.metadata?.avatar != null)
ExpandableImage(
room.metadata!.avatar!
.mxcToHttps(
ref.watch( ref.watch(
ClientStateController.provider.select( ClientStateController.provider.select(
(value) => value!.homeserverUrl!, (value) => value!.homeserverUrl!,
@ -46,10 +63,42 @@ class RoomAppbar extends ConsumerWidget implements PreferredSizeWidget {
.toString(), .toString(),
child: AvatarOrHash( child: AvatarOrHash(
room.metadata?.avatar, room.metadata?.avatar,
room.metadata?.name ?? "Unnamed Rooms", room.metadata?.name ?? "Unnamed Room",
height: 24, height: 64,
fallback: Icon(Icons.numbers), fallback: Icon(Icons.numbers),
), ),
),
Expanded(
child: Text(
room.metadata?.name ?? "Unnamed Room",
overflow: TextOverflow.ellipsis,
maxLines: 3,
style: Theme.of(context).textTheme.headlineSmall,
),
),
],
),
if (room.metadata?.topic?.isNotEmpty == true)
LinkifiedText(
room.metadata!.topic!,
style: Theme.of(context).textTheme.bodyLarge
?.copyWith(
color: Theme.of(
context,
).colorScheme.onSurfaceVariant,
),
),
],
),
),
),
),
leading: isDesktop && room != null
? AvatarOrHash(
room.metadata?.avatar,
room.metadata?.name ?? "Unnamed Room",
height: 24,
fallback: Icon(Icons.numbers),
) )
: DrawerButton(onPressed: () => onOpenDrawer(context)), : DrawerButton(onPressed: () => onOpenDrawer(context)),
scrolledUnderElevation: 0, scrolledUnderElevation: 0,
@ -70,6 +119,7 @@ class RoomAppbar extends ConsumerWidget implements PreferredSizeWidget {
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.labelMedium?.copyWith( style: Theme.of(context).textTheme.labelMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant, color: Theme.of(context).colorScheme.onSurfaceVariant,
decoration: TextDecoration.underline,
), ),
), ),
], ],