- Give the floating bar and search satellite the app's one shadow (nooDialogShadow) plus a smaller, tighter height on both platforms. - Let every tab's scrollable list compute its own bottom clearance (bottomBarClearance) instead of a flat 100px, so the floating bar no longer overlaps the last row/card. - Square the Android upload FAB off to a rounded-square radius when the bottom bar is attached, keeping it fully round only when floating. - Let a swipe on a file row trigger its action directly once dragged past 1.8x the reveal width, instead of requiring a second tap. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
121 lines
4.2 KiB
Dart
121 lines
4.2 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:lucide_icons_flutter/lucide_icons.dart';
|
|
import 'package:provider/provider.dart';
|
|
import '../../providers/settings_controller.dart';
|
|
import '../../theme/design_tokens.dart';
|
|
import '../noo/core/noo_button.dart';
|
|
import '../noo/nav/noo_bottom_bar.dart';
|
|
import '../noo/noo_layout.dart';
|
|
|
|
/// Shared loading/error/empty slivers for Recent/Activity/Trash/Shares (and
|
|
/// any future tab shaped like them: a `CustomScrollView` over one flat
|
|
/// list), restyled with noo tokens instead of each screen hand-rolling the
|
|
/// same centered column - see `FilesView`'s identical error/empty layout,
|
|
/// which this mirrors.
|
|
|
|
/// Centered spinner for a tab's first load (list still empty).
|
|
const Widget tabLoadingSliver = SliverFillRemaining(
|
|
hasScrollBody: false,
|
|
child: Center(child: CircularProgressIndicator()),
|
|
);
|
|
|
|
/// Centered error state: icon, title, message, and a Retry button.
|
|
Widget tabErrorSliver(
|
|
BuildContext context, {
|
|
required String title,
|
|
required String message,
|
|
required VoidCallback onRetry,
|
|
}) {
|
|
final colors = context.nooColors;
|
|
return SliverFillRemaining(
|
|
hasScrollBody: false,
|
|
child: Center(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(24),
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Icon(LucideIcons.circleAlert, size: 56, color: colors.danger),
|
|
const SizedBox(height: 16),
|
|
Text(
|
|
title,
|
|
textAlign: TextAlign.center,
|
|
style: NooText.cardTitle.copyWith(color: colors.danger),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
message,
|
|
textAlign: TextAlign.center,
|
|
style: NooText.body.copyWith(color: colors.fg3),
|
|
),
|
|
const SizedBox(height: 20),
|
|
NooButton(
|
|
variant: NooButtonVariant.secondary,
|
|
size: NooButtonSize.field,
|
|
icon: LucideIcons.refreshCw,
|
|
onTap: onRetry,
|
|
child: const Text('Retry'),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
/// Centered empty state: an icon over a message.
|
|
Widget tabEmptySliver(
|
|
BuildContext context, {
|
|
required IconData icon,
|
|
required String message,
|
|
}) {
|
|
final colors = context.nooColors;
|
|
return SliverFillRemaining(
|
|
hasScrollBody: false,
|
|
child: Center(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(24),
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Icon(icon, size: 56, color: colors.fg3),
|
|
const SizedBox(height: 12),
|
|
Text(
|
|
message,
|
|
textAlign: TextAlign.center,
|
|
style: NooText.cardTitle.copyWith(color: colors.fg2),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
/// How much bottom padding a tab's scrollable list needs to clear
|
|
/// [NooBottomBar] and not just butt up against it. [NooBottomBarStyle.
|
|
/// attached] bars aren't drawn behind (no `Scaffold.extendBody`), so
|
|
/// Scaffold already shrinks the body above them - this is then pure
|
|
/// breathing room, not overlap prevention. [NooBottomBarStyle.floating]
|
|
/// bars draw over an extended body instead, so nothing reserves space for
|
|
/// them automatically: the clearance has to cover the bar's own footprint
|
|
/// (see [NooBottomBar.rowHeight]/[NooBottomBar.floatingBottomMargin]) plus
|
|
/// the safe-area inset below it, or the last row ends up hidden under it.
|
|
double bottomBarClearance(BuildContext context) {
|
|
final settings = context.watch<SettingsController>();
|
|
if (settings.bottomBarStyle == NooBottomBarStyle.attached) return 100;
|
|
final barHeight = NooBottomBar.rowHeight(
|
|
NooLayout.navStyle(context),
|
|
NooBottomBarStyle.floating,
|
|
);
|
|
final safeBottom = MediaQuery.paddingOf(context).bottom;
|
|
return barHeight + NooBottomBar.floatingBottomMargin + safeBottom + 24;
|
|
}
|
|
|
|
/// Bottom padding sliver so the last row/card clears the bottom nav/FAB -
|
|
/// same trailing pair every rebuilt tab list ends on.
|
|
List<Widget> tabBottomInsetSlivers(BuildContext context) => [
|
|
SliverToBoxAdapter(child: SizedBox(height: bottomBarClearance(context))),
|
|
const SliverFillRemaining(hasScrollBody: false, child: SizedBox()),
|
|
];
|