Noo design system rebuild #1
@@ -340,12 +340,16 @@ Sidebar items are 38px tall with radius 12, an 18px icon and a 14/500 label. The
|
||||
Location, Modified, Created, ...). Versions and Activity reuse their own
|
||||
row/feed treatment. Same on every platform - this sheet has no iOS/Android
|
||||
split.
|
||||
- **Media viewer** (the full-screen photo/video viewer): a black stage
|
||||
regardless of theme, like a native photo/video viewer - not `bg`. A
|
||||
translucent, blurred top bar (back, filename, meta) and bottom bar float
|
||||
over the media; this is the one deliberate exception to "no blur" in
|
||||
product UI, since it's chrome over photo/video content, not over the app's
|
||||
own surfaces. Back is a plain arrow (`arrow-left`), not the iOS
|
||||
- **Media viewer** (the full-screen photo/video viewer): the stage is `bg`,
|
||||
following the app's own theme rather than a fixed black - a black stage
|
||||
in light mode read as jarringly out of place. A translucent, blurred top
|
||||
bar (back, filename, meta) and bottom bar float over the media in a fixed
|
||||
dark panel regardless of theme (it needs to read over arbitrary photo/
|
||||
video brightness, not over `bg`); this is the one deliberate exception to
|
||||
"no blur" in product UI, since it's chrome over photo/video content, not
|
||||
over the app's own surfaces. Both bars' background extends edge-to-edge
|
||||
behind the status bar/gesture area, with only their content padded clear
|
||||
of it. Back is a plain arrow (`arrow-left`), not the iOS
|
||||
chevron+label pushed-screen pattern - platform split still to do. The
|
||||
bottom bar holds every action in one row (share, favorite, open
|
||||
externally, download, delete, details) on every platform; don't add a
|
||||
|
||||
@@ -376,14 +376,13 @@ class _FileViewerScreenState extends State<FileViewerScreen> {
|
||||
final session = context.read<SessionController>();
|
||||
final ops = context.read<ItemOperations>();
|
||||
final colors = context.nooColors;
|
||||
// The stage behind the media itself is pure black rather than `bg` -
|
||||
// the near-universal "letterbox" convention of photo/video viewers
|
||||
// (matting an image/video in the app's warm neutral background reads
|
||||
// as unfinished, and black also hides any letterboxing from
|
||||
// `BoxFit.contain`/`AspectRatio` at the screen edges). PDFs/text/
|
||||
// unsupported-file previews aren't "viewed" the same way - they're
|
||||
// read, so they stay on the normal `bg` like any other screen.
|
||||
final stageColor = _isSwipeable ? Colors.black : colors.bg;
|
||||
// The stage follows the app's own theme (`bg`) rather than being pinned
|
||||
// to black - a black stage in light mode read as jarringly out of place
|
||||
// next to the rest of the light-themed app. The floating top/action
|
||||
// bars stay a fixed dark frosted panel regardless (see
|
||||
// FrostedGlassContainer's doc comment) since they need to read over
|
||||
// arbitrary photo/video brightness, not over this background.
|
||||
final stageColor = colors.bg;
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: stageColor,
|
||||
@@ -437,9 +436,13 @@ class _FileViewerScreenState extends State<FileViewerScreen> {
|
||||
offset: _controlsVisible ? Offset.zero : const Offset(0, -1.4),
|
||||
child: IgnorePointer(
|
||||
ignoring: !_controlsVisible,
|
||||
child: SafeArea(
|
||||
bottom: false,
|
||||
child: FrostedGlassContainer(
|
||||
// The blurred/tinted background lives outside the SafeArea
|
||||
// (not inside it) so it extends up behind the status bar
|
||||
// instead of stopping short beneath it - see the matching
|
||||
// fix/comment on MediaActionBar's own SafeArea.
|
||||
child: FrostedGlassContainer(
|
||||
child: SafeArea(
|
||||
bottom: false,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 6,
|
||||
@@ -447,10 +450,9 @@ class _FileViewerScreenState extends State<FileViewerScreen> {
|
||||
),
|
||||
child: IconTheme.merge(
|
||||
// Fixed white, not `colors.fg1`: this bar always
|
||||
// sits on the dark translucent panel over a black
|
||||
// media stage (see `FrostedGlassContainer`'s doc
|
||||
// comment), regardless of the app's light/dark
|
||||
// theme.
|
||||
// sits on the dark translucent panel over the media
|
||||
// stage (see `FrostedGlassContainer`'s doc comment),
|
||||
// regardless of the app's light/dark theme.
|
||||
data: const IconThemeData(
|
||||
color: Colors.white,
|
||||
size: 24,
|
||||
|
||||
@@ -1,13 +1,23 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../theme/design_tokens.dart';
|
||||
|
||||
/// Fraction of the available height a sheet can reach before it switches
|
||||
/// from sizing to its content to a draggable peek (see [_NooSheetBody]).
|
||||
const double _kNooSheetPeekFraction = 0.6;
|
||||
const double _kNooSheetMinFraction = 0.3;
|
||||
const double _kNooSheetMaxFraction = 0.95;
|
||||
|
||||
/// Mobile bottom sheet on a scrim: radius-28 top, grabber, 22px gap between
|
||||
/// sections (Noo Design System project, `components/overlays/Sheet.jsx`).
|
||||
/// `showModalBottomSheet` already supplies the scrim/backdrop-dismiss
|
||||
/// machinery, so this only standardizes the shape/padding - use it instead
|
||||
/// of a bare `showModalBottomSheet` for any new sheet. Scroll-controlled so a
|
||||
/// tall sheet (e.g. share) can grow past half the screen; content scrolls
|
||||
/// once it hits the top.
|
||||
/// of a bare `showModalBottomSheet` for any new sheet. Sizes to its content
|
||||
/// like a plain scroll view for anything that fits comfortably on screen
|
||||
/// (most sheets: a menu, a handful of settings rows); content long enough to
|
||||
/// need scrolling (Details' Versions/Activity tabs with many rows, a long
|
||||
/// accounts list, ...) instead opens at a native-style peek height the user
|
||||
/// can drag up to reveal more of, rather than snapping straight to nearly
|
||||
/// the full screen with nothing left to adjust - see [_NooSheetBody].
|
||||
Future<T?> showNooSheet<T>(
|
||||
BuildContext context, {
|
||||
required List<Widget> children,
|
||||
@@ -20,31 +30,111 @@ Future<T?> showNooSheet<T>(
|
||||
isScrollControlled: true,
|
||||
useSafeArea: true,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(NooRadii.sheetTop)),
|
||||
borderRadius: BorderRadius.vertical(
|
||||
top: Radius.circular(NooRadii.sheetTop),
|
||||
),
|
||||
),
|
||||
builder: (context) {
|
||||
return SafeArea(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.fromLTRB(20, 10, 20, 20),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
width: 36,
|
||||
height: 5,
|
||||
decoration: BoxDecoration(
|
||||
color: colors.surface3,
|
||||
borderRadius: BorderRadius.circular(NooRadii.pill),
|
||||
),
|
||||
),
|
||||
for (final child in children) ...[
|
||||
const SizedBox(height: 22),
|
||||
child,
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
builder: (context) => _NooSheetBody(children: children),
|
||||
);
|
||||
}
|
||||
|
||||
/// Renders [children] once, unconstrained, to measure their natural height
|
||||
/// against the sheet's available height. Content that fits within
|
||||
/// [_kNooSheetPeekFraction] just stays as that first (plain, content-sized)
|
||||
/// render - the common case for a short menu/option list. Content taller
|
||||
/// than that switches to a [DraggableScrollableSheet] opened at the peek
|
||||
/// fraction instead, so it never jumps straight to (near) full height with
|
||||
/// no room left to adjust. The one extra layout pass for tall content
|
||||
/// happens within the sheet's own entrance animation, so it isn't visible
|
||||
/// as a jump.
|
||||
class _NooSheetBody extends StatefulWidget {
|
||||
final List<Widget> children;
|
||||
|
||||
const _NooSheetBody({required this.children});
|
||||
|
||||
@override
|
||||
State<_NooSheetBody> createState() => _NooSheetBodyState();
|
||||
}
|
||||
|
||||
class _NooSheetBodyState extends State<_NooSheetBody> {
|
||||
final _contentKey = GlobalKey();
|
||||
bool _measured = false;
|
||||
bool _needsPeek = false;
|
||||
|
||||
void _measureAfterFrame(double availableHeight) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!mounted || _measured) return;
|
||||
final box = _contentKey.currentContext?.findRenderObject() as RenderBox?;
|
||||
final contentHeight = box?.size.height ?? 0;
|
||||
setState(() {
|
||||
_measured = true;
|
||||
_needsPeek = contentHeight > availableHeight * _kNooSheetPeekFraction;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildContent(ScrollController? scrollController) {
|
||||
final colors = context.nooColors;
|
||||
return SingleChildScrollView(
|
||||
controller: scrollController,
|
||||
padding: const EdgeInsets.fromLTRB(20, 10, 20, 20),
|
||||
child: Column(
|
||||
key: _measured ? null : _contentKey,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
width: 36,
|
||||
height: 5,
|
||||
decoration: BoxDecoration(
|
||||
color: colors.surface3,
|
||||
borderRadius: BorderRadius.circular(NooRadii.pill),
|
||||
),
|
||||
),
|
||||
for (final child in widget.children) ...[
|
||||
const SizedBox(height: 22),
|
||||
child,
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SafeArea(
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
if (!_measured) {
|
||||
_measureAfterFrame(constraints.maxHeight);
|
||||
return _buildContent(null);
|
||||
}
|
||||
if (!_needsPeek) {
|
||||
return _buildContent(null);
|
||||
}
|
||||
return DraggableScrollableSheet(
|
||||
initialChildSize: _kNooSheetPeekFraction,
|
||||
minChildSize: _kNooSheetMinFraction,
|
||||
maxChildSize: _kNooSheetMaxFraction,
|
||||
expand: false,
|
||||
builder: (context, scrollController) {
|
||||
// Plain `DraggableScrollableSheet` doesn't dismiss the modal
|
||||
// route on its own when dragged down to its floor - it just
|
||||
// stops resizing there - so pop explicitly once it's been
|
||||
// dragged (near) all the way down, matching a native
|
||||
// peek sheet's swipe-to-dismiss.
|
||||
return NotificationListener<DraggableScrollableNotification>(
|
||||
onNotification: (notification) {
|
||||
if (notification.extent <= notification.minExtent + 0.01) {
|
||||
Navigator.of(context).maybePop();
|
||||
}
|
||||
return false;
|
||||
},
|
||||
child: _buildContent(scrollController),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,9 +46,14 @@ class MediaActionBar extends StatelessWidget {
|
||||
// favorite/delete stay legible even when the ambient theme is light.
|
||||
const dark = NooColors.dark;
|
||||
|
||||
return SafeArea(
|
||||
top: false,
|
||||
child: FrostedGlassContainer(
|
||||
// The blurred/tinted background lives outside the SafeArea (not inside
|
||||
// it) so it extends all the way to the physical bottom edge, behind the
|
||||
// gesture bar/home indicator, instead of the panel itself stopping
|
||||
// short and leaving that strip unstyled - only the actual row content
|
||||
// needs padding up and away from the gesture area.
|
||||
return FrostedGlassContainer(
|
||||
child: SafeArea(
|
||||
top: false,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
|
||||
@@ -14,18 +14,26 @@ import 'noo_test_utils.dart';
|
||||
|
||||
/// A button that opens an overlay from a context below MaterialApp.
|
||||
Widget _launcher(void Function(BuildContext) open) => Builder(
|
||||
builder: (context) => TextButton(onPressed: () => open(context), child: const Text('open')),
|
||||
builder: (context) =>
|
||||
TextButton(onPressed: () => open(context), child: const Text('open')),
|
||||
);
|
||||
|
||||
Finder _dialogCard() => find.byWidgetPredicate(
|
||||
(w) => w is Container && w.decoration is BoxDecoration && (w.decoration as BoxDecoration).boxShadow != null,
|
||||
(w) =>
|
||||
w is Container &&
|
||||
w.decoration is BoxDecoration &&
|
||||
(w.decoration as BoxDecoration).boxShadow != null,
|
||||
);
|
||||
|
||||
void main() {
|
||||
setUpNooTests();
|
||||
|
||||
group('NooOverlayHeader', () {
|
||||
testNooWidgets('title, subtitle, leading and close', (tester, theme, c) async {
|
||||
testNooWidgets('title, subtitle, leading and close', (
|
||||
tester,
|
||||
theme,
|
||||
c,
|
||||
) async {
|
||||
var closed = 0;
|
||||
await pumpNoo(
|
||||
tester,
|
||||
@@ -41,23 +49,41 @@ void main() {
|
||||
theme: theme,
|
||||
);
|
||||
expect(find.text('Report.pdf'), findsOneWidget);
|
||||
expect(tester.widget<Text>(find.text('2 MB · Documents')).style!.color, c.fg3);
|
||||
expect(decorationOf(tester, find.byIcon(LucideIcons.x)).color, c.surface2);
|
||||
expect(
|
||||
tester.widget<Text>(find.text('2 MB · Documents')).style!.color,
|
||||
c.fg3,
|
||||
);
|
||||
expect(
|
||||
decorationOf(tester, find.byIcon(LucideIcons.x)).color,
|
||||
c.surface2,
|
||||
);
|
||||
await tester.tap(find.byIcon(LucideIcons.x));
|
||||
expect(closed, 1);
|
||||
});
|
||||
|
||||
testWidgets('no close button without onClose', (tester) async {
|
||||
await pumpNoo(tester, const SizedBox(width: 360, child: NooOverlayHeader(title: 'T')));
|
||||
await pumpNoo(
|
||||
tester,
|
||||
const SizedBox(width: 360, child: NooOverlayHeader(title: 'T')),
|
||||
);
|
||||
expect(find.byIcon(LucideIcons.x), findsNothing);
|
||||
});
|
||||
});
|
||||
|
||||
group('showNooSheet', () {
|
||||
testNooWidgets('shows children on a surface sheet and dismisses', (tester, theme, c) async {
|
||||
testNooWidgets('shows children on a surface sheet and dismisses', (
|
||||
tester,
|
||||
theme,
|
||||
c,
|
||||
) async {
|
||||
await pumpNoo(
|
||||
tester,
|
||||
_launcher((ctx) => showNooSheet<void>(ctx, children: const [Text('Section A'), Text('Section B')])),
|
||||
_launcher(
|
||||
(ctx) => showNooSheet<void>(
|
||||
ctx,
|
||||
children: const [Text('Section A'), Text('Section B')],
|
||||
),
|
||||
),
|
||||
theme: theme,
|
||||
);
|
||||
await tester.tap(find.text('open'));
|
||||
@@ -66,7 +92,8 @@ void main() {
|
||||
expect(find.text('Section B'), findsOneWidget);
|
||||
final sheet = tester.widget<BottomSheet>(find.byType(BottomSheet));
|
||||
expect(sheet.backgroundColor, c.surface);
|
||||
final gap = tester.getTopLeft(find.text('Section B')).dy -
|
||||
final gap =
|
||||
tester.getTopLeft(find.text('Section B')).dy -
|
||||
tester.getBottomLeft(find.text('Section A')).dy;
|
||||
expect(gap, 22);
|
||||
|
||||
@@ -74,13 +101,56 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('Section A'), findsNothing);
|
||||
});
|
||||
|
||||
testNooWidgets('long content opens as a draggable peek, not full height', (
|
||||
tester,
|
||||
theme,
|
||||
c,
|
||||
) async {
|
||||
await pumpNoo(
|
||||
tester,
|
||||
_launcher(
|
||||
(ctx) => showNooSheet<void>(
|
||||
ctx,
|
||||
children: [
|
||||
for (var i = 0; i < 30; i++)
|
||||
SizedBox(height: 40, child: Text('Row $i')),
|
||||
],
|
||||
),
|
||||
),
|
||||
theme: theme,
|
||||
surfaceSize: const Size(400, 700),
|
||||
);
|
||||
await tester.tap(find.text('open'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Content (30 * (40 + 22) =~ 1860px) far exceeds the peek fraction of
|
||||
// the 700px-tall surface, so it should switch to a draggable sheet
|
||||
// instead of the plain content-sized scroll view, opened at a peek
|
||||
// (well under half the content height) rather than snapping to
|
||||
// (near) the full 700px available.
|
||||
expect(find.text('Row 0'), findsOneWidget);
|
||||
final sheetHeight = tester
|
||||
.getSize(find.byType(DraggableScrollableSheet))
|
||||
.height;
|
||||
expect(sheetHeight, lessThan(500));
|
||||
expect(sheetHeight, greaterThan(150));
|
||||
});
|
||||
});
|
||||
|
||||
group('NooDialog', () {
|
||||
testNooWidgets('card is 540 wide, r24, single dialog shadow', (tester, theme, c) async {
|
||||
testNooWidgets('card is 540 wide, r24, single dialog shadow', (
|
||||
tester,
|
||||
theme,
|
||||
c,
|
||||
) async {
|
||||
await pumpNoo(
|
||||
tester,
|
||||
NooDialog(title: 'Share "Report.pdf"', onClose: () {}, children: const [Text('body')]),
|
||||
NooDialog(
|
||||
title: 'Share "Report.pdf"',
|
||||
onClose: () {},
|
||||
children: const [Text('body')],
|
||||
),
|
||||
theme: theme,
|
||||
surfaceSize: const Size(1200, 900),
|
||||
);
|
||||
@@ -92,7 +162,9 @@ void main() {
|
||||
expect(tester.getSize(card).width, 540);
|
||||
});
|
||||
|
||||
testWidgets('narrow screens shrink the card to fit with a 24px margin', (tester) async {
|
||||
testWidgets('narrow screens shrink the card to fit with a 24px margin', (
|
||||
tester,
|
||||
) async {
|
||||
await tester.binding.setSurfaceSize(const Size(400, 800));
|
||||
addTearDown(() => tester.binding.setSurfaceSize(null));
|
||||
await tester.pumpWidget(
|
||||
@@ -105,7 +177,11 @@ void main() {
|
||||
expect(tester.getSize(card).width, 400 - 48);
|
||||
});
|
||||
|
||||
testNooWidgets('header close is a 32px surface-2 circle', (tester, theme, c) async {
|
||||
testNooWidgets('header close is a 32px surface-2 circle', (
|
||||
tester,
|
||||
theme,
|
||||
c,
|
||||
) async {
|
||||
var closed = 0;
|
||||
await pumpNoo(
|
||||
tester,
|
||||
@@ -118,12 +194,19 @@ void main() {
|
||||
theme: theme,
|
||||
);
|
||||
expect(tester.getSize(find.byType(NooCloseButton)), const Size(32, 32));
|
||||
expect(decorationOf(tester, find.byIcon(LucideIcons.x)).color, c.surface2);
|
||||
expect(
|
||||
decorationOf(tester, find.byIcon(LucideIcons.x)).color,
|
||||
c.surface2,
|
||||
);
|
||||
await tester.tap(find.byType(NooCloseButton));
|
||||
expect(closed, 1);
|
||||
});
|
||||
|
||||
testNooWidgets('showNooDialog uses the scrim and closes via the button', (tester, theme, c) async {
|
||||
testNooWidgets('showNooDialog uses the scrim and closes via the button', (
|
||||
tester,
|
||||
theme,
|
||||
c,
|
||||
) async {
|
||||
Future<String?>? result;
|
||||
await pumpNoo(
|
||||
tester,
|
||||
@@ -140,7 +223,9 @@ void main() {
|
||||
await tester.tap(find.text('open'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('dialog body'), findsOneWidget);
|
||||
final barrier = tester.widgetList<ModalBarrier>(find.byType(ModalBarrier)).last;
|
||||
final barrier = tester
|
||||
.widgetList<ModalBarrier>(find.byType(ModalBarrier))
|
||||
.last;
|
||||
expect(barrier.color, c.scrim);
|
||||
|
||||
await tester.tap(find.byType(NooCloseButton));
|
||||
@@ -176,7 +261,10 @@ void main() {
|
||||
(ctx) => showNooDialog<void>(
|
||||
ctx,
|
||||
title: 'T',
|
||||
children: [for (var i = 0; i < 40; i++) SizedBox(height: 60, child: Text('row $i'))],
|
||||
children: [
|
||||
for (var i = 0; i < 40; i++)
|
||||
SizedBox(height: 60, child: Text('row $i')),
|
||||
],
|
||||
),
|
||||
),
|
||||
surfaceSize: const Size(1200, 600),
|
||||
@@ -189,13 +277,20 @@ void main() {
|
||||
});
|
||||
|
||||
group('NooTextField', () {
|
||||
testNooWidgets('surface-2, r14, fg-3 placeholder, typing', (tester, theme, c) async {
|
||||
testNooWidgets('surface-2, r14, fg-3 placeholder, typing', (
|
||||
tester,
|
||||
theme,
|
||||
c,
|
||||
) async {
|
||||
String? typed;
|
||||
await pumpNoo(
|
||||
tester,
|
||||
SizedBox(
|
||||
width: 400,
|
||||
child: NooTextField(placeholder: 'Name, email or group', onChanged: (v) => typed = v),
|
||||
child: NooTextField(
|
||||
placeholder: 'Name, email or group',
|
||||
onChanged: (v) => typed = v,
|
||||
),
|
||||
),
|
||||
theme: theme,
|
||||
);
|
||||
@@ -209,14 +304,20 @@ void main() {
|
||||
expect(typed, 'ali');
|
||||
});
|
||||
|
||||
testNooWidgets('mono URL field with inline Copy link button', (tester, theme, c) async {
|
||||
testNooWidgets('mono URL field with inline Copy link button', (
|
||||
tester,
|
||||
theme,
|
||||
c,
|
||||
) async {
|
||||
var copied = 0;
|
||||
await pumpNoo(
|
||||
tester,
|
||||
SizedBox(
|
||||
width: 480,
|
||||
child: NooTextField(
|
||||
controller: TextEditingController(text: 'https://cloud.example/s/abc'),
|
||||
controller: TextEditingController(
|
||||
text: 'https://cloud.example/s/abc',
|
||||
),
|
||||
mono: true,
|
||||
readOnly: true,
|
||||
onSurface: false,
|
||||
@@ -233,7 +334,10 @@ void main() {
|
||||
expect(field.style!.fontFamily, NooText.mono.fontFamily);
|
||||
expect(field.readOnly, isTrue);
|
||||
expect(decorationOf(tester, find.byType(TextField)).color, c.surface);
|
||||
expect(tester.getSize(find.byType(NooButton)).height, NooSizes.buttonField);
|
||||
expect(
|
||||
tester.getSize(find.byType(NooButton)).height,
|
||||
NooSizes.buttonField,
|
||||
);
|
||||
await tester.tap(find.text('Copy link'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(copied, 1);
|
||||
@@ -241,7 +345,11 @@ void main() {
|
||||
});
|
||||
|
||||
group('Share parts', () {
|
||||
testNooWidgets('NooShareSection title, trailing and caption', (tester, theme, c) async {
|
||||
testNooWidgets('NooShareSection title, trailing and caption', (
|
||||
tester,
|
||||
theme,
|
||||
c,
|
||||
) async {
|
||||
await pumpNoo(
|
||||
tester,
|
||||
const SizedBox(
|
||||
@@ -255,13 +363,26 @@ void main() {
|
||||
),
|
||||
theme: theme,
|
||||
);
|
||||
expect(tester.widget<Text>(find.text('Send file directly')).style!.color, c.fg1);
|
||||
expect(tester.widget<Text>(find.text("Link settings don't apply")).style!.color, c.fg3);
|
||||
expect(
|
||||
tester.widget<Text>(find.text('Send file directly')).style!.color,
|
||||
c.fg1,
|
||||
);
|
||||
expect(
|
||||
tester
|
||||
.widget<Text>(find.text("Link settings don't apply"))
|
||||
.style!
|
||||
.color,
|
||||
c.fg3,
|
||||
);
|
||||
expect(find.byIcon(LucideIcons.link), findsOneWidget);
|
||||
expect(find.text('content'), findsOneWidget);
|
||||
});
|
||||
|
||||
testNooWidgets('owner row is static, others get a tappable pill', (tester, theme, c) async {
|
||||
testNooWidgets('owner row is static, others get a tappable pill', (
|
||||
tester,
|
||||
theme,
|
||||
c,
|
||||
) async {
|
||||
var taps = 0;
|
||||
await pumpNoo(
|
||||
tester,
|
||||
@@ -296,13 +417,19 @@ void main() {
|
||||
expect(taps, 1);
|
||||
});
|
||||
|
||||
testWidgets('same parts lay out inside the sheet and the dialog', (tester) async {
|
||||
testWidgets('same parts lay out inside the sheet and the dialog', (
|
||||
tester,
|
||||
) async {
|
||||
List<Widget> parts() => [
|
||||
NooShareSection(
|
||||
title: 'Share with people',
|
||||
child: const NooTextField(placeholder: 'Name, email or group'),
|
||||
),
|
||||
const NooPersonAccessRow(avatar: NooAvatar(initials: 'JD'), name: 'Jane', permission: 'Can view'),
|
||||
const NooPersonAccessRow(
|
||||
avatar: NooAvatar(initials: 'JD'),
|
||||
name: 'Jane',
|
||||
permission: 'Can view',
|
||||
),
|
||||
];
|
||||
await pumpNoo(
|
||||
tester,
|
||||
@@ -312,7 +439,8 @@ void main() {
|
||||
_launcher((ctx) => showNooSheet<void>(ctx, children: parts())),
|
||||
Builder(
|
||||
builder: (ctx) => TextButton(
|
||||
onPressed: () => showNooDialog<void>(ctx, title: 'Share', children: parts()),
|
||||
onPressed: () =>
|
||||
showNooDialog<void>(ctx, title: 'Share', children: parts()),
|
||||
child: const Text('dialog'),
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user