1.3.21.3.2
Android APK Release / Build Android APK (push) Successful in 55m29s

This commit is contained in:
2026-05-26 16:32:18 +08:00
parent 546cef5ba6
commit 17673b2862
89 changed files with 24098 additions and 272 deletions
+19
View File
@@ -0,0 +1,19 @@
class AppUpdateInfo {
final String version;
final String title;
final String? description;
final Uri pageUrl;
final Uri? downloadUrl;
final String? fileName;
final DateTime? publishedAt;
const AppUpdateInfo({
required this.version,
required this.title,
required this.pageUrl,
this.description,
this.downloadUrl,
this.fileName,
this.publishedAt,
});
}
+114
View File
@@ -0,0 +1,114 @@
import '../../src/rust/api/ffi_types.dart' as ffi;
class SyncConfigModel {
final String baseUrl;
final String accessToken;
final String refreshToken;
final String localRoot;
final String remoteRoot;
final String syncMode;
final String conflictStrategy;
final String wcfDeleteMode;
final int maxConcurrentTransfers;
final int bandwidthLimitKbps;
final List<String> excludedPaths;
final int maxWorkers;
final String dataDir;
final String clientId;
final String logLevel;
const SyncConfigModel({
required this.baseUrl,
required this.accessToken,
required this.refreshToken,
required this.localRoot,
required this.dataDir,
required this.clientId,
this.remoteRoot = 'cloudreve://my',
this.syncMode = 'full',
this.conflictStrategy = 'keep_both',
this.wcfDeleteMode = 'wcf_delete_local_only',
this.maxConcurrentTransfers = 3,
this.bandwidthLimitKbps = 0,
this.excludedPaths = const [],
this.maxWorkers = 0,
this.logLevel = 'info',
});
SyncConfigModel copyWith({
String? baseUrl,
String? accessToken,
String? refreshToken,
String? localRoot,
String? remoteRoot,
String? syncMode,
String? conflictStrategy,
String? wcfDeleteMode,
int? maxConcurrentTransfers,
int? bandwidthLimitKbps,
List<String>? excludedPaths,
int? maxWorkers,
String? dataDir,
String? clientId,
String? logLevel,
}) {
return SyncConfigModel(
baseUrl: baseUrl ?? this.baseUrl,
accessToken: accessToken ?? this.accessToken,
refreshToken: refreshToken ?? this.refreshToken,
localRoot: localRoot ?? this.localRoot,
remoteRoot: remoteRoot ?? this.remoteRoot,
syncMode: syncMode ?? this.syncMode,
conflictStrategy: conflictStrategy ?? this.conflictStrategy,
wcfDeleteMode: wcfDeleteMode ?? this.wcfDeleteMode,
maxConcurrentTransfers:
maxConcurrentTransfers ?? this.maxConcurrentTransfers,
bandwidthLimitKbps: bandwidthLimitKbps ?? this.bandwidthLimitKbps,
excludedPaths: excludedPaths ?? this.excludedPaths,
maxWorkers: maxWorkers ?? this.maxWorkers,
dataDir: dataDir ?? this.dataDir,
clientId: clientId ?? this.clientId,
logLevel: logLevel ?? this.logLevel,
);
}
ffi.SyncConfigFfi toFfi() {
return ffi.SyncConfigFfi(
baseUrl: baseUrl,
accessToken: accessToken,
refreshToken: refreshToken,
localRoot: localRoot,
remoteRoot: remoteRoot,
syncMode: syncMode,
conflictStrategy: conflictStrategy,
wcfDeleteMode: wcfDeleteMode,
maxConcurrentTransfers: maxConcurrentTransfers,
bandwidthLimitKbps: BigInt.from(bandwidthLimitKbps),
excludedPaths: excludedPaths,
maxWorkers: maxWorkers,
dataDir: dataDir,
clientId: clientId,
logLevel: logLevel,
);
}
static SyncConfigModel fromFfi(ffi.SyncConfigFfi ffi) {
return SyncConfigModel(
baseUrl: ffi.baseUrl,
accessToken: ffi.accessToken,
refreshToken: ffi.refreshToken,
localRoot: ffi.localRoot,
remoteRoot: ffi.remoteRoot,
syncMode: ffi.syncMode,
conflictStrategy: ffi.conflictStrategy,
wcfDeleteMode: ffi.wcfDeleteMode,
maxConcurrentTransfers: ffi.maxConcurrentTransfers,
bandwidthLimitKbps: ffi.bandwidthLimitKbps.toInt(),
excludedPaths: ffi.excludedPaths,
maxWorkers: ffi.maxWorkers,
dataDir: ffi.dataDir,
clientId: ffi.clientId,
logLevel: ffi.logLevel,
);
}
}
+92
View File
@@ -0,0 +1,92 @@
import '../../src/rust/api/ffi_types.dart' as ffi;
sealed class SyncEventModel {}
class SyncStateChanged extends SyncEventModel {
final String newState;
SyncStateChanged(this.newState);
}
class SyncProgress extends SyncEventModel {
final int synced;
final int total;
final String currentFile;
SyncProgress(this.synced, this.total, this.currentFile);
}
class SyncFileUploaded extends SyncEventModel {
final String localPath;
final String remoteUri;
SyncFileUploaded(this.localPath, this.remoteUri);
}
class SyncFileDownloaded extends SyncEventModel {
final String localPath;
final String remoteUri;
SyncFileDownloaded(this.localPath, this.remoteUri);
}
class SyncConflictDetected extends SyncEventModel {
final String localPath;
final String conflictType;
SyncConflictDetected(this.localPath, this.conflictType);
}
class SyncError extends SyncEventModel {
final String message;
final bool recoverable;
SyncError(this.message, this.recoverable);
}
class SyncTokenExpired extends SyncEventModel {}
class SyncDiskSpaceWarning extends SyncEventModel {
final int availableMb;
SyncDiskSpaceWarning(this.availableMb);
}
class SyncInitialSyncComplete extends SyncEventModel {
final SyncSummaryModel summary;
SyncInitialSyncComplete(this.summary);
}
class SyncSummaryModel {
final int uploaded;
final int downloaded;
final int renamed;
final int moved;
final int conflicts;
final int failed;
final int skipped;
final int deletedLocal;
final int deletedRemote;
final int durationMs;
const SyncSummaryModel({
this.uploaded = 0,
this.downloaded = 0,
this.renamed = 0,
this.moved = 0,
this.conflicts = 0,
this.failed = 0,
this.skipped = 0,
this.deletedLocal = 0,
this.deletedRemote = 0,
this.durationMs = 0,
});
static SyncSummaryModel fromFfi(ffi.SyncSummaryFfi f) {
return SyncSummaryModel(
uploaded: f.uploaded,
downloaded: f.downloaded,
renamed: f.renamed,
moved: f.moved,
conflicts: f.conflicts,
failed: f.failed,
skipped: f.skipped,
deletedLocal: f.deletedLocal,
deletedRemote: f.deletedRemote,
durationMs: f.durationMs.toInt(),
);
}
}
+48
View File
@@ -0,0 +1,48 @@
import '../../src/rust/api/ffi_types.dart' as ffi;
class SyncStatusModel {
final String state;
final int syncedFiles;
final int totalFiles;
final int uploadingCount;
final int downloadingCount;
final int conflictCount;
final int errorCount;
final String? lastSyncTime;
final String? errorMessage;
const SyncStatusModel({
this.state = 'idle',
this.syncedFiles = 0,
this.totalFiles = 0,
this.uploadingCount = 0,
this.downloadingCount = 0,
this.conflictCount = 0,
this.errorCount = 0,
this.lastSyncTime,
this.errorMessage,
});
bool get isIdle => state == 'idle';
bool get isInitializing => state == 'initializing';
bool get isInitialSync => state == 'initialSync';
bool get isContinuous => state == 'continuous';
bool get isPaused => state == 'paused';
bool get isError => state == 'error';
bool get isStopped => state == 'stopped';
bool get isActive => isInitializing || isInitialSync || isContinuous;
static SyncStatusModel fromFfi(ffi.SyncStatusFfi f) {
return SyncStatusModel(
state: f.state,
syncedFiles: f.syncedFiles.toInt(),
totalFiles: f.totalFiles.toInt(),
uploadingCount: f.uploadingCount,
downloadingCount: f.downloadingCount,
conflictCount: f.conflictCount,
errorCount: f.errorCount,
lastSyncTime: f.lastSyncTime,
errorMessage: f.errorMessage,
);
}
}
+96
View File
@@ -0,0 +1,96 @@
class SyncTaskModel {
final String id;
final String trigger;
final int totalCount;
final int completedCount;
final int failedCount;
final String status;
final String createdAt;
final String updatedAt;
final String? finishedAt;
const SyncTaskModel({
required this.id,
required this.trigger,
required this.totalCount,
required this.completedCount,
required this.failedCount,
required this.status,
required this.createdAt,
required this.updatedAt,
this.finishedAt,
});
String get shortId => id.length > 8 ? id.substring(0, 8) : id;
String get triggerLabel => switch (trigger) {
'initial_sync' => '初始同步',
'continuous' => '持续同步',
'manual' => '手动同步',
_ => trigger,
};
String get statusLabel => switch (status) {
'pending' => '等待中',
'running' => '执行中',
'completed' => '已完成',
'failed' => '失败',
'cancelled' => '已取消',
_ => status,
};
double get progress =>
totalCount > 0 ? completedCount / totalCount : 0.0;
}
class SyncTaskItemModel {
final int id;
final String taskId;
final String relativePath;
final String actionType;
final String status;
final int fileSize;
final String? errorMessage;
final String createdAt;
final String updatedAt;
const SyncTaskItemModel({
required this.id,
required this.taskId,
required this.relativePath,
required this.actionType,
required this.status,
required this.fileSize,
this.errorMessage,
required this.createdAt,
required this.updatedAt,
});
String get actionLabel => switch (actionType) {
'upload' => '上传',
'download' => '下载',
'delete_local' => '删本地',
'delete_remote' => '删远程',
'rename' => '重命名',
'move' => '移动',
'mkdir_remote' => '创建远程目录',
'mkdir_local' => '创建本地目录',
'conflict_resolve' => '冲突解决',
'create_placeholder' => '创建占位符',
_ => actionType,
};
String get statusLabel => switch (status) {
'pending' => '未开始',
'running' => '进行中',
'completed' => '已完成',
'failed' => '失败',
'skipped' => '跳过',
_ => status,
};
String get filename {
final parts = relativePath.split('/');
return parts.isNotEmpty ? parts.last : relativePath;
}
}