From 88f722c8c22fe2e1e04935a8db1f184a4d346448 Mon Sep 17 00:00:00 2001 From: csf Date: Mon, 7 Mar 2022 22:54:34 +0800 Subject: [PATCH] add local/remote file manager --- lib/common.dart | 3 +- lib/main.dart | 2 +- lib/models/file_model.dart | 407 +++++++++++++++++++++++++++++++ lib/models/model.dart | 134 +++++----- lib/pages/connection_page.dart | 48 ++-- lib/pages/file_manager_page.dart | 167 ++++++++++--- lib/pages/remote_page.dart | 77 +----- lib/widgets/dialog.dart | 77 ++++++ 8 files changed, 712 insertions(+), 203 deletions(-) create mode 100644 lib/models/file_model.dart create mode 100644 lib/widgets/dialog.dart diff --git a/lib/common.dart b/lib/common.dart index 398e85076..6d9b69b76 100644 --- a/lib/common.dart +++ b/lib/common.dart @@ -1,9 +1,10 @@ import 'package:flutter/material.dart'; import 'dart:async'; import 'package:flutter_easyloading/flutter_easyloading.dart'; -import 'package:flutter_hbb/main.dart'; import 'package:tuple/tuple.dart'; +final globalKey = GlobalKey(); + typedef F = String Function(String); typedef FMethod = String Function(String, dynamic); diff --git a/lib/main.dart b/lib/main.dart index e82bf411c..9b893c3af 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -4,6 +4,7 @@ import 'package:provider/provider.dart'; import 'package:firebase_analytics/firebase_analytics.dart'; import 'package:firebase_analytics/observer.dart'; import 'package:firebase_core/firebase_core.dart'; +import 'common.dart'; import 'models/model.dart'; import 'pages/home_page.dart'; import 'pages/server_page.dart'; @@ -16,7 +17,6 @@ Future main() async { runApp(App()); } -final globalKey = GlobalKey(); class App extends StatelessWidget { @override diff --git a/lib/models/file_model.dart b/lib/models/file_model.dart new file mode 100644 index 000000000..e374dd362 --- /dev/null +++ b/lib/models/file_model.dart @@ -0,0 +1,407 @@ +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:flutter/material.dart'; + +import 'model.dart'; + + +// enum FileType { +// Dir = 0, +// DirLink = 2, +// DirDrive = 3, +// File = 4, +// FileLink = 5, +// } + +class FileDirectory { + // List entries = []; + List entries = []; + int id = 0; + String path = ""; + + FileDirectory(); + + FileDirectory.fromJson(Map json) { + id = json['id']; + path = json['path']; + if (json['entries'] != null) { + entries = []; + json['entries'].forEach((v) { + entries.add(new Entry.fromJson(v).toFileSystemEntity(path)); + }); + } + } + + // Map toJson() { + // final Map data = new Map(); + // data['entries'] = this.entries.map((v) => v.toJson()).toList(); + // data['id'] = this.id; + // data['path'] = this.path; + // return data; + // } + + clear(){ + entries = []; + id = 0; + path = ""; + } +} + +class Entry { + int entryType = 4; + int modifiedTime = 0; + String name = ""; + int size = 0; + + Entry(); + + Entry.fromJson(Map json) { + entryType = json['entry_type']; + modifiedTime = json['modified_time']; + name = json['name']; + size = json['size']; + } + + FileSystemEntity toFileSystemEntity(String parentPath){ + // is dir + if(entryType<=3){ + return RemoteDir("$parentPath/$name"); + }else { + return RemoteFile("$parentPath/$name",modifiedTime,size); + } + } + + Map toJson() { + final Map data = new Map(); + data['entry_type'] = this.entryType; + data['modified_time'] = this.modifiedTime; + data['name'] = this.name; + data['size'] = this.size; + return data; + } +} + + +// TODO 使用工厂单例模式 + +class RemoteFileModel extends ChangeNotifier{ + + FileDirectory _currentRemoteDir = FileDirectory(); + + FileDirectory get currentRemoteDir => _currentRemoteDir; + + tryUpdateRemoteDir(String fd){ + debugPrint("tryUpdateRemoteDir:$fd"); + try{ + final fileDir = FileDirectory.fromJson(jsonDecode(fd)); + _currentRemoteDir = fileDir; + debugPrint("_currentRemoteDir:${_currentRemoteDir.path}"); + notifyListeners(); + }catch(e){ + debugPrint("tryUpdateRemoteDir fail:$fd"); + } + } + + goToParentDirectory(){ + var parentPath = ""; + if(_currentRemoteDir.path == ""){ + parentPath = ""; + }else{ + parentPath = Directory(_currentRemoteDir.path).parent.path; + } + FFI.setByName("read_remote_dir", parentPath); + } + @override + void dispose() { + _currentRemoteDir.clear(); + super.dispose(); + } +} + + +class RemoteDir extends FileSystemEntity implements Directory{ + + // int entryType = 4; + // int modifiedTime = 0; + // String name = ""; + // int size = 0; + + + String path; + + RemoteDir(this.path); + + + @override + // TODO: implement absolute + Directory get absolute => throw UnimplementedError(); + + @override + Future create({bool recursive = false}) { + // TODO: implement create + throw UnimplementedError(); + } + + @override + void createSync({bool recursive = false}) { + // TODO: implement createSync + } + + @override + Future createTemp([String? prefix]) { + // TODO: implement createTemp + throw UnimplementedError(); + } + + @override + Directory createTempSync([String? prefix]) { + // TODO: implement createTempSync + throw UnimplementedError(); + } + + @override + Future exists() { + // TODO: implement exists + throw UnimplementedError(); + } + + @override + bool existsSync() { + // TODO: implement existsSync + throw UnimplementedError(); + } + + @override + Stream list({bool recursive = false, bool followLinks = true}) { + // TODO: implement list + throw UnimplementedError(); + } + + @override + List listSync({bool recursive = false, bool followLinks = true}) { + // TODO: implement listSync + throw UnimplementedError(); + } + + @override + Future rename(String newPath) { + // TODO: implement rename + throw UnimplementedError(); + } + + @override + Directory renameSync(String newPath) { + // TODO: implement renameSync + throw UnimplementedError(); + } + +} + +class RemoteFile extends FileSystemEntity implements File { + + // int entryType = 4; + // int modifiedTime = 0; + // String name = ""; + // int size = 0; + + RemoteFile(this.path,this.modifiedTime,this.size); + + var path; + var modifiedTime; + var size; + + + @override + DateTime lastModifiedSync() { + return DateTime.fromMillisecondsSinceEpoch(modifiedTime * 1000); + } + + @override + int lengthSync() { + return size; + } + + // *************************** + + @override + Future length() { + // TODO: implement length + throw UnimplementedError(); + } + + @override + Future lastModified() { + // TODO: implement lastModified + throw UnimplementedError(); + } + + @override + Future copy(String newPath) { + // TODO: implement copy + throw UnimplementedError(); + } + + @override + File copySync(String newPath) { + // TODO: implement copySync + throw UnimplementedError(); + } + + @override + Future create({bool recursive = false}) { + // TODO: implement create + throw UnimplementedError(); + } + + @override + void createSync({bool recursive = false}) { + // TODO: implement createSync + } + + @override + Future exists() { + // TODO: implement exists + throw UnimplementedError(); + } + + @override + bool existsSync() { + // TODO: implement existsSync + throw UnimplementedError(); + } + + @override + Future lastAccessed() { + // TODO: implement lastAccessed + throw UnimplementedError(); + } + + @override + DateTime lastAccessedSync() { + // TODO: implement lastAccessedSync + throw UnimplementedError(); + } + + @override + Future open({FileMode mode = FileMode.read}) { + // TODO: implement open + throw UnimplementedError(); + } + + @override + Stream> openRead([int? start, int? end]) { + // TODO: implement openRead + throw UnimplementedError(); + } + + @override + RandomAccessFile openSync({FileMode mode = FileMode.read}) { + // TODO: implement openSync + throw UnimplementedError(); + } + + @override + IOSink openWrite({FileMode mode = FileMode.write, Encoding encoding = utf8}) { + // TODO: implement openWrite + throw UnimplementedError(); + } + + @override + Future readAsBytes() { + // TODO: implement readAsBytes + throw UnimplementedError(); + } + + @override + Uint8List readAsBytesSync() { + // TODO: implement readAsBytesSync + throw UnimplementedError(); + } + + @override + Future> readAsLines({Encoding encoding = utf8}) { + // TODO: implement readAsLines + throw UnimplementedError(); + } + + @override + List readAsLinesSync({Encoding encoding = utf8}) { + // TODO: implement readAsLinesSync + throw UnimplementedError(); + } + + @override + Future readAsString({Encoding encoding = utf8}) { + // TODO: implement readAsString + throw UnimplementedError(); + } + + @override + String readAsStringSync({Encoding encoding = utf8}) { + // TODO: implement readAsStringSync + throw UnimplementedError(); + } + + @override + Future rename(String newPath) { + // TODO: implement rename + throw UnimplementedError(); + } + + @override + File renameSync(String newPath) { + // TODO: implement renameSync + throw UnimplementedError(); + } + + @override + Future setLastAccessed(DateTime time) { + // TODO: implement setLastAccessed + throw UnimplementedError(); + } + + @override + void setLastAccessedSync(DateTime time) { + // TODO: implement setLastAccessedSync + } + + @override + Future setLastModified(DateTime time) { + // TODO: implement setLastModified + throw UnimplementedError(); + } + + @override + void setLastModifiedSync(DateTime time) { + // TODO: implement setLastModifiedSync + } + + @override + Future writeAsBytes(List bytes, {FileMode mode = FileMode.write, bool flush = false}) { + // TODO: implement writeAsBytes + throw UnimplementedError(); + } + + @override + void writeAsBytesSync(List bytes, {FileMode mode = FileMode.write, bool flush = false}) { + // TODO: implement writeAsBytesSync + } + + @override + Future writeAsString(String contents, {FileMode mode = FileMode.write, Encoding encoding = utf8, bool flush = false}) { + // TODO: implement writeAsString + throw UnimplementedError(); + } + + @override + void writeAsStringSync(String contents, {FileMode mode = FileMode.write, Encoding encoding = utf8, bool flush = false}) { + // TODO: implement writeAsStringSync + } + + @override + // TODO: implement absolute + File get absolute => throw UnimplementedError(); + +} \ No newline at end of file diff --git a/lib/models/model.dart b/lib/models/model.dart index 3f50be145..88094c3bb 100644 --- a/lib/models/model.dart +++ b/lib/models/model.dart @@ -1,6 +1,7 @@ import 'package:flutter/services.dart'; import 'package:flutter_easyloading/flutter_easyloading.dart'; import 'package:flutter_hbb/models/chat_model.dart'; +import 'package:flutter_hbb/models/file_model.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'dart:math'; import 'dart:convert'; @@ -44,7 +45,7 @@ class FfiModel with ChangeNotifier { FfiModel() { Translator.call = translate; clear(); - () async { + () async { await PlatformFFI.init(); _initialized = true; print("FFI initialized"); @@ -98,20 +99,22 @@ class FfiModel with ChangeNotifier { _inputBlocked = false; _permissions.clear(); } - void update(String id, + + void update( + String id, BuildContext context, void Function( - Map evt, - String id, - ) - handleMsgbox) { + Map evt, + String id, + ) + handleMsgBox) { var pos; for (;;) { var evt = FFI.popEvent(); if (evt == null) break; var name = evt['name']; if (name == 'msgbox') { - handleMsgbox(evt, id); + handleMsgBox(evt, id); } else if (name == 'peer_info') { handlePeerInfo(evt, context); } else if (name == 'connection_ready') { @@ -129,9 +132,10 @@ class FfiModel with ChangeNotifier { Clipboard.setData(ClipboardData(text: evt['content'])); } else if (name == 'permission') { FFI.ffiModel.updatePermission(evt); - } else if (name == 'chat'){ - // FFI.setByName("chat",msg); - FFI.chatModel.receive(evt['text']??""); + } else if (name == 'chat') { + FFI.chatModel.receive(evt['text'] ?? ""); + } else if (name == 'file_dir') { + FFI.remoteFileModel.tryUpdateRemoteDir(evt['value'] ?? ""); } } if (pos != null) FFI.cursorModel.updateCursorPosition(pos); @@ -146,17 +150,17 @@ class FfiModel with ChangeNotifier { final pid = FFI.id; ui.decodeImageFromPixels( rgba, _display.width, _display.height, ui.PixelFormat.bgra8888, - (image) { - PlatformFFI.clearRgbaFrame(); - _decoding = false; - if (FFI.id != pid) return; - try { - // my throw exception, because the listener maybe already dispose - FFI.imageModel.update(image); - } catch (e) { - print('update image: $e'); - } - }); + (image) { + PlatformFFI.clearRgbaFrame(); + _decoding = false; + if (FFI.id != pid) return; + try { + // my throw exception, because the listener maybe already dispose + FFI.imageModel.update(image); + } catch (e) { + print('update image: $e'); + } + }); } } } @@ -213,9 +217,7 @@ class ImageModel with ChangeNotifier { if (isDesktop) { FFI.canvasModel.updateViewStyle(); } else { - final size = MediaQueryData - .fromWindow(ui.window) - .size; + final size = MediaQueryData.fromWindow(ui.window).size; final xscale = size.width / image.width; final yscale = size.height / image.height; FFI.canvasModel.scale = max(xscale, yscale); @@ -228,9 +230,7 @@ class ImageModel with ChangeNotifier { double get maxScale { if (_image == null) return 1.0; - final size = MediaQueryData - .fromWindow(ui.window) - .size; + final size = MediaQueryData.fromWindow(ui.window).size; final xscale = size.width / _image!.width; final yscale = size.height / _image!.height; return max(1.0, max(xscale, yscale)); @@ -238,9 +238,7 @@ class ImageModel with ChangeNotifier { double get minScale { if (_image == null) return 1.0; - final size = MediaQueryData - .fromWindow(ui.window) - .size; + final size = MediaQueryData.fromWindow(ui.window).size; final xscale = size.width / _image!.width; final yscale = size.height / _image!.height; return min(xscale, yscale); @@ -262,9 +260,7 @@ class CanvasModel with ChangeNotifier { void updateViewStyle() { final s = FFI.getByName('peer_option', 'view-style'); - final size = MediaQueryData - .fromWindow(ui.window) - .size; + final size = MediaQueryData.fromWindow(ui.window).size; final s1 = size.width / FFI.ffiModel.display.width; final s2 = size.height / FFI.ffiModel.display.height; if (s == 'shrink') { @@ -293,9 +289,7 @@ class CanvasModel with ChangeNotifier { } void moveDesktopMouse(double x, double y) { - final size = MediaQueryData - .fromWindow(ui.window) - .size; + final size = MediaQueryData.fromWindow(ui.window).size; final dw = FFI.ffiModel.display.width * _scale; final dh = FFI.ffiModel.display.height * _scale; var dxOffset = 0; @@ -390,9 +384,7 @@ class CursorModel with ChangeNotifier { // remote physical display coordinate Rect getVisibleRect() { - final size = MediaQueryData - .fromWindow(ui.window) - .size; + final size = MediaQueryData.fromWindow(ui.window).size; final xoffset = FFI.canvasModel.x; final yoffset = FFI.canvasModel.y; final scale = FFI.canvasModel.scale; @@ -418,7 +410,7 @@ class CursorModel with ChangeNotifier { FFI.tap(button); } - void move(double x, double y){ + void move(double x, double y) { moveLocal(x, y); FFI.moveMouse(_x, _y); } @@ -440,7 +432,7 @@ class CursorModel with ChangeNotifier { notifyListeners(); } - void updatePan(double dx, double dy,bool touchMode) { + void updatePan(double dx, double dy, bool touchMode) { if (FFI.imageModel.image == null) return; if (touchMode) { if (true) { @@ -528,17 +520,17 @@ class CursorModel with ChangeNotifier { final rgba = Uint8List.fromList(colors.map((s) => s as int).toList()); var pid = FFI.id; ui.decodeImageFromPixels(rgba, width, height, ui.PixelFormat.rgba8888, - (image) { - if (FFI.id != pid) return; - _image = image; - _images[id] = Tuple3(image, _hotx, _hoty); - try { - // my throw exception, because the listener maybe already dispose - notifyListeners(); - } catch (e) { - print('notify cursor: $e'); - } - }); + (image) { + if (FFI.id != pid) return; + _image = image; + _images[id] = Tuple3(image, _hotx, _hoty); + try { + // my throw exception, because the listener maybe already dispose + notifyListeners(); + } catch (e) { + print('notify cursor: $e'); + } + }); } void updateCursorId(Map evt) { @@ -567,8 +559,8 @@ class CursorModel with ChangeNotifier { notifyListeners(); } - void updateDisplayOriginWithCursor(double x, double y, double xCursor, - double yCursor) { + void updateDisplayOriginWithCursor( + double x, double y, double xCursor, double yCursor) { _displayOriginX = x; _displayOriginY = y; _x = xCursor; @@ -675,13 +667,9 @@ class ServerModel with ChangeNotifier { } } -enum MouseButtons { - left, - right, - wheel -} +enum MouseButtons { left, right, wheel } -extension ToString on MouseButtons{ +extension ToString on MouseButtons { String get value { switch (this) { case MouseButtons.left: @@ -694,7 +682,6 @@ extension ToString on MouseButtons{ } } - class FFI { static var id = ""; static var shift = false; @@ -708,6 +695,7 @@ class FFI { static final canvasModel = CanvasModel(); static final serverModel = ServerModel(); static final chatModel = ChatModel(); + static final remoteFileModel = RemoteFileModel(); static String getId() { return getByName('remote_id'); @@ -744,8 +732,8 @@ class FFI { static void sendMouse(String type, MouseButtons button) { if (!ffiModel.keyboard()) return; - setByName( - 'send_mouse', json.encode(modify({'type': type, 'buttons': button.value}))); + setByName('send_mouse', + json.encode(modify({'type': type, 'buttons': button.value}))); } static void inputKey(String name) { @@ -767,7 +755,7 @@ class FFI { return peers .map((s) => s as List) .map((s) => - Peer.fromJson(s[0] as String, s[1] as Map)) + Peer.fromJson(s[0] as String, s[1] as Map)) .toList(); } catch (e) { print('peers(): $e'); @@ -775,8 +763,12 @@ class FFI { return []; } - static void connect(String id) { - setByName('connect', id); + static void connect(String id, {bool isFileTransfer = false}) { + if (isFileTransfer) { + setByName('connect_file_transfer', id); + } else { + setByName('connect', id); + } FFI.id = id; } @@ -804,14 +796,8 @@ class FFI { static void close() { chatModel.release(); if (FFI.imageModel.image != null && !isDesktop) { - savePreference( - id, - cursorModel.x, - cursorModel.y, - canvasModel.x, - canvasModel.y, - canvasModel.scale, - ffiModel.pi.currentDisplay); + savePreference(id, cursorModel.x, cursorModel.y, canvasModel.x, + canvasModel.y, canvasModel.scale, ffiModel.pi.currentDisplay); } id = ""; setByName('close', ''); diff --git a/lib/pages/connection_page.dart b/lib/pages/connection_page.dart index 02c3a09ba..cc37932f5 100644 --- a/lib/pages/connection_page.dart +++ b/lib/pages/connection_page.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:flutter_hbb/pages/chat_page.dart'; import 'package:flutter_hbb/pages/file_manager_page.dart'; import 'package:provider/provider.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -55,21 +54,11 @@ class _ConnectionPageState extends State { getSearchBarUI(), Container(height: 12), getPeers(), - ElevatedButton( - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (BuildContext context) => FileManagerPage(), - ), - ); - }, - child: Text("File")), - ElevatedButton( - onPressed: () { - toggleChatOverlay(); - }, - child: Text("Chat")) + // ElevatedButton( + // onPressed: () { + // toggleChatOverlay(); + // }, + // child: Text("Chat Debug")) ]), ); } @@ -79,15 +68,24 @@ class _ConnectionPageState extends State { connect(id); } - void connect(String id) { + void connect(String id,{bool isFileTransfer = false}) { if (id == '') return; id = id.replaceAll(' ', ''); - Navigator.push( - context, - MaterialPageRoute( - builder: (BuildContext context) => RemotePage(id: id), - ), - ); + if (isFileTransfer) { + Navigator.push( + context, + MaterialPageRoute( + builder: (BuildContext context) => FileManagerPage(id: id), + ), + ); + }else{ + Navigator.push( + context, + MaterialPageRoute( + builder: (BuildContext context) => RemotePage(id: id), + ), + ); + } FocusScopeNode currentFocus = FocusScope.of(context); if (!currentFocus.hasPrimaryFocus) { currentFocus.unfocus(); @@ -262,6 +260,8 @@ class _ConnectionPageState extends State { items: [ PopupMenuItem( child: Text(translate('Remove')), value: 'remove'), + PopupMenuItem( + child: Text(translate('File transfer')), value: 'file'), ], elevation: 8, ); @@ -270,6 +270,8 @@ class _ConnectionPageState extends State { () async { removePreference(id); }(); + }else if (value == 'file') { + connect(id,isFileTransfer: true); } } } diff --git a/lib/pages/file_manager_page.dart b/lib/pages/file_manager_page.dart index 42117588e..925b54910 100644 --- a/lib/pages/file_manager_page.dart +++ b/lib/pages/file_manager_page.dart @@ -1,38 +1,145 @@ -import 'dart:io'; +import 'dart:async'; import 'package:file_manager/file_manager.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_easyloading/flutter_easyloading.dart'; +import 'package:flutter_hbb/models/file_model.dart'; +import 'package:provider/provider.dart'; -final FileManagerController controller = FileManagerController(); +import '../common.dart'; +import '../models/model.dart'; +import '../widgets/dialog.dart'; + +class FileManagerPage extends StatefulWidget { + FileManagerPage({Key? key, required this.id}) : super(key: key); + final String id; + + @override + State createState() => _FileManagerPageState(); +} + +class _FileManagerPageState extends State { + final _localFileModel = FileManagerController(); + final _remoteFileModel = FFI.remoteFileModel; + Timer? _interval; + Timer? _timer; + var _reconnects = 1; + var _isLocal = false; + + @override + void initState() { + super.initState(); + showLoading(translate('Connecting...')); + FFI.connect(widget.id, isFileTransfer: true); + _interval = Timer.periodic(Duration(milliseconds: 30), + (timer) => FFI.ffiModel.update(widget.id, context, handleMsgBox)); + } + + @override + void dispose() { + _localFileModel.dispose(); + _remoteFileModel.dispose(); + _interval?.cancel(); + FFI.close(); + EasyLoading.dismiss(); + super.dispose(); + } -class FileManagerPage extends StatelessWidget { @override Widget build(BuildContext context) { - return Scaffold( - body: FileManager( - controller: controller, - builder: (context, snapshot) { - final List entities = snapshot; - return ListView.builder( - itemCount: entities.length, - itemBuilder: (context, index) { - return Card( - child: ListTile( - leading: FileManager.isFile(entities[index]) - ? Icon(Icons.feed_outlined) - : Icon(Icons.folder), - title: Text(FileManager.basename(entities[index])), - onTap: () { - if (FileManager.isDirectory(entities[index])) { - controller.openDirectory(entities[index]); // open directory - } else { - // Perform file-related tasks. - } - }, - ), - ); - }, - ); - }, - )); + return ChangeNotifierProvider.value( + value: _remoteFileModel, + child: Scaffold( + appBar: AppBar( + leading: Row(children: [ + IconButton(icon: Icon(Icons.arrow_back), onPressed: goBack), + IconButton(icon: Icon(Icons.close), onPressed: clientClose), + ]), + leadingWidth: 200, + centerTitle: true, + title: Text(translate(_isLocal ? "Local" : "Remote")), + actions: [ + IconButton( + icon: Icon(Icons.change_circle), + onPressed: () => setState(() { + _isLocal = !_isLocal; + })), + ], + ), + body: + Consumer(builder: (context, remoteModel, child) { + return FileManager( + controller: _localFileModel, + builder: (context, localSnapshot) { + final snapshot = _isLocal + ? localSnapshot + : remoteModel.currentRemoteDir.entries; + return ListView.builder( + itemCount: snapshot.length, + itemBuilder: (context, index) { + return Card( + child: ListTile( + leading: FileManager.isFile(snapshot[index]) + ? Icon(Icons.feed_outlined) + : Icon(Icons.folder), + title: Text(FileManager.basename(snapshot[index])), + onTap: () { + if (FileManager.isDirectory(snapshot[index])) { + _isLocal + ? _localFileModel + .openDirectory(snapshot[index]) + : readRemoteDir( + snapshot[index].path); // open directory + } else { + // Perform file-related tasks. + } + }, + ), + ); + }, + ); + }); + }), + )); + } + + goBack() { + if (_isLocal) { + _localFileModel.goToParentDirectory(); + } else { + _remoteFileModel.goToParentDirectory(); + } + } + + void readRemoteDir(String path) { + FFI.setByName("read_remote_dir", path); + } + + void handleMsgBox(Map evt, String id) { + var type = evt['type']; + var title = evt['title']; + var text = evt['text']; + if (type == 're-input-password') { + wrongPasswordDialog(id); + } else if (type == 'input-password') { + enterPasswordDialog(id); + } else { + var hasRetry = evt['hasRetry'] == 'true'; + print(evt); + showMsgBox(type, title, text, hasRetry); + } + } + + void showMsgBox(String type, String title, String text, bool hasRetry) { + msgBox(type, title, text); + if (hasRetry) { + _timer?.cancel(); + _timer = Timer(Duration(seconds: _reconnects), () { + FFI.reconnect(); + showLoading(translate('Connecting...')); + }); + _reconnects *= 2; + } else { + _reconnects = 1; + } } } diff --git a/lib/pages/remote_page.dart b/lib/pages/remote_page.dart index 673d16cac..726b79268 100644 --- a/lib/pages/remote_page.dart +++ b/lib/pages/remote_page.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:flutter_easyloading/flutter_easyloading.dart'; -import 'package:flutter_hbb/main.dart'; import 'package:flutter_hbb/widgets/gesture_help.dart'; import 'package:provider/provider.dart'; import 'package:flutter/services.dart'; @@ -11,6 +10,7 @@ import 'package:wakelock/wakelock.dart'; import '../common.dart'; import '../gestures.dart'; import '../models/model.dart'; +import '../widgets/dialog.dart'; import 'chat_page.dart'; final initText = '\1' * 1024; @@ -228,7 +228,7 @@ class _RemotePageState extends State { final showActionButton = !_showBar || hideKeyboard; return WillPopScope( onWillPop: () async { - close(); + clientClose(); return false; }, child: Scaffold( @@ -279,7 +279,7 @@ class _RemotePageState extends State { color: Colors.white, icon: Icon(Icons.clear), onPressed: () { - close(); + clientClose(); }, ) ] + @@ -585,10 +585,6 @@ class _RemotePageState extends State { })); } - void close() { - msgBox('', 'Close', 'Are you sure to close the connection?'); - } - Widget getHelpTools() { final keyboard = isKeyboardShown(); if (!keyboard) { @@ -783,74 +779,7 @@ class ImagePainter extends CustomPainter { } } -void enterPasswordDialog(String id) { - final controller = TextEditingController(); - var remember = FFI.getByName('remember', id) == 'true'; - if (globalKey.currentContext == null) return; - showAlertDialog((setState) => Tuple3( - Text(translate('Password Required')), - Column(mainAxisSize: MainAxisSize.min, children: [ - PasswordWidget(controller: controller), - CheckboxListTile( - contentPadding: const EdgeInsets.all(0), - dense: true, - controlAffinity: ListTileControlAffinity.leading, - title: Text( - translate('Remember password'), - ), - value: remember, - onChanged: (v) { - if (v != null) { - setState(() => remember = v); - } - }, - ), - ]), - [ - TextButton( - style: flatButtonStyle, - onPressed: () { - DialogManager.reset(); - Navigator.pop(globalKey.currentContext!); - }, - child: Text(translate('Cancel')), - ), - TextButton( - style: flatButtonStyle, - onPressed: () { - var text = controller.text.trim(); - if (text == '') return; - FFI.login(text, remember); - DialogManager.reset(); - showLoading(translate('Logging in...')); - }, - child: Text(translate('OK')), - ), - ], - )); -} -void wrongPasswordDialog(String id) { - if (globalKey.currentContext == null) return; - showAlertDialog((_) => Tuple3(Text(translate('Wrong Password')), - Text(translate('Do you want to enter again?')), [ - TextButton( - style: flatButtonStyle, - onPressed: () { - DialogManager.reset(); - Navigator.pop(globalKey.currentContext!); - }, - child: Text(translate('Cancel')), - ), - TextButton( - style: flatButtonStyle, - onPressed: () { - enterPasswordDialog(id); - }, - child: Text(translate('Retry')), - ), - ])); -} CheckboxListTile getToggle( void Function(void Function()) setState, option, name) { diff --git a/lib/widgets/dialog.dart b/lib/widgets/dialog.dart new file mode 100644 index 000000000..f9aaceb22 --- /dev/null +++ b/lib/widgets/dialog.dart @@ -0,0 +1,77 @@ +import 'package:flutter/material.dart'; +import 'package:tuple/tuple.dart'; +import '../common.dart'; +import '../models/model.dart'; + +void clientClose() { + msgBox('', 'Close', 'Are you sure to close the connection?'); +} + +void enterPasswordDialog(String id) { + final controller = TextEditingController(); + var remember = FFI.getByName('remember', id) == 'true'; + if (globalKey.currentContext == null) return; + showAlertDialog((setState) => Tuple3( + Text(translate('Password Required')), + Column(mainAxisSize: MainAxisSize.min, children: [ + PasswordWidget(controller: controller), + CheckboxListTile( + contentPadding: const EdgeInsets.all(0), + dense: true, + controlAffinity: ListTileControlAffinity.leading, + title: Text( + translate('Remember password'), + ), + value: remember, + onChanged: (v) { + if (v != null) { + setState(() => remember = v); + } + }, + ), + ]), + [ + TextButton( + style: flatButtonStyle, + onPressed: () { + DialogManager.reset(); + Navigator.pop(globalKey.currentContext!); + }, + child: Text(translate('Cancel')), + ), + TextButton( + style: flatButtonStyle, + onPressed: () { + var text = controller.text.trim(); + if (text == '') return; + FFI.login(text, remember); + DialogManager.reset(); + showLoading(translate('Logging in...')); + }, + child: Text(translate('OK')), + ), + ], + )); +} + +void wrongPasswordDialog(String id) { + if (globalKey.currentContext == null) return; + showAlertDialog((_) => Tuple3(Text(translate('Wrong Password')), + Text(translate('Do you want to enter again?')), [ + TextButton( + style: flatButtonStyle, + onPressed: () { + DialogManager.reset(); + Navigator.pop(globalKey.currentContext!); + }, + child: Text(translate('Cancel')), + ), + TextButton( + style: flatButtonStyle, + onPressed: () { + enterPasswordDialog(id); + }, + child: Text(translate('Retry')), + ), + ])); +} \ No newline at end of file