主要合入内容:
- 文件管理器分页加载、排序偏好、桌面表头排序、移动端排序菜单、Ctrl/Cmd+F搜索、Esc 关闭搜索、鼠标右滑返回上级。 - 移动端同步状态页、同步统计卡片、同步任务/累计统计事件、同步页布局更新。 - Android 相册同步默认路径改为 DCIM/Camera,下载路径改用 external_path。 - Rust sync-core 更新:Android 日志依赖、AlbumUpload/AlbumDownload、MirrorWcf 统计修复、Linux FUSE镜像挂载与读写同步支持。 - 依赖更新:external_path、fl_chart、pdfrx 升级,并修了 pdfrx 新版本的deprecated API。 - 新增文件包括:sort_options.dart、sync_page_android.dart、sync_stats_card.dart、platform/fuse.rs、sync_engine/fuse.rs。
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
import 'package:cloudreve4_flutter/presentation/providers/admin_provider.dart';
|
||||
import 'package:cloudreve4_flutter/presentation/providers/auth_provider.dart';
|
||||
import 'package:cloudreve4_flutter/presentation/providers/download_manager_provider.dart';
|
||||
import 'package:cloudreve4_flutter/presentation/providers/file_manager_provider.dart';
|
||||
import 'package:cloudreve4_flutter/presentation/providers/navigation_provider.dart';
|
||||
import 'package:cloudreve4_flutter/presentation/providers/sync_provider.dart';
|
||||
import 'package:cloudreve4_flutter/presentation/providers/upload_manager_provider.dart';
|
||||
import 'package:cloudreve4_flutter/presentation/providers/user_setting_provider.dart';
|
||||
import 'package:cloudreve4_flutter/presentation/widgets/announcement_dialog.dart';
|
||||
import 'package:cloudreve4_flutter/presentation/widgets/gesture_handler_mixin.dart';
|
||||
import 'package:cloudreve4_flutter/presentation/widgets/glassmorphism_container.dart';
|
||||
@@ -57,12 +59,18 @@ class _AppShellState extends State<AppShell>
|
||||
final Set<int> _visitedPageIndexes = <int>{0};
|
||||
late AnimationController _syncSpinController;
|
||||
String? _lastClipboardShareId;
|
||||
String? _lastUserId;
|
||||
bool _cachedShowSyncTab = false;
|
||||
|
||||
bool get _showSyncTab =>
|
||||
defaultTargetPlatform != TargetPlatform.android &&
|
||||
defaultTargetPlatform != TargetPlatform.iOS;
|
||||
static bool _shouldShowSyncTab(double screenWidth) {
|
||||
if (defaultTargetPlatform != TargetPlatform.android &&
|
||||
defaultTargetPlatform != TargetPlatform.iOS) {
|
||||
return true;
|
||||
}
|
||||
return screenWidth >= 800;
|
||||
}
|
||||
|
||||
List<Widget> get _pages => _showSyncTab
|
||||
List<Widget> _pages(bool showSyncTab) => showSyncTab
|
||||
? [
|
||||
const OverviewPage(),
|
||||
const FilesPage(),
|
||||
@@ -78,7 +86,7 @@ class _AppShellState extends State<AppShell>
|
||||
];
|
||||
|
||||
int _clampedIndex(int index) {
|
||||
final maxIndex = _pages.length - 1;
|
||||
final maxIndex = _pages(_cachedShowSyncTab).length - 1;
|
||||
if (index < 0) return 0;
|
||||
if (index > maxIndex) return maxIndex;
|
||||
return index;
|
||||
@@ -95,6 +103,7 @@ class _AppShellState extends State<AppShell>
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_showPostLoginAnnouncement();
|
||||
_checkClipboardShareLink();
|
||||
_lastUserId = context.read<AuthProvider>().user?.id;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -112,6 +121,56 @@ class _AppShellState extends State<AppShell>
|
||||
}
|
||||
}
|
||||
|
||||
void _handleTabSelected(int index) {
|
||||
final nav = Provider.of<NavigationProvider>(context, listen: false);
|
||||
nav.setIndex(index);
|
||||
|
||||
final userSetting = Provider.of<UserSettingProvider>(
|
||||
context,
|
||||
listen: false,
|
||||
);
|
||||
if (index == 0 || index == _pages(_cachedShowSyncTab).length - 1) {
|
||||
userSetting.loadCapacity();
|
||||
}
|
||||
}
|
||||
|
||||
void _checkUserChange(AuthProvider auth) {
|
||||
final currentUserId = auth.user?.id;
|
||||
if (_lastUserId != null && currentUserId != _lastUserId) {
|
||||
_lastUserId = currentUserId;
|
||||
Future.microtask(() {
|
||||
if (mounted) _resetProvidersOnUserChange();
|
||||
});
|
||||
} else if (_lastUserId == null && currentUserId != null) {
|
||||
_lastUserId = currentUserId;
|
||||
}
|
||||
}
|
||||
|
||||
void _resetProvidersOnUserChange() {
|
||||
final fileManager = Provider.of<FileManagerProvider>(
|
||||
context,
|
||||
listen: false,
|
||||
);
|
||||
final userSetting = Provider.of<UserSettingProvider>(
|
||||
context,
|
||||
listen: false,
|
||||
);
|
||||
final admin = Provider.of<AdminProvider>(context, listen: false);
|
||||
final sync = Provider.of<SyncProvider>(context, listen: false);
|
||||
|
||||
fileManager.clearFiles();
|
||||
userSetting.clear();
|
||||
admin.clear();
|
||||
|
||||
if (sync.engineInitialized) {
|
||||
sync.resetSync();
|
||||
}
|
||||
|
||||
final nav = Provider.of<NavigationProvider>(context, listen: false);
|
||||
nav.setIndex(0);
|
||||
userSetting.loadCapacity();
|
||||
}
|
||||
|
||||
Future<void> _showPostLoginAnnouncement() async {
|
||||
final authProvider = context.read<AuthProvider>();
|
||||
if (!authProvider.isAuthenticated) return;
|
||||
@@ -193,6 +252,7 @@ class _AppShellState extends State<AppShell>
|
||||
Widget build(BuildContext context) {
|
||||
final screenWidth = MediaQuery.of(context).size.width;
|
||||
final isDesktop = screenWidth >= 1000;
|
||||
_cachedShowSyncTab = _shouldShowSyncTab(screenWidth);
|
||||
|
||||
return PopScope(
|
||||
canPop: false,
|
||||
@@ -217,8 +277,9 @@ class _AppShellState extends State<AppShell>
|
||||
}
|
||||
}
|
||||
},
|
||||
child: Consumer<NavigationProvider>(
|
||||
builder: (context, navProvider, _) {
|
||||
child: Consumer2<AuthProvider, NavigationProvider>(
|
||||
builder: (context, auth, navProvider, _) {
|
||||
_checkUserChange(auth);
|
||||
if (isDesktop) {
|
||||
return _buildDesktopLayout(context, navProvider);
|
||||
}
|
||||
@@ -229,7 +290,7 @@ class _AppShellState extends State<AppShell>
|
||||
}
|
||||
|
||||
Widget _buildPageContent(BuildContext context, int currentIndex) {
|
||||
final pages = _pages;
|
||||
final pages = _pages(_cachedShowSyncTab);
|
||||
final visibleIndex = _clampedIndex(currentIndex);
|
||||
_visitedPageIndexes.add(visibleIndex);
|
||||
|
||||
@@ -297,7 +358,7 @@ class _AppShellState extends State<AppShell>
|
||||
return NavigationBar(
|
||||
height: 64,
|
||||
selectedIndex: _clampedIndex(navProvider.currentIndex),
|
||||
onDestinationSelected: (i) => navProvider.setIndex(i),
|
||||
onDestinationSelected: _handleTabSelected,
|
||||
destinations: [
|
||||
const NavigationDestination(
|
||||
icon: Icon(LucideIcons.layoutDashboard),
|
||||
@@ -322,7 +383,7 @@ class _AppShellState extends State<AppShell>
|
||||
),
|
||||
label: '任务',
|
||||
),
|
||||
if (_showSyncTab)
|
||||
if (_cachedShowSyncTab)
|
||||
NavigationDestination(
|
||||
icon: Consumer<SyncProvider>(
|
||||
builder: (context, sync, _) {
|
||||
@@ -373,15 +434,18 @@ class _AppShellState extends State<AppShell>
|
||||
children: [
|
||||
NavigationRail(
|
||||
selectedIndex: _clampedIndex(navProvider.currentIndex),
|
||||
onDestinationSelected: (i) => navProvider.setIndex(i),
|
||||
onDestinationSelected: _handleTabSelected,
|
||||
leading: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
child: GestureDetector(
|
||||
onTap: () => navProvider.setIndex(_pages.length - 1),
|
||||
onTap: () =>
|
||||
navProvider.setIndex(_pages(_cachedShowSyncTab).length - 1),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
border: navProvider.currentIndex == _pages.length - 1
|
||||
border:
|
||||
navProvider.currentIndex ==
|
||||
_pages(_cachedShowSyncTab).length - 1
|
||||
? Border.all(
|
||||
color: theme.colorScheme.primary,
|
||||
width: 2.5,
|
||||
@@ -424,7 +488,7 @@ class _AppShellState extends State<AppShell>
|
||||
selectedIcon: const Icon(LucideIcons.listChecks, weight: 700),
|
||||
label: const Text('任务'),
|
||||
),
|
||||
if (_showSyncTab)
|
||||
if (_cachedShowSyncTab)
|
||||
NavigationRailDestination(
|
||||
icon: Consumer<SyncProvider>(
|
||||
builder: (context, sync, _) {
|
||||
|
||||
Reference in New Issue
Block a user