主要合入内容:
- 文件管理器分页加载、排序偏好、桌面表头排序、移动端排序菜单、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:
@@ -5,6 +5,7 @@ import 'package:lucide_icons/lucide_icons.dart';
|
||||
|
||||
import '../../../data/models/sync_task_model.dart';
|
||||
import '../../providers/sync_provider.dart';
|
||||
import '../../widgets/sync_stats_card.dart';
|
||||
import '../../widgets/toast_helper.dart';
|
||||
import 'sync_settings_page.dart';
|
||||
|
||||
@@ -47,191 +48,328 @@ class _SyncPageState extends State<SyncPage> {
|
||||
],
|
||||
),
|
||||
body: RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
final sync = context.read<SyncProvider>();
|
||||
sync.invalidateAllTaskDetails();
|
||||
await sync.loadRecentTasks();
|
||||
},
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
children: [
|
||||
_buildStatusCard(sync, theme),
|
||||
const SizedBox(height: 8),
|
||||
_buildActiveTasksSection(sync, theme),
|
||||
const SizedBox(height: 8),
|
||||
_buildCompletedTasksSection(sync, theme),
|
||||
const SizedBox(height: 32),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _navigateToSettings() {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(builder: (_) => const SyncSettingsPage()),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStatusCard(SyncProvider sync, ThemeData theme) {
|
||||
final isActive = sync.isActive;
|
||||
final isPaused = sync.isPaused;
|
||||
final hasError = sync.hasError;
|
||||
|
||||
Color statusColor;
|
||||
IconData statusIcon;
|
||||
String statusText;
|
||||
|
||||
if (isActive) {
|
||||
statusColor = theme.colorScheme.primary;
|
||||
statusIcon = Icons.sync;
|
||||
statusText = _syncModeLabel(sync);
|
||||
} else if (isPaused) {
|
||||
statusColor = Colors.orange;
|
||||
statusIcon = Icons.pause_circle_outline;
|
||||
statusText = '已暂停';
|
||||
} else if (hasError) {
|
||||
statusColor = theme.colorScheme.error;
|
||||
statusIcon = Icons.error_outline;
|
||||
statusText = '同步错误';
|
||||
} else if (sync.state == SyncState.stopped) {
|
||||
statusColor = theme.disabledColor;
|
||||
statusIcon = Icons.stop_circle_outlined;
|
||||
statusText = '已停止';
|
||||
} else {
|
||||
statusColor = theme.disabledColor;
|
||||
statusIcon = Icons.cloud_off;
|
||||
statusText = '未启动';
|
||||
}
|
||||
|
||||
return Card(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 5),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
onRefresh: () async {
|
||||
final sync = context.read<SyncProvider>();
|
||||
sync.invalidateAllTaskDetails();
|
||||
await sync.loadRecentTasks();
|
||||
},
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(statusIcon, color: statusColor, size: 28),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 5),
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final isWide = constraints.maxWidth >= 800;
|
||||
final statsCard = SyncStatsCard(
|
||||
uploaded: sync.cumUploaded,
|
||||
downloaded: sync.cumDownloaded,
|
||||
renamed: sync.cumRenamed,
|
||||
moved: sync.cumMoved,
|
||||
conflicts: sync.cumConflicts,
|
||||
failed: sync.cumFailed,
|
||||
deletedLocal: sync.cumDeletedLocal,
|
||||
deletedRemote: sync.cumDeletedRemote,
|
||||
skipped: sync.cumSkipped,
|
||||
);
|
||||
|
||||
if (isWide) {
|
||||
return IntrinsicHeight(
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Expanded(child: _buildStatusHeaderCard(sync, theme)),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(child: statsCard),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
return Column(
|
||||
children: [
|
||||
Text(
|
||||
statusText,
|
||||
style: theme.textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
if (sync.errorMessage != null)
|
||||
Text(
|
||||
sync.errorMessage!,
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: theme.colorScheme.error,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
_buildStatusHeaderCard(sync, theme),
|
||||
const SizedBox(height: 8),
|
||||
statsCard,
|
||||
],
|
||||
),
|
||||
),
|
||||
if (sync.activeWorkerCount > 0)
|
||||
Badge(
|
||||
label: Text('${sync.activeWorkerCount}'),
|
||||
child: Icon(LucideIcons.loader, color: statusColor, size: 24),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (isActive || isPaused) ...[
|
||||
const SizedBox(height: 12),
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: LinearProgressIndicator(
|
||||
value: sync.totalFiles > 0 ? sync.progress : null,
|
||||
minHeight: 6,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
sync.totalFiles > 0
|
||||
? '${sync.syncedFiles} / ${sync.totalFiles} 文件'
|
||||
: sync.state == SyncState.continuous
|
||||
? '持续同步中'
|
||||
: '正在同步...',
|
||||
style: theme.textTheme.bodySmall,
|
||||
),
|
||||
],
|
||||
if (sync.lastSummary != null) ...[
|
||||
const SizedBox(height: 12),
|
||||
Wrap(
|
||||
spacing: 12,
|
||||
runSpacing: 4,
|
||||
children: [
|
||||
_summaryChip(theme, '上传', sync.lastSummary!.uploaded),
|
||||
_summaryChip(theme, '下载', sync.lastSummary!.downloaded),
|
||||
_summaryChip(theme, '冲突', sync.lastSummary!.conflicts),
|
||||
_summaryChip(theme, '失败', sync.lastSummary!.failed),
|
||||
_summaryChip(theme, '重命名', sync.lastSummary!.renamed),
|
||||
_summaryChip(theme, '移动', sync.lastSummary!.moved),
|
||||
],
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
if (!sync.isActive && !sync.isPaused)
|
||||
Expanded(
|
||||
child: FilledButton.icon(
|
||||
onPressed: () => _navigateToSettings(),
|
||||
icon: const Icon(Icons.play_arrow, size: 18),
|
||||
label: const Text('开始同步'),
|
||||
),
|
||||
),
|
||||
if (sync.isActive)
|
||||
Expanded(
|
||||
child: OutlinedButton.icon(
|
||||
onPressed: () => sync.pause(),
|
||||
icon: const Icon(Icons.pause, size: 18),
|
||||
label: const Text('暂停'),
|
||||
),
|
||||
),
|
||||
if (sync.isPaused)
|
||||
Expanded(
|
||||
child: FilledButton.icon(
|
||||
onPressed: () => sync.resume(),
|
||||
icon: const Icon(Icons.play_arrow, size: 18),
|
||||
label: const Text('恢复'),
|
||||
),
|
||||
),
|
||||
if (sync.isActive || sync.isPaused) ...[
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: OutlinedButton.icon(
|
||||
onPressed: () => _stopSync(sync),
|
||||
icon: const Icon(Icons.stop, size: 18),
|
||||
label: const Text('停止'),
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: theme.colorScheme.error,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_buildActiveTasksSection(sync, theme),
|
||||
const SizedBox(height: 8),
|
||||
_buildCompletedTasksSection(sync, theme),
|
||||
const SizedBox(height: 32),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _summaryChip(ThemeData theme, String label, int value) {
|
||||
void _navigateToSettings() {
|
||||
Navigator.of(
|
||||
context,
|
||||
).push(MaterialPageRoute(builder: (_) => const SyncSettingsPage()));
|
||||
}
|
||||
|
||||
Widget _buildStatusHeaderCard(SyncProvider sync, ThemeData theme) {
|
||||
final isActive = sync.isActive;
|
||||
final isPaused = sync.isPaused;
|
||||
final hasError = sync.hasError;
|
||||
|
||||
Color statusColor;
|
||||
String statusText;
|
||||
|
||||
if (isActive) {
|
||||
statusColor = theme.colorScheme.primary;
|
||||
statusText = _syncModeLabel(sync);
|
||||
} else if (isPaused) {
|
||||
statusColor = Colors.orange;
|
||||
statusText = '已暂停';
|
||||
} else if (hasError) {
|
||||
statusColor = theme.colorScheme.error;
|
||||
statusText = '同步错误';
|
||||
} else if (sync.state == SyncState.stopped) {
|
||||
statusColor = theme.disabledColor;
|
||||
statusText = '已停止';
|
||||
} else {
|
||||
statusColor = theme.disabledColor;
|
||||
statusText = '未启动';
|
||||
}
|
||||
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [
|
||||
theme.colorScheme.primaryContainer.withValues(alpha: 0.3),
|
||||
theme.colorScheme.tertiaryContainer.withValues(alpha: 0.3),
|
||||
],
|
||||
),
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
),
|
||||
padding: const EdgeInsets.fromLTRB(24, 16, 24, 28),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Center(
|
||||
child: Text(
|
||||
statusText,
|
||||
style: theme.textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: statusColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (sync.errorMessage != null) ...[
|
||||
const SizedBox(height: 4),
|
||||
Center(
|
||||
child: Text(
|
||||
sync.errorMessage!,
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: theme.colorScheme.error,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 96,
|
||||
height: 96,
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
if (isActive)
|
||||
SizedBox(
|
||||
width: 96,
|
||||
height: 96,
|
||||
child: CircularProgressIndicator(
|
||||
value: sync.activeTotalCount > 0
|
||||
? sync.activeProgress
|
||||
: null,
|
||||
strokeWidth: 6,
|
||||
strokeCap: StrokeCap.round,
|
||||
backgroundColor:
|
||||
theme.colorScheme.surfaceContainerHighest,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
statusColor,
|
||||
),
|
||||
),
|
||||
)
|
||||
else
|
||||
SizedBox(
|
||||
width: 96,
|
||||
height: 96,
|
||||
child: CircularProgressIndicator(
|
||||
value: 0,
|
||||
strokeWidth: 6,
|
||||
backgroundColor:
|
||||
theme.colorScheme.surfaceContainerHighest,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
statusColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (isActive && sync.activeTotalCount > 0)
|
||||
Text(
|
||||
'${(sync.activeProgress * 100).toInt()}%',
|
||||
style: theme.textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: statusColor,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
isActive
|
||||
? (sync.state == SyncState.continuous
|
||||
? '持续同步'
|
||||
: '同步中')
|
||||
: isPaused
|
||||
? '已暂停'
|
||||
: hasError
|
||||
? '错误'
|
||||
: '未启动',
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: theme.hintColor,
|
||||
fontSize: 11,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 24),
|
||||
IntrinsicWidth(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildStatRow(
|
||||
Icons.file_upload_outlined,
|
||||
'${sync.cumUploaded}',
|
||||
'已上传',
|
||||
Colors.blue,
|
||||
theme,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_buildStatRow(
|
||||
Icons.file_download_outlined,
|
||||
'${sync.cumDownloaded}',
|
||||
'已下载',
|
||||
Colors.green,
|
||||
theme,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_buildStatRow(
|
||||
Icons.warning_amber_outlined,
|
||||
'${sync.cumConflicts}',
|
||||
'冲突',
|
||||
Colors.orange,
|
||||
theme,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (isActive && sync.currentFile != null) ...[
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
sync.currentFile!,
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: theme.hintColor,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
),
|
||||
],
|
||||
if (isActive && sync.activeTotalCount > 0) ...[
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'${sync.activeCompletedCount} / ${sync.activeTotalCount} 文件',
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: theme.hintColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
children: [
|
||||
if (!sync.isActive && !sync.isPaused)
|
||||
Expanded(
|
||||
child: FilledButton.icon(
|
||||
onPressed: () => _navigateToSettings(),
|
||||
icon: const Icon(Icons.play_arrow, size: 18),
|
||||
label: const Text('开始同步'),
|
||||
),
|
||||
),
|
||||
if (sync.isActive)
|
||||
Expanded(
|
||||
child: OutlinedButton.icon(
|
||||
onPressed: () => sync.pause(),
|
||||
icon: const Icon(Icons.pause, size: 18),
|
||||
label: const Text('暂停'),
|
||||
),
|
||||
),
|
||||
if (sync.isPaused)
|
||||
Expanded(
|
||||
child: FilledButton.icon(
|
||||
onPressed: () => sync.resume(),
|
||||
icon: const Icon(Icons.play_arrow, size: 18),
|
||||
label: const Text('恢复'),
|
||||
),
|
||||
),
|
||||
if (sync.isActive || sync.isPaused) ...[
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: OutlinedButton.icon(
|
||||
onPressed: () => _stopSync(sync),
|
||||
icon: const Icon(Icons.stop, size: 18),
|
||||
label: const Text('停止'),
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: theme.colorScheme.error,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStatRow(
|
||||
IconData icon,
|
||||
String value,
|
||||
String label,
|
||||
Color color,
|
||||
ThemeData theme,
|
||||
) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text('$label:', style: theme.textTheme.bodySmall?.copyWith(color: theme.hintColor)),
|
||||
Text(' $value', style: theme.textTheme.bodySmall?.copyWith(fontWeight: FontWeight.w600)),
|
||||
Icon(icon, size: 16, color: color),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
value,
|
||||
style: theme.textTheme.titleSmall?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
label,
|
||||
style: theme.textTheme.bodySmall?.copyWith(color: theme.hintColor),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -262,9 +400,13 @@ class _SyncPageState extends State<SyncPage> {
|
||||
|
||||
Widget _buildCompletedTasksSection(SyncProvider sync, ThemeData theme) {
|
||||
final tasks = sync.recentTasks
|
||||
.where((t) =>
|
||||
(t.status == 'completed' || t.status == 'failed' || t.status == 'cancelled') &&
|
||||
t.totalCount > 0)
|
||||
.where(
|
||||
(t) =>
|
||||
(t.status == 'completed' ||
|
||||
t.status == 'failed' ||
|
||||
t.status == 'cancelled') &&
|
||||
t.totalCount > 0,
|
||||
)
|
||||
.toList();
|
||||
|
||||
return _buildTaskSection(
|
||||
@@ -308,7 +450,10 @@ class _SyncPageState extends State<SyncPage> {
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Center(
|
||||
child: Text(emptyText, style: TextStyle(color: theme.hintColor)),
|
||||
child: Text(
|
||||
emptyText,
|
||||
style: TextStyle(color: theme.hintColor),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
@@ -357,9 +502,15 @@ class _SyncPageState extends State<SyncPage> {
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
vertical: 2,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: _statusColor(task.status, theme).withValues(alpha: 0.1),
|
||||
color: _statusColor(
|
||||
task.status,
|
||||
theme,
|
||||
).withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: Text(
|
||||
@@ -396,8 +547,7 @@ class _SyncPageState extends State<SyncPage> {
|
||||
),
|
||||
),
|
||||
),
|
||||
if (isExpanded)
|
||||
_buildTaskDetailList(task, theme),
|
||||
if (isExpanded) _buildTaskDetailList(task, theme),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -410,7 +560,13 @@ class _SyncPageState extends State<SyncPage> {
|
||||
if (_loadingDetails.contains(task.id)) {
|
||||
return const Padding(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: Center(child: SizedBox(width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2))),
|
||||
child: Center(
|
||||
child: SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -430,10 +586,41 @@ class _SyncPageState extends State<SyncPage> {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 6),
|
||||
child: Row(
|
||||
children: [
|
||||
SizedBox(width: 70, child: Text('操作', style: theme.textTheme.labelSmall?.copyWith(color: theme.hintColor))),
|
||||
Expanded(child: Text('文件名', style: theme.textTheme.labelSmall?.copyWith(color: theme.hintColor))),
|
||||
SizedBox(width: 55, child: Text('状态', style: theme.textTheme.labelSmall?.copyWith(color: theme.hintColor))),
|
||||
SizedBox(width: 110, child: Text('时间', style: theme.textTheme.labelSmall?.copyWith(color: theme.hintColor))),
|
||||
SizedBox(
|
||||
width: 70,
|
||||
child: Text(
|
||||
'操作',
|
||||
style: theme.textTheme.labelSmall?.copyWith(
|
||||
color: theme.hintColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'文件名',
|
||||
style: theme.textTheme.labelSmall?.copyWith(
|
||||
color: theme.hintColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: 55,
|
||||
child: Text(
|
||||
'状态',
|
||||
style: theme.textTheme.labelSmall?.copyWith(
|
||||
color: theme.hintColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: 110,
|
||||
child: Text(
|
||||
'时间',
|
||||
style: theme.textTheme.labelSmall?.copyWith(
|
||||
color: theme.hintColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -463,7 +650,10 @@ class _SyncPageState extends State<SyncPage> {
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 1),
|
||||
decoration: BoxDecoration(
|
||||
color: _actionColor(item.actionType, theme).withValues(alpha: 0.1),
|
||||
color: _actionColor(
|
||||
item.actionType,
|
||||
theme,
|
||||
).withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(3),
|
||||
),
|
||||
child: Text(
|
||||
@@ -560,7 +750,8 @@ class _SyncPageState extends State<SyncPage> {
|
||||
setState(() => _expandedTasks.add(taskId));
|
||||
sync.watchTaskDetail(taskId);
|
||||
|
||||
if (sync.getCachedTaskDetail(taskId) == null && !_loadingDetails.contains(taskId)) {
|
||||
if (sync.getCachedTaskDetail(taskId) == null &&
|
||||
!_loadingDetails.contains(taskId)) {
|
||||
setState(() => _loadingDetails.add(taskId));
|
||||
sync.getTaskDetail(taskId).whenComplete(() {
|
||||
if (mounted) {
|
||||
|
||||
Reference in New Issue
Block a user