rustdesk/flutter/lib/desktop/pages/desktop_home_page.dart
2022-09-19 18:38:19 +08:00

561 lines
17 KiB
Dart

import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart' hide MenuItem;
import 'package:flutter/services.dart';
import 'package:flutter_hbb/common.dart';
import 'package:flutter_hbb/desktop/pages/connection_page.dart';
import 'package:flutter_hbb/models/platform_model.dart';
import 'package:flutter_hbb/models/server_model.dart';
import 'package:flutter_hbb/utils/multi_window_manager.dart';
import 'package:get/get.dart';
import 'package:provider/provider.dart';
import 'package:tray_manager/tray_manager.dart';
import 'package:window_manager/window_manager.dart';
class DesktopHomePage extends StatefulWidget {
const DesktopHomePage({Key? key}) : super(key: key);
@override
State<DesktopHomePage> createState() => _DesktopHomePageState();
}
const borderColor = Color(0xFF2F65BA);
class _DesktopHomePageState extends State<DesktopHomePage>
with TrayListener, WindowListener, AutomaticKeepAliveClientMixin {
@override
bool get wantKeepAlive => true;
@override
void onWindowClose() async {
super.onWindowClose();
// close all sub windows
if (await windowManager.isPreventClose()) {
try {
await Future.wait([
saveWindowPosition(WindowType.Main),
rustDeskWinManager.closeAllSubWindows()
]);
} catch (err) {
debugPrint("$err");
} finally {
await windowManager.setPreventClose(false);
await windowManager.close();
}
}
}
@override
Widget build(BuildContext context) {
super.build(context);
return Row(
children: [
buildLeftPane(context),
const VerticalDivider(
width: 1,
thickness: 1,
),
Expanded(
child: buildRightPane(context),
),
],
);
}
buildLeftPane(BuildContext context) {
return ChangeNotifierProvider.value(
value: gFFI.serverModel,
child: Container(
width: 200,
color: MyTheme.color(context).bg,
child: Column(
children: [
buildTip(context),
buildIDBoard(context),
buildPasswordBoard(context),
],
),
),
);
}
buildRightPane(BuildContext context) {
return Container(
color: MyTheme.color(context).grayBg,
child: ConnectionPage(),
);
}
buildIDBoard(BuildContext context) {
final model = gFFI.serverModel;
return Container(
margin: const EdgeInsets.only(left: 20, right: 11),
height: 57,
child: Row(
crossAxisAlignment: CrossAxisAlignment.baseline,
textBaseline: TextBaseline.alphabetic,
children: [
Container(
width: 2,
decoration: const BoxDecoration(color: MyTheme.accent),
).marginOnly(top: 5),
Expanded(
child: Padding(
padding: const EdgeInsets.only(left: 7),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
height: 25,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
translate("ID"),
style: TextStyle(
fontSize: 14,
color: MyTheme.color(context).lightText),
).marginOnly(top: 5),
buildPopupMenu(context)
],
),
),
Flexible(
child: GestureDetector(
onDoubleTap: () {
Clipboard.setData(
ClipboardData(text: model.serverId.text));
showToast(translate("Copied"));
},
child: TextFormField(
controller: model.serverId,
readOnly: true,
decoration: InputDecoration(
border: InputBorder.none,
contentPadding: EdgeInsets.only(bottom: 20),
),
style: TextStyle(
fontSize: 22,
),
),
),
)
],
),
),
),
],
),
);
}
Widget buildPopupMenu(BuildContext context) {
RxBool hover = false.obs;
return InkWell(
onTap: () async {},
child: Obx(
() => CircleAvatar(
radius: 15,
backgroundColor: hover.value
? MyTheme.color(context).grayBg!
: MyTheme.color(context).bg!,
child: Icon(
Icons.more_vert_outlined,
size: 20,
color: hover.value
? MyTheme.color(context).text
: MyTheme.color(context).lightText,
),
),
),
onHover: (value) => hover.value = value,
);
}
buildPasswordBoard(BuildContext context) {
final model = gFFI.serverModel;
RxBool refreshHover = false.obs;
RxBool editHover = false.obs;
return Container(
margin: EdgeInsets.only(left: 20.0, right: 16, top: 13, bottom: 13),
child: Row(
crossAxisAlignment: CrossAxisAlignment.baseline,
textBaseline: TextBaseline.alphabetic,
children: [
Container(
width: 2,
height: 52,
decoration: BoxDecoration(color: MyTheme.accent),
),
Expanded(
child: Padding(
padding: const EdgeInsets.only(left: 7),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
translate("Password"),
style: TextStyle(
fontSize: 14, color: MyTheme.color(context).lightText),
),
Row(
children: [
Expanded(
child: GestureDetector(
onDoubleTap: () {
if (model.verificationMethod !=
kUsePermanentPassword) {
Clipboard.setData(
ClipboardData(text: model.serverPasswd.text));
showToast(translate("Copied"));
}
},
child: TextFormField(
controller: model.serverPasswd,
readOnly: true,
decoration: InputDecoration(
border: InputBorder.none,
contentPadding: EdgeInsets.only(bottom: 2),
),
style: TextStyle(fontSize: 15),
),
),
),
InkWell(
child: Obx(
() => Icon(
Icons.refresh,
color: refreshHover.value
? MyTheme.color(context).text
: Color(0xFFDDDDDD),
size: 22,
).marginOnly(right: 8, bottom: 2),
),
onTap: () => bind.mainUpdateTemporaryPassword(),
onHover: (value) => refreshHover.value = value,
),
InkWell(
child: Obx(
() => Icon(
Icons.edit,
color: editHover.value
? MyTheme.color(context).text
: Color(0xFFDDDDDD),
size: 22,
).marginOnly(right: 8, bottom: 2),
),
onTap: () => {},
onHover: (value) => editHover.value = value,
),
],
),
],
),
),
),
],
),
);
}
buildTip(BuildContext context) {
return Padding(
padding:
const EdgeInsets.only(left: 20.0, right: 16, top: 16.0, bottom: 5),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
translate("Your Desktop"),
style: TextStyle(fontWeight: FontWeight.normal, fontSize: 19),
),
SizedBox(
height: 10.0,
),
Text(
translate("desk_tip"),
overflow: TextOverflow.clip,
style: TextStyle(
fontSize: 12,
color: MyTheme.color(context).lighterText,
height: 1.25),
)
],
),
);
}
@override
void onTrayMenuItemClick(MenuItem menuItem) {
print('click ${menuItem.key}');
switch (menuItem.key) {
case "quit":
exit(0);
case "show":
// windowManager.show();
break;
default:
break;
}
}
@override
void initState() {
super.initState();
trayManager.addListener(this);
windowManager.addListener(this);
rustDeskWinManager.setMethodHandler((call, fromWindowId) async {
print(
"call ${call.method} with args ${call.arguments} from window ${fromWindowId}");
if (call.method == "main_window_on_top") {
window_on_top(null);
}
});
}
@override
void dispose() {
trayManager.removeListener(this);
windowManager.removeListener(this);
super.dispose();
}
}
/// common login dialog for desktop
/// call this directly
Future<bool> loginDialog() async {
String userName = "";
var userNameMsg = "";
String pass = "";
var passMsg = "";
var userContontroller = TextEditingController(text: userName);
var pwdController = TextEditingController(text: pass);
var isInProgress = false;
var completer = Completer<bool>();
gFFI.dialogManager.show((setState, close) {
submit() async {
setState(() {
userNameMsg = "";
passMsg = "";
isInProgress = true;
});
cancel() {
setState(() {
isInProgress = false;
});
}
userName = userContontroller.text;
pass = pwdController.text;
if (userName.isEmpty) {
userNameMsg = translate("Username missed");
cancel();
return;
}
if (pass.isEmpty) {
passMsg = translate("Password missed");
cancel();
return;
}
try {
final resp = await gFFI.userModel.login(userName, pass);
if (resp.containsKey('error')) {
passMsg = resp['error'];
cancel();
return;
}
// {access_token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJndWlkIjoiMDFkZjQ2ZjgtZjg3OS00MDE0LTk5Y2QtMGMwYzM2MmViZGJlIiwiZXhwIjoxNjYxNDg2NzYwfQ.GZpe1oI8TfM5yTYNrpcwbI599P4Z_-b2GmnwNl2Lr-w,
// token_type: Bearer, user: {id: , name: admin, email: null, note: null, status: null, grp: null, is_admin: true}}
debugPrint("$resp");
completer.complete(true);
} catch (err) {
// ignore: avoid_print
print(err.toString());
cancel();
return;
}
close();
}
cancel() {
completer.complete(false);
close();
}
return CustomAlertDialog(
title: Text(translate("Login")),
content: ConstrainedBox(
constraints: const BoxConstraints(minWidth: 500),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(
height: 8.0,
),
Row(
children: [
ConstrainedBox(
constraints: const BoxConstraints(minWidth: 100),
child: Text(
"${translate('Username')}:",
textAlign: TextAlign.start,
).marginOnly(bottom: 16.0)),
const SizedBox(
width: 24.0,
),
Expanded(
child: TextField(
decoration: InputDecoration(
border: const OutlineInputBorder(),
errorText: userNameMsg.isNotEmpty ? userNameMsg : null),
controller: userContontroller,
focusNode: FocusNode()..requestFocus(),
),
),
],
),
const SizedBox(
height: 8.0,
),
Row(
children: [
ConstrainedBox(
constraints: const BoxConstraints(minWidth: 100),
child: Text("${translate('Password')}:")
.marginOnly(bottom: 16.0)),
const SizedBox(
width: 24.0,
),
Expanded(
child: TextField(
obscureText: true,
decoration: InputDecoration(
border: const OutlineInputBorder(),
errorText: passMsg.isNotEmpty ? passMsg : null),
controller: pwdController,
),
),
],
),
const SizedBox(
height: 4.0,
),
Offstage(
offstage: !isInProgress, child: const LinearProgressIndicator())
],
),
),
actions: [
TextButton(onPressed: cancel, child: Text(translate("Cancel"))),
TextButton(onPressed: submit, child: Text(translate("OK"))),
],
onSubmit: submit,
onCancel: cancel,
);
});
return completer.future;
}
void setPasswordDialog() async {
final pw = await bind.mainGetPermanentPassword();
final p0 = TextEditingController(text: pw);
final p1 = TextEditingController(text: pw);
var errMsg0 = "";
var errMsg1 = "";
gFFI.dialogManager.show((setState, close) {
submit() {
setState(() {
errMsg0 = "";
errMsg1 = "";
});
final pass = p0.text.trim();
if (pass.length < 6) {
setState(() {
errMsg0 = translate("Too short, at least 6 characters.");
});
return;
}
if (p1.text.trim() != pass) {
setState(() {
errMsg1 = translate("The confirmation is not identical.");
});
return;
}
bind.mainSetPermanentPassword(password: pass);
close();
}
return CustomAlertDialog(
title: Text(translate("Set Password")),
content: ConstrainedBox(
constraints: const BoxConstraints(minWidth: 500),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(
height: 8.0,
),
Row(
children: [
ConstrainedBox(
constraints: const BoxConstraints(minWidth: 100),
child: Text(
"${translate('Password')}:",
textAlign: TextAlign.start,
).marginOnly(bottom: 16.0)),
const SizedBox(
width: 24.0,
),
Expanded(
child: TextField(
obscureText: true,
decoration: InputDecoration(
border: const OutlineInputBorder(),
errorText: errMsg0.isNotEmpty ? errMsg0 : null),
controller: p0,
focusNode: FocusNode()..requestFocus(),
),
),
],
),
const SizedBox(
height: 8.0,
),
Row(
children: [
ConstrainedBox(
constraints: const BoxConstraints(minWidth: 100),
child: Text("${translate('Confirmation')}:")
.marginOnly(bottom: 16.0)),
const SizedBox(
width: 24.0,
),
Expanded(
child: TextField(
obscureText: true,
decoration: InputDecoration(
border: const OutlineInputBorder(),
errorText: errMsg1.isNotEmpty ? errMsg1 : null),
controller: p1,
),
),
],
),
],
),
),
actions: [
TextButton(onPressed: close, child: Text(translate("Cancel"))),
TextButton(onPressed: submit, child: Text(translate("OK"))),
],
onSubmit: submit,
onCancel: close,
);
});
}