accessiblity fixes

This commit is contained in:
Henry Hiles 2026-03-01 14:40:14 -05:00
commit b594f5a1d1
No known key found for this signature in database
11 changed files with 147 additions and 118 deletions

View file

@ -4,23 +4,21 @@ import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:nexus/models/event.dart"; import "package:nexus/models/event.dart";
import "package:nexus/models/room.dart"; import "package:nexus/models/room.dart";
class MembersController extends AsyncNotifier<IList<Event>> { class MembersController extends Notifier<IList<Event>> {
final Room room; final Room room;
MembersController(this.room); MembersController(this.room);
@override @override
Future<IList<Event>> build() async => IList<Event> build() => (room.state["m.room.member"]?.values ?? [])
(room.state["m.room.member"]?.values ?? [])
.map( .map(
(eventRowId) => room.events.firstWhereOrNull( (eventRowId) =>
(event) => event.rowId == eventRowId, room.events.firstWhereOrNull((event) => event.rowId == eventRowId),
),
) )
.nonNulls .nonNulls
.where((member) => member.content["membership"] == "join") .where((member) => member.content["membership"] == "join")
.toIList(); .toIList();
static final provider = AsyncNotifierProvider.family static final provider = NotifierProvider.family
.autoDispose<MembersController, IList<Event>, Room>( .autoDispose<MembersController, IList<Event>, Room>(
MembersController.new, MembersController.new,
); );

View file

@ -37,9 +37,7 @@ class MessageController extends AsyncNotifier<Message?> {
if (!ref.mounted) return null; if (!ref.mounted) return null;
final members = await ref.watch( final members = ref.watch(MembersController.provider(config.room));
MembersController.provider(config.room).future,
);
final author = members.firstWhereOrNull( final author = members.firstWhereOrNull(
(member) => member.stateKey == event.authorId, (member) => member.stateKey == event.authorId,
); );

View file

@ -25,7 +25,7 @@ class RoomChatController extends AsyncNotifier<ChatController> {
@override @override
Future<ChatController> build() async { Future<ChatController> build() async {
final client = ref.watch(ClientController.provider.notifier); final client = ref.watch(ClientController.provider.notifier);
final room = ref.read(RoomsController.provider)[roomId]; var room = ref.read(RoomsController.provider)[roomId];
if (room == null) return InMemoryChatController(); if (room == null) return InMemoryChatController();
final state = await client.getRoomState( final state = await client.getRoomState(
@ -59,13 +59,16 @@ class RoomChatController extends AsyncNotifier<ChatController> {
const ISet.empty(), const ISet.empty(),
); );
room = ref.read(RoomsController.provider)[roomId];
if (room == null) return InMemoryChatController();
final messages = await ref.watch( final messages = await ref.watch(
MessagesController.provider( MessagesController.provider(
MessagesConfig( MessagesConfig(
room: room, room: room,
events: room.timeline events: room.timeline
.map( .map(
(timelineRowTuple) => room.events.firstWhereOrNull( (timelineRowTuple) => room!.events.firstWhereOrNull(
(event) => event.rowId == timelineRowTuple.eventRowId, (event) => event.rowId == timelineRowTuple.eventRowId,
), ),
) )
@ -91,7 +94,7 @@ class RoomChatController extends AsyncNotifier<ChatController> {
} else { } else {
final message = await ref.watch( final message = await ref.watch(
MessageController.provider( MessageController.provider(
MessageConfig(event: event, room: room, includeEdits: true), MessageConfig(event: event, room: room!, includeEdits: true),
).future, ).future,
); );
if (event.relationType == "m.replace") { if (event.relationType == "m.replace") {

View file

@ -97,6 +97,7 @@ class LoginPage extends HookConsumerWidget {
), ),
), ),
IconButton.filled( IconButton.filled(
tooltip: "Confirm homeserver choice",
onPressed: isLoading.value onPressed: isLoading.value
? null ? null
: () => setHomeserver(Uri.tryParse(homeserverUrl.text)), : () => setHomeserver(Uri.tryParse(homeserverUrl.text)),
@ -143,6 +144,7 @@ class LoginPage extends HookConsumerWidget {
? null ? null
: () => setHomeserver(homeserver.url), : () => setHomeserver(homeserver.url),
trailing: IconButton( trailing: IconButton(
tooltip: "Launch homeserver info page",
onPressed: () => launch(homeserver.url), onPressed: () => launch(homeserver.url),
icon: Icon(Icons.info_outline), icon: Icon(Icons.info_outline),
), ),

View file

@ -49,10 +49,15 @@ class Appbar extends StatelessWidget implements PreferredSizeWidget {
if (!(Platform.isAndroid || Platform.isIOS)) ...[ if (!(Platform.isAndroid || Platform.isIOS)) ...[
if (!Platform.isLinux) if (!Platform.isLinux)
IconButton( IconButton(
tooltip: "Maximize window",
onPressed: maximize, onPressed: maximize,
icon: const Icon(Icons.fullscreen), icon: const Icon(Icons.fullscreen),
), ),
IconButton(onPressed: () => exit(0), icon: const Icon(Icons.close)), IconButton(
tooltip: "Close window",
onPressed: () => exit(0),
icon: const Icon(Icons.close),
),
], ],
], ],
), ),

View file

@ -95,7 +95,27 @@ class ChatBox extends HookConsumerWidget {
spacing: 8, spacing: 8,
children: [ children: [
PopupMenuButton( PopupMenuButton(
itemBuilder: (context) => [], tooltip: "Add media",
itemBuilder: (context) => [
PopupMenuItem(
child: ListTile(
title: Text("Camera"),
leading: Icon(Icons.add_a_photo),
),
),
PopupMenuItem(
child: ListTile(
title: Text("Gallery"),
leading: Icon(Icons.add_photo_alternate),
),
),
PopupMenuItem(
child: ListTile(
title: Text("Files"),
leading: Icon(Icons.add_photo_alternate),
),
),
],
icon: Icon(Icons.add), icon: Icon(Icons.add),
// enabled: room.canSendDefaultMessages, TODO: Permissions check // enabled: room.canSendDefaultMessages, TODO: Permissions check
), ),
@ -138,6 +158,7 @@ class ChatBox extends HookConsumerWidget {
onPressed: send, onPressed: send,
// onPressed: room.canSendDefaultMessages ? send : null, // onPressed: room.canSendDefaultMessages ? send : null,
icon: Icon(Icons.send), icon: Icon(Icons.send),
tooltip: "Send message",
), ),
], ],
), ),

View file

@ -1,7 +1,6 @@
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/members_controller.dart"; import "package:nexus/controllers/members_controller.dart";
import "package:nexus/helpers/extensions/better_when.dart";
import "package:nexus/models/room.dart"; import "package:nexus/models/room.dart";
import "package:nexus/widgets/avatar_or_hash.dart"; import "package:nexus/widgets/avatar_or_hash.dart";
@ -10,12 +9,11 @@ class MemberList extends ConsumerWidget {
const MemberList(this.room, {super.key}); const MemberList(this.room, {super.key});
@override @override
Widget build(BuildContext context, WidgetRef ref) => Drawer( Widget build(BuildContext context, WidgetRef ref) {
final members = ref.watch(MembersController.provider(room));
return Drawer(
shape: Border(), shape: Border(),
child: ref child: ListView(
.watch(MembersController.provider(room))
.betterWhen(
data: (members) => ListView(
children: [ children: [
AppBar( AppBar(
scrolledUnderElevation: 0, scrolledUnderElevation: 0,
@ -27,6 +25,7 @@ class MemberList extends ConsumerWidget {
IconButton( IconButton(
onPressed: Scaffold.of(context).closeEndDrawer, onPressed: Scaffold.of(context).closeEndDrawer,
icon: Icon(Icons.close), icon: Icon(Icons.close),
tooltip: "Close member list",
), ),
], ],
), ),
@ -53,6 +52,6 @@ class MemberList extends ConsumerWidget {
), ),
], ],
), ),
),
); );
} }
}

View file

@ -2,7 +2,6 @@ import "package:flutter/material.dart";
import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:hooks_riverpod/hooks_riverpod.dart";
import "package:nexus/controllers/members_controller.dart"; import "package:nexus/controllers/members_controller.dart";
import "package:nexus/controllers/rooms_controller.dart"; import "package:nexus/controllers/rooms_controller.dart";
import "package:nexus/helpers/extensions/better_when.dart";
import "package:nexus/models/room.dart"; import "package:nexus/models/room.dart";
import "package:nexus/widgets/avatar_or_hash.dart"; import "package:nexus/widgets/avatar_or_hash.dart";
import "package:nexus/widgets/loading.dart"; import "package:nexus/widgets/loading.dart";
@ -32,28 +31,22 @@ class MentionOverlay extends ConsumerWidget {
color: Theme.of(context).colorScheme.surfaceContainerHigh, color: Theme.of(context).colorScheme.surfaceContainerHigh,
padding: EdgeInsets.all(8), padding: EdgeInsets.all(8),
child: switch (triggerCharacter) { child: switch (triggerCharacter) {
"@" => "@" => Consumer(
ref builder: (_, ref, _) {
.watch(MembersController.provider(room)) final members = ref.watch(MembersController.provider(room));
.betterWhen( return ListView(
data: (members) => ListView(
children: children:
(query.isEmpty (query.isEmpty
? members ? members
: members.where( : members.where(
(member) => (member) =>
member.stateKey member.stateKey?.toLowerCase().contains(
?.toLowerCase()
.contains(
query.toLowerCase(), query.toLowerCase(),
) == ) ==
true || true ||
(member.content["displayname"] (member.content["displayname"] as String?)
as String?)
?.toLowerCase() ?.toLowerCase()
.contains( .contains(query.toLowerCase()) ==
query.toLowerCase(),
) ==
true, true,
)) ))
.map( .map(
@ -84,7 +77,8 @@ class MentionOverlay extends ConsumerWidget {
), ),
) )
.toList(), .toList(),
), );
},
), ),
"#" => ListView( "#" => ListView(
children: children:

View file

@ -56,6 +56,8 @@ class RelationPreview extends ConsumerWidget {
), ),
), ),
IconButton( IconButton(
tooltip:
"Cancel ${relationType == RelationType.edit ? "edit" : "reply"}",
onPressed: onDismiss, onPressed: onDismiss,
icon: Icon(Icons.close), icon: Icon(Icons.close),
iconSize: 20, iconSize: 20,

View file

@ -52,9 +52,14 @@ class RoomAppbar extends StatelessWidget implements PreferredSizeWidget {
], ],
), ),
actions: [ actions: [
IconButton(onPressed: () {}, icon: Icon(Icons.push_pin)), IconButton(
onPressed: null,
icon: Icon(Icons.push_pin),
tooltip: "Open pinned messages",
),
IconButton( IconButton(
onPressed: () => onOpenMemberList(context), onPressed: () => onOpenMemberList(context),
tooltip: "Open member list",
icon: Icon(Icons.people), icon: Icon(Icons.people),
), ),
RoomMenu(room), RoomMenu(room),

View file

@ -155,6 +155,7 @@ class Sidebar extends HookConsumerWidget {
icon: Icon(Icons.add), icon: Icon(Icons.add),
), ),
IconButton( IconButton(
tooltip: "Explore other rooms",
onPressed: () => showDialog( onPressed: () => showDialog(
context: context, context: context,
builder: (context) => AlertDialog(title: Text("To-do")), builder: (context) => AlertDialog(title: Text("To-do")),
@ -162,6 +163,7 @@ class Sidebar extends HookConsumerWidget {
icon: Icon(Icons.explore), icon: Icon(Icons.explore),
), ),
IconButton( IconButton(
tooltip: "Open settings",
onPressed: () => Navigator.of( onPressed: () => Navigator.of(
context, context,
).push(MaterialPageRoute(builder: (_) => SettingsPage())), ).push(MaterialPageRoute(builder: (_) => SettingsPage())),