refine workspace navigation layout

This commit is contained in:
ngfchl
2026-07-19 14:30:57 +08:00
parent 764b646dfd
commit d0bfd135d4
2 changed files with 100 additions and 205 deletions
+3 -100
View File
@@ -243,7 +243,7 @@ class _MediaLibraryPageState extends ConsumerState<MediaLibraryPage> {
bool _detailSyncing = false;
bool _manualMatchPreparing = false;
_MediaWork? _detailWork;
_MediaWallFilter _wallFilter = _MediaWallFilter.all;
final _MediaWallFilter _wallFilter = _MediaWallFilter.all;
String? _activeCollectionKey;
final _searchController = TextEditingController();
@@ -335,7 +335,6 @@ class _MediaLibraryPageState extends ConsumerState<MediaLibraryPage> {
required bool compact,
}) {
final cs = ShadTheme.of(context).colorScheme;
final stats = state.statistics;
final title = Row(
children: [
Icon(
@@ -366,103 +365,7 @@ class _MediaLibraryPageState extends ConsumerState<MediaLibraryPage> {
),
],
);
final pills = [
_statPill(
context,
'全部',
stats.total.toString(),
selected: _wallFilter == _MediaWallFilter.all,
onTap: () => _selectWallFilter(_MediaWallFilter.all),
),
_statPill(
context,
'电影',
stats.movies.toString(),
selected: _wallFilter == _MediaWallFilter.movies,
onTap: () => _selectWallFilter(_MediaWallFilter.movies),
),
_statPill(
context,
'剧集',
stats.series.toString(),
selected: _wallFilter == _MediaWallFilter.series,
onTap: () => _selectWallFilter(_MediaWallFilter.series),
),
_statPill(
context,
'合集',
stats.collections.toString(),
selected: _wallFilter == _MediaWallFilter.collections,
onTap: () => _selectWallFilter(_MediaWallFilter.collections),
),
_statPill(
context,
'待匹配',
stats.unmatched.toString(),
selected: _wallFilter == _MediaWallFilter.unmatched,
onTap: () => _selectWallFilter(_MediaWallFilter.unmatched),
),
];
if (compact) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
title,
const SizedBox(height: 8),
Wrap(spacing: 0, runSpacing: 6, children: pills),
],
);
}
return Row(
children: [
Expanded(child: title),
...pills,
],
);
}
void _selectWallFilter(_MediaWallFilter filter) {
setState(() {
_wallFilter = filter;
_activeCollectionKey = null;
_detailWork = null;
});
}
Widget _statPill(
BuildContext context,
String label,
String value, {
required bool selected,
required VoidCallback onTap,
}) {
final cs = ShadTheme.of(context).colorScheme;
return Padding(
padding: const EdgeInsets.only(left: 8),
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(8),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
decoration: BoxDecoration(
color: selected ? cs.primary.withValues(alpha: 0.12) : cs.muted,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: selected ? cs.primary : cs.border),
),
child: Text(
'$label $value',
style: TextStyle(
fontSize: 12,
color: selected ? cs.primary : cs.mutedForeground,
fontWeight: selected ? FontWeight.w700 : FontWeight.w400,
),
),
),
),
),
);
return title;
}
Widget _buildToolbar(BuildContext context, MediaLibraryState state) {
@@ -506,7 +409,7 @@ class _MediaLibraryPageState extends ConsumerState<MediaLibraryPage> {
spacing: 8,
runSpacing: 8,
children: [
if (detailWork != null) ...[
if (detailWork != null && widget.showManagementToolbar) ...[
ShadButton.outline(
size: ShadButtonSize.sm,
onPressed: _detailSyncing
+97 -105
View File
@@ -249,13 +249,11 @@ class _WorkspacePageState extends ConsumerState<WorkspacePage> {
final topBar = _TopBar(
mode: _mode,
compact: compact,
onModeChanged: _changeMode,
searchController: _searchController,
searchFocusNode: _searchFocusNode,
searchOpen: _searchOpen,
onSearch: _submitSearch,
onToggleSearch: _toggleSearch,
onSettings: () => _showSettings(context),
onOpenMenu: () => _showMobileMenu(context),
uploadProgress: fp.uploadProgress,
);
@@ -307,11 +305,14 @@ class _WorkspacePageState extends ConsumerState<WorkspacePage> {
.read(fileProvider.notifier)
.setSection(section),
onSettings: () => _showSettings(context),
onModeChanged: _changeMode,
onSignOut: () =>
ref.read(authProvider.notifier).signOut(),
onTool: _openTool,
)
: _MediaSidebar(
onModeChanged: _changeMode,
onSettings: () => _showSettings(context),
onCreate: () =>
MediaLibraryPage.showCreateDialog(context, ref),
onTool: _openTool,
@@ -423,6 +424,8 @@ class _WorkspacePageState extends ConsumerState<WorkspacePage> {
? _CloudSidebar(
state: ref.read(fileProvider),
width: width,
showBrand: false,
onModeChanged: _changeMode,
onSection: (section) {
Navigator.of(sheetContext).pop();
ref.read(fileProvider.notifier).setSection(section);
@@ -442,6 +445,9 @@ class _WorkspacePageState extends ConsumerState<WorkspacePage> {
)
: _MediaSidebar(
width: width,
showBrand: false,
onModeChanged: _changeMode,
onSettings: () => _showSettings(context),
onCreate: () {
Navigator.of(sheetContext).pop();
MediaLibraryPage.showCreateDialog(context, ref);
@@ -551,26 +557,22 @@ class _WorkspacePageState extends ConsumerState<WorkspacePage> {
class _TopBar extends StatelessWidget {
final WorkspaceMode mode;
final bool compact;
final ValueChanged<WorkspaceMode> onModeChanged;
final TextEditingController searchController;
final FocusNode searchFocusNode;
final bool searchOpen;
final ValueChanged<String> onSearch;
final VoidCallback onToggleSearch;
final VoidCallback onSettings;
final VoidCallback onOpenMenu;
final UploadProgress? uploadProgress;
const _TopBar({
required this.mode,
required this.compact,
required this.onModeChanged,
required this.searchController,
required this.searchFocusNode,
required this.searchOpen,
required this.onSearch,
required this.onToggleSearch,
required this.onSettings,
required this.onOpenMenu,
required this.uploadProgress,
});
@@ -596,22 +598,6 @@ class _TopBar extends StatelessWidget {
icon: Icons.menu_rounded,
onTap: onOpenMenu,
),
const SizedBox(width: 4),
_ModeDisplayButton(
mode: mode,
compact: true,
onTap: () => onModeChanged(
mode == WorkspaceMode.cloud
? WorkspaceMode.media
: WorkspaceMode.cloud,
),
),
const SizedBox(width: 4),
_TopBarIconButton(
tooltip: '设置',
icon: Icons.settings_rounded,
onTap: onSettings,
),
],
),
),
@@ -624,31 +610,6 @@ class _TopBar extends StatelessWidget {
children: [
const SizedBox(width: 78),
const Expanded(child: DragToMoveArea(child: SizedBox.expand())),
OS26Glass(
radius: 13,
opacity: 0.42,
padding: const EdgeInsets.all(3),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
_ModeDisplayButton(
mode: mode,
onTap: () => onModeChanged(
mode == WorkspaceMode.cloud
? WorkspaceMode.media
: WorkspaceMode.cloud,
),
),
const SizedBox(width: 4),
_TopBarIconButton(
tooltip: '设置',
icon: Icons.settings_rounded,
onTap: onSettings,
),
],
),
),
const Expanded(child: DragToMoveArea(child: SizedBox.expand())),
if (mode == WorkspaceMode.cloud) ...[
_UploadListTopButton(progress: uploadProgress),
const SizedBox(width: 8),
@@ -713,14 +674,9 @@ class _TopBar extends StatelessWidget {
class _ModeDisplayButton extends StatelessWidget {
final WorkspaceMode mode;
final bool compact;
final VoidCallback onTap;
const _ModeDisplayButton({
required this.mode,
this.compact = false,
required this.onTap,
});
const _ModeDisplayButton({required this.mode, required this.onTap});
@override
Widget build(BuildContext context) {
@@ -735,33 +691,23 @@ class _ModeDisplayButton extends StatelessWidget {
button: true,
label: '$label,点击切换到$nextLabel',
child: ShadButton.ghost(
width: compact ? 124 : 136,
height: compact ? 38 : 34,
padding: EdgeInsets.zero,
height: 40,
padding: const EdgeInsets.symmetric(horizontal: 8),
onPressed: onTap,
child: AnimatedContainer(
duration: const Duration(milliseconds: 160),
decoration: BoxDecoration(
color: cs.secondary,
borderRadius: BorderRadius.circular(10),
boxShadow: [
BoxShadow(
color: cs.border.withValues(alpha: 0.7),
blurRadius: 10,
offset: const Offset(0, 3),
),
],
),
duration: const Duration(milliseconds: 180),
decoration: BoxDecoration(borderRadius: BorderRadius.circular(8)),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon, size: 15, color: cs.foreground),
const SizedBox(width: 7),
Icon(icon, size: 20, color: cs.foreground),
const SizedBox(width: 8),
Text(
label,
style: TextStyle(
fontSize: compact ? 11 : 12,
fontWeight: FontWeight.w700,
fontSize: 14,
fontWeight: FontWeight.w800,
color: cs.foreground,
),
),
@@ -1220,6 +1166,8 @@ class _MobileMenuRow extends StatelessWidget {
class _CloudSidebar extends StatelessWidget {
final FileState state;
final double width;
final bool showBrand;
final ValueChanged<WorkspaceMode> onModeChanged;
final ValueChanged<WorkspaceSection> onSection;
final VoidCallback onSettings;
final VoidCallback onSignOut;
@@ -1228,6 +1176,8 @@ class _CloudSidebar extends StatelessWidget {
const _CloudSidebar({
required this.state,
this.width = 250,
this.showBrand = true,
required this.onModeChanged,
required this.onSection,
required this.onSettings,
required this.onSignOut,
@@ -1242,19 +1192,23 @@ class _CloudSidebar extends StatelessWidget {
return SizedBox(
width: width,
child: OS26Glass(
radius: 24,
opacity: 0.56,
padding: const EdgeInsets.fromLTRB(14, 18, 14, 12),
radius: showBrand ? 24 : 0,
opacity: showBrand ? 0.56 : 0,
padding: EdgeInsets.fromLTRB(14, showBrand ? 18 : 14, 14, 12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_SidebarBrand(
icon: Icons.cloud_sync_rounded,
title: '光鸭云盘',
subtitle: 'Cloud Workspace',
imageAsset: 'assets/branding/guangya_icon.png',
),
const SizedBox(height: 18),
if (showBrand) ...[
_SidebarBrand(
icon: Icons.cloud_sync_rounded,
title: '光鸭云盘',
subtitle: 'Cloud Workspace',
imageAsset: 'assets/branding/guangya_icon.png',
onSwitchMode: () => onModeChanged(WorkspaceMode.media),
onSettings: onSettings,
),
const SizedBox(height: 18),
],
Expanded(
child: ListView(
children: [
@@ -1287,12 +1241,6 @@ class _CloudSidebar extends StatelessWidget {
],
),
),
_SidebarTile(
icon: Icons.settings_rounded,
label: '工作区设置',
selected: false,
onTap: onSettings,
),
_SidebarTile(
icon: Icons.logout_rounded,
label: '退出登录',
@@ -1335,11 +1283,17 @@ class _CloudSidebar extends StatelessWidget {
class _MediaSidebar extends ConsumerWidget {
final double width;
final bool showBrand;
final ValueChanged<WorkspaceMode> onModeChanged;
final VoidCallback onSettings;
final VoidCallback onCreate;
final ValueChanged<WorkspaceTool> onTool;
const _MediaSidebar({
this.width = 250,
this.showBrand = true,
required this.onModeChanged,
required this.onSettings,
required this.onCreate,
required this.onTool,
});
@@ -1351,18 +1305,22 @@ class _MediaSidebar extends ConsumerWidget {
return SizedBox(
width: width,
child: OS26Glass(
radius: 24,
opacity: 0.56,
padding: const EdgeInsets.fromLTRB(14, 18, 14, 12),
radius: showBrand ? 24 : 0,
opacity: showBrand ? 0.56 : 0,
padding: EdgeInsets.fromLTRB(14, showBrand ? 18 : 14, 14, 12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_SidebarBrand(
icon: Icons.play_circle_fill_rounded,
title: '光鸭影视',
subtitle: 'Media Center',
),
const SizedBox(height: 18),
if (showBrand) ...[
_SidebarBrand(
icon: Icons.play_circle_fill_rounded,
title: '光鸭影视',
subtitle: 'Media Center',
onSwitchMode: () => onModeChanged(WorkspaceMode.cloud),
onSettings: onSettings,
),
const SizedBox(height: 18),
],
_SidebarTile(
icon: Icons.home_rounded,
label: '首页',
@@ -1442,18 +1400,22 @@ class _SidebarBrand extends StatelessWidget {
final String title;
final String subtitle;
final String? imageAsset;
final VoidCallback? onSwitchMode;
final VoidCallback? onSettings;
const _SidebarBrand({
required this.icon,
required this.title,
required this.subtitle,
this.imageAsset,
this.onSwitchMode,
this.onSettings,
});
@override
Widget build(BuildContext context) {
final cs = ShadTheme.of(context).colorScheme;
return Row(
final brand = Row(
children: [
Container(
width: 46,
@@ -1511,6 +1473,33 @@ class _SidebarBrand extends StatelessWidget {
),
],
);
return Row(
children: [
Expanded(
child: Semantics(
button: onSwitchMode != null,
label: onSwitchMode == null ? title : '$title,点击切换工作区',
child: Material(
color: Colors.transparent,
child: InkWell(
borderRadius: BorderRadius.circular(12),
onTap: onSwitchMode,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 2),
child: brand,
),
),
),
),
),
if (onSettings != null)
_TopBarIconButton(
tooltip: '设置',
icon: Icons.settings_rounded,
onTap: onSettings!,
),
],
);
}
}
@@ -1535,17 +1524,20 @@ class _SidebarTile extends StatelessWidget {
return Padding(
padding: const EdgeInsets.only(bottom: 6),
child: InkWell(
borderRadius: BorderRadius.circular(10),
borderRadius: BorderRadius.circular(8),
onTap: onTap,
child: AnimatedContainer(
duration: const Duration(milliseconds: 140),
height: 42,
duration: const Duration(milliseconds: 180),
height: 44,
padding: const EdgeInsets.symmetric(horizontal: 9),
decoration: BoxDecoration(
color: selected
? const Color(0xFFFFB18A).withValues(alpha: 0.72)
? cs.primary.withValues(alpha: 0.14)
: Colors.transparent,
borderRadius: BorderRadius.circular(10),
borderRadius: BorderRadius.circular(8),
border: selected
? Border.all(color: cs.primary.withValues(alpha: 0.24))
: null,
),
child: Row(
children: [
@@ -1554,8 +1546,8 @@ class _SidebarTile extends StatelessWidget {
height: 28,
decoration: BoxDecoration(
color: selected
? Colors.white.withValues(alpha: 0.36)
: const Color(0xFFFFE4D2).withValues(alpha: 0.82),
? cs.primary.withValues(alpha: 0.18)
: cs.muted,
borderRadius: BorderRadius.circular(8),
),
child: Icon(icon, size: 17, color: cs.primary),