fix(app): 日记可见性修复 — 私密日记仅本地 + Web 端 ID 修复 + 分享按钮
问题修复: 1. Web端保存的日记看不到:createJournal 返回值未捕获,server ID 丢失导致 后续元素保存用错 ID。现在使用 saved.id 贯穿全部操作。 2. 管理端看不到新建日记:后端 list_journals 添加 is_private 过滤,admin/teacher 查看他人日记时排除私密日记。 3. RemoteJournalRepository 添加 onJournalChanged 变更通知流,HomeBloc 可自动刷新。 4. SyncEngine(native + web)enqueue 添加 is_private 防御性检查,私密日记不入队。 5. 编辑器 _persistState 条件入队:仅非私密日记同步到后端。 6. 分享流程改造:首次从私密变为公开时入队 create 操作上传。 7. 日记卡片添加可见性标签(仅自己可见/班级可见/公开),私密日记可点击分享。 8. 首页 _sharePrivateJournal 弹出 ShareBottomSheet 主动分享。
This commit is contained in:
@@ -24,6 +24,10 @@ import '../../../core/theme/app_shadows.dart';
|
||||
import '../../../core/theme/app_typography.dart';
|
||||
import '../../../data/models/journal_entry.dart';
|
||||
import '../../../data/repositories/journal_repository.dart';
|
||||
import '../../../data/repositories/class_repository.dart';
|
||||
import '../../../data/services/sync_engine.dart';
|
||||
import '../../auth/bloc/auth_bloc.dart';
|
||||
import '../../editor/widgets/share_bottom_sheet.dart';
|
||||
import '../bloc/home_bloc.dart';
|
||||
|
||||
class HomePage extends StatelessWidget {
|
||||
@@ -659,9 +663,22 @@ class _JournalCard extends StatelessWidget {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'${journal.date.month}月${journal.date.day}日',
|
||||
style: TextStyle(fontSize: 11, color: theme.colorScheme.onSurfaceVariant),
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
'${journal.date.month}月${journal.date.day}日',
|
||||
style: TextStyle(fontSize: 11, color: theme.colorScheme.onSurfaceVariant),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
// 可见性标签
|
||||
_VisibilityBadge(
|
||||
isPrivate: journal.isPrivate,
|
||||
sharedToClass: journal.sharedToClass,
|
||||
onTap: journal.isPrivate
|
||||
? () => _sharePrivateJournal(context, journal)
|
||||
: null,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
@@ -702,6 +719,152 @@ class _JournalCard extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 分享私密日记 — 弹出分享面板,将日记变为公开并上传到后端
|
||||
Future<void> _sharePrivateJournal(BuildContext context, JournalEntry entry) async {
|
||||
String? userClassId;
|
||||
String userClassName = '我的班级';
|
||||
|
||||
try {
|
||||
final authState = context.read<AuthBloc>().state;
|
||||
if (authState is Authenticated) {
|
||||
try {
|
||||
final classRepo = context.read<ClassRepository>();
|
||||
final classes = await classRepo.getMyClasses();
|
||||
if (classes.isNotEmpty) {
|
||||
userClassId = classes.first.id;
|
||||
userClassName = classes.first.name;
|
||||
}
|
||||
} catch (_) {
|
||||
// 没有班级信息,使用默认值
|
||||
}
|
||||
}
|
||||
} catch (_) {}
|
||||
|
||||
// ignore: use_build_context_synchronously
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
builder: (sheetContext) => ShareBottomSheet(
|
||||
classId: userClassId,
|
||||
className: userClassName,
|
||||
onDecision: (shareToClass) async {
|
||||
try {
|
||||
final repo = context.read<JournalRepository>();
|
||||
// 将私密日记变为公开
|
||||
final updated = entry.copyWith(
|
||||
isPrivate: false,
|
||||
sharedToClass: shareToClass,
|
||||
);
|
||||
await repo.updateJournal(updated);
|
||||
|
||||
// 首次从私密变为公开 → 入队 SyncEngine 上传到后端
|
||||
final syncEngine = context.read<SyncEngine>();
|
||||
syncEngine.enqueue(PendingOperation(
|
||||
id: updated.id,
|
||||
type: SyncOperationType.create,
|
||||
endpoint: '/diary/journals',
|
||||
data: updated.toJson(),
|
||||
version: updated.version,
|
||||
createdAt: DateTime.now(),
|
||||
));
|
||||
|
||||
// 刷新首页列表
|
||||
// ignore: use_build_context_synchronously
|
||||
context.read<HomeBloc>().add(const HomeRefresh());
|
||||
} catch (e) {
|
||||
debugPrint('分享日记失败: $e');
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 可见性标签 — 显示日记的可见性状态
|
||||
///
|
||||
/// - 私密:🔒 仅自己可见(可点击分享)
|
||||
/// - 分享到班级:🏫 班级可见
|
||||
/// - 公开:🌐 所有人可见
|
||||
class _VisibilityBadge extends StatelessWidget {
|
||||
const _VisibilityBadge({
|
||||
required this.isPrivate,
|
||||
required this.sharedToClass,
|
||||
this.onTap,
|
||||
});
|
||||
|
||||
final bool isPrivate;
|
||||
final bool sharedToClass;
|
||||
final VoidCallback? onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (isPrivate) {
|
||||
// 私密日记 — 显示锁定图标,可点击分享
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
customBorder: const StadiumBorder(),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.tertiarySoftLight,
|
||||
borderRadius: AppRadius.pillBorder,
|
||||
),
|
||||
child: const Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(Icons.lock_outline, size: 12, color: Color(0xFFB8860B)),
|
||||
SizedBox(width: 3),
|
||||
Text(
|
||||
'仅自己可见',
|
||||
style: TextStyle(fontSize: 10, fontWeight: FontWeight.w500, color: Color(0xFFB8860B)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (sharedToClass) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.secondary.withValues(alpha: 0.15),
|
||||
borderRadius: AppRadius.pillBorder,
|
||||
),
|
||||
child: const Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(Icons.groups, size: 12, color: AppColors.secondary),
|
||||
SizedBox(width: 3),
|
||||
Text(
|
||||
'班级可见',
|
||||
style: TextStyle(fontSize: 10, fontWeight: FontWeight.w500, color: AppColors.secondary),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.accent.withValues(alpha: 0.12),
|
||||
borderRadius: AppRadius.pillBorder,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(Icons.public, size: 12, color: AppColors.accent),
|
||||
const SizedBox(width: 3),
|
||||
Text(
|
||||
'公开',
|
||||
style: TextStyle(fontSize: 10, fontWeight: FontWeight.w500, color: AppColors.accent),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _EmptyJournalState extends StatelessWidget {
|
||||
|
||||
Reference in New Issue
Block a user