add local/remote file manager

This commit is contained in:
csf 2022-03-07 22:54:34 +08:00
parent e9f8fd1175
commit 88f722c8c2
8 changed files with 712 additions and 203 deletions

View File

@ -1,9 +1,10 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'dart:async'; import 'dart:async';
import 'package:flutter_easyloading/flutter_easyloading.dart'; import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_hbb/main.dart';
import 'package:tuple/tuple.dart'; import 'package:tuple/tuple.dart';
final globalKey = GlobalKey<NavigatorState>();
typedef F = String Function(String); typedef F = String Function(String);
typedef FMethod = String Function(String, dynamic); typedef FMethod = String Function(String, dynamic);

View File

@ -4,6 +4,7 @@ import 'package:provider/provider.dart';
import 'package:firebase_analytics/firebase_analytics.dart'; import 'package:firebase_analytics/firebase_analytics.dart';
import 'package:firebase_analytics/observer.dart'; import 'package:firebase_analytics/observer.dart';
import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_core/firebase_core.dart';
import 'common.dart';
import 'models/model.dart'; import 'models/model.dart';
import 'pages/home_page.dart'; import 'pages/home_page.dart';
import 'pages/server_page.dart'; import 'pages/server_page.dart';
@ -16,7 +17,6 @@ Future<Null> main() async {
runApp(App()); runApp(App());
} }
final globalKey = GlobalKey<NavigatorState>();
class App extends StatelessWidget { class App extends StatelessWidget {
@override @override

407
lib/models/file_model.dart Normal file
View File

@ -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<Entry> entries = [];
List<FileSystemEntity> entries = [];
int id = 0;
String path = "";
FileDirectory();
FileDirectory.fromJson(Map<String, dynamic> json) {
id = json['id'];
path = json['path'];
if (json['entries'] != null) {
entries = <FileSystemEntity>[];
json['entries'].forEach((v) {
entries.add(new Entry.fromJson(v).toFileSystemEntity(path));
});
}
}
// Map<String, dynamic> toJson() {
// final Map<String, dynamic> data = new Map<String, dynamic>();
// 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<String, dynamic> 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<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
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<Directory> create({bool recursive = false}) {
// TODO: implement create
throw UnimplementedError();
}
@override
void createSync({bool recursive = false}) {
// TODO: implement createSync
}
@override
Future<Directory> createTemp([String? prefix]) {
// TODO: implement createTemp
throw UnimplementedError();
}
@override
Directory createTempSync([String? prefix]) {
// TODO: implement createTempSync
throw UnimplementedError();
}
@override
Future<bool> exists() {
// TODO: implement exists
throw UnimplementedError();
}
@override
bool existsSync() {
// TODO: implement existsSync
throw UnimplementedError();
}
@override
Stream<FileSystemEntity> list({bool recursive = false, bool followLinks = true}) {
// TODO: implement list
throw UnimplementedError();
}
@override
List<FileSystemEntity> listSync({bool recursive = false, bool followLinks = true}) {
// TODO: implement listSync
throw UnimplementedError();
}
@override
Future<Directory> 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<int> length() {
// TODO: implement length
throw UnimplementedError();
}
@override
Future<DateTime> lastModified() {
// TODO: implement lastModified
throw UnimplementedError();
}
@override
Future<File> copy(String newPath) {
// TODO: implement copy
throw UnimplementedError();
}
@override
File copySync(String newPath) {
// TODO: implement copySync
throw UnimplementedError();
}
@override
Future<File> create({bool recursive = false}) {
// TODO: implement create
throw UnimplementedError();
}
@override
void createSync({bool recursive = false}) {
// TODO: implement createSync
}
@override
Future<bool> exists() {
// TODO: implement exists
throw UnimplementedError();
}
@override
bool existsSync() {
// TODO: implement existsSync
throw UnimplementedError();
}
@override
Future<DateTime> lastAccessed() {
// TODO: implement lastAccessed
throw UnimplementedError();
}
@override
DateTime lastAccessedSync() {
// TODO: implement lastAccessedSync
throw UnimplementedError();
}
@override
Future<RandomAccessFile> open({FileMode mode = FileMode.read}) {
// TODO: implement open
throw UnimplementedError();
}
@override
Stream<List<int>> 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<Uint8List> readAsBytes() {
// TODO: implement readAsBytes
throw UnimplementedError();
}
@override
Uint8List readAsBytesSync() {
// TODO: implement readAsBytesSync
throw UnimplementedError();
}
@override
Future<List<String>> readAsLines({Encoding encoding = utf8}) {
// TODO: implement readAsLines
throw UnimplementedError();
}
@override
List<String> readAsLinesSync({Encoding encoding = utf8}) {
// TODO: implement readAsLinesSync
throw UnimplementedError();
}
@override
Future<String> readAsString({Encoding encoding = utf8}) {
// TODO: implement readAsString
throw UnimplementedError();
}
@override
String readAsStringSync({Encoding encoding = utf8}) {
// TODO: implement readAsStringSync
throw UnimplementedError();
}
@override
Future<File> 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<File> writeAsBytes(List<int> bytes, {FileMode mode = FileMode.write, bool flush = false}) {
// TODO: implement writeAsBytes
throw UnimplementedError();
}
@override
void writeAsBytesSync(List<int> bytes, {FileMode mode = FileMode.write, bool flush = false}) {
// TODO: implement writeAsBytesSync
}
@override
Future<File> 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();
}

View File

@ -1,6 +1,7 @@
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart'; import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_hbb/models/chat_model.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 'package:shared_preferences/shared_preferences.dart';
import 'dart:math'; import 'dart:math';
import 'dart:convert'; import 'dart:convert';
@ -44,7 +45,7 @@ class FfiModel with ChangeNotifier {
FfiModel() { FfiModel() {
Translator.call = translate; Translator.call = translate;
clear(); clear();
() async { () async {
await PlatformFFI.init(); await PlatformFFI.init();
_initialized = true; _initialized = true;
print("FFI initialized"); print("FFI initialized");
@ -98,20 +99,22 @@ class FfiModel with ChangeNotifier {
_inputBlocked = false; _inputBlocked = false;
_permissions.clear(); _permissions.clear();
} }
void update(String id,
void update(
String id,
BuildContext context, BuildContext context,
void Function( void Function(
Map<String, dynamic> evt, Map<String, dynamic> evt,
String id, String id,
) )
handleMsgbox) { handleMsgBox) {
var pos; var pos;
for (;;) { for (;;) {
var evt = FFI.popEvent(); var evt = FFI.popEvent();
if (evt == null) break; if (evt == null) break;
var name = evt['name']; var name = evt['name'];
if (name == 'msgbox') { if (name == 'msgbox') {
handleMsgbox(evt, id); handleMsgBox(evt, id);
} else if (name == 'peer_info') { } else if (name == 'peer_info') {
handlePeerInfo(evt, context); handlePeerInfo(evt, context);
} else if (name == 'connection_ready') { } else if (name == 'connection_ready') {
@ -129,9 +132,10 @@ class FfiModel with ChangeNotifier {
Clipboard.setData(ClipboardData(text: evt['content'])); Clipboard.setData(ClipboardData(text: evt['content']));
} else if (name == 'permission') { } else if (name == 'permission') {
FFI.ffiModel.updatePermission(evt); FFI.ffiModel.updatePermission(evt);
} else if (name == 'chat'){ } else if (name == 'chat') {
// FFI.setByName("chat",msg); FFI.chatModel.receive(evt['text'] ?? "");
FFI.chatModel.receive(evt['text']??""); } else if (name == 'file_dir') {
FFI.remoteFileModel.tryUpdateRemoteDir(evt['value'] ?? "");
} }
} }
if (pos != null) FFI.cursorModel.updateCursorPosition(pos); if (pos != null) FFI.cursorModel.updateCursorPosition(pos);
@ -146,17 +150,17 @@ class FfiModel with ChangeNotifier {
final pid = FFI.id; final pid = FFI.id;
ui.decodeImageFromPixels( ui.decodeImageFromPixels(
rgba, _display.width, _display.height, ui.PixelFormat.bgra8888, rgba, _display.width, _display.height, ui.PixelFormat.bgra8888,
(image) { (image) {
PlatformFFI.clearRgbaFrame(); PlatformFFI.clearRgbaFrame();
_decoding = false; _decoding = false;
if (FFI.id != pid) return; if (FFI.id != pid) return;
try { try {
// my throw exception, because the listener maybe already dispose // my throw exception, because the listener maybe already dispose
FFI.imageModel.update(image); FFI.imageModel.update(image);
} catch (e) { } catch (e) {
print('update image: $e'); print('update image: $e');
} }
}); });
} }
} }
} }
@ -213,9 +217,7 @@ class ImageModel with ChangeNotifier {
if (isDesktop) { if (isDesktop) {
FFI.canvasModel.updateViewStyle(); FFI.canvasModel.updateViewStyle();
} else { } else {
final size = MediaQueryData final size = MediaQueryData.fromWindow(ui.window).size;
.fromWindow(ui.window)
.size;
final xscale = size.width / image.width; final xscale = size.width / image.width;
final yscale = size.height / image.height; final yscale = size.height / image.height;
FFI.canvasModel.scale = max(xscale, yscale); FFI.canvasModel.scale = max(xscale, yscale);
@ -228,9 +230,7 @@ class ImageModel with ChangeNotifier {
double get maxScale { double get maxScale {
if (_image == null) return 1.0; if (_image == null) return 1.0;
final size = MediaQueryData final size = MediaQueryData.fromWindow(ui.window).size;
.fromWindow(ui.window)
.size;
final xscale = size.width / _image!.width; final xscale = size.width / _image!.width;
final yscale = size.height / _image!.height; final yscale = size.height / _image!.height;
return max(1.0, max(xscale, yscale)); return max(1.0, max(xscale, yscale));
@ -238,9 +238,7 @@ class ImageModel with ChangeNotifier {
double get minScale { double get minScale {
if (_image == null) return 1.0; if (_image == null) return 1.0;
final size = MediaQueryData final size = MediaQueryData.fromWindow(ui.window).size;
.fromWindow(ui.window)
.size;
final xscale = size.width / _image!.width; final xscale = size.width / _image!.width;
final yscale = size.height / _image!.height; final yscale = size.height / _image!.height;
return min(xscale, yscale); return min(xscale, yscale);
@ -262,9 +260,7 @@ class CanvasModel with ChangeNotifier {
void updateViewStyle() { void updateViewStyle() {
final s = FFI.getByName('peer_option', 'view-style'); final s = FFI.getByName('peer_option', 'view-style');
final size = MediaQueryData final size = MediaQueryData.fromWindow(ui.window).size;
.fromWindow(ui.window)
.size;
final s1 = size.width / FFI.ffiModel.display.width; final s1 = size.width / FFI.ffiModel.display.width;
final s2 = size.height / FFI.ffiModel.display.height; final s2 = size.height / FFI.ffiModel.display.height;
if (s == 'shrink') { if (s == 'shrink') {
@ -293,9 +289,7 @@ class CanvasModel with ChangeNotifier {
} }
void moveDesktopMouse(double x, double y) { void moveDesktopMouse(double x, double y) {
final size = MediaQueryData final size = MediaQueryData.fromWindow(ui.window).size;
.fromWindow(ui.window)
.size;
final dw = FFI.ffiModel.display.width * _scale; final dw = FFI.ffiModel.display.width * _scale;
final dh = FFI.ffiModel.display.height * _scale; final dh = FFI.ffiModel.display.height * _scale;
var dxOffset = 0; var dxOffset = 0;
@ -390,9 +384,7 @@ class CursorModel with ChangeNotifier {
// remote physical display coordinate // remote physical display coordinate
Rect getVisibleRect() { Rect getVisibleRect() {
final size = MediaQueryData final size = MediaQueryData.fromWindow(ui.window).size;
.fromWindow(ui.window)
.size;
final xoffset = FFI.canvasModel.x; final xoffset = FFI.canvasModel.x;
final yoffset = FFI.canvasModel.y; final yoffset = FFI.canvasModel.y;
final scale = FFI.canvasModel.scale; final scale = FFI.canvasModel.scale;
@ -418,7 +410,7 @@ class CursorModel with ChangeNotifier {
FFI.tap(button); FFI.tap(button);
} }
void move(double x, double y){ void move(double x, double y) {
moveLocal(x, y); moveLocal(x, y);
FFI.moveMouse(_x, _y); FFI.moveMouse(_x, _y);
} }
@ -440,7 +432,7 @@ class CursorModel with ChangeNotifier {
notifyListeners(); notifyListeners();
} }
void updatePan(double dx, double dy,bool touchMode) { void updatePan(double dx, double dy, bool touchMode) {
if (FFI.imageModel.image == null) return; if (FFI.imageModel.image == null) return;
if (touchMode) { if (touchMode) {
if (true) { if (true) {
@ -528,17 +520,17 @@ class CursorModel with ChangeNotifier {
final rgba = Uint8List.fromList(colors.map((s) => s as int).toList()); final rgba = Uint8List.fromList(colors.map((s) => s as int).toList());
var pid = FFI.id; var pid = FFI.id;
ui.decodeImageFromPixels(rgba, width, height, ui.PixelFormat.rgba8888, ui.decodeImageFromPixels(rgba, width, height, ui.PixelFormat.rgba8888,
(image) { (image) {
if (FFI.id != pid) return; if (FFI.id != pid) return;
_image = image; _image = image;
_images[id] = Tuple3(image, _hotx, _hoty); _images[id] = Tuple3(image, _hotx, _hoty);
try { try {
// my throw exception, because the listener maybe already dispose // my throw exception, because the listener maybe already dispose
notifyListeners(); notifyListeners();
} catch (e) { } catch (e) {
print('notify cursor: $e'); print('notify cursor: $e');
} }
}); });
} }
void updateCursorId(Map<String, dynamic> evt) { void updateCursorId(Map<String, dynamic> evt) {
@ -567,8 +559,8 @@ class CursorModel with ChangeNotifier {
notifyListeners(); notifyListeners();
} }
void updateDisplayOriginWithCursor(double x, double y, double xCursor, void updateDisplayOriginWithCursor(
double yCursor) { double x, double y, double xCursor, double yCursor) {
_displayOriginX = x; _displayOriginX = x;
_displayOriginY = y; _displayOriginY = y;
_x = xCursor; _x = xCursor;
@ -675,13 +667,9 @@ class ServerModel with ChangeNotifier {
} }
} }
enum MouseButtons { enum MouseButtons { left, right, wheel }
left,
right,
wheel
}
extension ToString on MouseButtons{ extension ToString on MouseButtons {
String get value { String get value {
switch (this) { switch (this) {
case MouseButtons.left: case MouseButtons.left:
@ -694,7 +682,6 @@ extension ToString on MouseButtons{
} }
} }
class FFI { class FFI {
static var id = ""; static var id = "";
static var shift = false; static var shift = false;
@ -708,6 +695,7 @@ class FFI {
static final canvasModel = CanvasModel(); static final canvasModel = CanvasModel();
static final serverModel = ServerModel(); static final serverModel = ServerModel();
static final chatModel = ChatModel(); static final chatModel = ChatModel();
static final remoteFileModel = RemoteFileModel();
static String getId() { static String getId() {
return getByName('remote_id'); return getByName('remote_id');
@ -744,8 +732,8 @@ class FFI {
static void sendMouse(String type, MouseButtons button) { static void sendMouse(String type, MouseButtons button) {
if (!ffiModel.keyboard()) return; if (!ffiModel.keyboard()) return;
setByName( setByName('send_mouse',
'send_mouse', json.encode(modify({'type': type, 'buttons': button.value}))); json.encode(modify({'type': type, 'buttons': button.value})));
} }
static void inputKey(String name) { static void inputKey(String name) {
@ -767,7 +755,7 @@ class FFI {
return peers return peers
.map((s) => s as List<dynamic>) .map((s) => s as List<dynamic>)
.map((s) => .map((s) =>
Peer.fromJson(s[0] as String, s[1] as Map<String, dynamic>)) Peer.fromJson(s[0] as String, s[1] as Map<String, dynamic>))
.toList(); .toList();
} catch (e) { } catch (e) {
print('peers(): $e'); print('peers(): $e');
@ -775,8 +763,12 @@ class FFI {
return []; return [];
} }
static void connect(String id) { static void connect(String id, {bool isFileTransfer = false}) {
setByName('connect', id); if (isFileTransfer) {
setByName('connect_file_transfer', id);
} else {
setByName('connect', id);
}
FFI.id = id; FFI.id = id;
} }
@ -804,14 +796,8 @@ class FFI {
static void close() { static void close() {
chatModel.release(); chatModel.release();
if (FFI.imageModel.image != null && !isDesktop) { if (FFI.imageModel.image != null && !isDesktop) {
savePreference( savePreference(id, cursorModel.x, cursorModel.y, canvasModel.x,
id, canvasModel.y, canvasModel.scale, ffiModel.pi.currentDisplay);
cursorModel.x,
cursorModel.y,
canvasModel.x,
canvasModel.y,
canvasModel.scale,
ffiModel.pi.currentDisplay);
} }
id = ""; id = "";
setByName('close', ''); setByName('close', '');

View File

@ -1,5 +1,4 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hbb/pages/chat_page.dart';
import 'package:flutter_hbb/pages/file_manager_page.dart'; import 'package:flutter_hbb/pages/file_manager_page.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
@ -55,21 +54,11 @@ class _ConnectionPageState extends State<ConnectionPage> {
getSearchBarUI(), getSearchBarUI(),
Container(height: 12), Container(height: 12),
getPeers(), getPeers(),
ElevatedButton( // ElevatedButton(
onPressed: () { // onPressed: () {
Navigator.push( // toggleChatOverlay();
context, // },
MaterialPageRoute( // child: Text("Chat Debug"))
builder: (BuildContext context) => FileManagerPage(),
),
);
},
child: Text("File")),
ElevatedButton(
onPressed: () {
toggleChatOverlay();
},
child: Text("Chat"))
]), ]),
); );
} }
@ -79,15 +68,24 @@ class _ConnectionPageState extends State<ConnectionPage> {
connect(id); connect(id);
} }
void connect(String id) { void connect(String id,{bool isFileTransfer = false}) {
if (id == '') return; if (id == '') return;
id = id.replaceAll(' ', ''); id = id.replaceAll(' ', '');
Navigator.push( if (isFileTransfer) {
context, Navigator.push(
MaterialPageRoute( context,
builder: (BuildContext context) => RemotePage(id: id), MaterialPageRoute(
), builder: (BuildContext context) => FileManagerPage(id: id),
); ),
);
}else{
Navigator.push(
context,
MaterialPageRoute(
builder: (BuildContext context) => RemotePage(id: id),
),
);
}
FocusScopeNode currentFocus = FocusScope.of(context); FocusScopeNode currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus) { if (!currentFocus.hasPrimaryFocus) {
currentFocus.unfocus(); currentFocus.unfocus();
@ -262,6 +260,8 @@ class _ConnectionPageState extends State<ConnectionPage> {
items: [ items: [
PopupMenuItem<String>( PopupMenuItem<String>(
child: Text(translate('Remove')), value: 'remove'), child: Text(translate('Remove')), value: 'remove'),
PopupMenuItem<String>(
child: Text(translate('File transfer')), value: 'file'),
], ],
elevation: 8, elevation: 8,
); );
@ -270,6 +270,8 @@ class _ConnectionPageState extends State<ConnectionPage> {
() async { () async {
removePreference(id); removePreference(id);
}(); }();
}else if (value == 'file') {
connect(id,isFileTransfer: true);
} }
} }
} }

View File

@ -1,38 +1,145 @@
import 'dart:io'; import 'dart:async';
import 'package:file_manager/file_manager.dart'; import 'package:file_manager/file_manager.dart';
import 'package:flutter/material.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<StatefulWidget> createState() => _FileManagerPageState();
}
class _FileManagerPageState extends State<FileManagerPage> {
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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return ChangeNotifierProvider.value(
body: FileManager( value: _remoteFileModel,
controller: controller, child: Scaffold(
builder: (context, snapshot) { appBar: AppBar(
final List<FileSystemEntity> entities = snapshot; leading: Row(children: [
return ListView.builder( IconButton(icon: Icon(Icons.arrow_back), onPressed: goBack),
itemCount: entities.length, IconButton(icon: Icon(Icons.close), onPressed: clientClose),
itemBuilder: (context, index) { ]),
return Card( leadingWidth: 200,
child: ListTile( centerTitle: true,
leading: FileManager.isFile(entities[index]) title: Text(translate(_isLocal ? "Local" : "Remote")),
? Icon(Icons.feed_outlined) actions: [
: Icon(Icons.folder), IconButton(
title: Text(FileManager.basename(entities[index])), icon: Icon(Icons.change_circle),
onTap: () { onPressed: () => setState(() {
if (FileManager.isDirectory(entities[index])) { _isLocal = !_isLocal;
controller.openDirectory(entities[index]); // open directory })),
} else { ],
// Perform file-related tasks. ),
} body:
}, Consumer<RemoteFileModel>(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<String, dynamic> 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;
}
} }
} }

View File

@ -1,6 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart'; import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_hbb/main.dart';
import 'package:flutter_hbb/widgets/gesture_help.dart'; import 'package:flutter_hbb/widgets/gesture_help.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
@ -11,6 +10,7 @@ import 'package:wakelock/wakelock.dart';
import '../common.dart'; import '../common.dart';
import '../gestures.dart'; import '../gestures.dart';
import '../models/model.dart'; import '../models/model.dart';
import '../widgets/dialog.dart';
import 'chat_page.dart'; import 'chat_page.dart';
final initText = '\1' * 1024; final initText = '\1' * 1024;
@ -228,7 +228,7 @@ class _RemotePageState extends State<RemotePage> {
final showActionButton = !_showBar || hideKeyboard; final showActionButton = !_showBar || hideKeyboard;
return WillPopScope( return WillPopScope(
onWillPop: () async { onWillPop: () async {
close(); clientClose();
return false; return false;
}, },
child: Scaffold( child: Scaffold(
@ -279,7 +279,7 @@ class _RemotePageState extends State<RemotePage> {
color: Colors.white, color: Colors.white,
icon: Icon(Icons.clear), icon: Icon(Icons.clear),
onPressed: () { onPressed: () {
close(); clientClose();
}, },
) )
] + ] +
@ -585,10 +585,6 @@ class _RemotePageState extends State<RemotePage> {
})); }));
} }
void close() {
msgBox('', 'Close', 'Are you sure to close the connection?');
}
Widget getHelpTools() { Widget getHelpTools() {
final keyboard = isKeyboardShown(); final keyboard = isKeyboardShown();
if (!keyboard) { 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( CheckboxListTile getToggle(
void Function(void Function()) setState, option, name) { void Function(void Function()) setState, option, name) {

77
lib/widgets/dialog.dart Normal file
View File

@ -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')),
),
]));
}