Space never presses a focused button; chat toasts open the chat; plain wheel always scrolls the page
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
cf80bb2167
commit
f2272d7980
8 files changed
+56
-21
No files matched your search
@@ -64,7 +64,7 @@ when you're ready", "Waiting for Bob and Mika · 2/3 ready", "Bob is
|
||||
loading…") or what went wrong, with Retry or Cancel when it helps. Chat and activity open in a panel on the right (chat button or
|
||||
Tab), ready to type; it shows messages, and its "Activity" toggle adds
|
||||
joins, ready, seeks and the like between them. Joins, leaves, chat and
|
||||
other news also pop up bottom left with the person's avatar. Unread chat
|
||||
other news also pop up bottom left with the person's avatar; click a message to open the chat. Unread chat
|
||||
shows as a count on the chat buttons and, in fullscreen with the controls
|
||||
hidden, as a small chip top right.
|
||||
|
||||
|
||||
@@ -501,7 +501,6 @@ class _SchedulePageState extends State<SchedulePage> {
|
||||
(byDay[DateTime(l.year, l.month, l.day)] ??= []).add(e);
|
||||
}
|
||||
return HScroll(
|
||||
wheel: false,
|
||||
controller: _scroll,
|
||||
builder: (c) => ListView.separated(
|
||||
key: const ValueKey('schedule-board'),
|
||||
|
||||
@@ -669,7 +669,11 @@ class AppShellState extends State<AppShell> with SingleTickerProviderStateMixin
|
||||
),
|
||||
),
|
||||
if (online && _shown != PlayerMode.none) _playerLayer(area),
|
||||
Positioned(left: 16, bottom: _fullscreen ? 90 : 16, child: const PartyToasts()),
|
||||
Positioned(left: 16, bottom: _fullscreen ? 90 : 16, child: PartyToasts(
|
||||
onOpenChat: () {
|
||||
if (!_chatOpen) _toggleChat();
|
||||
},
|
||||
)),
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
@@ -90,17 +90,17 @@ Future<T?> showPopover<T>(
|
||||
},
|
||||
);
|
||||
|
||||
/// Horizontal lists for mouse users: drag with the mouse, and the plain
|
||||
/// (vertical) wheel moves them sideways. At either end the wheel is left
|
||||
/// to the page, so a row never traps vertical scrolling.
|
||||
/// Horizontal lists for mouse users: drag with the mouse or Shift+wheel;
|
||||
/// the plain wheel stays vertical (the page scrolls under the cursor, not
|
||||
/// the row).
|
||||
class HScroll extends StatefulWidget {
|
||||
final ScrollController? controller;
|
||||
final Widget Function(ScrollController controller) builder;
|
||||
/// Map the plain (vertical) wheel to sideways scrolling. Off where the
|
||||
/// children scroll vertically themselves (the schedule's day columns):
|
||||
/// there only mouse drag and Shift+wheel move sideways.
|
||||
/// Also map the plain (vertical) wheel to sideways scrolling (at either
|
||||
/// end the wheel goes to the page). Off by default: one wheel doing two
|
||||
/// things was confusing.
|
||||
final bool wheel;
|
||||
const HScroll({super.key, this.controller, required this.builder, this.wheel = true});
|
||||
const HScroll({super.key, this.controller, required this.builder, this.wheel = false});
|
||||
|
||||
@override
|
||||
State<HScroll> createState() => _HScrollState();
|
||||
|
||||
@@ -92,6 +92,7 @@ class PartyStrip extends StatelessWidget {
|
||||
message: 'Your profile',
|
||||
child: InkWell(
|
||||
key: const ValueKey('strip-profile'),
|
||||
canRequestFocus: false,
|
||||
customBorder: const CircleBorder(),
|
||||
onTap: onProfile,
|
||||
child: StatusAvatar(
|
||||
|
||||
@@ -43,10 +43,13 @@ ShellToast shellToastFor(ToastItem t, AvatarInfo Function(String name) avatar) {
|
||||
}
|
||||
|
||||
/// The coordinator's toasts as cards with avatars, bottom-left: each
|
||||
/// slides in, stays [life], then slides out and collapses its gap.
|
||||
/// slides in, stays [life], then slides out and collapses its gap. A click
|
||||
/// on a chat toast opens the chat ([onOpenChat]); the rest let clicks
|
||||
/// through.
|
||||
class PartyToasts extends StatefulWidget {
|
||||
final Duration life;
|
||||
const PartyToasts({super.key, this.life = const Duration(seconds: 5)});
|
||||
final VoidCallback? onOpenChat;
|
||||
const PartyToasts({super.key, this.life = const Duration(seconds: 5), this.onOpenChat});
|
||||
|
||||
@override
|
||||
State<PartyToasts> createState() => _PartyToastsState();
|
||||
@@ -101,13 +104,37 @@ class _PartyToastsState extends State<PartyToasts> {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _open(ShellToast t) {
|
||||
widget.onOpenChat?.call();
|
||||
setState(() => t.leaving = true);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => IgnorePointer(child: ToastColumn(toasts: _shown.reversed.take(6).toList().reversed.toList()));
|
||||
Widget build(BuildContext context) => ToastColumn(
|
||||
toasts: _shown.reversed.take(6).toList().reversed.toList(),
|
||||
onTap: widget.onOpenChat == null ? null : _open,
|
||||
);
|
||||
}
|
||||
|
||||
class ToastColumn extends StatelessWidget {
|
||||
final List<ShellToast> toasts;
|
||||
const ToastColumn({super.key, required this.toasts});
|
||||
/// Clicks on chat toasts; null (or any other toast): clicks pass through.
|
||||
final ValueChanged<ShellToast>? onTap;
|
||||
const ToastColumn({super.key, required this.toasts, this.onTap});
|
||||
|
||||
Widget _card(ShellToast t) {
|
||||
final card = ToastCard(toast: t);
|
||||
final tap = onTap;
|
||||
if (!t.chat || tap == null || t.leaving) return IgnorePointer(child: card);
|
||||
return MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: GestureDetector(
|
||||
key: ValueKey('toast-open-chat-${t.id}'),
|
||||
onTap: () => tap(t),
|
||||
child: card,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -139,7 +166,7 @@ class ToastColumn extends StatelessWidget {
|
||||
duration: easeTime,
|
||||
curve: ease,
|
||||
opacity: t.leaving ? 0 : 1,
|
||||
child: Padding(padding: const EdgeInsets.only(top: 8), child: ToastCard(toast: t)),
|
||||
child: Padding(padding: const EdgeInsets.only(top: 8), child: _card(t)),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -4,13 +4,15 @@ import 'package:flutter/services.dart';
|
||||
import '../state/session_state.dart';
|
||||
import 'text_focus.dart';
|
||||
|
||||
/// Tab and Shift+Tab never move focus (the app's [Shortcuts] override):
|
||||
/// a button that got focus that way would be pressed by Space, the ready
|
||||
/// key.
|
||||
/// Tab and Shift+Tab never move focus, and Space never presses the
|
||||
/// focused button (the app's [Shortcuts] override): Space is the ready key,
|
||||
/// and a button keeps focus after a click. Text fields still type spaces
|
||||
/// (their own shortcuts come first).
|
||||
Map<ShortcutActivator, Intent> get appShortcuts => {
|
||||
...WidgetsApp.defaultShortcuts,
|
||||
const SingleActivator(LogicalKeyboardKey.tab): const DoNothingIntent(),
|
||||
const SingleActivator(LogicalKeyboardKey.tab, shift: true): const DoNothingIntent(),
|
||||
const SingleActivator(LogicalKeyboardKey.space): const DoNothingIntent(),
|
||||
};
|
||||
|
||||
/// The window's two app-wide keys, ahead of whatever has focus: while in a
|
||||
|
||||
@@ -525,8 +525,9 @@ One window, `AppShell` (`lib/src/shell/`), whatever the connection state
|
||||
through for views that replace each other (the old one gone after 35%, the
|
||||
new one fading and scaling in from 0.985), `showPopover` (cards that fade
|
||||
and scale in from where they open; they scroll when the window is short)
|
||||
and `HScroll` (horizontal rows: mouse drag, the vertical wheel scrolls them
|
||||
sideways until an end, then the page).
|
||||
and `HScroll` (horizontal rows: mouse drag or Shift+wheel; the plain wheel
|
||||
always scrolls the page). Space never presses a focused button
|
||||
(`appShortcuts`): it is the ready key.
|
||||
|
||||
- **Party strip** (`party_strip.dart`, 60 px): your avatar (profile card; 42 px like the party's),
|
||||
the Library / Schedule / Settings tabs with a growing underline (an
|
||||
@@ -687,7 +688,8 @@ sideways until an end, then the page).
|
||||
**Toasts** (`toasts.dart`): the coordinator's toasts bottom left, with
|
||||
the avatar of their `ToastItem.actor` (feed and chat toasts; the app's
|
||||
own have none); each
|
||||
slides in, stays 5 s, then slides out and collapses its gap.
|
||||
slides in, stays 5 s, then slides out and collapses its gap. A click on
|
||||
a chat toast opens the chat; the others let clicks through.
|
||||
|
||||
Settings persist in two Hive boxes: `connect_form` (last form, recent
|
||||
servers and links, the profile picture and colour; saved even when a join
|
||||
|
||||
Reference in new issue
Block a user