Noo design system rebuild #1
@@ -78,7 +78,7 @@ Catalog:
|
|||||||
| Folder | Components |
|
| Folder | Components |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `core/` | `NooButton`, `NooFab`, `NooChip`, `NooSegmentedControl`, `NooToggle`, `NooSearchField`, `NooAvatar`, `NooBadge`, `NooProgressBar` |
|
| `core/` | `NooButton`, `NooFab`, `NooChip`, `NooSegmentedControl`, `NooToggle`, `NooSearchField`, `NooAvatar`, `NooBadge`, `NooProgressBar` |
|
||||||
| `lists/` | `NooGroupedList`, `NooSettingsRow`, `NooTabOrderRow`, `NooBanner`, `NooSummaryCard` |
|
| `lists/` | `NooGroupedList`, `NooSettingsRow`, `NooTabOrderRow`, `NooBanner`, `NooSummaryCard`, `NooSelectionBar` |
|
||||||
| `files/` | `NooFileKind` (spec §1.2 tiles; `NooFileKind.from(name:, mimeType:, isDirectory:)`), `NooFileTile`, `NooStatusIcon`/`NooSyncStatus`, `NooFileRow` (mobile 64px), `NooFileTableHeader`/`NooFileTableRow` (desktop), `NooSwipeAction` |
|
| `files/` | `NooFileKind` (spec §1.2 tiles; `NooFileKind.from(name:, mimeType:, isDirectory:)`), `NooFileTile`, `NooStatusIcon`/`NooSyncStatus`, `NooFileRow` (mobile 64px), `NooFileTableHeader`/`NooFileTableRow` (desktop), `NooSwipeAction` |
|
||||||
| `media/` | `NooGridCard`, `NooPhotoTile` (video badge, selection), `NooPhotoGroupHeader`/`NooPhotoGrid` (sliver, or `.box`), `NooActivityItem`, `NooStatCard` |
|
| `media/` | `NooGridCard`, `NooPhotoTile` (video badge, selection), `NooPhotoGroupHeader`/`NooPhotoGrid` (sliver, or `.box`), `NooActivityItem`, `NooStatCard` |
|
||||||
| `nav/` | `NooBottomBar`, `NooTopBar` (a `PreferredSizeWidget`) with `NooTopBarButton`/`NooTopBarBack`, `NooDrawer` with its `Account`/`Storage`/`Item`/`Link` parts, `NooSidebar` with `NooSidebarItem`/`Divider`/`Account`/`Storage`, `NooToolbar` |
|
| `nav/` | `NooBottomBar`, `NooTopBar` (a `PreferredSizeWidget`) with `NooTopBarButton`/`NooTopBarBack`, `NooDrawer` with its `Account`/`Storage`/`Item`/`Link` parts, `NooSidebar` with `NooSidebarItem`/`Divider`/`Account`/`Storage`, `NooToolbar` |
|
||||||
|
|||||||
@@ -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_kind.dart';
|
||||||
import '../widgets/noo/files/noo_file_row.dart';
|
import '../widgets/noo/files/noo_file_row.dart';
|
||||||
import '../widgets/noo/files/noo_file_table.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/noo/noo_layout.dart';
|
||||||
import '../widgets/tabs/tab_state_slivers.dart';
|
import '../widgets/tabs/tab_state_slivers.dart';
|
||||||
|
|
||||||
@@ -162,14 +161,20 @@ class _SharesViewState extends State<SharesView> {
|
|||||||
c.setSharedWithMe(scope == _ShareScope.withYou);
|
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) {
|
Widget _buildMobileList(BuildContext context, List<NextcloudShare> shares) {
|
||||||
|
final colors = context.nooColors;
|
||||||
return SliverPadding(
|
return SliverPadding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: NooSpace.sm),
|
padding: const EdgeInsets.symmetric(horizontal: NooSpace.sm),
|
||||||
sliver: SliverToBoxAdapter(
|
sliver: SliverList(
|
||||||
child: NooGroupedList(
|
delegate: SliverChildBuilderDelegate((context, index) {
|
||||||
children: [
|
final share = shares[index];
|
||||||
for (final share in shares)
|
final row = NooFileRow(
|
||||||
NooFileRow(
|
|
||||||
kind: NooFileKind.from(name: share.name, isDirectory: share.isFolder),
|
kind: NooFileKind.from(name: share.name, isDirectory: share.isFolder),
|
||||||
name: share.name,
|
name: share.name,
|
||||||
meta: '${share.ownerDisplayName} · ${_permissionLabel(share.permissions)}',
|
meta: '${share.ownerDisplayName} · ${_permissionLabel(share.permissions)}',
|
||||||
@@ -177,9 +182,26 @@ class _SharesViewState extends State<SharesView> {
|
|||||||
onTap: share.isFolder ? () => _openFolder(context, share) : null,
|
onTap: share.isFolder ? () => _openFolder(context, share) : null,
|
||||||
trailing: Icon(_shareTypeIcon(share.shareType), size: 16),
|
trailing: Icon(_shareTypeIcon(share.shareType), size: 16),
|
||||||
onMore: () => _confirmUnshare(context, share),
|
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),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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_table.dart';
|
||||||
import '../widgets/noo/files/noo_file_tile.dart';
|
import '../widgets/noo/files/noo_file_tile.dart';
|
||||||
import '../widgets/noo/lists/noo_banner.dart';
|
import '../widgets/noo/lists/noo_banner.dart';
|
||||||
import '../widgets/noo/lists/noo_grouped_list.dart';
|
|
||||||
import '../widgets/noo/noo_layout.dart';
|
import '../widgets/noo/noo_layout.dart';
|
||||||
import '../widgets/tabs/tab_location.dart';
|
import '../widgets/tabs/tab_location.dart';
|
||||||
import '../widgets/tabs/tab_state_slivers.dart';
|
import '../widgets/tabs/tab_state_slivers.dart';
|
||||||
@@ -103,14 +102,20 @@ 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) {
|
Widget _buildMobileList(BuildContext context, List<NextcloudItem> trash) {
|
||||||
|
final colors = context.nooColors;
|
||||||
return SliverPadding(
|
return SliverPadding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: NooSpace.sm),
|
padding: const EdgeInsets.symmetric(horizontal: NooSpace.sm),
|
||||||
sliver: SliverToBoxAdapter(
|
sliver: SliverList(
|
||||||
child: NooGroupedList(
|
delegate: SliverChildBuilderDelegate((context, index) {
|
||||||
children: [
|
final item = trash[index];
|
||||||
for (final item in trash)
|
final row = NooFileRow(
|
||||||
NooFileRow(
|
|
||||||
kind: NooFileKind.from(
|
kind: NooFileKind.from(
|
||||||
name: item.name,
|
name: item.name,
|
||||||
mimeType: item.mimeType,
|
mimeType: item.mimeType,
|
||||||
@@ -132,9 +137,26 @@ class _TrashViewState extends State<TrashView> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
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),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,6 +92,18 @@ class FilesControlsRow extends StatelessWidget {
|
|||||||
void _showSortSheet(BuildContext context, FilesController files) {
|
void _showSortSheet(BuildContext context, FilesController files) {
|
||||||
showNooSheet(
|
showNooSheet(
|
||||||
context,
|
context,
|
||||||
|
children: [
|
||||||
|
// `showNooSheet`'s `children` are built once, up front - a bare
|
||||||
|
// checkmark/value here would freeze at whatever it was when the
|
||||||
|
// sheet opened, since tapping a row calls `files.set...` and
|
||||||
|
// notifies the controller, not this already-built widget tree.
|
||||||
|
// `ListenableBuilder` re-runs its `builder` on every
|
||||||
|
// `FilesController.notifyListeners()` instead, so the selection
|
||||||
|
// updates live - see the identical fix in `_showFilterSheet`.
|
||||||
|
ListenableBuilder(
|
||||||
|
listenable: files,
|
||||||
|
builder: (context, _) => Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
NooSegmentedControl<bool>(
|
NooSegmentedControl<bool>(
|
||||||
fill: true,
|
fill: true,
|
||||||
@@ -106,6 +118,7 @@ class FilesControlsRow extends StatelessWidget {
|
|||||||
NooSegmentOption(value: false, icon: LucideIcons.arrowDown, label: 'Descending'),
|
NooSegmentOption(value: false, icon: LucideIcons.arrowDown, label: 'Descending'),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 22),
|
||||||
NooGroupedList(
|
NooGroupedList(
|
||||||
children: [
|
children: [
|
||||||
for (final field in FileSortField.values)
|
for (final field in FileSortField.values)
|
||||||
@@ -122,12 +135,23 @@ class FilesControlsRow extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showFilterSheet(BuildContext context, FilesController files) {
|
void _showFilterSheet(BuildContext context, FilesController files) {
|
||||||
showNooSheet(
|
showNooSheet(
|
||||||
context,
|
context,
|
||||||
|
children: [
|
||||||
|
// See `_showSortSheet`'s comment: without this, none of the
|
||||||
|
// checkmarks/toggles below would move until the sheet is closed
|
||||||
|
// and reopened.
|
||||||
|
ListenableBuilder(
|
||||||
|
listenable: files,
|
||||||
|
builder: (context, _) => Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
NooGroupedList(
|
NooGroupedList(
|
||||||
label: 'Show',
|
label: 'Show',
|
||||||
@@ -155,6 +179,7 @@ class FilesControlsRow extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 22),
|
||||||
NooGroupedList(
|
NooGroupedList(
|
||||||
children: [
|
children: [
|
||||||
NooSettingsRow(
|
NooSettingsRow(
|
||||||
@@ -179,6 +204,9 @@ class FilesControlsRow extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user