Noo design system rebuild #1
@@ -245,11 +245,23 @@ blocks are noted where they matter:
|
||||
near-black `bg` (a dark, diffuse shadow needs real density close to the
|
||||
edge to be visible against an already-dark backdrop), so a second,
|
||||
tighter, more opaque contact shadow underneath it gives real elevation
|
||||
in both themes. Also added `NooTopBar.androidTitleReplacement`: Android has no
|
||||
large title to put a second search row under the way iOS's `search:`
|
||||
slot does, so an inline search bar (`AppTopBar` passes a plain
|
||||
`ShellSearchLauncher()` when search isn't in the bottom bar) replaces the
|
||||
title in its flexible slot instead of sitting beside it as a bare icon.
|
||||
in both themes. The card's border can go missing wherever an opaque row
|
||||
sits against it too (every corner but the header's, which has no
|
||||
full-bleed fill of its own) if a `Container` combines `border` with its
|
||||
own `clipBehavior` - that paints the border as part of the *outer*
|
||||
decoration, then the clipped child on top right up to the same boundary,
|
||||
with no gap for the border's own stroke to show through. Fixed the same
|
||||
way any bordered-and-clipped `Container` should be: no `clipBehavior` on
|
||||
the bordered `Container` itself, and a 1px-inset `ClipRRect` (radius
|
||||
reduced by that same 1px) around the filled, clipped content instead, so
|
||||
it never paints over the border. Also added
|
||||
`NooTopBar.androidTitleTrailing`: Android has no large title to put a
|
||||
second search row under the way iOS's `search:` slot does, so an inline
|
||||
search bar (`AppTopBar` passes a plain `ShellSearchLauncher()` when
|
||||
search isn't in the bottom bar) sits to the title's own right in that
|
||||
flexible slot - `Flexible`, not `Expanded`, so the title still shrinks/
|
||||
ellipsizes if there's truly no room but doesn't claim more than it needs
|
||||
otherwise - instead of the title being replaced by it.
|
||||
- [`SyncedHeaderScaffold`](../../lib/widgets/synced_header_scaffold.dart) —
|
||||
the pull-to-sync `CustomScrollView` header with the persistent sync-status
|
||||
chip and pull-to-refresh gesture/spinner. Every screen (including
|
||||
|
||||
@@ -16,9 +16,9 @@ import 'shell/shell_common.dart';
|
||||
/// title, then an inline search field that opens [SearchView].
|
||||
/// - Android: `menu`, title, avatar actions. Upload has no top-bar icon
|
||||
/// here - it's the [NooFab] `MainShellView` shows instead. When search
|
||||
/// isn't in the bottom bar, the title itself is replaced by an inline
|
||||
/// search-field-styled launcher (`androidTitleReplacement`) rather than
|
||||
/// a bare search icon sitting beside it.
|
||||
/// isn't in the bottom bar, an inline search-field-styled launcher
|
||||
/// (`androidTitleTrailing`) sits to the title's own right, rather than a
|
||||
/// bare search icon over in `actions`.
|
||||
///
|
||||
/// [style] is resolved by the caller (`NooLayout.navStyle`) rather than
|
||||
/// read from context here, since [preferredSize] is a plain getter with no
|
||||
@@ -68,9 +68,9 @@ class AppTopBar extends StatelessWidget implements PreferredSizeWidget {
|
||||
? () => Scaffold.of(context).openDrawer()
|
||||
: null,
|
||||
// Android has no large title to put a second search row under the
|
||||
// way iOS does (`search:` below) - an inline bar replaces the title
|
||||
// itself instead of sitting beside it as a plain icon action.
|
||||
androidTitleReplacement: !_ios && _showSearch
|
||||
// way iOS does (`search:` below) - an inline bar sits beside the
|
||||
// title instead, rather than a plain search icon over in `actions`.
|
||||
androidTitleTrailing: !_ios && _showSearch
|
||||
? const ShellSearchLauncher()
|
||||
: null,
|
||||
actions: [
|
||||
|
||||
@@ -75,9 +75,20 @@ class _AvatarMenuContent extends StatelessWidget {
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
child: Container(
|
||||
clipBehavior: Clip.antiAlias,
|
||||
// No `color`/`clipBehavior` here - a `Container` with both a
|
||||
// `border` and `clipBehavior` set paints the border as part
|
||||
// of its *outer* decoration, then paints its (clipped) child
|
||||
// on top right up to that same boundary with no gap for the
|
||||
// border's own stroke width to show through. An opaque child
|
||||
// touching that edge - every row below has its own full-bleed
|
||||
// `Material` fill - then paints straight over the inner half
|
||||
// of the border, which read as the border going missing
|
||||
// specifically wherever an opaque row sits (every corner but
|
||||
// the two by the header, which has no opaque fill of its
|
||||
// own). The 1px `Padding` + inset `ClipRRect` below keeps the
|
||||
// clipped, filled content entirely inside the border's own
|
||||
// stroke instead of racing it for the same pixels.
|
||||
decoration: BoxDecoration(
|
||||
color: colors.surface,
|
||||
border: Border.all(color: colors.line),
|
||||
borderRadius: BorderRadius.circular(NooRadii.card),
|
||||
// `nooDialogShadow` alone is a wide, soft, fairly faint
|
||||
@@ -99,8 +110,12 @@ class _AvatarMenuContent extends StatelessWidget {
|
||||
nooDialogShadow,
|
||||
],
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(1),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(NooRadii.card - 1),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
color: colors.surface,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
@@ -116,7 +131,8 @@ class _AvatarMenuContent extends StatelessWidget {
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
session.username,
|
||||
@@ -180,6 +196,8 @@ class _AvatarMenuContent extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,14 +56,15 @@ class NooTopBar extends StatelessWidget implements PreferredSizeWidget {
|
||||
/// iOS only: the search field shown below the large title.
|
||||
final Widget? search;
|
||||
|
||||
/// Android only: replaces the plain title text with an arbitrary widget
|
||||
/// (e.g. a search-field-styled launcher) in that same flexible middle
|
||||
/// slot, between [leading]/the menu button and [actions]. iOS already
|
||||
/// has a dedicated [search] slot below its large title for this; Android
|
||||
/// has no large title to put a second row under, so a caller that wants
|
||||
/// an inline search bar there replaces the title with one instead of
|
||||
/// adding a row.
|
||||
final Widget? androidTitleReplacement;
|
||||
/// Android only: an extra widget (e.g. a search-field-styled launcher) in
|
||||
/// the flexible middle slot, to the title's own right - [title] keeps
|
||||
/// its natural (shrink-to-fit, ellipsized) width on the left, and this
|
||||
/// takes whatever space is left rather than replacing it. iOS already has
|
||||
/// a dedicated [search] slot below its large title for this; Android has
|
||||
/// no large title to put a second row under, so a caller that wants an
|
||||
/// inline search bar there puts it beside the title instead of adding a
|
||||
/// row.
|
||||
final Widget? androidTitleTrailing;
|
||||
|
||||
/// Defaults to `bg` - the top bar sits on the screen background.
|
||||
final Color? backgroundColor;
|
||||
@@ -76,7 +77,7 @@ class NooTopBar extends StatelessWidget implements PreferredSizeWidget {
|
||||
this.onMenu,
|
||||
this.actions = const [],
|
||||
this.search,
|
||||
this.androidTitleReplacement,
|
||||
this.androidTitleTrailing,
|
||||
this.backgroundColor,
|
||||
});
|
||||
|
||||
@@ -188,10 +189,25 @@ class NooTopBar extends StatelessWidget implements PreferredSizeWidget {
|
||||
spacing: 4,
|
||||
children: [
|
||||
?lead,
|
||||
// [androidTitleTrailing]'s own doc comment explains why this
|
||||
// isn't a straight replacement: the title keeps its natural
|
||||
// width (`Flexible`, not `Expanded` - it still shrinks/
|
||||
// ellipsizes if there's truly no room, but doesn't force the
|
||||
// row that wide otherwise) and sits to the trailing content's
|
||||
// left rather than being displaced by it.
|
||||
if (androidTitleTrailing != null) ...[
|
||||
Flexible(
|
||||
child: Text(
|
||||
title,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: NooText.title.copyWith(height: 1, color: colors.fg1),
|
||||
),
|
||||
),
|
||||
Expanded(child: androidTitleTrailing!),
|
||||
] else
|
||||
Expanded(
|
||||
child:
|
||||
androidTitleReplacement ??
|
||||
Text(
|
||||
child: Text(
|
||||
title,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
|
||||
Reference in New Issue
Block a user