此提交包含多项变更: 1. 新增鸿蒙平台支持,完善设备检测与数据库适配 2. 替换旧版分享插件API为SharePlus 3. 批量迁移StateNotifier到Notifier以适配新版Riverpod 4. 修复zip编码判断、图表API参数等bug 5. 更新应用图标、启动页资源与多尺寸适配图标 6. 调整Android最小SDK版本与应用名称 7. 优化日志打印与正则表达式使用 8. 修正编辑器画布样式初始化与配置逻辑 9. 更新依赖与CI插件配置
207 lines
5.7 KiB
Dart
207 lines
5.7 KiB
Dart
// ============================================================
|
||
// 闲言APP — 协作画布Riverpod状态管理
|
||
// 创建时间: 2026-05-14
|
||
// 更新时间: 2026-05-15
|
||
// 作用: CanvasState + CanvasNotifier,封装Engine和SyncService
|
||
// 上次更新: 修复participants回调未绑定、joinCanvas传递peerDeviceId
|
||
// ============================================================
|
||
|
||
import 'dart:ui';
|
||
|
||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||
|
||
import 'package:xianyan/core/utils/logger.dart';
|
||
import 'package:xianyan/features/file_transfer/providers/shared_signaling_provider.dart';
|
||
import 'package:xianyan/features/file_transfer/services/signaling_service.dart';
|
||
import '../models/stroke.dart';
|
||
import '../services/canvas_engine.dart';
|
||
import '../services/canvas_sync_service.dart';
|
||
|
||
class CanvasState {
|
||
const CanvasState({
|
||
this.strokes = const [],
|
||
this.activeStroke,
|
||
this.currentTool = StrokeType.pen,
|
||
this.currentColor = '#000000',
|
||
this.currentWidth = 3.0,
|
||
this.remoteCursors = const {},
|
||
this.participants = const [],
|
||
this.isConnected = false,
|
||
this.canvasId,
|
||
});
|
||
|
||
final List<Stroke> strokes;
|
||
final Stroke? activeStroke;
|
||
final StrokeType currentTool;
|
||
final String currentColor;
|
||
final double currentWidth;
|
||
final Map<String, Offset> remoteCursors;
|
||
final List<String> participants;
|
||
final bool isConnected;
|
||
final String? canvasId;
|
||
|
||
CanvasState copyWith({
|
||
List<Stroke>? strokes,
|
||
Stroke? activeStroke,
|
||
StrokeType? currentTool,
|
||
String? currentColor,
|
||
double? currentWidth,
|
||
Map<String, Offset>? remoteCursors,
|
||
List<String>? participants,
|
||
bool? isConnected,
|
||
String? canvasId,
|
||
bool clearActiveStroke = false,
|
||
}) {
|
||
return CanvasState(
|
||
strokes: strokes ?? this.strokes,
|
||
activeStroke: clearActiveStroke
|
||
? null
|
||
: (activeStroke ?? this.activeStroke),
|
||
currentTool: currentTool ?? this.currentTool,
|
||
currentColor: currentColor ?? this.currentColor,
|
||
currentWidth: currentWidth ?? this.currentWidth,
|
||
remoteCursors: remoteCursors ?? this.remoteCursors,
|
||
participants: participants ?? this.participants,
|
||
isConnected: isConnected ?? this.isConnected,
|
||
canvasId: canvasId ?? this.canvasId,
|
||
);
|
||
}
|
||
}
|
||
|
||
class CanvasNotifier extends Notifier<CanvasState> {
|
||
@override
|
||
CanvasState build() {
|
||
ref.onDispose(_onDispose);
|
||
return const CanvasState();
|
||
}
|
||
|
||
late final SignalingService _signaling = ref.read(sharedSignalingProvider);
|
||
late final CanvasEngine _engine;
|
||
late final CanvasSyncService _syncService;
|
||
|
||
CanvasNotifier() {
|
||
_engine = CanvasEngine();
|
||
_syncService = CanvasSyncService(signaling: _signaling);
|
||
|
||
_engine.addListener(_onEngineChanged);
|
||
|
||
_syncService.onRemoteStroke = _handleRemoteStroke;
|
||
_syncService.onRemoteSnapshot = _handleRemoteSnapshot;
|
||
_syncService.onRemoteCursor = _handleRemoteCursor;
|
||
_syncService.onParticipantsChanged = _handleParticipantsChanged;
|
||
_syncService.onSnapshotRequest = _handleSnapshotRequest;
|
||
}
|
||
|
||
CanvasEngine get engine => _engine;
|
||
|
||
void setUserId(String userId) {
|
||
_engine.userId = userId;
|
||
}
|
||
|
||
void setTool(StrokeType tool) {
|
||
_engine.setTool(tool);
|
||
}
|
||
|
||
void setColor(String color) {
|
||
_engine.setColor(color);
|
||
}
|
||
|
||
void setWidth(double width) {
|
||
_engine.setWidth(width);
|
||
}
|
||
|
||
void startStroke(Offset point) {
|
||
_engine.startStroke(point);
|
||
}
|
||
|
||
void addPoint(Offset point) {
|
||
_engine.addPoint(point);
|
||
}
|
||
|
||
void endStroke() {
|
||
final stroke = _engine.endStroke();
|
||
if (stroke != null) {
|
||
_syncService.broadcastStroke(stroke);
|
||
}
|
||
}
|
||
|
||
void undo() {
|
||
_engine.undo();
|
||
}
|
||
|
||
void redo() {
|
||
_engine.redo();
|
||
}
|
||
|
||
void clearCanvas() {
|
||
_engine.clearCanvas();
|
||
}
|
||
|
||
void joinCanvas(String canvasId, String deviceId, {String? peerDeviceId}) {
|
||
_syncService.joinCanvas(canvasId, deviceId, peerId: peerDeviceId);
|
||
state = state.copyWith(canvasId: canvasId, isConnected: true);
|
||
Future.delayed(const Duration(milliseconds: 500), () {
|
||
_syncService.requestSnapshot();
|
||
});
|
||
}
|
||
|
||
void leaveCanvas() {
|
||
_syncService.leaveCanvas();
|
||
state = state.copyWith(isConnected: false);
|
||
}
|
||
|
||
void broadcastCursor(Offset position) {
|
||
_syncService.broadcastCursor(position);
|
||
}
|
||
|
||
void _onEngineChanged() {
|
||
state = state.copyWith(
|
||
strokes: _engine.strokes,
|
||
activeStroke: _engine.activeStroke,
|
||
currentTool: _engine.currentTool,
|
||
currentColor: _engine.currentColor,
|
||
currentWidth: _engine.currentWidth,
|
||
clearActiveStroke: _engine.activeStroke == null,
|
||
);
|
||
}
|
||
|
||
void _handleRemoteStroke(Stroke stroke) {
|
||
Log.d('CanvasProvider: received remote stroke from ${stroke.userId}');
|
||
_engine.mergeRemoteStroke(stroke);
|
||
}
|
||
|
||
void _handleRemoteSnapshot(List<Stroke> strokes) {
|
||
Log.i('CanvasProvider: received snapshot with ${strokes.length} strokes');
|
||
_engine.mergeSnapshot(strokes);
|
||
}
|
||
|
||
void _handleRemoteCursor(String userId, Offset position) {
|
||
final updated = Map<String, Offset>.from(state.remoteCursors);
|
||
updated[userId] = position;
|
||
state = state.copyWith(remoteCursors: updated);
|
||
}
|
||
|
||
void _handleParticipantsChanged(List<String> participants) {
|
||
state = state.copyWith(participants: participants);
|
||
}
|
||
|
||
void _handleSnapshotRequest(String requestingDeviceId, String canvasId) {
|
||
if (state.canvasId != canvasId) return;
|
||
final strokes = _engine.strokes;
|
||
Log.i(
|
||
'CanvasProvider: sending snapshot response to $requestingDeviceId with ${strokes.length} strokes',
|
||
);
|
||
_syncService.sendSnapshotResponse(requestingDeviceId, strokes);
|
||
}
|
||
|
||
void _onDispose() {
|
||
_engine.removeListener(_onEngineChanged);
|
||
_engine.dispose();
|
||
_syncService.dispose();
|
||
}
|
||
}
|
||
|
||
final canvasProvider = NotifierProvider<CanvasNotifier, CanvasState>(
|
||
CanvasNotifier.new,
|
||
);
|