add local/remote file manager
This commit is contained in:
parent
e9f8fd1175
commit
88f722c8c2
@ -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);
|
||||||
|
|
||||||
|
@ -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
407
lib/models/file_model.dart
Normal 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();
|
||||||
|
|
||||||
|
}
|
@ -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', '');
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
77
lib/widgets/dialog.dart
Normal 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')),
|
||||||
|
),
|
||||||
|
]));
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user