Fix Trash/Shares list perf and live-update the Files sort/filter sheet

Trash and Shares built their mobile lists eagerly via NooGroupedList's
own Column instead of a lazy sliver, so an account with hundreds of
items made those tabs laggy - both now use a SliverList/
SliverChildBuilderDelegate instead, matching Files' own lazy list,
while keeping the same one-continuous-card look via per-row corner
rounding and a 1px divider.

FilesControlsRow's sort/filter sheets also had a live-update bug: since
showNooSheet's children are built once up front, tapping a row inside
the open sheet updated the controller but the already-built
checkmarks/toggles stayed stale until the sheet was closed and
reopened. Wrapping each sheet's content in a ListenableBuilder fixes
that.

Also adds NooSelectionBar to styling.md's widget inventory table.
This commit is contained in:
2026-09-28 13:02:31 -04:00
parent 254db255a7
commit c9889c69b8
4 changed files with 187 additions and 115 deletions
+37 -15
View File
@@ -11,7 +11,6 @@ import '../widgets/noo/core/noo_segmented_control.dart';
import '../widgets/noo/files/noo_file_kind.dart';
import '../widgets/noo/files/noo_file_row.dart';
import '../widgets/noo/files/noo_file_table.dart';
import '../widgets/noo/lists/noo_grouped_list.dart';
import '../widgets/noo/noo_layout.dart';
import '../widgets/tabs/tab_state_slivers.dart';
@@ -162,24 +161,47 @@ class _SharesViewState extends State<SharesView> {
c.setSharedWithMe(scope == _ShareScope.withYou);
}
// A lazily-built `SliverList`, not `NooGroupedList` (its own `Column`
// isn't lazy - see files_view.dart's `_buildMobileRow` doc comment, and
// trash_view.dart's `_buildMobileList`, which had the same bug: a heavy
// account's full share list built eagerly up front). Each row still
// reads as one continuous radius-20 card via per-row corner rounding +
// a 1px `line` divider.
Widget _buildMobileList(BuildContext context, List<NextcloudShare> shares) {
final colors = context.nooColors;
return SliverPadding(
padding: const EdgeInsets.symmetric(horizontal: NooSpace.sm),
sliver: SliverToBoxAdapter(
child: NooGroupedList(
children: [
for (final share in shares)
NooFileRow(
kind: NooFileKind.from(name: share.name, isDirectory: share.isFolder),
name: share.name,
meta: '${share.ownerDisplayName} · ${_permissionLabel(share.permissions)}',
iosStyle: NooLayout.iosStyle(context),
onTap: share.isFolder ? () => _openFolder(context, share) : null,
trailing: Icon(_shareTypeIcon(share.shareType), size: 16),
onMore: () => _confirmUnshare(context, share),
sliver: SliverList(
delegate: SliverChildBuilderDelegate((context, index) {
final share = shares[index];
final row = NooFileRow(
kind: NooFileKind.from(name: share.name, isDirectory: share.isFolder),
name: share.name,
meta: '${share.ownerDisplayName} · ${_permissionLabel(share.permissions)}',
iosStyle: NooLayout.iosStyle(context),
onTap: share.isFolder ? () => _openFolder(context, share) : null,
trailing: Icon(_shareTypeIcon(share.shareType), size: 16),
onMore: () => _confirmUnshare(context, share),
);
final isFirst = index == 0;
final isLast = index == shares.length - 1;
return Column(
children: [
ClipRRect(
borderRadius: BorderRadius.vertical(
top: isFirst
? const Radius.circular(NooRadii.card)
: Radius.zero,
bottom: isLast
? const Radius.circular(NooRadii.card)
: Radius.zero,
),
child: row,
),
],
),
if (!isLast) Container(height: 1, color: colors.line),
],
);
}, childCount: shares.length),
),
);
}
+50 -28
View File
@@ -11,7 +11,6 @@ import '../widgets/noo/files/noo_file_row.dart';
import '../widgets/noo/files/noo_file_table.dart';
import '../widgets/noo/files/noo_file_tile.dart';
import '../widgets/noo/lists/noo_banner.dart';
import '../widgets/noo/lists/noo_grouped_list.dart';
import '../widgets/noo/noo_layout.dart';
import '../widgets/tabs/tab_location.dart';
import '../widgets/tabs/tab_state_slivers.dart';
@@ -103,38 +102,61 @@ class _TrashViewState extends State<TrashView> {
);
}
// A lazily-built `SliverList`, not `NooGroupedList` (its own `Column`
// isn't lazy - see files_view.dart's `_buildMobileRow` doc comment for
// the same reasoning): Trash can hold hundreds of items, and building
// every row eagerly up front is what made this tab laggy. Each row still
// reads as one continuous radius-20 card via per-row corner rounding +
// a 1px `line` divider, matching `NooGroupedList`'s look.
Widget _buildMobileList(BuildContext context, List<NextcloudItem> trash) {
final colors = context.nooColors;
return SliverPadding(
padding: const EdgeInsets.symmetric(horizontal: NooSpace.sm),
sliver: SliverToBoxAdapter(
child: NooGroupedList(
children: [
for (final item in trash)
NooFileRow(
kind: NooFileKind.from(
name: item.name,
mimeType: item.mimeType,
isDirectory: item.isFolder,
sliver: SliverList(
delegate: SliverChildBuilderDelegate((context, index) {
final item = trash[index];
final row = NooFileRow(
kind: NooFileKind.from(
name: item.name,
mimeType: item.mimeType,
isDirectory: item.isFolder,
),
name: item.name,
meta: _meta(item),
iosStyle: NooLayout.iosStyle(context),
trailing: SizedBox.square(
dimension: 40,
child: Semantics(
button: true,
label: 'Restore',
child: InkResponse(
onTap: () => _restore(context, item),
radius: 20,
child: const Icon(LucideIcons.rotateCcw),
),
name: item.name,
meta: _meta(item),
iosStyle: NooLayout.iosStyle(context),
trailing: SizedBox.square(
dimension: 40,
child: Semantics(
button: true,
label: 'Restore',
child: InkResponse(
onTap: () => _restore(context, item),
radius: 20,
child: const Icon(LucideIcons.rotateCcw),
),
),
),
onMore: () => _confirmDeleteForever(context, item),
),
],
),
),
onMore: () => _confirmDeleteForever(context, item),
);
final isFirst = index == 0;
final isLast = index == trash.length - 1;
return Column(
children: [
ClipRRect(
borderRadius: BorderRadius.vertical(
top: isFirst
? const Radius.circular(NooRadii.card)
: Radius.zero,
bottom: isLast
? const Radius.circular(NooRadii.card)
: Radius.zero,
),
child: row,
),
if (!isLast) Container(height: 1, color: colors.line),
],
);
}, childCount: trash.length),
),
);
}