123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602 |
- import 'dart:async';
- import 'dart:convert';
- import 'package:flutter/material.dart';
- import 'package:flutter_hbb/common.dart';
- import 'package:flutter_hbb/common/widgets/dialog.dart';
- import 'package:flutter_hbb/utils/event_loop.dart';
- import 'package:get/get.dart';
- import 'package:path/path.dart' as path;
- import 'package:flutter_hbb/web/dummy.dart'
- if (dart.library.html) 'package:flutter_hbb/web/web_unique.dart';
- import '../consts.dart';
- import 'model.dart';
- import 'platform_model.dart';
- enum SortBy {
- name,
- type,
- modified,
- size;
- @override
- String toString() {
- final str = this.name.toString();
- return "${str[0].toUpperCase()}${str.substring(1)}";
- }
- }
- class JobID {
- int _count = 0;
- int next() {
- _count++;
- return _count;
- }
- }
- typedef GetSessionID = SessionID Function();
- typedef GetDialogManager = OverlayDialogManager? Function();
- class FileModel {
- final WeakReference<FFI> parent;
- // late final String sessionId;
- late final FileFetcher fileFetcher;
- late final JobController jobController;
- late final FileController localController;
- late final FileController remoteController;
- late final GetSessionID getSessionID;
- late final GetDialogManager getDialogManager;
- SessionID get sessionId => getSessionID();
- late final FileDialogEventLoop evtLoop;
- FileModel(this.parent) {
- getSessionID = () => parent.target!.sessionId;
- getDialogManager = () => parent.target?.dialogManager;
- fileFetcher = FileFetcher(getSessionID);
- jobController = JobController(getSessionID, getDialogManager);
- localController = FileController(
- isLocal: true,
- getSessionID: getSessionID,
- rootState: parent,
- jobController: jobController,
- fileFetcher: fileFetcher,
- getOtherSideDirectoryData: () => remoteController.directoryData());
- remoteController = FileController(
- isLocal: false,
- getSessionID: getSessionID,
- rootState: parent,
- jobController: jobController,
- fileFetcher: fileFetcher,
- getOtherSideDirectoryData: () => localController.directoryData());
- evtLoop = FileDialogEventLoop();
- }
- Future<void> onReady() async {
- await evtLoop.onReady();
- if (!isWeb) await localController.onReady();
- await remoteController.onReady();
- }
- Future<void> close() async {
- await evtLoop.close();
- parent.target?.dialogManager.dismissAll();
- await localController.close();
- await remoteController.close();
- }
- Future<void> refreshAll() async {
- if (!isWeb) await localController.refresh();
- await remoteController.refresh();
- }
- void receiveFileDir(Map<String, dynamic> evt) {
- if (evt['is_local'] == "false") {
- // init remote home, the remote connection will send one dir event when established. TODO opt
- remoteController.initDirAndHome(evt);
- }
- fileFetcher.tryCompleteTask(evt['value'], evt['is_local']);
- }
- Future<void> postOverrideFileConfirm(Map<String, dynamic> evt) async {
- evtLoop.pushEvent(
- _FileDialogEvent(WeakReference(this), FileDialogType.overwrite, evt));
- }
- Future<void> overrideFileConfirm(Map<String, dynamic> evt,
- {bool? overrideConfirm, bool skip = false}) async {
- // If `skip == true`, it means to skip this file without showing dialog.
- // Because `resp` may be null after the user operation or the last remembered operation,
- // and we should distinguish them.
- final resp = overrideConfirm ??
- (!skip
- ? await showFileConfirmDialog(translate("Overwrite"),
- "${evt['read_path']}", true, evt['is_identical'] == "true")
- : null);
- final id = int.tryParse(evt['id']) ?? 0;
- if (false == resp) {
- final jobIndex = jobController.getJob(id);
- if (jobIndex != -1) {
- await jobController.cancelJob(id);
- final job = jobController.jobTable[jobIndex];
- job.state = JobState.done;
- jobController.jobTable.refresh();
- }
- } else {
- var need_override = false;
- if (resp == null) {
- // skip
- need_override = false;
- } else {
- // overwrite
- need_override = true;
- }
- // Update the loop config.
- if (fileConfirmCheckboxRemember) {
- evtLoop.setSkip(!need_override);
- }
- await bind.sessionSetConfirmOverrideFile(
- sessionId: sessionId,
- actId: id,
- fileNum: int.parse(evt['file_num']),
- needOverride: need_override,
- remember: fileConfirmCheckboxRemember,
- isUpload: evt['is_upload'] == "true");
- }
- // Update the loop config.
- if (fileConfirmCheckboxRemember) {
- evtLoop.setOverrideConfirm(resp);
- }
- }
- bool fileConfirmCheckboxRemember = false;
- Future<bool?> showFileConfirmDialog(
- String title, String content, bool showCheckbox, bool isIdentical) async {
- fileConfirmCheckboxRemember = false;
- return await parent.target?.dialogManager.show<bool?>(
- (setState, Function(bool? v) close, context) {
- cancel() => close(false);
- submit() => close(true);
- return CustomAlertDialog(
- title: Row(
- children: [
- const Icon(Icons.warning_rounded, color: Colors.red),
- Text(title).paddingOnly(
- left: 10,
- ),
- ],
- ),
- contentBoxConstraints:
- BoxConstraints(minHeight: 100, minWidth: 400, maxWidth: 400),
- content: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- mainAxisSize: MainAxisSize.min,
- children: [
- Text(translate("This file exists, skip or overwrite this file?"),
- style: const TextStyle(fontWeight: FontWeight.bold)),
- const SizedBox(height: 5),
- Text(content),
- Offstage(
- offstage: !isIdentical,
- child: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- const SizedBox(height: 12),
- Text(translate("identical_file_tip"),
- style: const TextStyle(fontWeight: FontWeight.w500))
- ],
- ),
- ),
- showCheckbox
- ? CheckboxListTile(
- contentPadding: const EdgeInsets.all(0),
- dense: true,
- controlAffinity: ListTileControlAffinity.leading,
- title: Text(
- translate("Do this for all conflicts"),
- ),
- value: fileConfirmCheckboxRemember,
- onChanged: (v) {
- if (v == null) return;
- setState(() => fileConfirmCheckboxRemember = v);
- },
- )
- : const SizedBox.shrink()
- ]),
- actions: [
- dialogButton(
- "Cancel",
- icon: Icon(Icons.close_rounded),
- onPressed: cancel,
- isOutline: true,
- ),
- dialogButton(
- "Skip",
- icon: Icon(Icons.navigate_next_rounded),
- onPressed: () => close(null),
- isOutline: true,
- ),
- dialogButton(
- "OK",
- icon: Icon(Icons.done_rounded),
- onPressed: submit,
- ),
- ],
- onSubmit: submit,
- onCancel: cancel,
- );
- }, useAnimation: false);
- }
- void onSelectedFiles(dynamic obj) {
- localController.selectedItems.clear();
- try {
- int handleIndex = int.parse(obj['handleIndex']);
- final file = jsonDecode(obj['file']);
- var entry = Entry.fromJson(file);
- entry.path = entry.name;
- final otherSideData = remoteController.directoryData();
- final toPath = otherSideData.directory.path;
- final isWindows = otherSideData.options.isWindows;
- final showHidden = otherSideData.options.showHidden;
- final jobID = jobController.addTransferJob(entry, false);
- webSendLocalFiles(
- handleIndex: handleIndex,
- actId: jobID,
- path: entry.path,
- to: PathUtil.join(toPath, entry.name, isWindows),
- fileNum: 0,
- includeHidden: showHidden,
- isRemote: false,
- );
- } catch (e) {
- debugPrint("Failed to decode onSelectedFiles: $e");
- }
- }
- }
- class DirectoryData {
- final DirectoryOptions options;
- final FileDirectory directory;
- DirectoryData(this.directory, this.options);
- }
- class FileController {
- final bool isLocal;
- final GetSessionID getSessionID;
- SessionID get sessionId => getSessionID();
- final FileFetcher fileFetcher;
- final options = DirectoryOptions().obs;
- final directory = FileDirectory().obs;
- final history = RxList<String>.empty(growable: true);
- final sortBy = SortBy.name.obs;
- var sortAscending = true;
- final JobController jobController;
- final WeakReference<FFI> rootState;
- final DirectoryData Function() getOtherSideDirectoryData;
- late final SelectedItems selectedItems = SelectedItems(isLocal: isLocal);
- FileController(
- {required this.isLocal,
- required this.getSessionID,
- required this.rootState,
- required this.jobController,
- required this.fileFetcher,
- required this.getOtherSideDirectoryData});
- String get homePath => options.value.home;
- void set homePath(String path) => options.value.home = path;
- OverlayDialogManager? get dialogManager => rootState.target?.dialogManager;
- String get shortPath {
- final dirPath = directory.value.path;
- if (dirPath.startsWith(homePath)) {
- var path = dirPath.replaceFirst(homePath, "");
- if (path.isEmpty) return "";
- if (path[0] == "/" || path[0] == "\\") {
- // remove more '/' or '\'
- path = path.replaceFirst(path[0], "");
- }
- return path;
- } else {
- return dirPath.replaceFirst(homePath, "");
- }
- }
- DirectoryData directoryData() {
- return DirectoryData(directory.value, options.value);
- }
- Future<void> onReady() async {
- if (isLocal) {
- options.value.home = await bind.mainGetHomeDir();
- }
- options.value.showHidden = (await bind.sessionGetPeerOption(
- sessionId: sessionId,
- name: isLocal ? "local_show_hidden" : "remote_show_hidden"))
- .isNotEmpty;
- options.value.isWindows = isLocal
- ? isWindows
- : rootState.target?.ffiModel.pi.platform == kPeerPlatformWindows;
- await Future.delayed(Duration(milliseconds: 100));
- final dir = (await bind.sessionGetPeerOption(
- sessionId: sessionId, name: isLocal ? "local_dir" : "remote_dir"));
- openDirectory(dir.isEmpty ? options.value.home : dir);
- await Future.delayed(Duration(seconds: 1));
- if (directory.value.path.isEmpty) {
- openDirectory(options.value.home);
- }
- }
- Future<void> close() async {
- // save config
- Map<String, String> msgMap = {};
- msgMap[isLocal ? "local_dir" : "remote_dir"] = directory.value.path;
- msgMap[isLocal ? "local_show_hidden" : "remote_show_hidden"] =
- options.value.showHidden ? "Y" : "";
- for (final msg in msgMap.entries) {
- await bind.sessionPeerOption(
- sessionId: sessionId, name: msg.key, value: msg.value);
- }
- directory.value.clear();
- options.value.clear();
- }
- void toggleShowHidden({bool? showHidden}) {
- options.value.showHidden = showHidden ?? !options.value.showHidden;
- refresh();
- }
- void changeSortStyle(SortBy sort, {bool? isLocal, bool ascending = true}) {
- sortBy.value = sort;
- sortAscending = ascending;
- directory.update((dir) {
- dir?.changeSortStyle(sort, ascending: ascending);
- });
- }
- Future<void> refresh() async {
- await openDirectory(directory.value.path);
- }
- Future<void> openDirectory(String path, {bool isBack = false}) async {
- if (path == ".") {
- refresh();
- return;
- }
- if (path == "..") {
- goToParentDirectory();
- return;
- }
- if (!isBack) {
- pushHistory();
- }
- final showHidden = options.value.showHidden;
- final isWindows = options.value.isWindows;
- // process /C:\ -> C:\ on Windows
- if (isWindows && path.length > 1 && path[0] == '/') {
- path = path.substring(1);
- if (path[path.length - 1] != '\\') {
- path = "$path\\";
- }
- }
- try {
- final fd = await fileFetcher.fetchDirectory(path, isLocal, showHidden);
- fd.format(isWindows, sort: sortBy.value);
- directory.value = fd;
- } catch (e) {
- debugPrint("Failed to openDirectory $path: $e");
- }
- }
- void pushHistory() {
- if (history.isNotEmpty && history.last == directory.value.path) {
- return;
- }
- history.add(directory.value.path);
- }
- void goToHomeDirectory() {
- if (isLocal) {
- openDirectory(homePath);
- return;
- }
- homePath = "";
- openDirectory(homePath);
- }
- void goBack() {
- if (history.isEmpty) return;
- final path = history.removeAt(history.length - 1);
- if (path.isEmpty) return;
- if (directory.value.path == path) {
- goBack();
- return;
- }
- openDirectory(path, isBack: true);
- }
- void goToParentDirectory() {
- final isWindows = options.value.isWindows;
- final dirPath = directory.value.path;
- var parent = PathUtil.dirname(dirPath, isWindows);
- // specially for C:\, D:\, goto '/'
- if (parent == dirPath && isWindows) {
- openDirectory('/');
- return;
- }
- openDirectory(parent);
- }
- // TODO deprecated this
- void initDirAndHome(Map<String, dynamic> evt) {
- try {
- final fd = FileDirectory.fromJson(jsonDecode(evt['value']));
- fd.format(options.value.isWindows, sort: sortBy.value);
- if (fd.id > 0) {
- final jobIndex = jobController.getJob(fd.id);
- if (jobIndex != -1) {
- final job = jobController.jobTable[jobIndex];
- var totalSize = 0;
- var fileCount = fd.entries.length;
- for (var element in fd.entries) {
- totalSize += element.size;
- }
- job.totalSize = totalSize;
- job.fileCount = fileCount;
- debugPrint("update receive details: ${fd.path}");
- jobController.jobTable.refresh();
- }
- } else if (options.value.home.isEmpty) {
- options.value.home = fd.path;
- debugPrint("init remote home: ${fd.path}");
- directory.value = fd;
- }
- } catch (e) {
- debugPrint("initDirAndHome err=$e");
- }
- }
- /// sendFiles from current side (FileController.isLocal) to other side (SelectedItems).
- void sendFiles(SelectedItems items, DirectoryData otherSideData) {
- /// ignore wrong items side status
- if (items.isLocal != isLocal) {
- return;
- }
- // alias
- final isRemoteToLocal = !isLocal;
- final toPath = otherSideData.directory.path;
- final isWindows = otherSideData.options.isWindows;
- final showHidden = otherSideData.options.showHidden;
- for (var from in items.items) {
- final jobID = jobController.addTransferJob(from, isRemoteToLocal);
- bind.sessionSendFiles(
- sessionId: sessionId,
- actId: jobID,
- path: from.path,
- to: PathUtil.join(toPath, from.name, isWindows),
- fileNum: 0,
- includeHidden: showHidden,
- isRemote: isRemoteToLocal,
- isDir: from.isDirectory);
- debugPrint(
- "path: ${from.path}, toPath: $toPath, to: ${PathUtil.join(toPath, from.name, isWindows)}");
- }
- }
- bool _removeCheckboxRemember = false;
- Future<void> removeAction(SelectedItems items) async {
- _removeCheckboxRemember = false;
- if (items.isLocal != isLocal) {
- debugPrint("Failed to removeFile, wrong files");
- return;
- }
- final isWindows = options.value.isWindows;
- await Future.forEach(items.items, (Entry item) async {
- final jobID = JobController.jobID.next();
- var title = "";
- var content = "";
- late final List<Entry> entries;
- if (item.isFile) {
- title = translate("Are you sure you want to delete this file?");
- content = item.name;
- entries = [item];
- } else if (item.isDirectory) {
- title = translate("Not an empty directory");
- dialogManager?.showLoading(translate("Waiting"));
- final fd = await fileFetcher.fetchDirectoryRecursiveToRemove(
- jobID, item.path, items.isLocal, true);
- if (fd.path.isEmpty) {
- fd.path = item.path;
- }
- fd.format(isWindows);
- dialogManager?.dismissAll();
- if (fd.entries.isEmpty) {
- var deleteJobId = jobController.addDeleteDirJob(item, !isLocal, 0);
- final confirm = await showRemoveDialog(
- translate(
- "Are you sure you want to delete this empty directory?"),
- item.name,
- false);
- if (confirm == true) {
- sendRemoveEmptyDir(
- item.path,
- 0,
- deleteJobId,
- );
- } else {
- jobController.updateJobStatus(deleteJobId,
- error: "cancel", state: JobState.done);
- }
- return;
- }
- entries = fd.entries;
- } else {
- entries = [];
- }
- int deleteJobId;
- if (item.isDirectory) {
- deleteJobId =
- jobController.addDeleteDirJob(item, !isLocal, entries.length);
- } else {
- deleteJobId = jobController.addDeleteFileJob(item, !isLocal);
- }
- for (var i = 0; i < entries.length; i++) {
- final dirShow = item.isDirectory
- ? "${translate("Are you sure you want to delete the file of this directory?")}\n"
- : "";
- final count = entries.length > 1 ? "${i + 1}/${entries.length}" : "";
- content = "$dirShow\n\n${entries[i].path}".trim();
- final confirm = await showRemoveDialog(
- count.isEmpty ? title : "$title ($count)",
- content,
- item.isDirectory,
- );
- try {
- if (confirm == true) {
- sendRemoveFile(entries[i].path, i, deleteJobId);
- final res = await jobController.jobResultListener.start();
- // handle remove res;
- if (item.isDirectory &&
- res['file_num'] == (entries.length - 1).toString()) {
- sendRemoveEmptyDir(item.path, i, deleteJobId);
- }
- } else {
- jobController.updateJobStatus(deleteJobId,
- file_num: i, error: "cancel");
- }
- if (_removeCheckboxRemember) {
- if (confirm == true) {
- for (var j = i + 1; j < entries.length; j++) {
- sendRemoveFile(entries[j].path, j, deleteJobId);
- final res = await jobController.jobResultListener.start();
- if (item.isDirectory &&
- res['file_num'] == (entries.length - 1).toString()) {
- sendRemoveEmptyDir(item.path, i, deleteJobId);
- }
- }
- } else {
- jobController.updateJobStatus(deleteJobId,
- error: "cancel",
- file_num: entries.length,
- state: JobState.done);
- }
- break;
- }
- } catch (e) {
- print("remove error: $e");
- }
- }
- });
- refresh();
- }
- Future<bool?> showRemoveDialog(
- String title, String content, bool showCheckbox) async {
- return await dialogManager?.show<bool>(
- (setState, Function(bool v) close, context) {
- cancel() => close(false);
- submit() => close(true);
- return CustomAlertDialog(
- title: Row(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- const Icon(Icons.warning_rounded, color: Colors.red),
- Expanded(
- child: Text(title).paddingOnly(
- left: 10,
- ),
- ),
- ],
- ),
- contentBoxConstraints:
- BoxConstraints(minHeight: 100, minWidth: 400, maxWidth: 400),
- content: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(content),
- Text(
- translate("This is irreversible!"),
- style: const TextStyle(
- fontWeight: FontWeight.bold,
- color: Colors.red,
- ),
- ).paddingOnly(top: 20),
- showCheckbox
- ? CheckboxListTile(
- contentPadding: const EdgeInsets.all(0),
- dense: true,
- controlAffinity: ListTileControlAffinity.leading,
- title: Text(
- translate("Do this for all conflicts"),
- ),
- value: _removeCheckboxRemember,
- onChanged: (v) {
- if (v == null) return;
- setState(() => _removeCheckboxRemember = v);
- },
- )
- : const SizedBox.shrink()
- ],
- ),
- actions: [
- dialogButton(
- "Cancel",
- icon: Icon(Icons.close_rounded),
- onPressed: cancel,
- isOutline: true,
- ),
- dialogButton(
- "OK",
- icon: Icon(Icons.done_rounded),
- onPressed: submit,
- ),
- ],
- onSubmit: submit,
- onCancel: cancel,
- );
- }, useAnimation: false);
- }
- void sendRemoveFile(String path, int fileNum, int actId) {
- bind.sessionRemoveFile(
- sessionId: sessionId,
- actId: actId,
- path: path,
- isRemote: !isLocal,
- fileNum: fileNum);
- }
- void sendRemoveEmptyDir(String path, int fileNum, int actId) {
- history.removeWhere((element) => element.contains(path));
- bind.sessionRemoveAllEmptyDirs(
- sessionId: sessionId, actId: actId, path: path, isRemote: !isLocal);
- }
- Future<void> createDir(String path) async {
- bind.sessionCreateDir(
- sessionId: sessionId,
- actId: JobController.jobID.next(),
- path: path,
- isRemote: !isLocal);
- }
- Future<void> renameAction(Entry item, bool isLocal) async {
- final textEditingController = TextEditingController(text: item.name);
- String? errorText;
- dialogManager?.show((setState, close, context) {
- textEditingController.addListener(() {
- if (errorText != null) {
- setState(() {
- errorText = null;
- });
- }
- });
- submit() async {
- final newName = textEditingController.text;
- if (newName.isEmpty || newName == item.name) {
- close();
- return;
- }
- if (directory.value.entries.any((e) => e.name == newName)) {
- setState(() {
- errorText = translate("Already exists");
- });
- return;
- }
- if (!PathUtil.validName(newName, options.value.isWindows)) {
- setState(() {
- if (item.isDirectory) {
- errorText = translate("Invalid folder name");
- } else {
- errorText = translate("Invalid file name");
- }
- });
- return;
- }
- await bind.sessionRenameFile(
- sessionId: sessionId,
- actId: JobController.jobID.next(),
- path: item.path,
- newName: newName,
- isRemote: !isLocal);
- close();
- }
- return CustomAlertDialog(
- content: Column(
- children: [
- DialogTextField(
- title: '${translate('Rename')} ${item.name}',
- controller: textEditingController,
- errorText: errorText,
- ),
- ],
- ),
- actions: [
- dialogButton(
- "Cancel",
- icon: Icon(Icons.close_rounded),
- onPressed: close,
- isOutline: true,
- ),
- dialogButton(
- "OK",
- icon: Icon(Icons.done_rounded),
- onPressed: submit,
- ),
- ],
- onSubmit: submit,
- onCancel: close,
- );
- });
- }
- }
- const _kOneWayFileTransferError = 'one-way-file-transfer-tip';
- class JobController {
- static final JobID jobID = JobID();
- final jobTable = List<JobProgress>.empty(growable: true).obs;
- final jobResultListener = JobResultListener<Map<String, dynamic>>();
- final GetSessionID getSessionID;
- final GetDialogManager getDialogManager;
- SessionID get sessionId => getSessionID();
- OverlayDialogManager? get alogManager => getDialogManager();
- int _lastTimeShowMsgbox = DateTime.now().millisecondsSinceEpoch;
- JobController(this.getSessionID, this.getDialogManager);
- int getJob(int id) {
- return jobTable.indexWhere((element) => element.id == id);
- }
- // return jobID
- int addTransferJob(Entry from, bool isRemoteToLocal) {
- final jobID = JobController.jobID.next();
- jobTable.add(JobProgress()
- ..type = JobType.transfer
- ..fileName = path.basename(from.path)
- ..jobName = from.path
- ..totalSize = from.size
- ..state = JobState.inProgress
- ..id = jobID
- ..isRemoteToLocal = isRemoteToLocal);
- return jobID;
- }
- int addDeleteFileJob(Entry file, bool isRemote) {
- final jobID = JobController.jobID.next();
- jobTable.add(JobProgress()
- ..type = JobType.deleteFile
- ..fileName = path.basename(file.path)
- ..jobName = file.path
- ..totalSize = file.size
- ..state = JobState.none
- ..id = jobID
- ..isRemoteToLocal = isRemote);
- return jobID;
- }
- int addDeleteDirJob(Entry file, bool isRemote, int fileCount) {
- final jobID = JobController.jobID.next();
- jobTable.add(JobProgress()
- ..type = JobType.deleteDir
- ..fileName = path.basename(file.path)
- ..jobName = file.path
- ..fileCount = fileCount
- ..totalSize = file.size
- ..state = JobState.none
- ..id = jobID
- ..isRemoteToLocal = isRemote);
- return jobID;
- }
- void tryUpdateJobProgress(Map<String, dynamic> evt) {
- try {
- int id = int.parse(evt['id']);
- // id = index + 1
- final jobIndex = getJob(id);
- if (jobIndex >= 0 && jobTable.length > jobIndex) {
- final job = jobTable[jobIndex];
- job.fileNum = int.parse(evt['file_num']);
- job.speed = double.parse(evt['speed']);
- job.finishedSize = int.parse(evt['finished_size']);
- job.recvJobRes = true;
- jobTable.refresh();
- }
- } catch (e) {
- debugPrint("Failed to tryUpdateJobProgress, evt: ${evt.toString()}");
- }
- }
- Future<bool> jobDone(Map<String, dynamic> evt) async {
- if (jobResultListener.isListening) {
- jobResultListener.complete(evt);
- // return;
- }
- int id = -1;
- int? fileNum = 0;
- double? speed = 0;
- try {
- id = int.parse(evt['id']);
- } catch (_) {}
- final jobIndex = getJob(id);
- if (jobIndex == -1) return true;
- final job = jobTable[jobIndex];
- job.recvJobRes = true;
- if (job.type == JobType.deleteFile) {
- job.state = JobState.done;
- } else if (job.type == JobType.deleteDir) {
- try {
- fileNum = int.tryParse(evt['file_num']);
- } catch (_) {}
- if (fileNum != null) {
- if (fileNum < job.fileNum) return true; // file_num can be 0 at last
- job.fileNum = fileNum;
- if (fileNum >= job.fileCount - 1) {
- job.state = JobState.done;
- }
- }
- } else {
- try {
- fileNum = int.tryParse(evt['file_num']);
- speed = double.tryParse(evt['speed']);
- } catch (_) {}
- if (fileNum != null) job.fileNum = fileNum;
- if (speed != null) job.speed = speed;
- job.state = JobState.done;
- }
- jobTable.refresh();
- if (job.type == JobType.deleteDir) {
- return job.state == JobState.done;
- } else {
- return true;
- }
- }
- void jobError(Map<String, dynamic> evt) {
- final err = evt['err'].toString();
- int jobIndex = getJob(int.parse(evt['id']));
- if (jobIndex != -1) {
- final job = jobTable[jobIndex];
- job.state = JobState.error;
- job.err = err;
- job.recvJobRes = true;
- if (job.type == JobType.transfer) {
- int? fileNum = int.tryParse(evt['file_num']);
- if (fileNum != null) job.fileNum = fileNum;
- if (err == "skipped") {
- job.state = JobState.done;
- job.finishedSize = job.totalSize;
- }
- } else if (job.type == JobType.deleteDir) {
- if (jobResultListener.isListening) {
- jobResultListener.complete(evt);
- }
- int? fileNum = int.tryParse(evt['file_num']);
- if (fileNum != null) job.fileNum = fileNum;
- } else if (job.type == JobType.deleteFile) {
- if (jobResultListener.isListening) {
- jobResultListener.complete(evt);
- }
- }
- jobTable.refresh();
- }
- if (err == _kOneWayFileTransferError) {
- if (DateTime.now().millisecondsSinceEpoch - _lastTimeShowMsgbox > 3000) {
- final dm = alogManager;
- if (dm != null) {
- _lastTimeShowMsgbox = DateTime.now().millisecondsSinceEpoch;
- msgBox(sessionId, 'custom-nocancel', 'Error', err, '', dm);
- }
- }
- }
- debugPrint("jobError $evt");
- }
- void updateJobStatus(int id,
- {int? file_num, String? error, JobState? state}) {
- final jobIndex = getJob(id);
- if (jobIndex < 0) return;
- final job = jobTable[jobIndex];
- job.recvJobRes = true;
- if (file_num != null) {
- job.fileNum = file_num;
- }
- if (error != null) {
- job.err = error;
- job.state = JobState.error;
- }
- if (state != null) {
- job.state = state;
- }
- if (job.type == JobType.deleteFile && error == null) {
- job.state = JobState.done;
- }
- jobTable.refresh();
- }
- Future<void> cancelJob(int id) async {
- await bind.sessionCancelJob(sessionId: sessionId, actId: id);
- }
- void loadLastJob(Map<String, dynamic> evt) {
- debugPrint("load last job: $evt");
- Map<String, dynamic> jobDetail = json.decode(evt['value']);
- // int id = int.parse(jobDetail['id']);
- String remote = jobDetail['remote'];
- String to = jobDetail['to'];
- bool showHidden = jobDetail['show_hidden'];
- int fileNum = jobDetail['file_num'];
- bool isRemote = jobDetail['is_remote'];
- final currJobId = JobController.jobID.next();
- String fileName = path.basename(isRemote ? remote : to);
- var jobProgress = JobProgress()
- ..type = JobType.transfer
- ..fileName = fileName
- ..jobName = isRemote ? remote : to
- ..id = currJobId
- ..isRemoteToLocal = isRemote
- ..fileNum = fileNum
- ..remote = remote
- ..to = to
- ..showHidden = showHidden
- ..state = JobState.paused;
- jobTable.add(jobProgress);
- bind.sessionAddJob(
- sessionId: sessionId,
- isRemote: isRemote,
- includeHidden: showHidden,
- actId: currJobId,
- path: isRemote ? remote : to,
- to: isRemote ? to : remote,
- fileNum: fileNum,
- );
- }
- void resumeJob(int jobId) {
- final jobIndex = getJob(jobId);
- if (jobIndex != -1) {
- final job = jobTable[jobIndex];
- bind.sessionResumeJob(
- sessionId: sessionId, actId: job.id, isRemote: job.isRemoteToLocal);
- job.state = JobState.inProgress;
- jobTable.refresh();
- } else {
- debugPrint("jobId $jobId is not exists");
- }
- }
- void updateFolderFiles(Map<String, dynamic> evt) {
- // ret: "{\"id\":1,\"num_entries\":12,\"total_size\":1264822.0}"
- Map<String, dynamic> info = json.decode(evt['info']);
- int id = info['id'];
- int num_entries = info['num_entries'];
- double total_size = info['total_size'];
- final jobIndex = getJob(id);
- if (jobIndex != -1) {
- final job = jobTable[jobIndex];
- job.fileCount = num_entries;
- job.totalSize = total_size.toInt();
- jobTable.refresh();
- }
- debugPrint("update folder files: $info");
- }
- }
- class JobResultListener<T> {
- Completer<T>? _completer;
- Timer? _timer;
- final int _timeoutSecond = 5;
- bool get isListening => _completer != null;
- clear() {
- if (_completer != null) {
- _timer?.cancel();
- _timer = null;
- _completer!.completeError("Cancel manually");
- _completer = null;
- return;
- }
- }
- Future<T> start() {
- if (_completer != null) return Future.error("Already start listen");
- _completer = Completer();
- _timer = Timer(Duration(seconds: _timeoutSecond), () {
- if (!_completer!.isCompleted) {
- _completer!.completeError("Time out");
- }
- _completer = null;
- });
- return _completer!.future;
- }
- complete(T res) {
- if (_completer != null) {
- _timer?.cancel();
- _timer = null;
- _completer!.complete(res);
- _completer = null;
- return;
- }
- }
- }
- class FileFetcher {
- // Map<String,Completer<FileDirectory>> localTasks = {}; // now we only use read local dir sync
- Map<String, Completer<FileDirectory>> remoteTasks = {};
- Map<int, Completer<FileDirectory>> readRecursiveTasks = {};
- final GetSessionID getSessionID;
- SessionID get sessionId => getSessionID();
- FileFetcher(this.getSessionID);
- Future<FileDirectory> registerReadTask(bool isLocal, String path) {
- // final jobs = isLocal?localJobs:remoteJobs; // maybe we will use read local dir async later
- final tasks = remoteTasks; // bypass now
- if (tasks.containsKey(path)) {
- throw "Failed to registerReadTask, already have same read job";
- }
- final c = Completer<FileDirectory>();
- tasks[path] = c;
- Timer(Duration(seconds: 2), () {
- tasks.remove(path);
- if (c.isCompleted) return;
- c.completeError("Failed to read dir, timeout");
- });
- return c.future;
- }
- Future<FileDirectory> registerReadRecursiveTask(int actID) {
- final tasks = readRecursiveTasks;
- if (tasks.containsKey(actID)) {
- throw "Failed to registerRemoveTask, already have same ReadRecursive job";
- }
- final c = Completer<FileDirectory>();
- tasks[actID] = c;
- Timer(Duration(seconds: 2), () {
- tasks.remove(actID);
- if (c.isCompleted) return;
- c.completeError("Failed to read dir, timeout");
- });
- return c.future;
- }
- tryCompleteTask(String? msg, String? isLocalStr) {
- if (msg == null || isLocalStr == null) return;
- late final Map<Object, Completer<FileDirectory>> tasks;
- try {
- final fd = FileDirectory.fromJson(jsonDecode(msg));
- if (fd.id > 0) {
- // fd.id > 0 is result for read recursive
- // to-do later,will be better if every fetch use ID,so that there will only one task map for read and recursive read
- tasks = readRecursiveTasks;
- final completer = tasks.remove(fd.id);
- completer?.complete(fd);
- } else if (fd.path.isNotEmpty) {
- // result for normal read dir
- // final jobs = isLocal?localJobs:remoteJobs; // maybe we will use read local dir async later
- tasks = remoteTasks; // bypass now
- final completer = tasks.remove(fd.path);
- completer?.complete(fd);
- }
- } catch (e) {
- debugPrint("tryCompleteJob err: $e");
- }
- }
- Future<FileDirectory> fetchDirectory(
- String path, bool isLocal, bool showHidden) async {
- try {
- if (isLocal) {
- final res = await bind.sessionReadLocalDirSync(
- sessionId: sessionId, path: path, showHidden: showHidden);
- final fd = FileDirectory.fromJson(jsonDecode(res));
- return fd;
- } else {
- await bind.sessionReadRemoteDir(
- sessionId: sessionId, path: path, includeHidden: showHidden);
- return registerReadTask(isLocal, path);
- }
- } catch (e) {
- return Future.error(e);
- }
- }
- Future<FileDirectory> fetchDirectoryRecursiveToRemove(
- int actID, String path, bool isLocal, bool showHidden) async {
- // TODO test Recursive is show hidden default?
- try {
- await bind.sessionReadDirToRemoveRecursive(
- sessionId: sessionId,
- actId: actID,
- path: path,
- isRemote: !isLocal,
- showHidden: showHidden);
- return registerReadRecursiveTask(actID);
- } catch (e) {
- return Future.error(e);
- }
- }
- }
- class FileDirectory {
- List<Entry> entries = [];
- int id = 0;
- String path = "";
- FileDirectory();
- FileDirectory.fromJson(Map<String, dynamic> json) {
- id = json['id'];
- path = json['path'];
- json['entries'].forEach((v) {
- entries.add(Entry.fromJson(v));
- });
- }
- // generate full path for every entry , init sort style if need.
- format(bool isWindows, {SortBy? sort}) {
- for (var entry in entries) {
- entry.path = PathUtil.join(path, entry.name, isWindows);
- }
- if (sort != null) {
- changeSortStyle(sort);
- }
- }
- changeSortStyle(SortBy sort, {bool ascending = true}) {
- entries = _sortList(entries, sort, ascending);
- }
- clear() {
- entries = [];
- id = 0;
- path = "";
- }
- }
- class Entry {
- int entryType = 4;
- int modifiedTime = 0;
- String name = "";
- String path = "";
- int size = 0;
- Entry();
- Entry.fromJson(Map<String, dynamic> json) {
- entryType = json['entry_type'];
- modifiedTime = json['modified_time'];
- name = json['name'];
- size = json['size'];
- }
- bool get isFile => entryType > 3;
- bool get isDirectory => entryType < 3;
- bool get isDrive => entryType == 3;
- DateTime lastModified() {
- return DateTime.fromMillisecondsSinceEpoch(modifiedTime * 1000);
- }
- }
- enum JobState { none, inProgress, done, error, paused }
- extension JobStateDisplay on JobState {
- String display() {
- switch (this) {
- case JobState.none:
- return translate("Waiting");
- case JobState.inProgress:
- return translate("Transfer file");
- case JobState.done:
- return translate("Finished");
- case JobState.error:
- return translate("Error");
- default:
- return "";
- }
- }
- }
- enum JobType { none, transfer, deleteFile, deleteDir }
- class JobProgress {
- JobType type = JobType.none;
- JobState state = JobState.none;
- var recvJobRes = false;
- var id = 0;
- var fileNum = 0;
- var speed = 0.0;
- var finishedSize = 0;
- var totalSize = 0;
- var fileCount = 0;
- // [isRemote == true] means [remote -> local]
- // var isRemote = false;
- // to-do use enum
- var isRemoteToLocal = false;
- var jobName = "";
- var fileName = "";
- var remote = "";
- var to = "";
- var showHidden = false;
- var err = "";
- int lastTransferredSize = 0;
- clear() {
- type = JobType.none;
- state = JobState.none;
- recvJobRes = false;
- id = 0;
- fileNum = 0;
- speed = 0;
- finishedSize = 0;
- jobName = "";
- fileName = "";
- fileCount = 0;
- remote = "";
- to = "";
- err = "";
- }
- String display() {
- if (type == JobType.transfer) {
- if (state == JobState.done && err == "skipped") {
- return translate("Skipped");
- }
- } else if (type == JobType.deleteFile) {
- if (err == "cancel") {
- return translate("Cancel");
- }
- }
- return state.display();
- }
- String getStatus() {
- int handledFileCount = recvJobRes ? fileNum + 1 : fileNum;
- if (handledFileCount >= fileCount) {
- handledFileCount = fileCount;
- }
- if (state == JobState.done) {
- handledFileCount = fileCount;
- finishedSize = totalSize;
- }
- final filesStr = "$handledFileCount/$fileCount files";
- final sizeStr = totalSize > 0 ? readableFileSize(totalSize.toDouble()) : "";
- final sizePercentStr = totalSize > 0 && finishedSize > 0
- ? "${readableFileSize(finishedSize.toDouble())} / ${readableFileSize(totalSize.toDouble())}"
- : "";
- if (type == JobType.deleteFile) {
- return display();
- } else if (type == JobType.deleteDir) {
- var res = '';
- if (state == JobState.done || state == JobState.error) {
- res = display();
- }
- if (filesStr.isNotEmpty) {
- if (res.isNotEmpty) {
- res += " ";
- }
- res += filesStr;
- }
- if (sizeStr.isNotEmpty) {
- if (res.isNotEmpty) {
- res += ", ";
- }
- res += sizeStr;
- }
- return res;
- } else if (type == JobType.transfer) {
- var res = "";
- if (state != JobState.inProgress && state != JobState.none) {
- res += display();
- }
- if (filesStr.isNotEmpty) {
- if (res.isNotEmpty) {
- res += ", ";
- }
- res += filesStr;
- }
- if (sizeStr.isNotEmpty && state != JobState.inProgress) {
- if (res.isNotEmpty) {
- res += ", ";
- }
- res += sizeStr;
- }
- if (sizePercentStr.isNotEmpty && state == JobState.inProgress) {
- if (res.isNotEmpty) {
- res += ", ";
- }
- res += sizePercentStr;
- }
- return res;
- }
- return '';
- }
- }
- class _PathStat {
- final String path;
- final DateTime dateTime;
- _PathStat(this.path, this.dateTime);
- }
- class PathUtil {
- static final windowsContext = path.Context(style: path.Style.windows);
- static final posixContext = path.Context(style: path.Style.posix);
- static String join(String path1, String path2, bool isWindows) {
- final pathUtil = isWindows ? windowsContext : posixContext;
- return pathUtil.join(path1, path2);
- }
- static List<String> split(String path, bool isWindows) {
- final pathUtil = isWindows ? windowsContext : posixContext;
- return pathUtil.split(path);
- }
- static String dirname(String path, bool isWindows) {
- final pathUtil = isWindows ? windowsContext : posixContext;
- return pathUtil.dirname(path);
- }
- static bool validName(String name, bool isWindows) {
- final unixFileNamePattern = RegExp(r'^[^/\0]+$');
- final windowsFileNamePattern = RegExp(r'^[^<>:"/\\|?*]+$');
- final reg = isWindows ? windowsFileNamePattern : unixFileNamePattern;
- return reg.hasMatch(name);
- }
- }
- class DirectoryOptions {
- String home;
- bool showHidden;
- bool isWindows;
- DirectoryOptions(
- {this.home = "", this.showHidden = false, this.isWindows = false});
- clear() {
- home = "";
- showHidden = false;
- isWindows = false;
- }
- }
- class SelectedItems {
- final bool isLocal;
- final items = RxList<Entry>.empty(growable: true);
- SelectedItems({required this.isLocal});
- void add(Entry e) {
- if (e.isDrive) return;
- if (!items.contains(e)) {
- items.add(e);
- }
- }
- void remove(Entry e) {
- items.remove(e);
- }
- void clear() {
- items.clear();
- }
- void selectAll(List<Entry> entries) {
- items.clear();
- items.addAll(entries);
- }
- static bool valid(RxList<Entry> items) {
- if (items.isNotEmpty) {
- // exclude DirDrive type
- return items.any((item) => !item.isDrive);
- }
- return false;
- }
- }
- // edited from [https://github.com/DevsOnFlutter/file_manager/blob/c1bf7f0225b15bcb86eba602c60acd5c4da90dd8/lib/file_manager.dart#L22]
- List<Entry> _sortList(List<Entry> list, SortBy sortType, bool ascending) {
- if (sortType == SortBy.name) {
- // making list of only folders.
- final dirs = list
- .where((element) => element.isDirectory || element.isDrive)
- .toList();
- // sorting folder list by name.
- dirs.sort((a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase()));
- // making list of only flies.
- final files = list.where((element) => element.isFile).toList();
- // sorting files list by name.
- files.sort((a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase()));
- // first folders will go to list (if available) then files will go to list.
- return ascending
- ? [...dirs, ...files]
- : [...dirs.reversed.toList(), ...files.reversed.toList()];
- } else if (sortType == SortBy.modified) {
- // making the list of Path & DateTime
- List<_PathStat> pathStat = [];
- for (Entry e in list) {
- pathStat.add(_PathStat(e.name, e.lastModified()));
- }
- // sort _pathStat according to date
- pathStat.sort((b, a) => a.dateTime.compareTo(b.dateTime));
- // sorting [list] according to [_pathStat]
- list.sort((a, b) => pathStat
- .indexWhere((element) => element.path == a.name)
- .compareTo(pathStat.indexWhere((element) => element.path == b.name)));
- return ascending ? list : list.reversed.toList();
- } else if (sortType == SortBy.type) {
- // making list of only folders.
- final dirs = list.where((element) => element.isDirectory).toList();
- // sorting folders by name.
- dirs.sort((a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase()));
- // making the list of files
- final files = list.where((element) => element.isFile).toList();
- // sorting files list by extension.
- files.sort((a, b) => a.name
- .toLowerCase()
- .split('.')
- .last
- .compareTo(b.name.toLowerCase().split('.').last));
- return ascending
- ? [...dirs, ...files]
- : [...dirs.reversed.toList(), ...files.reversed.toList()];
- } else if (sortType == SortBy.size) {
- // create list of path and size
- Map<String, int> sizeMap = {};
- for (Entry e in list) {
- sizeMap[e.name] = e.size;
- }
- // making list of only folders.
- final dirs = list.where((element) => element.isDirectory).toList();
- // sorting folder list by name.
- dirs.sort((a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase()));
- // making list of only flies.
- final files = list.where((element) => element.isFile).toList();
- // creating sorted list of [_sizeMapList] by size.
- final List<MapEntry<String, int>> sizeMapList = sizeMap.entries.toList();
- sizeMapList.sort((b, a) => a.value.compareTo(b.value));
- // sort [list] according to [_sizeMapList]
- files.sort((a, b) => sizeMapList
- .indexWhere((element) => element.key == a.name)
- .compareTo(sizeMapList.indexWhere((element) => element.key == b.name)));
- return ascending
- ? [...dirs, ...files]
- : [...dirs.reversed.toList(), ...files.reversed.toList()];
- }
- return [];
- }
- /// Define a general queue which can accepts different dialog type.
- ///
- /// [Visibility]
- /// The `_FileDialogType` and `_DialogEvent` are invisible for other models.
- enum FileDialogType { overwrite, unknown }
- class _FileDialogEvent extends BaseEvent<FileDialogType, Map<String, dynamic>> {
- WeakReference<FileModel> fileModel;
- bool? _overrideConfirm;
- bool _skip = false;
- _FileDialogEvent(this.fileModel, super.type, super.data);
- void setOverrideConfirm(bool? confirm) {
- _overrideConfirm = confirm;
- }
- void setSkip(bool skip) {
- _skip = skip;
- }
- @override
- EventCallback<Map<String, dynamic>>? findCallback(FileDialogType type) {
- final model = fileModel.target;
- if (model == null) {
- return null;
- }
- switch (type) {
- case FileDialogType.overwrite:
- return (data) async {
- return await model.overrideFileConfirm(data,
- overrideConfirm: _overrideConfirm, skip: _skip);
- };
- default:
- debugPrint("Unknown event type: $type with $data");
- return null;
- }
- }
- }
- class FileDialogEventLoop
- extends BaseEventLoop<FileDialogType, Map<String, dynamic>> {
- bool? _overrideConfirm;
- bool _skip = false;
- @override
- Future<void> onPreConsume(
- BaseEvent<FileDialogType, Map<String, dynamic>> evt) async {
- var event = evt as _FileDialogEvent;
- event.setOverrideConfirm(_overrideConfirm);
- event.setSkip(_skip);
- debugPrint(
- "FileDialogEventLoop: consuming<jobId: ${evt.data['id']} overrideConfirm: $_overrideConfirm, skip: $_skip>");
- }
- @override
- Future<void> onEventsClear() {
- _overrideConfirm = null;
- _skip = false;
- return super.onEventsClear();
- }
- void setOverrideConfirm(bool? confirm) {
- _overrideConfirm = confirm;
- }
- void setSkip(bool skip) {
- _skip = skip;
- }
- }
|