fix(#289): make the nav panel pinnable inside a channel too

The channel chat screen had a plain Drawer bolted onto it, so the panel could
only be pinned open on the four primary views. Pinning it while reading a
channel is the wide-screen case that was actually asked for: keeping the
channel list and its unread counts visible without pulling it out.

- AppShell now serves pushed detail screens as well: selectedIndex and
  onDestinationSelected are optional, and the bottom bar is omitted when they
  are absent (a pushed chat screen has no bottom bar).
- New appBarBuilder(context, pinned) lets a screen vary its app bar with the
  dock state. ChannelChatScreen uses it to drop its hamburger when the panel
  is already pinned open, since there is nothing left to summon.
- ChannelChatScreen builds on AppShell instead of a bare Scaffold, so it gets
  the same transient/pinned layout as everything else.

The four primary views are untouched; the new parameters are additive.

flutter analyze clean, dart format applied. Not yet run on hardware.
pull/324/head
Strycher 4 days ago
parent c73ea7b82e
commit 5c95a768e0

@ -28,6 +28,7 @@ import '../services/block_service.dart';
import '../services/chat_text_scale_service.dart'; import '../services/chat_text_scale_service.dart';
import '../services/translation_service.dart'; import '../services/translation_service.dart';
import '../utils/emoji_utils.dart'; import '../utils/emoji_utils.dart';
import '../widgets/app_shell.dart';
import '../widgets/channel_drawer_list.dart'; import '../widgets/channel_drawer_list.dart';
import '../widgets/mention_autocomplete.dart'; import '../widgets/mention_autocomplete.dart';
import '../helpers/emoji_shortcodes.dart'; import '../helpers/emoji_shortcodes.dart';
@ -294,27 +295,28 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return AppShell(
drawer: Drawer( // No bottom bar: this is a pushed detail screen. The panel is still
width: 300, // pinnable here, which is the wide-screen point - keeping the channel
child: SafeArea( // list and its unread counts visible while reading a channel.
child: ChannelDrawerList( drawerContent: ChannelDrawerList(
currentChannelIndex: widget.channel.index, currentChannelIndex: widget.channel.index,
onChannelSelected: _switchChannel, onChannelSelected: _switchChannel,
),
),
), ),
appBar: AppBar( appBarBuilder: (context, pinned) => AppBar(
// Explicit hamburger: this is a pushed route, so Scaffold would show a // Pinned: the panel is already docked, so no hamburger is needed.
// back arrow here instead of the drawer button. System back still pops // Unpinned: this is a pushed route, so Scaffold would put a back arrow
// to the channel list. // in this slot; the explicit hamburger replaces it. System back still
leading: Builder( // pops to the channel list.
builder: (context) => IconButton( leading: pinned
icon: const Icon(Icons.menu), ? null
tooltip: context.l10n.channels_title, : Builder(
onPressed: () => Scaffold.of(context).openDrawer(), builder: (context) => IconButton(
), icon: const Icon(Icons.menu),
), tooltip: context.l10n.channels_title,
onPressed: () => Scaffold.of(context).openDrawer(),
),
),
title: Row( title: Row(
children: [ children: [
_channelIcon(widget.channel), _channelIcon(widget.channel),

@ -15,12 +15,21 @@ class AppShell extends StatelessWidget {
static const double wideBreakpoint = 720; static const double wideBreakpoint = 720;
static const double _drawerWidth = 300; static const double _drawerWidth = 300;
final int selectedIndex; /// Bottom bar tab. Null on pushed detail screens (a channel chat), which
final ValueChanged<int> onDestinationSelected; /// carry the nav panel but no bottom bar.
final int? selectedIndex;
final ValueChanged<int>? onDestinationSelected;
final int contactsUnreadCount; final int contactsUnreadCount;
final int channelsUnreadCount; final int channelsUnreadCount;
final PreferredSizeWidget? appBar; final PreferredSizeWidget? appBar;
/// App bar that needs to know whether the panel is docked, so a pushed
/// screen can drop its hamburger when the panel is already pinned open.
/// Takes precedence over [appBar].
final PreferredSizeWidget Function(BuildContext context, bool pinned)?
appBarBuilder;
final Widget body; final Widget body;
final Widget? floatingActionButton; final Widget? floatingActionButton;
@ -29,10 +38,11 @@ class AppShell extends StatelessWidget {
const AppShell({ const AppShell({
super.key, super.key,
required this.selectedIndex,
required this.onDestinationSelected,
required this.body, required this.body,
this.selectedIndex,
this.onDestinationSelected,
this.appBar, this.appBar,
this.appBarBuilder,
this.floatingActionButton, this.floatingActionButton,
this.drawerContent, this.drawerContent,
this.contactsUnreadCount = 0, this.contactsUnreadCount = 0,
@ -54,22 +64,30 @@ class AppShell extends StatelessWidget {
); );
} }
Widget _bottomBar() { /// Null on detail screens, which show the panel but no bottom bar.
Widget? _bottomBar() {
final index = selectedIndex;
final onSelected = onDestinationSelected;
if (index == null || onSelected == null) return null;
return SafeArea( return SafeArea(
top: false, top: false,
child: QuickSwitchBar( child: QuickSwitchBar(
selectedIndex: selectedIndex, selectedIndex: index,
onDestinationSelected: onDestinationSelected, onDestinationSelected: onSelected,
contactsUnreadCount: contactsUnreadCount, contactsUnreadCount: contactsUnreadCount,
channelsUnreadCount: channelsUnreadCount, channelsUnreadCount: channelsUnreadCount,
), ),
); );
} }
PreferredSizeWidget? _appBar(BuildContext context, bool pinned) {
return appBarBuilder?.call(context, pinned) ?? appBar;
}
/// Wide + pinned: the panel is laid out beside the body, not overlaid. /// Wide + pinned: the panel is laid out beside the body, not overlaid.
Widget _buildPinned(BuildContext context) { Widget _buildPinned(BuildContext context) {
return Scaffold( return Scaffold(
appBar: appBar, appBar: _appBar(context, true),
body: SafeArea( body: SafeArea(
top: false, top: false,
child: Row( child: Row(
@ -93,7 +111,7 @@ class AppShell extends StatelessWidget {
/// the hamburger into the app bar automatically. /// the hamburger into the app bar automatically.
Widget _buildTransient(BuildContext context, bool isWide) { Widget _buildTransient(BuildContext context, bool isWide) {
return Scaffold( return Scaffold(
appBar: appBar, appBar: _appBar(context, false),
drawer: Drawer( drawer: Drawer(
width: _drawerWidth, width: _drawerWidth,
child: _NavPanel(isWide: isWide, content: drawerContent), child: _NavPanel(isWide: isWide, content: drawerContent),

Loading…
Cancel
Save

Powered by TurnKey Linux.