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

@ -15,12 +15,21 @@ class AppShell extends StatelessWidget {
static const double wideBreakpoint = 720;
static const double _drawerWidth = 300;
final int selectedIndex;
final ValueChanged<int> onDestinationSelected;
/// Bottom bar tab. Null on pushed detail screens (a channel chat), which
/// carry the nav panel but no bottom bar.
final int? selectedIndex;
final ValueChanged<int>? onDestinationSelected;
final int contactsUnreadCount;
final int channelsUnreadCount;
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? floatingActionButton;
@ -29,10 +38,11 @@ class AppShell extends StatelessWidget {
const AppShell({
super.key,
required this.selectedIndex,
required this.onDestinationSelected,
required this.body,
this.selectedIndex,
this.onDestinationSelected,
this.appBar,
this.appBarBuilder,
this.floatingActionButton,
this.drawerContent,
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(
top: false,
child: QuickSwitchBar(
selectedIndex: selectedIndex,
onDestinationSelected: onDestinationSelected,
selectedIndex: index,
onDestinationSelected: onSelected,
contactsUnreadCount: contactsUnreadCount,
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.
Widget _buildPinned(BuildContext context) {
return Scaffold(
appBar: appBar,
appBar: _appBar(context, true),
body: SafeArea(
top: false,
child: Row(
@ -93,7 +111,7 @@ class AppShell extends StatelessWidget {
/// the hamburger into the app bar automatically.
Widget _buildTransient(BuildContext context, bool isWide) {
return Scaffold(
appBar: appBar,
appBar: _appBar(context, false),
drawer: Drawer(
width: _drawerWidth,
child: _NavPanel(isWide: isWide, content: drawerContent),

Loading…
Cancel
Save

Powered by TurnKey Linux.