Merge branch 'master' into record

This commit is contained in:
RustDesk
2022-09-27 15:32:27 +08:00
committed by GitHub
76 changed files with 1734 additions and 1072 deletions

View File

@@ -17,6 +17,8 @@ import 'package:shared_preferences/shared_preferences.dart';
import 'package:window_manager/window_manager.dart';
import 'common/widgets/overlay.dart';
import 'mobile/pages/file_manager_page.dart';
import 'mobile/pages/remote_page.dart';
import 'models/model.dart';
import 'models/platform_model.dart';
@@ -76,59 +78,22 @@ class IconFont {
class ColorThemeExtension extends ThemeExtension<ColorThemeExtension> {
const ColorThemeExtension({
required this.bg,
required this.grayBg,
required this.text,
required this.lightText,
required this.lighterText,
required this.placeholder,
required this.border,
});
final Color? bg;
final Color? grayBg;
final Color? text;
final Color? lightText;
final Color? lighterText;
final Color? placeholder;
final Color? border;
static const light = ColorThemeExtension(
bg: Color(0xFFFFFFFF),
grayBg: Color(0xFFEEEEEE),
text: Color(0xFF222222),
lightText: Color(0xFF666666),
lighterText: Color(0xFF888888),
placeholder: Color(0xFFAAAAAA),
border: Color(0xFFCCCCCC),
);
static const dark = ColorThemeExtension(
bg: Color(0xFF252525),
grayBg: Color(0xFF141414),
text: Color(0xFFFFFFFF),
lightText: Color(0xFF999999),
lighterText: Color(0xFF777777),
placeholder: Color(0xFF555555),
border: Color(0xFF555555),
);
@override
ThemeExtension<ColorThemeExtension> copyWith(
{Color? bg,
Color? grayBg,
Color? text,
Color? lightText,
Color? lighterText,
Color? placeholder,
Color? border}) {
ThemeExtension<ColorThemeExtension> copyWith({Color? border}) {
return ColorThemeExtension(
bg: bg ?? this.bg,
grayBg: grayBg ?? this.grayBg,
text: text ?? this.text,
lightText: lightText ?? this.lightText,
lighterText: lighterText ?? this.lighterText,
placeholder: placeholder ?? this.placeholder,
border: border ?? this.border,
);
}
@@ -140,12 +105,6 @@ class ColorThemeExtension extends ThemeExtension<ColorThemeExtension> {
return this;
}
return ColorThemeExtension(
bg: Color.lerp(bg, other.bg, t),
grayBg: Color.lerp(grayBg, other.grayBg, t),
text: Color.lerp(text, other.text, t),
lightText: Color.lerp(lightText, other.lightText, t),
lighterText: Color.lerp(lighterText, other.lighterText, t),
placeholder: Color.lerp(placeholder, other.placeholder, t),
border: Color.lerp(border, other.border, t),
);
}
@@ -170,6 +129,14 @@ class MyTheme {
static ThemeData lightTheme = ThemeData(
brightness: Brightness.light,
backgroundColor: Color(0xFFFFFFFF),
scaffoldBackgroundColor: Color(0xFFEEEEEE),
textTheme: const TextTheme(
titleLarge: TextStyle(fontSize: 19, color: Colors.black87),
bodySmall: TextStyle(fontSize: 12, color: Colors.black54, height: 1.25),
bodyMedium: TextStyle(fontSize: 14, color: Colors.black54, height: 1.25),
),
hintColor: Color(0xFFAAAAAA),
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
tabBarTheme: const TabBarTheme(
@@ -177,6 +144,12 @@ class MyTheme {
),
splashColor: Colors.transparent,
highlightColor: Colors.transparent,
splashFactory: isDesktop ? NoSplash.splashFactory : null,
textButtonTheme: isDesktop
? TextButtonThemeData(
style: ButtonStyle(splashFactory: NoSplash.splashFactory),
)
: null,
).copyWith(
extensions: <ThemeExtension<dynamic>>[
ColorThemeExtension.light,
@@ -185,6 +158,13 @@ class MyTheme {
);
static ThemeData darkTheme = ThemeData(
brightness: Brightness.dark,
backgroundColor: Color(0xFF252525),
scaffoldBackgroundColor: Color(0xFF141414),
textTheme: const TextTheme(
titleLarge: TextStyle(fontSize: 19),
bodySmall: TextStyle(fontSize: 12, height: 1.25),
bodyMedium: TextStyle(fontSize: 14, height: 1.25)),
cardColor: Color(0xFF252525),
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
tabBarTheme: const TabBarTheme(
@@ -192,6 +172,12 @@ class MyTheme {
),
splashColor: Colors.transparent,
highlightColor: Colors.transparent,
splashFactory: isDesktop ? NoSplash.splashFactory : null,
textButtonTheme: isDesktop
? TextButtonThemeData(
style: ButtonStyle(splashFactory: NoSplash.splashFactory),
)
: null,
).copyWith(
extensions: <ThemeExtension<dynamic>>[
ColorThemeExtension.dark,
@@ -1073,14 +1059,38 @@ void connect(BuildContext context, String id,
assert(!(isFileTransfer && isTcpTunneling && isRDP),
"more than one connect type");
FocusScopeNode currentFocus = FocusScope.of(context);
if (isFileTransfer) {
await rustDeskWinManager.newFileTransfer(id);
} else if (isTcpTunneling || isRDP) {
await rustDeskWinManager.newPortForward(id, isRDP);
if (isDesktop) {
if (isFileTransfer) {
await rustDeskWinManager.newFileTransfer(id);
} else if (isTcpTunneling || isRDP) {
await rustDeskWinManager.newPortForward(id, isRDP);
} else {
await rustDeskWinManager.newRemoteDesktop(id);
}
} else {
await rustDeskWinManager.newRemoteDesktop(id);
if (isFileTransfer) {
if (!await PermissionManager.check("file")) {
if (!await PermissionManager.request("file")) {
return;
}
}
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();
}

View File

@@ -1,16 +1,18 @@
import 'package:contextmenu/contextmenu.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hbb/common/widgets/peer_widget.dart';
import 'package:flutter_hbb/common/widgets/peers_view.dart';
import 'package:flutter_hbb/models/ab_model.dart';
import 'package:get/get.dart';
import 'package:provider/provider.dart';
import '../../common.dart';
import '../../desktop/pages/desktop_home_page.dart';
import '../../mobile/pages/settings_page.dart';
import '../../models/platform_model.dart';
class AddressBook extends StatefulWidget {
const AddressBook({Key? key}) : super(key: key);
final EdgeInsets? menuPadding;
const AddressBook({Key? key, this.menuPadding}) : super(key: key);
@override
State<StatefulWidget> createState() {
@@ -37,11 +39,16 @@ class _AddressBookState extends State<AddressBook> {
});
handleLogin() {
loginDialog().then((success) {
if (success) {
setState(() {});
}
});
// TODO refactor login dialog for desktop and mobile
if (isDesktop) {
loginDialog().then((success) {
if (success) {
setState(() {});
}
});
} else {
showLogin(gFFI.dialogManager);
}
}
Future<Widget> buildAddressBook(BuildContext context) async {
@@ -108,7 +115,8 @@ class _AddressBookState extends State<AddressBook> {
Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
side: const BorderSide(color: MyTheme.grayBg)),
side: BorderSide(
color: Theme.of(context).scaffoldBackgroundColor)),
child: Container(
width: 200,
height: double.infinity,
@@ -174,7 +182,9 @@ class _AddressBookState extends State<AddressBook> {
Expanded(
child: Align(
alignment: Alignment.topLeft,
child: AddressBookPeerWidget()),
child: AddressBookPeersView(
menuPadding: widget.menuPadding,
)),
)
],
));
@@ -206,7 +216,8 @@ class _AddressBookState extends State<AddressBook> {
child: Text(
tagName,
style: TextStyle(
color: rxTags.contains(tagName) ? MyTheme.white : null),
color:
rxTags.contains(tagName) ? Colors.white : null), // TODO
),
),
),

View File

@@ -72,3 +72,86 @@ void changeIdDialog() {
);
});
}
void changeWhiteList({Function()? callback}) async {
var newWhiteList = (await bind.mainGetOption(key: 'whitelist')).split(',');
var newWhiteListField = newWhiteList.join('\n');
var controller = TextEditingController(text: newWhiteListField);
var msg = "";
var isInProgress = false;
gFFI.dialogManager.show((setState, close) {
return CustomAlertDialog(
title: Text(translate("IP Whitelisting")),
content: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(translate("whitelist_sep")),
const SizedBox(
height: 8.0,
),
Row(
children: [
Expanded(
child: TextField(
maxLines: null,
decoration: InputDecoration(
border: const OutlineInputBorder(),
errorText: msg.isEmpty ? null : translate(msg),
),
controller: controller,
focusNode: FocusNode()..requestFocus()),
),
],
),
const SizedBox(
height: 4.0,
),
Offstage(
offstage: !isInProgress, child: const LinearProgressIndicator())
],
),
actions: [
TextButton(onPressed: close, child: Text(translate("Cancel"))),
TextButton(
onPressed: () async {
await bind.mainSetOption(key: 'whitelist', value: '');
callback?.call();
close();
},
child: Text(translate("Clear"))),
TextButton(
onPressed: () async {
setState(() {
msg = "";
isInProgress = true;
});
newWhiteListField = controller.text.trim();
var newWhiteList = "";
if (newWhiteListField.isEmpty) {
// pass
} else {
final ips =
newWhiteListField.trim().split(RegExp(r"[\s,;\n]+"));
// test ip
final ipMatch = RegExp(r"^\d+\.\d+\.\d+\.\d+$");
for (final ip in ips) {
if (!ipMatch.hasMatch(ip)) {
msg = "${translate("Invalid IP")} $ip";
setState(() {
isInProgress = false;
});
return;
}
}
newWhiteList = ips.join(',');
}
await bind.mainSetOption(key: 'whitelist', value: newWhiteList);
callback?.call();
close();
},
child: Text(translate("OK"))),
],
onCancel: close,
);
});
}

View File

@@ -26,7 +26,7 @@ class DraggableChatWindow extends StatelessWidget {
position: position,
width: width,
height: height,
builder: (_, onPanUpdate) {
builder: (context, onPanUpdate) {
return isIOS
? ChatPage(chatModel: chatModel)
: Scaffold(
@@ -35,16 +35,16 @@ class DraggableChatWindow extends StatelessWidget {
onPanUpdate: onPanUpdate,
appBar: isDesktop
? _buildDesktopAppBar()
: _buildMobileAppBar(),
: _buildMobileAppBar(context),
),
body: ChatPage(chatModel: chatModel),
);
});
}
Widget _buildMobileAppBar() {
Widget _buildMobileAppBar(BuildContext context) {
return Container(
color: MyTheme.accent50,
color: Theme.of(context).colorScheme.primary,
height: 50,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
@@ -169,17 +169,17 @@ class DraggableMobileActions extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
IconButton(
color: MyTheme.white,
color: Colors.white,
onPressed: onBackPressed,
splashRadius: 20,
icon: const Icon(Icons.arrow_back)),
IconButton(
color: MyTheme.white,
color: Colors.white,
onPressed: onHomePressed,
splashRadius: 20,
icon: const Icon(Icons.home)),
IconButton(
color: MyTheme.white,
color: Colors.white,
onPressed: onRecentPressed,
splashRadius: 20,
icon: const Icon(Icons.more_horiz)),
@@ -190,7 +190,7 @@ class DraggableMobileActions extends StatelessWidget {
endIndent: 10,
),
IconButton(
color: MyTheme.white,
color: Colors.white,
onPressed: onHidePressed,
splashRadius: 20,
icon: const Icon(Icons.keyboard_arrow_down)),

View File

@@ -1,6 +1,7 @@
import 'package:contextmenu/contextmenu.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hbb/consts.dart';
import 'package:get/get.dart';
import '../../common.dart';
@@ -14,7 +15,7 @@ import '../../desktop/widgets/popup_menu.dart';
class _PopupMenuTheme {
static const Color commonColor = MyTheme.accent;
// kMinInteractiveDimension
static const double height = 25.0;
static const double height = 20.0;
static const double dividerHeight = 3.0;
}
@@ -73,12 +74,18 @@ class _PeerCardState extends State<_PeerCard>
_showPeerMenu(peer.id);
},
child: ListTile(
contentPadding: const EdgeInsets.only(left: 12),
contentPadding: const EdgeInsets.only(left: 12), //
subtitle: Text('${peer.username}@${peer.hostname}'),
title: Text(peer.alias.isEmpty ? formatID(peer.id) : peer.alias),
title: Row(children: [
getOnline(4, peer.online),
Text(peer.alias.isEmpty ? formatID(peer.id) : peer.alias)
]),
leading: Container(
decoration: BoxDecoration(
color: str2color('${peer.id}${peer.platform}', 0x7f),
borderRadius: BorderRadius.circular(4),
),
padding: const EdgeInsets.all(6),
color: str2color('${peer.id}${peer.platform}', 0x7f),
child: getPlatformImage(peer.platform)),
trailing: InkWell(
child: const Padding(
@@ -105,7 +112,9 @@ class _PeerCardState extends State<_PeerCard>
return MouseRegion(
onEnter: (evt) {
deco.value = BoxDecoration(
border: Border.all(color: MyTheme.button, width: _borderWidth),
border: Border.all(
color: Theme.of(context).colorScheme.secondary,
width: _borderWidth),
borderRadius: peerCardUiType.value == PeerUiType.grid
? BorderRadius.circular(_cardRadis)
: null);
@@ -127,8 +136,10 @@ class _PeerCardState extends State<_PeerCard>
Widget _buildPeerTile(
BuildContext context, Peer peer, Rx<BoxDecoration?> deco) {
final greyStyle =
TextStyle(fontSize: 12, color: MyTheme.color(context).lighterText);
final greyStyle = TextStyle(
fontSize: 11,
color: Theme.of(context).textTheme.titleLarge?.color?.withOpacity(0.6));
final alias = bind.mainGetPeerOptionSync(id: peer.id, key: 'alias');
return Obx(
() => Container(
foregroundDecoration: deco.value,
@@ -144,66 +155,36 @@ class _PeerCardState extends State<_PeerCard>
),
Expanded(
child: Container(
decoration: BoxDecoration(color: MyTheme.color(context).bg),
decoration:
BoxDecoration(color: Theme.of(context).backgroundColor),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Row(children: [
Padding(
padding: const EdgeInsets.fromLTRB(0, 4, 4, 4),
child: CircleAvatar(
radius: 5,
backgroundColor: peer.online
? Colors.green
: Colors.yellow)),
Text(
formatID(peer.id),
style:
const TextStyle(fontWeight: FontWeight.w400),
),
]),
getOnline(8, peer.online),
Expanded(
child: Text(
alias.isEmpty ? formatID(peer.id) : alias,
overflow: TextOverflow.ellipsis,
)),
]).marginOnly(bottom: 2),
Align(
alignment: Alignment.centerLeft,
child: FutureBuilder<String>(
future: bind.mainGetPeerOption(
id: peer.id, key: 'alias'),
builder: (_, snapshot) {
if (snapshot.hasData) {
final name = snapshot.data!.isEmpty
? '${peer.username}@${peer.hostname}'
: snapshot.data!;
return Tooltip(
message: name,
waitDuration: const Duration(seconds: 1),
child: Text(
name,
style: greyStyle,
textAlign: TextAlign.start,
overflow: TextOverflow.ellipsis,
),
);
} else {
// alias has not arrived
return Text(
'${peer.username}@${peer.hostname}',
style: greyStyle,
textAlign: TextAlign.start,
overflow: TextOverflow.ellipsis,
);
}
},
child: Text(
'${peer.username}@${peer.hostname}',
style: greyStyle,
textAlign: TextAlign.start,
overflow: TextOverflow.ellipsis,
),
),
],
),
).marginOnly(top: 2),
),
_actionMore(peer),
],
).paddingSymmetric(horizontal: 4.0),
).paddingOnly(left: 10.0, top: 3.0),
),
)
],
@@ -268,21 +249,19 @@ class _PeerCardState extends State<_PeerCard>
),
),
Container(
color: MyTheme.color(context).bg,
color: Theme.of(context).backgroundColor,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(children: [
Padding(
padding: const EdgeInsets.fromLTRB(0, 4, 8, 4),
child: CircleAvatar(
radius: 5,
backgroundColor: peer.online
? Colors.green
: Colors.yellow)),
Text(
peer.alias.isEmpty ? formatID(peer.id) : peer.alias)
]).paddingSymmetric(vertical: 8),
Expanded(
child: Row(children: [
getOnline(8, peer.online),
Expanded(
child: Text(
peer.alias.isEmpty ? formatID(peer.id) : peer.alias,
overflow: TextOverflow.ellipsis,
)),
]).paddingSymmetric(vertical: 8)),
_actionMore(peer),
],
).paddingSymmetric(horizontal: 12.0),
@@ -308,13 +287,21 @@ class _PeerCardState extends State<_PeerCard>
child: CircleAvatar(
radius: 14,
backgroundColor: _iconMoreHover.value
? MyTheme.color(context).grayBg!
: MyTheme.color(context).bg!,
? Theme.of(context).scaffoldBackgroundColor
: Theme.of(context).backgroundColor,
// ? Theme.of(context).scaffoldBackgroundColor!
// : Theme.of(context).backgroundColor!,
child: Icon(Icons.more_vert,
size: 18,
color: _iconMoreHover.value
? MyTheme.color(context).text
: MyTheme.color(context).lightText))));
? Theme.of(context).textTheme.titleLarge?.color
: Theme.of(context)
.textTheme
.titleLarge
?.color
?.withOpacity(0.5)))));
// ? MyTheme.color(context).text
// : MyTheme.color(context).lightText))));
/// Show the peer menu and handle user's choice.
/// User might remove the peer or send a file to the peer.
@@ -333,8 +320,10 @@ class _PeerCardState extends State<_PeerCard>
abstract class BasePeerCard extends StatelessWidget {
final Peer peer;
final EdgeInsets? menuPadding;
BasePeerCard({required this.peer, Key? key}) : super(key: key);
BasePeerCard({required this.peer, this.menuPadding, Key? key})
: super(key: key);
@override
Widget build(BuildContext context) {
@@ -379,6 +368,7 @@ abstract class BasePeerCard extends StatelessWidget {
isRDP: isRDP,
);
},
padding: menuPadding,
dismissOnClicked: true,
);
}
@@ -428,17 +418,25 @@ abstract class BasePeerCard extends StatelessWidget {
Expanded(
child: Align(
alignment: Alignment.centerRight,
child: IconButton(
padding: EdgeInsets.zero,
icon: const Icon(Icons.edit),
onPressed: () => _rdpDialog(id),
),
child: Transform.scale(
scale: 0.8,
child: IconButton(
icon: const Icon(Icons.edit),
padding: EdgeInsets.zero,
onPressed: () {
if (Navigator.canPop(context)) {
Navigator.pop(context);
}
_rdpDialog(id);
},
)),
))
],
)),
proc: () {
connect(context, id, isRDP: true);
},
padding: menuPadding,
dismissOnClicked: true,
);
}
@@ -453,6 +451,7 @@ abstract class BasePeerCard extends StatelessWidget {
proc: () {
bind.mainWol(id: id);
},
padding: menuPadding,
dismissOnClicked: true,
);
}
@@ -461,6 +460,7 @@ abstract class BasePeerCard extends StatelessWidget {
Future<MenuEntryBase<String>> _forceAlwaysRelayAction(String id) async {
const option = 'force-always-relay';
return MenuEntrySwitch<String>(
switchType: SwitchType.scheckbox,
text: translate('Always connect via relay'),
getter: () async {
return (await bind.mainGetPeerOption(id: id, key: option)).isNotEmpty;
@@ -475,6 +475,7 @@ abstract class BasePeerCard extends StatelessWidget {
}
await bind.mainSetPeerOption(id: id, key: option, value: value);
},
padding: menuPadding,
dismissOnClicked: true,
);
}
@@ -489,13 +490,15 @@ abstract class BasePeerCard extends StatelessWidget {
proc: () {
_rename(id, isAddressBook);
},
padding: menuPadding,
dismissOnClicked: true,
);
}
@protected
MenuEntryBase<String> _removeAction(
String id, Future<void> Function() reloadFunc) {
String id, Future<void> Function() reloadFunc,
{bool isLan = false}) {
return MenuEntryButton<String>(
childBuilder: (TextStyle? style) => Text(
translate('Remove'),
@@ -503,12 +506,16 @@ abstract class BasePeerCard extends StatelessWidget {
),
proc: () {
() async {
await bind.mainRemovePeer(id: id);
if (isLan) {
// TODO
} else {
await bind.mainRemovePeer(id: id);
}
removePreference(id);
await reloadFunc();
// Get.forceAppUpdate(); // TODO use inner model / state
}();
},
padding: menuPadding,
dismissOnClicked: true,
);
}
@@ -523,6 +530,7 @@ abstract class BasePeerCard extends StatelessWidget {
proc: () {
bind.mainForgetPassword(id: id);
},
padding: menuPadding,
dismissOnClicked: true,
);
}
@@ -543,6 +551,7 @@ abstract class BasePeerCard extends StatelessWidget {
}
}();
},
padding: menuPadding,
dismissOnClicked: true,
);
}
@@ -561,10 +570,10 @@ abstract class BasePeerCard extends StatelessWidget {
if (favs.remove(id)) {
await bind.mainStoreFav(favs: favs);
await reloadFunc();
// Get.forceAppUpdate(); // TODO use inner model / state
}
}();
},
padding: menuPadding,
dismissOnClicked: true,
);
}
@@ -606,8 +615,6 @@ abstract class BasePeerCard extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding:
const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: Form(
child: TextFormField(
controller: controller,
@@ -634,7 +641,8 @@ abstract class BasePeerCard extends StatelessWidget {
}
class RecentPeerCard extends BasePeerCard {
RecentPeerCard({required Peer peer, Key? key}) : super(peer: peer, key: key);
RecentPeerCard({required Peer peer, EdgeInsets? menuPadding, Key? key})
: super(peer: peer, menuPadding: menuPadding, key: key);
@override
Future<List<MenuEntryBase<String>>> _buildMenuItems(
@@ -642,15 +650,13 @@ class RecentPeerCard extends BasePeerCard {
final List<MenuEntryBase<String>> menuItems = [
_connectAction(context, peer),
_transferFileAction(context, peer.id),
_tcpTunnelingAction(context, peer.id),
];
MenuEntryBase<String>? rdpAction;
if (peer.platform == 'Windows') {
rdpAction = _rdpAction(context, peer.id);
if (isDesktop) {
menuItems.add(_tcpTunnelingAction(context, peer.id));
}
menuItems.add(await _forceAlwaysRelayAction(peer.id));
if (rdpAction != null) {
menuItems.add(rdpAction);
if (peer.platform == 'Windows') {
menuItems.add(_rdpAction(context, peer.id));
}
menuItems.add(_wolAction(peer.id));
menuItems.add(MenuEntryDivider());
@@ -658,15 +664,17 @@ class RecentPeerCard extends BasePeerCard {
menuItems.add(_removeAction(peer.id, () async {
await bind.mainLoadRecentPeers();
}));
menuItems.add(_unrememberPasswordAction(peer.id));
if (await bind.mainPeerHasPassword(id: peer.id)) {
menuItems.add(_unrememberPasswordAction(peer.id));
}
menuItems.add(_addFavAction(peer.id));
return menuItems;
}
}
class FavoritePeerCard extends BasePeerCard {
FavoritePeerCard({required Peer peer, Key? key})
: super(peer: peer, key: key);
FavoritePeerCard({required Peer peer, EdgeInsets? menuPadding, Key? key})
: super(peer: peer, menuPadding: menuPadding, key: key);
@override
Future<List<MenuEntryBase<String>>> _buildMenuItems(
@@ -674,15 +682,13 @@ class FavoritePeerCard extends BasePeerCard {
final List<MenuEntryBase<String>> menuItems = [
_connectAction(context, peer),
_transferFileAction(context, peer.id),
_tcpTunnelingAction(context, peer.id),
];
MenuEntryBase<String>? rdpAction;
if (peer.platform == 'Windows') {
rdpAction = _rdpAction(context, peer.id);
if (isDesktop) {
menuItems.add(_tcpTunnelingAction(context, peer.id));
}
menuItems.add(await _forceAlwaysRelayAction(peer.id));
if (rdpAction != null) {
menuItems.add(rdpAction);
if (peer.platform == 'Windows') {
menuItems.add(_rdpAction(context, peer.id));
}
menuItems.add(_wolAction(peer.id));
menuItems.add(MenuEntryDivider());
@@ -690,7 +696,9 @@ class FavoritePeerCard extends BasePeerCard {
menuItems.add(_removeAction(peer.id, () async {
await bind.mainLoadFavPeers();
}));
menuItems.add(_unrememberPasswordAction(peer.id));
if (await bind.mainPeerHasPassword(id: peer.id)) {
menuItems.add(_unrememberPasswordAction(peer.id));
}
menuItems.add(_rmFavAction(peer.id, () async {
await bind.mainLoadFavPeers();
}));
@@ -699,8 +707,8 @@ class FavoritePeerCard extends BasePeerCard {
}
class DiscoveredPeerCard extends BasePeerCard {
DiscoveredPeerCard({required Peer peer, Key? key})
: super(peer: peer, key: key);
DiscoveredPeerCard({required Peer peer, EdgeInsets? menuPadding, Key? key})
: super(peer: peer, menuPadding: menuPadding, key: key);
@override
Future<List<MenuEntryBase<String>>> _buildMenuItems(
@@ -708,30 +716,24 @@ class DiscoveredPeerCard extends BasePeerCard {
final List<MenuEntryBase<String>> menuItems = [
_connectAction(context, peer),
_transferFileAction(context, peer.id),
_tcpTunnelingAction(context, peer.id),
];
MenuEntryBase<String>? rdpAction;
if (peer.platform == 'Windows') {
rdpAction = _rdpAction(context, peer.id);
if (isDesktop) {
menuItems.add(_tcpTunnelingAction(context, peer.id));
}
menuItems.add(await _forceAlwaysRelayAction(peer.id));
if (rdpAction != null) {
menuItems.add(rdpAction);
if (peer.platform == 'Windows') {
menuItems.add(_rdpAction(context, peer.id));
}
menuItems.add(_wolAction(peer.id));
menuItems.add(MenuEntryDivider());
menuItems.add(_renameAction(peer.id, false));
menuItems.add(_removeAction(peer.id, () async {
await bind.mainLoadLanPeers();
}));
menuItems.add(_unrememberPasswordAction(peer.id));
menuItems.add(_removeAction(peer.id, () async {}));
return menuItems;
}
}
class AddressBookPeerCard extends BasePeerCard {
AddressBookPeerCard({required Peer peer, Key? key})
: super(peer: peer, key: key);
AddressBookPeerCard({required Peer peer, EdgeInsets? menuPadding, Key? key})
: super(peer: peer, menuPadding: menuPadding, key: key);
@override
Future<List<MenuEntryBase<String>>> _buildMenuItems(
@@ -739,22 +741,21 @@ class AddressBookPeerCard extends BasePeerCard {
final List<MenuEntryBase<String>> menuItems = [
_connectAction(context, peer),
_transferFileAction(context, peer.id),
_tcpTunnelingAction(context, peer.id),
];
MenuEntryBase<String>? rdpAction;
if (peer.platform == 'Windows') {
rdpAction = _rdpAction(context, peer.id);
if (isDesktop) {
menuItems.add(_tcpTunnelingAction(context, peer.id));
}
menuItems.add(await _forceAlwaysRelayAction(peer.id));
if (rdpAction != null) {
menuItems.add(rdpAction);
if (peer.platform == 'Windows') {
menuItems.add(_rdpAction(context, peer.id));
}
menuItems.add(_wolAction(peer.id));
menuItems.add(MenuEntryDivider());
menuItems.add(_renameAction(peer.id, false));
menuItems.add(_removeAction(peer.id, () async {}));
menuItems.add(_unrememberPasswordAction(peer.id));
menuItems.add(_addFavAction(peer.id));
if (await bind.mainPeerHasPassword(id: peer.id)) {
menuItems.add(_unrememberPasswordAction(peer.id));
}
menuItems.add(_editTagAction(peer.id));
return menuItems;
}
@@ -762,7 +763,8 @@ class AddressBookPeerCard extends BasePeerCard {
@protected
@override
MenuEntryBase<String> _removeAction(
String id, Future<void> Function() reloadFunc) {
String id, Future<void> Function() reloadFunc,
{bool isLan = false}) {
return MenuEntryButton<String>(
childBuilder: (TextStyle? style) => Text(
translate('Remove'),
@@ -774,6 +776,7 @@ class AddressBookPeerCard extends BasePeerCard {
await gFFI.abModel.updateAb();
}();
},
padding: super.menuPadding,
dismissOnClicked: true,
);
}
@@ -788,6 +791,7 @@ class AddressBookPeerCard extends BasePeerCard {
proc: () {
_abEditTag(id);
},
padding: super.menuPadding,
dismissOnClicked: true,
);
}
@@ -869,7 +873,7 @@ class AddressBookPeerCard extends BasePeerCard {
child: Text(
tagName,
style: TextStyle(
color: rxTags.contains(tagName) ? MyTheme.white : null),
color: rxTags.contains(tagName) ? Colors.white : null),
),
),
),
@@ -995,3 +999,13 @@ void _rdpDialog(String id) async {
);
});
}
Widget getOnline(double rightPadding, bool online) {
return Tooltip(
message: translate(online ? 'Online' : 'Offline'),
waitDuration: const Duration(seconds: 1),
child: Padding(
padding: EdgeInsets.fromLTRB(0, 4, rightPadding, 4),
child: CircleAvatar(
radius: 3, backgroundColor: online ? Colors.green : kColorWarn)));
}

View File

@@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_hbb/common/widgets/peer_widget.dart';
import 'package:flutter_hbb/common/widgets/peercard_widget.dart';
import 'package:flutter_hbb/common/widgets/peers_view.dart';
import 'package:flutter_hbb/common/widgets/peer_card.dart';
import 'package:flutter_hbb/consts.dart';
import 'package:get/get.dart';
@@ -42,9 +42,6 @@ class _PeerTabPageState extends State<PeerTabPage>
// hard code for now
Future<void> _handleTabSelection(int index) async {
// reset search text
peerSearchText.value = "";
peerSearchTextController.clear();
_tabIndex.value = index;
await bind.mainSetLocalOption(
key: 'peer-tab-index', value: index.toString());
@@ -101,6 +98,7 @@ class _PeerTabPageState extends State<PeerTabPage>
}
Widget _createTabBar(BuildContext context) {
final textColor = Theme.of(context).textTheme.titleLarge?.color;
return ListView(
scrollDirection: Axis.horizontal,
shrinkWrap: true,
@@ -111,9 +109,9 @@ class _PeerTabPageState extends State<PeerTabPage>
padding: const EdgeInsets.symmetric(horizontal: 8),
decoration: BoxDecoration(
color: _tabIndex.value == t.key
? MyTheme.color(context).bg
? Theme.of(context).backgroundColor
: null,
borderRadius: BorderRadius.circular(2),
borderRadius: BorderRadius.circular(isDesktop ? 2 : 6),
),
child: Align(
alignment: Alignment.center,
@@ -123,9 +121,9 @@ class _PeerTabPageState extends State<PeerTabPage>
style: TextStyle(
height: 1,
fontSize: 14,
color: _tabIndex.value == t.key
? MyTheme.color(context).text
: MyTheme.color(context).lightText),
color:
_tabIndex.value == t.key ? textColor : textColor
?..withOpacity(0.5)),
),
)),
onTap: () async => await _handleTabSelection(t.key),
@@ -147,7 +145,8 @@ class _PeerTabPageState extends State<PeerTabPage>
}
Widget _createPeerViewTypeSwitch(BuildContext context) {
final activeDeco = BoxDecoration(color: MyTheme.color(context).bg);
final textColor = Theme.of(context).textTheme.titleLarge?.color;
final activeDeco = BoxDecoration(color: Theme.of(context).backgroundColor);
return Row(
children: [PeerUiType.grid, PeerUiType.list]
.map((type) => Obx(
@@ -166,9 +165,9 @@ class _PeerTabPageState extends State<PeerTabPage>
? Icons.grid_view_rounded
: Icons.list,
size: 18,
color: peerCardUiType.value == type
? MyTheme.color(context).text
: MyTheme.color(context).lightText,
color:
peerCardUiType.value == type ? textColor : textColor
?..withOpacity(0.5),
)),
),
))
@@ -199,9 +198,9 @@ class _PeerSearchBarState extends State<PeerSearchBar> {
drawer = true;
});
},
icon: const Icon(
icon: Icon(
Icons.search_rounded,
color: MyTheme.dark,
color: Theme.of(context).hintColor,
));
}
@@ -212,7 +211,7 @@ class _PeerSearchBarState extends State<PeerSearchBar> {
return Container(
width: 120,
decoration: BoxDecoration(
color: MyTheme.color(context).bg,
color: Theme.of(context).backgroundColor,
borderRadius: BorderRadius.circular(6),
),
child: Obx(() => Row(
@@ -222,7 +221,7 @@ class _PeerSearchBarState extends State<PeerSearchBar> {
children: [
Icon(
Icons.search_rounded,
color: MyTheme.color(context).placeholder,
color: Theme.of(context).hintColor,
).marginSymmetric(horizontal: 4),
Expanded(
child: TextField(
@@ -234,7 +233,11 @@ class _PeerSearchBarState extends State<PeerSearchBar> {
focusNode: focusNode,
textAlign: TextAlign.start,
maxLines: 1,
cursorColor: MyTheme.color(context).lightText,
cursorColor: Theme.of(context)
.textTheme
.titleLarge
?.color
?.withOpacity(0.5),
cursorHeight: 18,
cursorWidth: 1,
style: const TextStyle(fontSize: 14),
@@ -244,8 +247,7 @@ class _PeerSearchBarState extends State<PeerSearchBar> {
hintText:
focused.value ? null : translate("Search ID"),
hintStyle: TextStyle(
fontSize: 14,
color: MyTheme.color(context).placeholder),
fontSize: 14, color: Theme.of(context).hintColor),
border: InputBorder.none,
isDense: true,
),
@@ -262,9 +264,9 @@ class _PeerSearchBarState extends State<PeerSearchBar> {
drawer = false;
});
},
icon: const Icon(
icon: Icon(
Icons.close,
color: MyTheme.dark,
color: Theme.of(context).hintColor,
)),
],
),

View File

@@ -11,34 +11,34 @@ import 'package:window_manager/window_manager.dart';
import '../../common.dart';
import '../../models/peer_model.dart';
import '../../models/platform_model.dart';
import 'peercard_widget.dart';
import 'peer_card.dart';
typedef OffstageFunc = bool Function(Peer peer);
typedef PeerCardWidgetFunc = Widget Function(Peer peer);
typedef PeerCardBuilder = BasePeerCard Function(Peer peer);
/// for peer search text, global obs value
final peerSearchText = "".obs;
final peerSearchTextController =
TextEditingController(text: peerSearchText.value);
class _PeerWidget extends StatefulWidget {
class _PeersView extends StatefulWidget {
final Peers peers;
final OffstageFunc offstageFunc;
final PeerCardWidgetFunc peerCardWidgetFunc;
final PeerCardBuilder peerCardBuilder;
const _PeerWidget(
const _PeersView(
{required this.peers,
required this.offstageFunc,
required this.peerCardWidgetFunc,
required this.peerCardBuilder,
Key? key})
: super(key: key);
@override
_PeerWidgetState createState() => _PeerWidgetState();
_PeersViewState createState() => _PeersViewState();
}
/// State for the peer widget.
class _PeerWidgetState extends State<_PeerWidget> with WindowListener {
class _PeersViewState extends State<_PeersView> with WindowListener {
static const int _maxQueryCount = 3;
final space = isDesktop ? 12.0 : 8.0;
final _curPeers = <String>{};
@@ -60,7 +60,7 @@ class _PeerWidgetState extends State<_PeerWidget> with WindowListener {
return width;
}();
_PeerWidgetState() {
_PeersViewState() {
_startCheckOnlines();
}
@@ -119,7 +119,7 @@ class _PeerWidgetState extends State<_PeerWidget> with WindowListener {
}
_lastChangeTime = DateTime.now();
},
child: widget.peerCardWidgetFunc(peer),
child: widget.peerCardBuilder(peer),
);
cards.add(Offstage(
key: ValueKey("off${peer.id}"),
@@ -198,40 +198,41 @@ class _PeerWidgetState extends State<_PeerWidget> with WindowListener {
}
}
abstract class BasePeerWidget extends StatelessWidget {
abstract class BasePeersView extends StatelessWidget {
final String name;
final String loadEvent;
final OffstageFunc offstageFunc;
final PeerCardWidgetFunc peerCardWidgetFunc;
final PeerCardBuilder peerCardBuilder;
final List<Peer> initPeers;
const BasePeerWidget({
const BasePeersView({
Key? key,
required this.name,
required this.loadEvent,
required this.offstageFunc,
required this.peerCardWidgetFunc,
required this.peerCardBuilder,
required this.initPeers,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return _PeerWidget(
return _PeersView(
peers: Peers(name: name, loadEvent: loadEvent, peers: initPeers),
offstageFunc: offstageFunc,
peerCardWidgetFunc: peerCardWidgetFunc);
peerCardBuilder: peerCardBuilder);
}
}
class RecentPeerWidget extends BasePeerWidget {
RecentPeerWidget({Key? key})
class RecentPeersView extends BasePeersView {
RecentPeersView({Key? key, EdgeInsets? menuPadding})
: super(
key: key,
name: 'recent peer',
loadEvent: 'load_recent_peers',
offstageFunc: (Peer peer) => false,
peerCardWidgetFunc: (Peer peer) => RecentPeerCard(
peerCardBuilder: (Peer peer) => RecentPeerCard(
peer: peer,
menuPadding: menuPadding,
),
initPeers: [],
);
@@ -244,15 +245,16 @@ class RecentPeerWidget extends BasePeerWidget {
}
}
class FavoritePeerWidget extends BasePeerWidget {
FavoritePeerWidget({Key? key})
class FavoritePeersView extends BasePeersView {
FavoritePeersView({Key? key, EdgeInsets? menuPadding})
: super(
key: key,
name: 'favorite peer',
loadEvent: 'load_fav_peers',
offstageFunc: (Peer peer) => false,
peerCardWidgetFunc: (Peer peer) => FavoritePeerCard(
peerCardBuilder: (Peer peer) => FavoritePeerCard(
peer: peer,
menuPadding: menuPadding,
),
initPeers: [],
);
@@ -265,15 +267,16 @@ class FavoritePeerWidget extends BasePeerWidget {
}
}
class DiscoveredPeerWidget extends BasePeerWidget {
DiscoveredPeerWidget({Key? key})
class DiscoveredPeersView extends BasePeersView {
DiscoveredPeersView({Key? key, EdgeInsets? menuPadding})
: super(
key: key,
name: 'discovered peer',
loadEvent: 'load_lan_peers',
offstageFunc: (Peer peer) => false,
peerCardWidgetFunc: (Peer peer) => DiscoveredPeerCard(
peerCardBuilder: (Peer peer) => DiscoveredPeerCard(
peer: peer,
menuPadding: menuPadding,
),
initPeers: [],
);
@@ -286,16 +289,17 @@ class DiscoveredPeerWidget extends BasePeerWidget {
}
}
class AddressBookPeerWidget extends BasePeerWidget {
AddressBookPeerWidget({Key? key})
class AddressBookPeersView extends BasePeersView {
AddressBookPeersView({Key? key, EdgeInsets? menuPadding})
: super(
key: key,
name: 'address book peer',
loadEvent: 'load_address_book_peers',
offstageFunc: (Peer peer) =>
!_hitTag(gFFI.abModel.selectedTags, peer.tags),
peerCardWidgetFunc: (Peer peer) => AddressBookPeerCard(
peerCardBuilder: (Peer peer) => AddressBookPeerCard(
peer: peer,
menuPadding: menuPadding,
),
initPeers: _loadPeers(),
);

View File

@@ -11,12 +11,16 @@ const String kAppTypeDesktopPortForward = "port forward";
const String kTabLabelHomePage = "Home";
const String kTabLabelSettingPage = "Settings";
const Color kColorWarn = Color.fromARGB(255, 245, 133, 59);
const int kMobileDefaultDisplayWidth = 720;
const int kMobileDefaultDisplayHeight = 1280;
const int kDesktopDefaultDisplayWidth = 1080;
const int kDesktopDefaultDisplayHeight = 720;
const Size kConnectionManagerWindowSize = Size(300, 400);
/// [kDefaultScrollAmountMultiplier] indicates how many rows can be scrolled after a minimum scroll action of mouse
const kDefaultScrollAmountMultiplier = 5.0;
const kDefaultScrollDuration = Duration(milliseconds: 50);

View File

@@ -5,14 +5,16 @@ import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_hbb/common/widgets/address_book.dart';
import 'package:flutter_hbb/consts.dart';
import 'package:get/get.dart';
import 'package:url_launcher/url_launcher_string.dart';
import '../../common.dart';
import '../../common/formatter/id_formatter.dart';
import '../../common/widgets/peer_tab_page.dart';
import '../../common/widgets/peer_widget.dart';
import '../../common/widgets/peers_view.dart';
import '../../models/platform_model.dart';
import '../widgets/button.dart';
/// Connection page for connecting to a remote peer.
class ConnectionPage extends StatefulWidget {
@@ -74,10 +76,18 @@ class _ConnectionPageState extends State<ConnectionPage> {
translate('Address Book')
],
children: [
RecentPeerWidget(),
FavoritePeerWidget(),
DiscoveredPeerWidget(),
const AddressBook(),
RecentPeersView(
menuPadding: EdgeInsets.only(left: 12.0, right: 3.0),
),
FavoritePeersView(
menuPadding: EdgeInsets.only(left: 12.0, right: 3.0),
),
DiscoveredPeersView(
menuPadding: EdgeInsets.only(left: 12.0, right: 3.0),
),
const AddressBook(
menuPadding: EdgeInsets.only(left: 12.0, right: 3.0),
),
],
)),
],
@@ -100,10 +110,6 @@ class _ConnectionPageState extends State<ConnectionPage> {
/// UI for the remote ID TextField.
/// Search for a peer and connect to it if the id exists.
Widget _buildRemoteIDTextField(BuildContext context) {
RxBool ftHover = false.obs;
RxBool ftPressed = false.obs;
RxBool connHover = false.obs;
RxBool connPressed = false.obs;
RxBool inputFocused = false.obs;
FocusNode focusNode = FocusNode();
focusNode.addListener(() {
@@ -113,7 +119,7 @@ class _ConnectionPageState extends State<ConnectionPage> {
width: 320 + 20 * 2,
padding: const EdgeInsets.fromLTRB(20, 24, 20, 22),
decoration: BoxDecoration(
color: MyTheme.color(context).bg,
color: Theme.of(context).backgroundColor,
borderRadius: const BorderRadius.all(Radius.circular(13)),
),
child: Ink(
@@ -123,7 +129,10 @@ class _ConnectionPageState extends State<ConnectionPage> {
children: [
Text(
translate('Control Remote Desktop'),
style: const TextStyle(fontSize: 19, height: 1),
style: Theme.of(context)
.textTheme
.titleLarge
?.merge(TextStyle(height: 1)),
),
],
).marginOnly(bottom: 15),
@@ -142,13 +151,12 @@ class _ConnectionPageState extends State<ConnectionPage> {
height: 1,
),
maxLines: 1,
cursorColor: MyTheme.color(context).text!,
cursorColor:
Theme.of(context).textTheme.titleLarge?.color,
decoration: InputDecoration(
hintText: inputFocused.value
? null
: translate('Enter Remote ID'),
hintStyle: TextStyle(
color: MyTheme.color(context).placeholder),
border: OutlineInputBorder(
borderRadius: BorderRadius.zero,
borderSide: BorderSide(
@@ -180,84 +188,17 @@ class _ConnectionPageState extends State<ConnectionPage> {
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Obx(() => InkWell(
onTapDown: (_) => ftPressed.value = true,
onTapUp: (_) => ftPressed.value = false,
onTapCancel: () => ftPressed.value = false,
onHover: (value) => ftHover.value = value,
onTap: () {
onConnect(isFileTransfer: true);
},
child: Container(
height: 27,
alignment: Alignment.center,
decoration: BoxDecoration(
color: ftPressed.value
? MyTheme.accent
: Colors.transparent,
border: Border.all(
color: ftPressed.value
? MyTheme.accent
: ftHover.value
? MyTheme.hoverBorder
: MyTheme.border,
),
borderRadius: BorderRadius.circular(5),
),
child: Text(
translate(
"Transfer File",
),
style: TextStyle(
fontSize: 12,
color: ftPressed.value
? MyTheme.color(context).bg
: MyTheme.color(context).text),
).marginSymmetric(horizontal: 12),
),
)),
Button(
isOutline: true,
onTap: () {
onConnect(isFileTransfer: true);
},
text: "Transfer File",
),
const SizedBox(
width: 17,
),
Obx(
() => InkWell(
onTapDown: (_) => connPressed.value = true,
onTapUp: (_) => connPressed.value = false,
onTapCancel: () => connPressed.value = false,
onHover: (value) => connHover.value = value,
onTap: onConnect,
child: ConstrainedBox(
constraints: BoxConstraints(
minWidth: 80.0,
),
child: Container(
height: 27,
decoration: BoxDecoration(
color: connPressed.value
? MyTheme.accent
: MyTheme.button,
border: Border.all(
color: connPressed.value
? MyTheme.accent
: connHover.value
? MyTheme.hoverBorder
: MyTheme.button,
),
borderRadius: BorderRadius.circular(5),
),
child: Center(
child: Text(
translate(
"Connect",
),
style: TextStyle(
fontSize: 12,
color: MyTheme.color(context).bg),
),
).marginSymmetric(horizontal: 12),
)),
),
),
Button(onTap: onConnect, text: "Connect"),
],
),
)
@@ -289,7 +230,11 @@ class _ConnectionPageState extends State<ConnectionPage> {
width: 8,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: svcStopped.value ? Colors.redAccent : Colors.green,
color: svcStopped.value || svcStatusCode.value == 0
? kColorWarn
: (svcStatusCode.value == 1
? Color.fromARGB(255, 50, 190, 166)
: Color.fromARGB(255, 224, 79, 95)),
),
).paddingSymmetric(horizontal: 12.0);
if (svcStopped.value) {

View File

@@ -5,6 +5,8 @@ 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/desktop/pages/desktop_setting_page.dart';
import 'package:flutter_hbb/desktop/pages/desktop_tab_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';
@@ -12,6 +14,9 @@ import 'package:get/get.dart';
import 'package:provider/provider.dart';
import 'package:tray_manager/tray_manager.dart';
import 'package:window_manager/window_manager.dart';
import 'package:url_launcher/url_launcher.dart';
import '../widgets/button.dart';
class DesktopHomePage extends StatefulWidget {
const DesktopHomePage({Key? key}) : super(key: key);
@@ -26,6 +31,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
with TrayListener, WindowListener, AutomaticKeepAliveClientMixin {
@override
bool get wantKeepAlive => true;
var updateUrl = '';
@override
void onWindowClose() async {
@@ -68,12 +74,13 @@ class _DesktopHomePageState extends State<DesktopHomePage>
value: gFFI.serverModel,
child: Container(
width: 200,
color: MyTheme.color(context).bg,
color: Theme.of(context).backgroundColor,
child: Column(
children: [
buildTip(context),
buildIDBoard(context),
buildPasswordBoard(context),
buildHelpCards(),
],
),
),
@@ -82,7 +89,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
buildRightPane(BuildContext context) {
return Container(
color: MyTheme.color(context).grayBg,
color: Theme.of(context).scaffoldBackgroundColor,
child: ConnectionPage(),
);
}
@@ -116,7 +123,11 @@ class _DesktopHomePageState extends State<DesktopHomePage>
translate("ID"),
style: TextStyle(
fontSize: 14,
color: MyTheme.color(context).lightText),
color: Theme.of(context)
.textTheme
.titleLarge
?.color
?.withOpacity(0.5)),
).marginOnly(top: 5),
buildPopupMenu(context)
],
@@ -152,21 +163,20 @@ class _DesktopHomePageState extends State<DesktopHomePage>
}
Widget buildPopupMenu(BuildContext context) {
final textColor = Theme.of(context).textTheme.titleLarge?.color;
RxBool hover = false.obs;
return InkWell(
onTap: () async {},
onTap: DesktopTabPage.onAddSetting,
child: Obx(
() => CircleAvatar(
radius: 15,
backgroundColor: hover.value
? MyTheme.color(context).grayBg!
: MyTheme.color(context).bg!,
? Theme.of(context).scaffoldBackgroundColor
: Theme.of(context).backgroundColor,
child: Icon(
Icons.more_vert_outlined,
size: 20,
color: hover.value
? MyTheme.color(context).text
: MyTheme.color(context).lightText,
color: hover.value ? textColor : textColor?.withOpacity(0.5),
),
),
),
@@ -178,6 +188,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
final model = gFFI.serverModel;
RxBool refreshHover = false.obs;
RxBool editHover = false.obs;
final textColor = Theme.of(context).textTheme.titleLarge?.color;
return Container(
margin: EdgeInsets.only(left: 20.0, right: 16, top: 13, bottom: 13),
child: Row(
@@ -198,7 +209,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
Text(
translate("Password"),
style: TextStyle(
fontSize: 14, color: MyTheme.color(context).lightText),
fontSize: 14, color: textColor?.withOpacity(0.5)),
),
Row(
children: [
@@ -228,8 +239,8 @@ class _DesktopHomePageState extends State<DesktopHomePage>
() => Icon(
Icons.refresh,
color: refreshHover.value
? MyTheme.color(context).text
: Color(0xFFDDDDDD),
? textColor
: Color(0xFFDDDDDD), // TODO
size: 22,
).marginOnly(right: 8, bottom: 2),
),
@@ -241,12 +252,12 @@ class _DesktopHomePageState extends State<DesktopHomePage>
() => Icon(
Icons.edit,
color: editHover.value
? MyTheme.color(context).text
: Color(0xFFDDDDDD),
? textColor
: Color(0xFFDDDDDD), // TODO
size: 22,
).marginOnly(right: 8, bottom: 2),
),
onTap: () => {},
onTap: () => DesktopSettingPage.switch2page(1),
onHover: (value) => editHover.value = value,
),
],
@@ -270,7 +281,11 @@ class _DesktopHomePageState extends State<DesktopHomePage>
children: [
Text(
translate("Your Desktop"),
style: TextStyle(fontWeight: FontWeight.normal, fontSize: 19),
style: Theme.of(context).textTheme.titleLarge,
// style: TextStyle(
// // color: MyTheme.color(context).text,
// fontWeight: FontWeight.normal,
// fontSize: 19),
),
SizedBox(
height: 10.0,
@@ -278,16 +293,93 @@ class _DesktopHomePageState extends State<DesktopHomePage>
Text(
translate("desk_tip"),
overflow: TextOverflow.clip,
style: TextStyle(
fontSize: 12,
color: MyTheme.color(context).lighterText,
height: 1.25),
style: Theme.of(context).textTheme.bodySmall,
)
],
),
);
}
Widget buildHelpCards() {
if (Platform.isWindows) {
if (!bind.mainIsInstalled()) {
return buildInstallCard(
"", "install_tip", "Install", bind.mainGotoInstall);
} else if (bind.mainIsInstalledLowerVersion()) {
return buildInstallCard("Status", "Your installation is lower version.",
"Click to upgrade", bind.mainUpdateMe);
}
}
if (updateUrl.isNotEmpty) {
return buildInstallCard(
"Status",
"There is a newer version of ${bind.mainGetAppNameSync()} ${bind.mainGetNewVersion()} available.",
"Click to download", () async {
final Uri url = Uri.parse('https://rustdesk.com');
await launchUrl(url);
});
}
if (Platform.isMacOS) {}
if (bind.mainIsInstalledLowerVersion()) {}
return Container();
}
Widget buildInstallCard(String title, String content, String btnText,
GestureTapCallback onPressed) {
return Container(
margin: EdgeInsets.only(top: 20),
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.centerLeft,
end: Alignment.centerRight,
colors: [
Color.fromARGB(255, 226, 66, 188),
Color.fromARGB(255, 244, 114, 124),
],
)),
padding: EdgeInsets.all(20),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: (title.isNotEmpty
? <Widget>[
Center(
child: Text(
translate(title),
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 15),
).marginOnly(bottom: 6)),
]
: <Widget>[]) +
<Widget>[
Text(
translate(content),
style: TextStyle(
height: 1.5,
color: Colors.white,
fontWeight: FontWeight.normal,
fontSize: 13),
).marginOnly(bottom: 20),
Row(mainAxisAlignment: MainAxisAlignment.center, children: [
Button(
padding: 8,
isOutline: true,
text: translate(btnText),
textColor: Colors.white,
borderColor: Colors.white,
textSize: 20,
radius: 10,
onTap: onPressed,
)
]),
],
)),
);
}
@override
void onTrayMenuItemClick(MenuItem menuItem) {
debugPrint('click ${menuItem.key}');
@@ -305,6 +397,10 @@ class _DesktopHomePageState extends State<DesktopHomePage>
@override
void initState() {
super.initState();
Timer(const Duration(seconds: 5), () async {
updateUrl = await bind.mainGetSoftwareUpdateUrl();
if (updateUrl.isNotEmpty) setState(() {});
});
trayManager.addListener(this);
windowManager.addListener(this);
rustDeskWinManager.setMethodHandler((call, fromWindowId) async {
@@ -331,7 +427,7 @@ Future<bool> loginDialog() async {
var userNameMsg = "";
String pass = "";
var passMsg = "";
var userContontroller = TextEditingController(text: userName);
var userController = TextEditingController(text: userName);
var pwdController = TextEditingController(text: pass);
var isInProgress = false;
@@ -349,7 +445,7 @@ Future<bool> loginDialog() async {
});
}
userName = userContontroller.text;
userName = userController.text;
pass = pwdController.text;
if (userName.isEmpty) {
userNameMsg = translate("Username missed");
@@ -385,6 +481,7 @@ Future<bool> loginDialog() async {
close();
}
// 登录dialog
return CustomAlertDialog(
title: Text(translate("Login")),
content: ConstrainedBox(
@@ -411,7 +508,7 @@ Future<bool> loginDialog() async {
decoration: InputDecoration(
border: const OutlineInputBorder(),
errorText: userNameMsg.isNotEmpty ? userNameMsg : null),
controller: userContontroller,
controller: userController,
focusNode: FocusNode()..requestFocus(),
),
),

View File

@@ -5,7 +5,9 @@ import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hbb/common.dart';
import 'package:flutter_hbb/consts.dart';
import 'package:flutter_hbb/desktop/pages/desktop_home_page.dart';
import 'package:flutter_hbb/desktop/pages/desktop_tab_page.dart';
import 'package:flutter_hbb/models/platform_model.dart';
import 'package:flutter_hbb/models/server_model.dart';
import 'package:get/get.dart';
@@ -18,7 +20,7 @@ import '../../common/widgets/dialog.dart';
const double _kTabWidth = 235;
const double _kTabHeight = 42;
const double _kCardFixedWidth = 560;
const double _kCardFixedWidth = 540;
const double _kCardLeftMargin = 15;
const double _kContentHMargin = 15;
const double _kContentHSubMargin = _kContentHMargin + 33;
@@ -28,6 +30,8 @@ const double _kListViewBottomMargin = 15;
const double _kTitleFontSize = 20;
const double _kContentFontSize = 15;
const Color _accentColor = MyTheme.accent;
const String _kSettingPageControllerTag = "settingPageController";
const String _kSettingPageIndexTag = "settingPageIndex";
class _TabInfo {
late final String label;
@@ -37,10 +41,30 @@ class _TabInfo {
}
class DesktopSettingPage extends StatefulWidget {
const DesktopSettingPage({Key? key}) : super(key: key);
final int initialPage;
const DesktopSettingPage({Key? key, required this.initialPage})
: super(key: key);
@override
State<DesktopSettingPage> createState() => _DesktopSettingPageState();
static void switch2page(int page) {
if (page >= 5) return;
try {
if (Get.isRegistered<PageController>(tag: _kSettingPageControllerTag)) {
DesktopTabPage.onAddSetting(initialPage: page);
PageController controller = Get.find(tag: _kSettingPageControllerTag);
RxInt selectedIndex = Get.find(tag: _kSettingPageIndexTag);
selectedIndex.value = page;
controller.jumpToPage(page);
} else {
DesktopTabPage.onAddSetting(initialPage: page);
}
} catch (e) {
debugPrint('$e');
}
}
}
class _DesktopSettingPageState extends State<DesktopSettingPage>
@@ -50,12 +74,12 @@ class _DesktopSettingPageState extends State<DesktopSettingPage>
_TabInfo('Security', Icons.enhanced_encryption_outlined,
Icons.enhanced_encryption),
_TabInfo('Network', Icons.link_outlined, Icons.link),
_TabInfo('Acount', Icons.person_outline, Icons.person),
_TabInfo('Account', Icons.person_outline, Icons.person),
_TabInfo('About', Icons.info_outline, Icons.info)
];
late PageController controller;
RxInt selectedIndex = 0.obs;
late RxInt selectedIndex;
@override
bool get wantKeepAlive => true;
@@ -63,14 +87,24 @@ class _DesktopSettingPageState extends State<DesktopSettingPage>
@override
void initState() {
super.initState();
controller = PageController();
selectedIndex = (widget.initialPage < 5 ? widget.initialPage : 0).obs;
Get.put<RxInt>(selectedIndex, tag: _kSettingPageIndexTag);
controller = PageController(initialPage: widget.initialPage);
Get.put<PageController>(controller, tag: _kSettingPageControllerTag);
}
@override
void dispose() {
super.dispose();
Get.delete<PageController>(tag: _kSettingPageControllerTag);
Get.delete<RxInt>(tag: _kSettingPageIndexTag);
}
@override
Widget build(BuildContext context) {
super.build(context);
return Scaffold(
backgroundColor: MyTheme.color(context).bg,
backgroundColor: Theme.of(context).backgroundColor,
body: Row(
children: <Widget>[
SizedBox(
@@ -85,7 +119,7 @@ class _DesktopSettingPageState extends State<DesktopSettingPage>
const VerticalDivider(thickness: 1, width: 1),
Expanded(
child: Container(
color: MyTheme.color(context).grayBg,
color: Theme.of(context).scaffoldBackgroundColor,
child: DesktopScrollWrapper(
scrollController: controller,
child: PageView(
@@ -94,7 +128,7 @@ class _DesktopSettingPageState extends State<DesktopSettingPage>
_General(),
_Safety(),
_Network(),
_Acount(),
_Account(),
_About(),
],
)),
@@ -387,7 +421,7 @@ class _Safety extends StatefulWidget {
class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
@override
bool get wantKeepAlive => true;
bool locked = true;
bool locked = bind.mainIsInstalled();
final scrollController = ScrollController();
@override
@@ -533,7 +567,7 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
_OptionCheckBox(context, 'Deny remote access', 'stop-service',
checkedIcon: const Icon(
Icons.warning_amber_rounded,
color: Color.fromARGB(255, 255, 204, 0),
color: kColorWarn,
),
enabled: enabled),
Offstage(
@@ -541,6 +575,8 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
child: _OptionCheckBox(context, 'Enable RDP', 'enable-rdp',
enabled: enabled),
),
_OptionCheckBox(context, 'Deny LAN Discovery', 'enable-lan-discovery',
reverse: true, enabled: enabled),
...directIp(context),
whitelist(),
]);
@@ -700,14 +736,14 @@ class _NetworkState extends State<_Network> with AutomaticKeepAliveClientMixin {
}
}
class _Acount extends StatefulWidget {
const _Acount({Key? key}) : super(key: key);
class _Account extends StatefulWidget {
const _Account({Key? key}) : super(key: key);
@override
State<_Acount> createState() => _AcountState();
State<_Account> createState() => _AccountState();
}
class _AcountState extends State<_Acount> {
class _AccountState extends State<_Account> {
@override
Widget build(BuildContext context) {
final scrollController = ScrollController();
@@ -717,12 +753,12 @@ class _AcountState extends State<_Acount> {
physics: NeverScrollableScrollPhysics(),
controller: scrollController,
children: [
_Card(title: 'Acount', children: [login()]),
_Card(title: 'Account', children: [accountAction()]),
],
).marginOnly(bottom: _kListViewBottomMargin));
}
Widget login() {
Widget accountAction() {
return _futureBuilder(future: () async {
return await gFFI.userModel.getUserName();
}(), hasData: (data) {
@@ -730,12 +766,14 @@ class _AcountState extends State<_Acount> {
return _Button(
username.isEmpty ? 'Login' : 'Logout',
() => {
loginDialog().then((success) {
if (success) {
// refresh frame
setState(() {});
}
})
username.isEmpty
? loginDialog().then((success) {
if (success) {
// refresh frame
setState(() {});
}
})
: gFFI.userModel.logOut()
});
});
}
@@ -859,7 +897,9 @@ Widget _Card({required String title, required List<Widget> children}) {
}
Color? _disabledTextColor(BuildContext context, bool enabled) {
return enabled ? null : MyTheme.color(context).lighterText;
return enabled
? null
: Theme.of(context).textTheme.titleLarge?.color?.withOpacity(0.6);
}
// ignore: non_constant_identifier_names
@@ -1339,91 +1379,6 @@ void changeServer() async {
});
}
void changeWhiteList({Function()? callback}) async {
Map<String, dynamic> oldOptions = jsonDecode(await bind.mainGetOptions());
var newWhiteList = ((oldOptions['whitelist'] ?? "") as String).split(',');
var newWhiteListField = newWhiteList.join('\n');
var controller = TextEditingController(text: newWhiteListField);
var msg = "";
var isInProgress = false;
gFFI.dialogManager.show((setState, close) {
return CustomAlertDialog(
title: Text(translate("IP Whitelisting")),
content: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(translate("whitelist_sep")),
const SizedBox(
height: 8.0,
),
Row(
children: [
Expanded(
child: TextField(
maxLines: null,
decoration: InputDecoration(
border: const OutlineInputBorder(),
errorText: msg.isEmpty ? null : translate(msg),
),
controller: controller,
focusNode: FocusNode()..requestFocus()),
),
],
),
const SizedBox(
height: 4.0,
),
Offstage(
offstage: !isInProgress, child: const LinearProgressIndicator())
],
),
actions: [
TextButton(onPressed: close, child: Text(translate("Cancel"))),
TextButton(
onPressed: () async {
await bind.mainSetOption(key: 'whitelist', value: '');
callback?.call();
close();
},
child: Text(translate("Clear"))),
TextButton(
onPressed: () async {
setState(() {
msg = "";
isInProgress = true;
});
newWhiteListField = controller.text.trim();
var newWhiteList = "";
if (newWhiteListField.isEmpty) {
// pass
} else {
final ips =
newWhiteListField.trim().split(RegExp(r"[\s,;\n]+"));
// test ip
final ipMatch = RegExp(r"^\d+\.\d+\.\d+\.\d+$");
for (final ip in ips) {
if (!ipMatch.hasMatch(ip)) {
msg = "${translate("Invalid IP")} $ip";
setState(() {
isInProgress = false;
});
return;
}
}
newWhiteList = ips.join(',');
}
oldOptions['whitelist'] = newWhiteList;
await bind.mainSetOptions(json: jsonEncode(oldOptions));
callback?.call();
close();
},
child: Text(translate("OK"))),
],
onCancel: close,
);
});
}
void changeSocks5Proxy() async {
var socks = await bind.mainGetSocks();

View File

@@ -14,6 +14,23 @@ class DesktopTabPage extends StatefulWidget {
@override
State<DesktopTabPage> createState() => _DesktopTabPageState();
static void onAddSetting({int initialPage = 0}) {
try {
DesktopTabController tabController = Get.find();
tabController.add(TabInfo(
key: kTabLabelSettingPage,
label: kTabLabelSettingPage,
selectedIcon: Icons.build_sharp,
unselectedIcon: Icons.build_outlined,
page: DesktopSettingPage(
key: const ValueKey(kTabLabelSettingPage),
initialPage: initialPage,
)));
} catch (e) {
debugPrint('$e');
}
}
}
class _DesktopTabPageState extends State<DesktopTabPage> {
@@ -22,6 +39,7 @@ class _DesktopTabPageState extends State<DesktopTabPage> {
@override
void initState() {
super.initState();
Get.put<DesktopTabController>(tabController);
tabController.add(TabInfo(
key: kTabLabelHomePage,
label: kTabLabelHomePage,
@@ -33,6 +51,12 @@ class _DesktopTabPageState extends State<DesktopTabPage> {
)));
}
@override
void dispose() {
super.dispose();
Get.delete<DesktopTabController>();
}
@override
Widget build(BuildContext context) {
RxBool fullscreen = false.obs;
@@ -42,13 +66,13 @@ class _DesktopTabPageState extends State<DesktopTabPage> {
OverlayEntry(builder: (context) {
gFFI.dialogManager.setOverlayState(Overlay.of(context));
return Scaffold(
backgroundColor: MyTheme.color(context).bg,
backgroundColor: Theme.of(context).backgroundColor,
body: DesktopTab(
controller: tabController,
tail: ActionIcon(
message: 'Settings',
icon: IconFont.menu,
onTap: onAddSetting,
onTap: DesktopTabPage.onAddSetting,
isClose: false,
),
));
@@ -62,13 +86,4 @@ class _DesktopTabPageState extends State<DesktopTabPage> {
fullscreen.value ? kFullScreenEdgeSize : kWindowEdgeSize,
child: tabWidget));
}
void onAddSetting() {
tabController.add(TabInfo(
key: kTabLabelSettingPage,
label: kTabLabelSettingPage,
selectedIcon: Icons.build_sharp,
unselectedIcon: Icons.build_outlined,
page: DesktopSettingPage(key: const ValueKey(kTabLabelSettingPage))));
}
}

View File

@@ -104,7 +104,7 @@ class _FileManagerPageState extends State<FileManagerPage>
return false;
},
child: Scaffold(
backgroundColor: MyTheme.color(context).bg,
backgroundColor: Theme.of(context).backgroundColor,
body: Row(
children: [
Flexible(flex: 3, child: body(isLocal: true)),

View File

@@ -72,7 +72,7 @@ class _FileManagerTabPageState extends State<FileManagerTabPage> {
decoration: BoxDecoration(
border: Border.all(color: MyTheme.color(context).border!)),
child: Scaffold(
backgroundColor: MyTheme.color(context).bg,
backgroundColor: Theme.of(context).backgroundColor,
body: DesktopTab(
controller: tabController,
onWindowCloseButton: handleWindowCloseButton,

View File

@@ -70,7 +70,7 @@ class _PortForwardPageState extends State<PortForwardPage>
Widget build(BuildContext context) {
super.build(context);
return Scaffold(
backgroundColor: MyTheme.color(context).grayBg,
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
body: FutureBuilder(future: () async {
if (!widget.isRDP) {
refreshTunnelConfig();
@@ -80,7 +80,8 @@ class _PortForwardPageState extends State<PortForwardPage>
return Container(
decoration: BoxDecoration(
border: Border.all(
width: 20, color: MyTheme.color(context).grayBg!)),
width: 20,
color: Theme.of(context).scaffoldBackgroundColor)),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
@@ -88,7 +89,7 @@ class _PortForwardPageState extends State<PortForwardPage>
Flexible(
child: Container(
decoration: BoxDecoration(
color: MyTheme.color(context).bg,
color: Theme.of(context).backgroundColor,
border: Border.all(width: 1, color: MyTheme.border)),
child:
widget.isRDP ? buildRdp(context) : buildTunnel(context),
@@ -131,7 +132,7 @@ class _PortForwardPageState extends State<PortForwardPage>
return Theme(
data: Theme.of(context)
.copyWith(backgroundColor: MyTheme.color(context).bg),
.copyWith(backgroundColor: Theme.of(context).backgroundColor),
child: Obx(() => ListView.builder(
controller: ScrollController(),
itemCount: pfs.length + 2,
@@ -139,7 +140,7 @@ class _PortForwardPageState extends State<PortForwardPage>
if (index == 0) {
return Container(
height: 25,
color: MyTheme.color(context).grayBg,
color: Theme.of(context).scaffoldBackgroundColor,
child: Row(children: [
text('Local Port'),
const SizedBox(width: _kColumn1Width),
@@ -166,7 +167,7 @@ class _PortForwardPageState extends State<PortForwardPage>
return Container(
height: _kRowHeight,
decoration: BoxDecoration(color: MyTheme.color(context).bg),
decoration: BoxDecoration(color: Theme.of(context).backgroundColor),
child: Row(children: [
buildTunnelInputCell(context,
controller: localPortController,
@@ -216,11 +217,12 @@ class _PortForwardPageState extends State<PortForwardPage>
{required TextEditingController controller,
List<TextInputFormatter>? inputFormatters,
String? hint}) {
final textColor = Theme.of(context).textTheme.titleLarge?.color;
return Expanded(
child: TextField(
controller: controller,
inputFormatters: inputFormatters,
cursorColor: MyTheme.color(context).text,
cursorColor: textColor,
cursorHeight: 20,
cursorWidth: 1,
decoration: InputDecoration(
@@ -228,12 +230,12 @@ class _PortForwardPageState extends State<PortForwardPage>
borderSide: BorderSide(color: MyTheme.color(context).border!)),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: MyTheme.color(context).border!)),
fillColor: MyTheme.color(context).bg,
fillColor: Theme.of(context).backgroundColor,
contentPadding: const EdgeInsets.all(10),
hintText: hint,
hintStyle: TextStyle(
color: MyTheme.color(context).placeholder, fontSize: 16)),
style: TextStyle(color: MyTheme.color(context).text, fontSize: 16),
hintStyle:
TextStyle(color: Theme.of(context).hintColor, fontSize: 16)),
style: TextStyle(color: textColor, fontSize: 16),
).marginAll(10),
);
}
@@ -250,7 +252,7 @@ class _PortForwardPageState extends State<PortForwardPage>
? MyTheme.currentThemeMode() == ThemeMode.dark
? const Color(0xFF202020)
: const Color(0xFFF4F5F6)
: MyTheme.color(context).bg),
: Theme.of(context).backgroundColor),
child: Row(children: [
text(pf.localPort.toString()),
const SizedBox(width: _kColumn1Width),
@@ -292,7 +294,7 @@ class _PortForwardPageState extends State<PortForwardPage>
).marginOnly(left: _kTextLeftMargin));
return Theme(
data: Theme.of(context)
.copyWith(backgroundColor: MyTheme.color(context).bg),
.copyWith(backgroundColor: Theme.of(context).backgroundColor),
child: ListView.builder(
controller: ScrollController(),
itemCount: 2,
@@ -300,7 +302,7 @@ class _PortForwardPageState extends State<PortForwardPage>
if (index == 0) {
return Container(
height: 25,
color: MyTheme.color(context).grayBg,
color: Theme.of(context).scaffoldBackgroundColor,
child: Row(children: [
text1('Local Port'),
const SizedBox(width: _kColumn1Width),
@@ -311,7 +313,8 @@ class _PortForwardPageState extends State<PortForwardPage>
} else {
return Container(
height: _kRowHeight,
decoration: BoxDecoration(color: MyTheme.color(context).bg),
decoration:
BoxDecoration(color: Theme.of(context).backgroundColor),
child: Row(children: [
Expanded(
child: Align(

View File

@@ -80,7 +80,7 @@ class _PortForwardTabPageState extends State<PortForwardTabPage> {
decoration: BoxDecoration(
border: Border.all(color: MyTheme.color(context).border!)),
child: Scaffold(
backgroundColor: MyTheme.color(context).bg,
backgroundColor: Theme.of(context).backgroundColor,
body: DesktopTab(
controller: tabController,
onWindowCloseButton: () async {

View File

@@ -11,7 +11,6 @@ import 'package:provider/provider.dart';
import 'package:wakelock/wakelock.dart';
import 'package:flutter_custom_cursor/flutter_custom_cursor.dart';
import '../../consts.dart';
import '../widgets/remote_menubar.dart';
import '../../common.dart';
import '../../mobile/widgets/dialog.dart';
@@ -45,7 +44,6 @@ class _RemotePageState extends State<RemotePage>
late RxBool _keyboardEnabled;
final FocusNode _rawKeyFocusNode = FocusNode();
var _isPhysicalMouse = false;
var _imageFocused = false;
Function(bool)? _onEnterOrLeaveImage4Menubar;
@@ -139,7 +137,7 @@ class _RemotePageState extends State<RemotePage>
Widget buildBody(BuildContext context) {
return Scaffold(
backgroundColor: MyTheme.color(context).bg,
backgroundColor: Theme.of(context).backgroundColor,
body: Overlay(
initialEntries: [
OverlayEntry(builder: (context) {
@@ -445,6 +443,7 @@ class ImagePainter extends CustomPainter {
}
class QualityMonitor extends StatelessWidget {
static const textStyle = TextStyle(color: MyTheme.grayBg);
final QualityMonitorModel qualityMonitorModel;
QualityMonitor(this.qualityMonitorModel);
@@ -464,23 +463,23 @@ class QualityMonitor extends StatelessWidget {
children: [
Text(
"Speed: ${qualityMonitorModel.data.speed ?? ''}",
style: const TextStyle(color: MyTheme.grayBg),
style: textStyle,
),
Text(
"FPS: ${qualityMonitorModel.data.fps ?? ''}",
style: const TextStyle(color: MyTheme.grayBg),
style: textStyle,
),
Text(
"Delay: ${qualityMonitorModel.data.delay ?? ''} ms",
style: const TextStyle(color: MyTheme.grayBg),
style: textStyle,
),
Text(
"Target Bitrate: ${qualityMonitorModel.data.targetBitrate ?? ''}kb",
style: const TextStyle(color: MyTheme.grayBg),
style: textStyle,
),
Text(
"Codec: ${qualityMonitorModel.data.codecFormat ?? ''}",
style: const TextStyle(color: MyTheme.grayBg),
style: textStyle,
),
],
),

View File

@@ -91,7 +91,7 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
decoration: BoxDecoration(
border: Border.all(color: MyTheme.color(context).border!)),
child: Scaffold(
backgroundColor: MyTheme.color(context).bg,
backgroundColor: Theme.of(context).backgroundColor,
body: DesktopTab(
controller: tabController,
showTabBar: fullscreen.isFalse,

View File

@@ -69,7 +69,7 @@ class _DesktopServerPageState extends State<DesktopServerPage>
OverlayEntry(builder: (context) {
gFFI.dialogManager.setOverlayState(Overlay.of(context));
return Scaffold(
backgroundColor: MyTheme.color(context).bg,
backgroundColor: Theme.of(context).backgroundColor,
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
@@ -145,7 +145,7 @@ class ConnectionManagerState extends State<ConnectionManager> {
windowManager.startDragging();
},
child: Container(
color: MyTheme.color(context).bg,
color: Theme.of(context).backgroundColor,
),
),
),
@@ -310,14 +310,15 @@ class _CmHeaderState extends State<_CmHeader>
],
),
),
Offstage(
offstage: client.isFileTransfer,
child: IconButton(
onPressed: () => checkClickTime(
client.id, () => gFFI.chatModel.toggleCMChatPage(client.id)),
icon: Icon(Icons.message_outlined),
),
)
Consumer<ServerModel>(
builder: (_, model, child) => Offstage(
offstage: !client.authorized || client.isFileTransfer,
child: IconButton(
onPressed: () => checkClickTime(client.id,
() => gFFI.chatModel.toggleCMChatPage(client.id)),
icon: Icon(Icons.message_outlined),
),
))
],
);
}

View File

@@ -0,0 +1,84 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../common.dart';
class Button extends StatefulWidget {
GestureTapCallback onTap;
String text;
double? textSize;
double? minWidth;
bool isOutline;
double? padding;
Color? textColor;
double? radius;
Color? borderColor;
Button({
Key? key,
this.minWidth,
this.isOutline = false,
this.textSize,
this.padding,
this.textColor,
this.radius,
this.borderColor,
required this.onTap,
required this.text,
}) : super(key: key);
@override
State<Button> createState() => _ButtonState();
}
class _ButtonState extends State<Button> {
RxBool hover = false.obs;
RxBool pressed = false.obs;
@override
Widget build(BuildContext context) {
return Obx(() => InkWell(
onTapDown: (_) => pressed.value = true,
onTapUp: (_) => pressed.value = false,
onTapCancel: () => pressed.value = false,
onHover: (value) => hover.value = value,
onTap: widget.onTap,
child: ConstrainedBox(
constraints: BoxConstraints(
minWidth: widget.minWidth ?? 70.0,
),
child: Container(
padding: EdgeInsets.all(widget.padding ?? 4.5),
alignment: Alignment.center,
decoration: BoxDecoration(
color: pressed.value
? MyTheme.accent
: (widget.isOutline
? Colors.transparent
: MyTheme.button),
border: Border.all(
color: pressed.value
? MyTheme.accent
: hover.value
? MyTheme.hoverBorder
: (widget.isOutline
? widget.borderColor ?? MyTheme.border
: MyTheme.button),
),
borderRadius: BorderRadius.circular(widget.radius ?? 5),
),
child: Text(
translate(
widget.text,
),
style: TextStyle(
fontSize: widget.textSize ?? 12.0,
color: pressed.value || !widget.isOutline
? Theme.of(context).backgroundColor
: widget.textColor ??
Theme.of(context).textTheme.titleLarge?.color),
).marginSymmetric(horizontal: 12),
)),
));
}
}

View File

@@ -14,7 +14,8 @@ import 'package:flutter/material.dart';
// void setState(VoidCallback fn) { }
// enum Menu { itemOne, itemTwo, itemThree, itemFour }
const Duration _kMenuDuration = Duration(milliseconds: 300);
// const Duration _kMenuDuration = Duration(milliseconds: 300);
const Duration _kMenuDuration = Duration(milliseconds: 0);
const double _kMenuCloseIntervalEnd = 2.0 / 3.0;
const double _kMenuHorizontalPadding = 16.0;
const double _kMenuDividerHeight = 16.0;
@@ -22,7 +23,7 @@ const double _kMenuDividerHeight = 16.0;
const double _kMenuMinWidth = 2.0 * _kMenuWidthStep;
const double _kMenuMaxWidth = double.infinity;
// const double _kMenuVerticalPadding = 8.0;
const double _kMenuVerticalPadding = 0.0;
const double _kMenuVerticalPadding = 8.0;
const double _kMenuWidthStep = 0.0;
//const double _kMenuScreenPadding = 8.0;
const double _kMenuScreenPadding = 0.0;

View File

@@ -1,7 +1,6 @@
import 'dart:core';
import 'package:flutter/material.dart';
import 'package:flutter_hbb/common.dart';
import 'package:get/get.dart';
import './material_mod_popup_menu.dart' as mod_menu;
@@ -78,7 +77,8 @@ class MyPopupMenuItemState<T, W extends PopupMenuChildrenItem<T>>
duration: kThemeChangeDuration,
child: Container(
alignment: AlignmentDirectional.centerStart,
constraints: BoxConstraints(minHeight: widget.height),
constraints: BoxConstraints(
minHeight: widget.height, maxHeight: widget.height),
padding:
widget.padding ?? const EdgeInsets.symmetric(horizontal: 16),
child: widget.child,
@@ -156,12 +156,14 @@ class MenuEntryRadios<T> extends MenuEntryBase<T> {
final RadioCurOptionGetter curOptionGetter;
final RadioOptionSetter optionSetter;
final RxString _curOption = "".obs;
final EdgeInsets? padding;
MenuEntryRadios({
required this.text,
required this.optionsGetter,
required this.curOptionGetter,
required this.optionSetter,
this.padding,
dismissOnClicked = false,
RxBool? enabled,
}) : super(dismissOnClicked: dismissOnClicked, enabled: enabled) {
@@ -189,30 +191,37 @@ class MenuEntryRadios<T> extends MenuEntryBase<T> {
height: conf.height,
child: TextButton(
child: Container(
padding: padding,
alignment: AlignmentDirectional.centerStart,
constraints: BoxConstraints(minHeight: conf.height),
constraints:
BoxConstraints(minHeight: conf.height, maxHeight: conf.height),
child: Row(
children: [
Text(
opt.text,
style: TextStyle(
color: MyTheme.color(context).text,
color: Theme.of(context).textTheme.titleLarge?.color,
fontSize: MenuConfig.fontSize,
fontWeight: FontWeight.normal),
),
Expanded(
child: Align(
alignment: Alignment.centerRight,
child: SizedBox(
width: 20.0,
height: 20.0,
child: Obx(() => opt.value == curOption.value
? Icon(
Icons.check,
color: conf.commonColor,
)
: const SizedBox.shrink())),
)),
alignment: Alignment.centerRight,
child: Transform.scale(
scale: MenuConfig.iconScale,
child: Obx(() => opt.value == curOption.value
? IconButton(
padding: const EdgeInsets.fromLTRB(
8.0, 0.0, 8.0, 0.0),
hoverColor: Colors.transparent,
focusColor: Colors.transparent,
onPressed: () {},
icon: Icon(
Icons.check,
color: conf.commonColor,
))
: const SizedBox.shrink()),
))),
],
),
),
@@ -239,12 +248,14 @@ class MenuEntrySubRadios<T> extends MenuEntryBase<T> {
final RadioCurOptionGetter curOptionGetter;
final RadioOptionSetter optionSetter;
final RxString _curOption = "".obs;
final EdgeInsets? padding;
MenuEntrySubRadios({
required this.text,
required this.optionsGetter,
required this.curOptionGetter,
required this.optionSetter,
this.padding,
dismissOnClicked = false,
RxBool? enabled,
}) : super(
@@ -275,28 +286,34 @@ class MenuEntrySubRadios<T> extends MenuEntryBase<T> {
height: conf.height,
child: TextButton(
child: Container(
padding: padding,
alignment: AlignmentDirectional.centerStart,
constraints: BoxConstraints(minHeight: conf.height),
constraints:
BoxConstraints(minHeight: conf.height, maxHeight: conf.height),
child: Row(
children: [
Text(
opt.text,
style: TextStyle(
color: MyTheme.color(context).text,
color: Theme.of(context).textTheme.titleLarge?.color,
fontSize: MenuConfig.fontSize,
fontWeight: FontWeight.normal),
),
Expanded(
child: Align(
alignment: Alignment.centerRight,
child: SizedBox(
width: 20.0,
height: 20.0,
child: Transform.scale(
scale: MenuConfig.iconScale,
child: Obx(() => opt.value == curOption.value
? Icon(
Icons.check,
color: conf.commonColor,
)
? IconButton(
padding: EdgeInsets.zero,
hoverColor: Colors.transparent,
focusColor: Colors.transparent,
onPressed: () {},
icon: Icon(
Icons.check,
color: conf.commonColor,
))
: const SizedBox.shrink())),
)),
],
@@ -318,7 +335,7 @@ class MenuEntrySubRadios<T> extends MenuEntryBase<T> {
return [
PopupMenuChildrenItem(
enabled: super.enabled,
padding: EdgeInsets.zero,
padding: padding,
height: conf.height,
itemBuilder: (BuildContext context) =>
options.map((opt) => _buildSecondMenu(context, conf, opt)).toList(),
@@ -327,7 +344,7 @@ class MenuEntrySubRadios<T> extends MenuEntryBase<T> {
Text(
text,
style: TextStyle(
color: MyTheme.color(context).text,
color: Theme.of(context).textTheme.titleLarge?.color,
fontSize: MenuConfig.fontSize,
fontWeight: FontWeight.normal),
),
@@ -345,28 +362,37 @@ class MenuEntrySubRadios<T> extends MenuEntryBase<T> {
}
}
enum SwitchType {
sswitch,
scheckbox,
}
typedef SwitchGetter = Future<bool> Function();
typedef SwitchSetter = Future<void> Function(bool);
abstract class MenuEntrySwitchBase<T> extends MenuEntryBase<T> {
final SwitchType switchType;
final String text;
final EdgeInsets? padding;
Rx<TextStyle>? textStyle;
MenuEntrySwitchBase({
required this.switchType,
required this.text,
required dismissOnClicked,
this.textStyle,
this.padding,
RxBool? enabled,
}) : super(dismissOnClicked: dismissOnClicked, enabled: enabled);
RxBool get curOption;
Future<void> setOption(bool option);
Future<void> setOption(bool? option);
@override
List<mod_menu.PopupMenuEntry<T>> build(
BuildContext context, MenuConfig conf) {
textStyle ??= const TextStyle(
color: Colors.black,
textStyle ??= TextStyle(
color: Theme.of(context).textTheme.titleLarge?.color,
fontSize: MenuConfig.fontSize,
fontWeight: FontWeight.normal)
.obs;
@@ -376,6 +402,7 @@ abstract class MenuEntrySwitchBase<T> extends MenuEntryBase<T> {
height: conf.height,
child: TextButton(
child: Container(
padding: padding,
alignment: AlignmentDirectional.centerStart,
height: conf.height,
child: Row(children: [
@@ -386,16 +413,33 @@ abstract class MenuEntrySwitchBase<T> extends MenuEntryBase<T> {
Expanded(
child: Align(
alignment: Alignment.centerRight,
child: Obx(() => Switch(
value: curOption.value,
onChanged: (v) {
if (super.dismissOnClicked &&
Navigator.canPop(context)) {
Navigator.pop(context);
}
setOption(v);
},
)),
child: Transform.scale(
scale: MenuConfig.iconScale,
child: Obx(() {
if (switchType == SwitchType.sswitch) {
return Switch(
value: curOption.value,
onChanged: (v) {
if (super.dismissOnClicked &&
Navigator.canPop(context)) {
Navigator.pop(context);
}
setOption(v);
},
);
} else {
return Checkbox(
value: curOption.value,
onChanged: (v) {
if (super.dismissOnClicked &&
Navigator.canPop(context)) {
Navigator.pop(context);
}
setOption(v);
},
);
}
})),
))
])),
onPressed: () {
@@ -416,15 +460,19 @@ class MenuEntrySwitch<T> extends MenuEntrySwitchBase<T> {
final RxBool _curOption = false.obs;
MenuEntrySwitch({
required SwitchType switchType,
required String text,
required this.getter,
required this.setter,
Rx<TextStyle>? textStyle,
EdgeInsets? padding,
dismissOnClicked = false,
RxBool? enabled,
}) : super(
switchType: switchType,
text: text,
textStyle: textStyle,
padding: padding,
dismissOnClicked: dismissOnClicked,
enabled: enabled,
) {
@@ -436,11 +484,13 @@ class MenuEntrySwitch<T> extends MenuEntrySwitchBase<T> {
@override
RxBool get curOption => _curOption;
@override
setOption(bool option) async {
await setter(option);
final opt = await getter();
if (_curOption.value != opt) {
_curOption.value = opt;
setOption(bool? option) async {
if (option != null) {
await setter(option);
final opt = await getter();
if (_curOption.value != opt) {
_curOption.value = opt;
}
}
}
}
@@ -453,32 +503,40 @@ class MenuEntrySwitch2<T> extends MenuEntrySwitchBase<T> {
final SwitchSetter setter;
MenuEntrySwitch2({
required SwitchType switchType,
required String text,
required this.getter,
required this.setter,
Rx<TextStyle>? textStyle,
EdgeInsets? padding,
dismissOnClicked = false,
RxBool? enabled,
}) : super(
switchType: switchType,
text: text,
textStyle: textStyle,
padding: padding,
dismissOnClicked: dismissOnClicked);
@override
RxBool get curOption => getter();
@override
setOption(bool option) async {
await setter(option);
setOption(bool? option) async {
if (option != null) {
await setter(option);
}
}
}
class MenuEntrySubMenu<T> extends MenuEntryBase<T> {
final String text;
final List<MenuEntryBase<T>> entries;
final EdgeInsets? padding;
MenuEntrySubMenu({
required this.text,
required this.entries,
this.padding,
RxBool? enabled,
}) : super(enabled: enabled);
@@ -490,7 +548,7 @@ class MenuEntrySubMenu<T> extends MenuEntryBase<T> {
PopupMenuChildrenItem(
enabled: super.enabled,
height: conf.height,
padding: EdgeInsets.zero,
padding: padding,
position: mod_menu.PopupMenuPosition.overSide,
itemBuilder: (BuildContext context) => entries
.map((entry) => entry.build(context, conf))
@@ -501,7 +559,9 @@ class MenuEntrySubMenu<T> extends MenuEntryBase<T> {
Obx(() => Text(
text,
style: TextStyle(
color: super.enabled!.value ? Colors.black : Colors.grey,
color: super.enabled!.value
? Theme.of(context).textTheme.titleLarge?.color
: Colors.grey,
fontSize: MenuConfig.fontSize,
fontWeight: FontWeight.normal),
)),
@@ -522,10 +582,12 @@ class MenuEntrySubMenu<T> extends MenuEntryBase<T> {
class MenuEntryButton<T> extends MenuEntryBase<T> {
final Widget Function(TextStyle? style) childBuilder;
Function() proc;
final EdgeInsets? padding;
MenuEntryButton({
required this.childBuilder,
required this.proc,
this.padding,
dismissOnClicked = false,
RxBool? enabled,
}) : super(
@@ -534,8 +596,8 @@ class MenuEntryButton<T> extends MenuEntryBase<T> {
);
Widget _buildChild(BuildContext context, MenuConfig conf) {
const enabledStyle = TextStyle(
color: Colors.black,
final enabledStyle = TextStyle(
color: Theme.of(context).textTheme.titleLarge?.color,
fontSize: MenuConfig.fontSize,
fontWeight: FontWeight.normal);
const disabledStyle = TextStyle(
@@ -553,8 +615,10 @@ class MenuEntryButton<T> extends MenuEntryBase<T> {
}
: null,
child: Container(
padding: padding,
alignment: AlignmentDirectional.centerStart,
constraints: BoxConstraints(minHeight: conf.height),
constraints:
BoxConstraints(minHeight: conf.height, maxHeight: conf.height),
child: childBuilder(
super.enabled!.value ? enabledStyle : disabledStyle),
),

View File

@@ -20,7 +20,7 @@ import './material_mod_popup_menu.dart' as mod_menu;
class _MenubarTheme {
static const Color commonColor = MyTheme.accent;
// kMinInteractiveDimension
static const double height = 25.0;
static const double height = 20.0;
static const double dividerHeight = 12.0;
}
@@ -391,31 +391,41 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
List<MenuEntryBase<String>> _getControlMenu(BuildContext context) {
final pi = widget.ffi.ffiModel.pi;
final perms = widget.ffi.ffiModel.permissions;
const EdgeInsets padding = EdgeInsets.only(left: 14.0, right: 5.0);
final List<MenuEntryBase<String>> displayMenu = [];
displayMenu.addAll([
MenuEntryButton<String>(
childBuilder: (TextStyle? style) => Row(
children: [
Text(
translate('OS Password'),
style: style,
),
Expanded(
child: Align(
alignment: Alignment.centerRight,
child: IconButton(
padding: EdgeInsets.zero,
icon: const Icon(Icons.edit),
onPressed: () => showSetOSPassword(
widget.id, false, widget.ffi.dialogManager),
),
))
],
),
childBuilder: (TextStyle? style) => Container(
alignment: AlignmentDirectional.center,
height: _MenubarTheme.height,
child: Row(
children: [
Text(
translate('OS Password'),
style: style,
),
Expanded(
child: Align(
alignment: Alignment.centerRight,
child: Transform.scale(
scale: 0.8,
child: IconButton(
padding: EdgeInsets.zero,
icon: const Icon(Icons.edit),
onPressed: () {
if (Navigator.canPop(context)) {
Navigator.pop(context);
}
showSetOSPassword(
widget.id, false, widget.ffi.dialogManager);
})),
))
],
)),
proc: () {
showSetOSPassword(widget.id, false, widget.ffi.dialogManager);
},
padding: padding,
dismissOnClicked: true,
),
MenuEntryButton<String>(
@@ -426,6 +436,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
proc: () {
connect(context, widget.id, isFileTransfer: true);
},
padding: padding,
dismissOnClicked: true,
),
MenuEntryButton<String>(
@@ -433,6 +444,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
translate('TCP Tunneling'),
style: style,
),
padding: padding,
proc: () {
connect(context, widget.id, isTcpTunneling: true);
},
@@ -451,6 +463,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
proc: () {
showAuditDialog(widget.id, widget.ffi.dialogManager);
},
padding: padding,
dismissOnClicked: true,
),
);
@@ -467,6 +480,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
proc: () {
bind.sessionCtrlAltDel(id: widget.id);
},
padding: padding,
dismissOnClicked: true,
));
}
@@ -483,6 +497,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
proc: () {
showRestartRemoteDevice(pi, widget.id, gFFI.dialogManager);
},
padding: padding,
dismissOnClicked: true,
));
}
@@ -496,6 +511,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
proc: () {
bind.sessionLockScreen(id: widget.id);
},
padding: padding,
dismissOnClicked: true,
));
@@ -513,6 +529,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
value: '${blockInput.value ? "un" : ""}block-input');
blockInput.value = !blockInput.value;
},
padding: padding,
dismissOnClicked: true,
));
}
@@ -527,6 +544,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
proc: () {
bind.sessionRefresh(id: widget.id);
},
padding: padding,
dismissOnClicked: true,
));
}
@@ -547,6 +565,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
// }
// }();
// },
// padding: padding,
// dismissOnClicked: true,
// ));
// }
@@ -559,6 +578,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
proc: () {
widget.ffi.cursorModel.reset();
},
padding: padding,
dismissOnClicked: true,
));
}
@@ -567,125 +587,155 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
}
List<MenuEntryBase<String>> _getDisplayMenu(dynamic futureData) {
const EdgeInsets padding = EdgeInsets.only(left: 18.0, right: 8.0);
final displayMenu = [
MenuEntryRadios<String>(
text: translate('Ratio'),
optionsGetter: () => [
MenuEntryRadioOption(
text: translate('Scale original'), value: 'original'),
MenuEntryRadioOption(
text: translate('Scale adaptive'), value: 'adaptive'),
],
curOptionGetter: () async {
return await bind.sessionGetOption(
id: widget.id, arg: 'view-style') ??
'adaptive';
},
optionSetter: (String oldValue, String newValue) async {
await bind.sessionPeerOption(
id: widget.id, name: "view-style", value: newValue);
widget.ffi.canvasModel.updateViewStyle();
}),
text: translate('Ratio'),
optionsGetter: () => [
MenuEntryRadioOption(
text: translate('Scale original'),
value: 'original',
dismissOnClicked: true,
),
MenuEntryRadioOption(
text: translate('Scale adaptive'),
value: 'adaptive',
dismissOnClicked: true,
),
],
curOptionGetter: () async {
return await bind.sessionGetOption(
id: widget.id, arg: 'view-style') ??
'adaptive';
},
optionSetter: (String oldValue, String newValue) async {
await bind.sessionPeerOption(
id: widget.id, name: "view-style", value: newValue);
widget.ffi.canvasModel.updateViewStyle();
},
padding: padding,
dismissOnClicked: true,
),
MenuEntryDivider<String>(),
MenuEntryRadios<String>(
text: translate('Scroll Style'),
optionsGetter: () => [
MenuEntryRadioOption(
text: translate('ScrollAuto'), value: 'scrollauto'),
MenuEntryRadioOption(
text: translate('Scrollbar'), value: 'scrollbar'),
],
curOptionGetter: () async {
return await bind.sessionGetOption(
id: widget.id, arg: 'scroll-style') ??
'';
},
optionSetter: (String oldValue, String newValue) async {
await bind.sessionPeerOption(
id: widget.id, name: "scroll-style", value: newValue);
widget.ffi.canvasModel.updateScrollStyle();
}),
text: translate('Scroll Style'),
optionsGetter: () => [
MenuEntryRadioOption(
text: translate('ScrollAuto'),
value: 'scrollauto',
dismissOnClicked: true,
),
MenuEntryRadioOption(
text: translate('Scrollbar'),
value: 'scrollbar',
dismissOnClicked: true,
),
],
curOptionGetter: () async {
return await bind.sessionGetOption(
id: widget.id, arg: 'scroll-style') ??
'';
},
optionSetter: (String oldValue, String newValue) async {
await bind.sessionPeerOption(
id: widget.id, name: "scroll-style", value: newValue);
widget.ffi.canvasModel.updateScrollStyle();
},
padding: padding,
dismissOnClicked: true,
),
MenuEntryDivider<String>(),
MenuEntryRadios<String>(
text: translate('Image Quality'),
optionsGetter: () => [
MenuEntryRadioOption(
text: translate('Good image quality'), value: 'best'),
MenuEntryRadioOption(
text: translate('Balanced'), value: 'balanced'),
MenuEntryRadioOption(
text: translate('Optimize reaction time'), value: 'low'),
MenuEntryRadioOption(
text: translate('Custom'),
value: 'custom',
dismissOnClicked: true),
],
curOptionGetter: () async {
String quality =
await bind.sessionGetImageQuality(id: widget.id) ?? 'balanced';
if (quality == '') quality = 'balanced';
return quality;
},
optionSetter: (String oldValue, String newValue) async {
if (oldValue != newValue) {
await bind.sessionSetImageQuality(id: widget.id, value: newValue);
}
text: translate('Image Quality'),
optionsGetter: () => [
MenuEntryRadioOption(
text: translate('Good image quality'),
value: 'best',
dismissOnClicked: true,
),
MenuEntryRadioOption(
text: translate('Balanced'),
value: 'balanced',
dismissOnClicked: true,
),
MenuEntryRadioOption(
text: translate('Optimize reaction time'),
value: 'low',
dismissOnClicked: true,
),
MenuEntryRadioOption(
text: translate('Custom'),
value: 'custom',
dismissOnClicked: true),
],
curOptionGetter: () async {
String quality =
await bind.sessionGetImageQuality(id: widget.id) ?? 'balanced';
if (quality == '') quality = 'balanced';
return quality;
},
optionSetter: (String oldValue, String newValue) async {
if (oldValue != newValue) {
await bind.sessionSetImageQuality(id: widget.id, value: newValue);
}
if (newValue == 'custom') {
final btnCancel = msgBoxButton(translate('Close'), () {
widget.ffi.dialogManager.dismissAll();
});
final quality =
await bind.sessionGetCustomImageQuality(id: widget.id);
double initValue = quality != null && quality.isNotEmpty
? quality[0].toDouble()
: 50.0;
const minValue = 10.0;
const maxValue = 100.0;
if (initValue < minValue) {
initValue = minValue;
}
if (initValue > maxValue) {
initValue = maxValue;
}
final RxDouble sliderValue = RxDouble(initValue);
final rxReplay = rxdart.ReplaySubject<double>();
rxReplay
.throttleTime(const Duration(milliseconds: 1000),
trailing: true, leading: false)
.listen((double v) {
() async {
await bind.sessionSetCustomImageQuality(
id: widget.id, value: v.toInt());
}();
});
final slider = Obx(() {
return Slider(
value: sliderValue.value,
min: minValue,
max: maxValue,
divisions: 90,
onChanged: (double value) {
sliderValue.value = value;
rxReplay.add(value);
},
);
});
final content = Row(
children: [
slider,
SizedBox(
width: 90,
child: Obx(() => Text(
'${sliderValue.value.round()}% Bitrate',
style: const TextStyle(fontSize: 15),
)))
],
);
msgBoxCommon(widget.ffi.dialogManager, 'Custom Image Quality',
content, [btnCancel]);
if (newValue == 'custom') {
final btnCancel = msgBoxButton(translate('Close'), () {
widget.ffi.dialogManager.dismissAll();
});
final quality =
await bind.sessionGetCustomImageQuality(id: widget.id);
double initValue = quality != null && quality.isNotEmpty
? quality[0].toDouble()
: 50.0;
const minValue = 10.0;
const maxValue = 100.0;
if (initValue < minValue) {
initValue = minValue;
}
}),
if (initValue > maxValue) {
initValue = maxValue;
}
final RxDouble sliderValue = RxDouble(initValue);
final rxReplay = rxdart.ReplaySubject<double>();
rxReplay
.throttleTime(const Duration(milliseconds: 1000),
trailing: true, leading: false)
.listen((double v) {
() async {
await bind.sessionSetCustomImageQuality(
id: widget.id, value: v.toInt());
}();
});
final slider = Obx(() {
return Slider(
value: sliderValue.value,
min: minValue,
max: maxValue,
divisions: 90,
onChanged: (double value) {
sliderValue.value = value;
rxReplay.add(value);
},
);
});
final content = Row(
children: [
slider,
SizedBox(
width: 90,
child: Obx(() => Text(
'${sliderValue.value.round()}% Bitrate',
style: const TextStyle(fontSize: 15),
)))
],
);
msgBoxCommon(widget.ffi.dialogManager, 'Custom Image Quality',
content, [btnCancel]);
}
},
padding: padding,
),
MenuEntryDivider<String>(),
];
@@ -701,30 +751,49 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
} finally {}
if (codecs.length == 2 && (codecs[0] || codecs[1])) {
displayMenu.add(MenuEntryRadios<String>(
text: translate('Codec Preference'),
optionsGetter: () {
final list = [
MenuEntryRadioOption(text: translate('Auto'), value: 'auto'),
MenuEntryRadioOption(text: 'VP9', value: 'vp9'),
];
if (codecs[0]) {
list.add(MenuEntryRadioOption(text: 'H264', value: 'h264'));
}
if (codecs[1]) {
list.add(MenuEntryRadioOption(text: 'H265', value: 'h265'));
}
return list;
},
curOptionGetter: () async {
return await bind.sessionGetOption(
id: widget.id, arg: 'codec-preference') ??
'auto';
},
optionSetter: (String oldValue, String newValue) async {
await bind.sessionPeerOption(
id: widget.id, name: "codec-preference", value: newValue);
bind.sessionChangePreferCodec(id: widget.id);
}));
text: translate('Codec Preference'),
optionsGetter: () {
final list = [
MenuEntryRadioOption(
text: translate('Auto'),
value: 'auto',
dismissOnClicked: true,
),
MenuEntryRadioOption(
text: 'VP9',
value: 'vp9',
dismissOnClicked: true,
),
];
if (codecs[0]) {
list.add(MenuEntryRadioOption(
text: 'H264',
value: 'h264',
dismissOnClicked: true,
));
}
if (codecs[1]) {
list.add(MenuEntryRadioOption(
text: 'H265',
value: 'h265',
dismissOnClicked: true,
));
}
return list;
},
curOptionGetter: () async {
return await bind.sessionGetOption(
id: widget.id, arg: 'codec-preference') ??
'auto';
},
optionSetter: (String oldValue, String newValue) async {
await bind.sessionPeerOption(
id: widget.id, name: "codec-preference", value: newValue);
bind.sessionChangePreferCodec(id: widget.id);
},
padding: padding,
dismissOnClicked: true,
));
}
}
@@ -732,62 +801,74 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
displayMenu.add(() {
final state = ShowRemoteCursorState.find(widget.id);
return MenuEntrySwitch2<String>(
text: translate('Show remote cursor'),
getter: () {
return state;
},
setter: (bool v) async {
state.value = v;
await bind.sessionToggleOption(
id: widget.id, value: 'show-remote-cursor');
});
switchType: SwitchType.scheckbox,
text: translate('Show remote cursor'),
getter: () {
return state;
},
setter: (bool v) async {
state.value = v;
await bind.sessionToggleOption(
id: widget.id, value: 'show-remote-cursor');
},
padding: padding,
dismissOnClicked: true,
);
}());
/// Show quality monitor
displayMenu.add(MenuEntrySwitch<String>(
text: translate('Show quality monitor'),
getter: () async {
return bind.sessionGetToggleOptionSync(
id: widget.id, arg: 'show-quality-monitor');
},
setter: (bool v) async {
await bind.sessionToggleOption(
id: widget.id, value: 'show-quality-monitor');
widget.ffi.qualityMonitorModel.checkShowQualityMonitor(widget.id);
}));
switchType: SwitchType.scheckbox,
text: translate('Show quality monitor'),
getter: () async {
return bind.sessionGetToggleOptionSync(
id: widget.id, arg: 'show-quality-monitor');
},
setter: (bool v) async {
await bind.sessionToggleOption(
id: widget.id, value: 'show-quality-monitor');
widget.ffi.qualityMonitorModel.checkShowQualityMonitor(widget.id);
},
padding: padding,
dismissOnClicked: true,
));
final perms = widget.ffi.ffiModel.permissions;
final pi = widget.ffi.ffiModel.pi;
if (perms['audio'] != false) {
displayMenu.add(_createSwitchMenuEntry('Mute', 'disable-audio'));
displayMenu
.add(_createSwitchMenuEntry('Mute', 'disable-audio', padding, true));
}
if (Platform.isWindows &&
pi.platform == 'Windows' &&
perms['file'] != false) {
displayMenu.add(_createSwitchMenuEntry(
'Allow file copy and paste', 'enable-file-transfer'));
'Allow file copy and paste', 'enable-file-transfer', padding, true));
}
if (perms['keyboard'] != false) {
if (perms['clipboard'] != false) {
displayMenu.add(
_createSwitchMenuEntry('Disable clipboard', 'disable-clipboard'));
displayMenu.add(_createSwitchMenuEntry(
'Disable clipboard', 'disable-clipboard', padding, true));
}
displayMenu.add(_createSwitchMenuEntry(
'Lock after session end', 'lock-after-session-end'));
'Lock after session end', 'lock-after-session-end', padding, true));
if (pi.platform == 'Windows') {
displayMenu.add(MenuEntrySwitch2<String>(
dismissOnClicked: true,
text: translate('Privacy mode'),
getter: () {
return PrivacyModeState.find(widget.id);
},
setter: (bool v) async {
await bind.sessionToggleOption(
id: widget.id, value: 'privacy-mode');
}));
switchType: SwitchType.scheckbox,
text: translate('Privacy mode'),
getter: () {
return PrivacyModeState.find(widget.id);
},
setter: (bool v) async {
await bind.sessionToggleOption(
id: widget.id, value: 'privacy-mode');
},
padding: padding,
dismissOnClicked: true,
));
}
}
return displayMenu;
@@ -796,34 +877,39 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
List<MenuEntryBase<String>> _getKeyboardMenu() {
final keyboardMenu = [
MenuEntryRadios<String>(
text: translate('Ratio'),
optionsGetter: () => [
MenuEntryRadioOption(
text: translate('Legacy mode'), value: 'legacy'),
MenuEntryRadioOption(text: translate('Map mode'), value: 'map'),
],
curOptionGetter: () async {
return await bind.sessionGetKeyboardName(id: widget.id);
},
optionSetter: (String oldValue, String newValue) async {
await bind.sessionSetKeyboardMode(
id: widget.id, keyboardMode: newValue);
widget.ffi.canvasModel.updateViewStyle();
})
text: translate('Ratio'),
optionsGetter: () => [
MenuEntryRadioOption(text: translate('Legacy mode'), value: 'legacy'),
MenuEntryRadioOption(text: translate('Map mode'), value: 'map'),
],
curOptionGetter: () async {
return await bind.sessionGetKeyboardName(id: widget.id);
},
optionSetter: (String oldValue, String newValue) async {
await bind.sessionSetKeyboardMode(
id: widget.id, keyboardMode: newValue);
widget.ffi.canvasModel.updateViewStyle();
},
)
];
return keyboardMenu;
}
MenuEntrySwitch<String> _createSwitchMenuEntry(String text, String option) {
MenuEntrySwitch<String> _createSwitchMenuEntry(
String text, String option, EdgeInsets? padding, bool dismissOnClicked) {
return MenuEntrySwitch<String>(
text: translate(text),
getter: () async {
return bind.sessionGetToggleOptionSync(id: widget.id, arg: option);
},
setter: (bool v) async {
await bind.sessionToggleOption(id: widget.id, value: option);
});
switchType: SwitchType.scheckbox,
text: translate(text),
getter: () async {
return bind.sessionGetToggleOptionSync(id: widget.id, arg: option);
},
setter: (bool v) async {
await bind.sessionToggleOption(id: widget.id, value: option);
},
padding: padding,
dismissOnClicked: dismissOnClicked,
);
}
}

View File

@@ -490,6 +490,15 @@ class _ListView extends StatelessWidget {
this.tabBuilder,
this.labelGetter});
/// Check whether to show ListView
///
/// Conditions:
/// - hide single item when only has one item (home) on [DesktopTabPage].
bool isHideSingleItem() {
return state.value.tabs.length == 1 &&
controller.tabType == DesktopTabType.main;
}
@override
Widget build(BuildContext context) {
return Obx(() => ListView(
@@ -497,38 +506,41 @@ class _ListView extends StatelessWidget {
scrollDirection: Axis.horizontal,
shrinkWrap: true,
physics: const BouncingScrollPhysics(),
children: state.value.tabs.asMap().entries.map((e) {
final index = e.key;
final tab = e.value;
return _Tab(
index: index,
label: labelGetter == null
? Rx<String>(tab.label)
: labelGetter!(tab.label),
selectedIcon: tab.selectedIcon,
unselectedIcon: tab.unselectedIcon,
closable: tab.closable,
selected: state.value.selected,
onClose: () {
if (tab.onTabCloseButton != null) {
tab.onTabCloseButton!();
} else {
controller.remove(index);
}
},
onSelected: () => controller.jumpTo(index),
tabBuilder: tabBuilder == null
? null
: (Widget icon, Widget labelWidget, TabThemeConf themeConf) {
return tabBuilder!(
tab.label,
icon,
labelWidget,
themeConf,
);
children: isHideSingleItem()
? List.empty()
: state.value.tabs.asMap().entries.map((e) {
final index = e.key;
final tab = e.value;
return _Tab(
index: index,
label: labelGetter == null
? Rx<String>(tab.label)
: labelGetter!(tab.label),
selectedIcon: tab.selectedIcon,
unselectedIcon: tab.unselectedIcon,
closable: tab.closable,
selected: state.value.selected,
onClose: () {
if (tab.onTabCloseButton != null) {
tab.onTabCloseButton!();
} else {
controller.remove(index);
}
},
);
}).toList()));
onSelected: () => controller.jumpTo(index),
tabBuilder: tabBuilder == null
? null
: (Widget icon, Widget labelWidget,
TabThemeConf themeConf) {
return tabBuilder!(
tab.label,
icon,
labelWidget,
themeConf,
);
},
);
}).toList()));
}
}

View File

@@ -103,6 +103,7 @@ void runMainApp(bool startService) async {
restoreWindowPosition(WindowType.Main);
await windowManager.show();
await windowManager.focus();
await windowManager.setOpacity(1);
});
}
@@ -190,8 +191,13 @@ void runPortForwardScreen(Map<String, dynamic> argument) async {
void runConnectionManagerScreen() async {
// initialize window
WindowOptions windowOptions =
getHiddenTitleBarWindowOptions(size: const Size(300, 400));
await initEnv(kAppTypeMain);
getHiddenTitleBarWindowOptions(size: kConnectionManagerWindowSize);
// ensure initial window size to be changed
await windowManager.setSize(kConnectionManagerWindowSize);
await Future.wait([
windowManager.setAlignment(Alignment.topRight),
initEnv(kAppTypeMain)
]);
runApp(GetMaterialApp(
debugShowCheckedModeBanner: false,
theme: MyTheme.lightTheme,
@@ -206,17 +212,17 @@ void runConnectionManagerScreen() async {
home: const DesktopServerPage(),
builder: _keepScaleBuilder()));
windowManager.waitUntilReadyToShow(windowOptions, () async {
await windowManager.setAlignment(Alignment.topRight);
await windowManager.show();
await windowManager.focus();
await windowManager.setAlignment(Alignment.topRight); // ensure
windowManager.show();
windowManager.focus();
windowManager.setOpacity(1);
windowManager.setAlignment(Alignment.topRight); // ensure
});
}
WindowOptions getHiddenTitleBarWindowOptions({Size? size}) {
return WindowOptions(
size: size,
center: true,
center: false,
backgroundColor: Colors.transparent,
skipTaskbar: false,
titleBarStyle: TitleBarStyle.hidden,

View File

@@ -45,7 +45,7 @@ class ChatPage extends StatelessWidget implements PageShape {
return ChangeNotifierProvider.value(
value: chatModel,
child: Container(
color: MyTheme.color(context).grayBg,
color: Theme.of(context).scaffoldBackgroundColor,
child: Consumer<ChatModel>(builder: (context, chatModel, child) {
final currentUser = chatModel.currentUser;
return Stack(
@@ -62,11 +62,18 @@ class ChatPage extends StatelessWidget implements PageShape {
inputOptions: InputOptions(
sendOnEnter: true,
inputDecoration: defaultInputDecoration(
fillColor: MyTheme.color(context).bg),
hintText: "${translate('Write a message')}...",
fillColor: Theme.of(context).backgroundColor),
sendButtonBuilder: defaultSendButton(
color: MyTheme.color(context).text!),
inputTextStyle:
TextStyle(color: MyTheme.color(context).text)),
color: Theme.of(context)
.textTheme
.titleLarge!
.color!),
inputTextStyle: TextStyle(
color: Theme.of(context)
.textTheme
.titleLarge
?.color)),
messageOptions: MessageOptions(
showOtherUsersAvatar: false,
showTime: true,
@@ -81,7 +88,8 @@ class ChatPage extends StatelessWidget implements PageShape {
)),
);
}),
chatModel.currentID == ChatModel.clientModeID
desktopType == DesktopType.cm ||
chatModel.currentID == ChatModel.clientModeID
? SizedBox.shrink()
: Padding(
padding: EdgeInsets.all(12),

View File

@@ -2,7 +2,6 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_hbb/common/formatter/id_formatter.dart';
import 'package:flutter_hbb/mobile/pages/file_manager_page.dart';
import 'package:get/get.dart';
import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher.dart';
@@ -10,12 +9,11 @@ import 'package:url_launcher/url_launcher.dart';
import '../../common.dart';
import '../../common/widgets/address_book.dart';
import '../../common/widgets/peer_tab_page.dart';
import '../../common/widgets/peer_widget.dart';
import '../../common/widgets/peers_view.dart';
import '../../consts.dart';
import '../../models/model.dart';
import '../../models/platform_model.dart';
import 'home_page.dart';
import 'remote_page.dart';
import 'scan_page.dart';
import 'settings_page.dart';
@@ -84,9 +82,9 @@ class _ConnectionPageState extends State<ConnectionPage> {
translate('Address Book')
],
children: [
RecentPeerWidget(),
FavoritePeerWidget(),
DiscoveredPeerWidget(),
RecentPeersView(),
FavoritePeersView(),
DiscoveredPeersView(),
const AddressBook(),
],
)),
@@ -97,38 +95,7 @@ class _ConnectionPageState extends State<ConnectionPage> {
/// Connects to the selected peer.
void onConnect() {
var id = _idController.id;
connect(id);
}
/// Connect to a peer with [id].
/// If [isFileTransfer], starts a session only for file transfer.
void connect(String id, {bool isFileTransfer = false}) async {
if (id == '') return;
id = id.replaceAll(' ', '');
if (isFileTransfer) {
if (!await PermissionManager.check("file")) {
if (!await PermissionManager.request("file")) {
return;
}
}
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();
}
connect(context, id);
}
/// UI for software update.
@@ -161,8 +128,8 @@ class _ConnectionPageState extends State<ConnectionPage> {
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 2),
child: Ink(
decoration: const BoxDecoration(
color: MyTheme.white,
decoration: BoxDecoration(
color: Theme.of(context).cardColor,
borderRadius: BorderRadius.all(Radius.circular(13)),
),
child: Row(

View File

@@ -58,7 +58,7 @@ class _FileManagerPageState extends State<FileManagerPage> {
return false;
},
child: Scaffold(
backgroundColor: MyTheme.grayBg,
// backgroundColor: MyTheme.grayBg,
appBar: AppBar(
leading: Row(children: [
IconButton(
@@ -69,7 +69,7 @@ class _FileManagerPageState extends State<FileManagerPage> {
title: ToggleSwitch(
initialLabelIndex: model.isLocal ? 0 : 1,
activeBgColor: [MyTheme.idColor],
inactiveBgColor: MyTheme.grayBg,
// inactiveBgColor: MyTheme.grayBg,
inactiveFgColor: Colors.black54,
totalSwitches: 2,
minWidth: 100,
@@ -465,6 +465,9 @@ class _FileManagerPageState extends State<FileManagerPage> {
);
case JobState.none:
break;
case JobState.paused:
// TODO: Handle this case.
break;
}
return null;
}
@@ -530,8 +533,7 @@ class BottomSheetBody extends StatelessWidget {
children: [
Text(title, style: TextStyle(fontSize: 18)),
Text(text,
style: TextStyle(
fontSize: 14, color: MyTheme.grayBg))
style: TextStyle(fontSize: 14)) // TODO color
],
)
],
@@ -548,7 +550,7 @@ class BottomSheetBody extends StatelessWidget {
));
},
onClosing: () {},
backgroundColor: MyTheme.grayBg,
// backgroundColor: MyTheme.grayBg,
enableDrag: false,
);
}

View File

@@ -59,7 +59,7 @@ class _HomePageState extends State<HomePage> {
return false;
},
child: Scaffold(
backgroundColor: MyTheme.grayBg,
// backgroundColor: MyTheme.grayBg,
appBar: AppBar(
centerTitle: true,
title: Text("RustDesk"),
@@ -73,7 +73,7 @@ class _HomePageState extends State<HomePage> {
.toList(),
currentIndex: _selectedIndex,
type: BottomNavigationBarType.fixed,
selectedItemColor: MyTheme.accent,
selectedItemColor: MyTheme.accent, //
unselectedItemColor: MyTheme.darkGray,
onTap: (index) => setState(() {
// close chat overlay when go chat page
@@ -95,7 +95,7 @@ class WebHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: MyTheme.grayBg,
// backgroundColor: MyTheme.grayBg,
appBar: AppBar(
centerTitle: true,
title: Text("RustDesk" + (isWeb ? " (Beta) " : "")),

View File

@@ -325,17 +325,13 @@ class _RemotePageState extends State<RemotePage> {
},
onPointerSignal: (e) {
if (e is PointerScrollEvent) {
var dx = e.scrollDelta.dx;
var dy = e.scrollDelta.dy;
if (dx > 0)
dx = -1;
else if (dx < 0) dx = 1;
if (dy > 0)
var dy = 0;
if (e.scrollDelta.dy > 0) {
dy = -1;
else if (dy < 0) dy = 1;
bind.sessionSendMouse(
id: widget.id,
msg: '{"type": "wheel", "x": "$dx", "y": "$dy"}');
} else if (e.scrollDelta.dy < 0) {
dy = 1;
}
gFFI.scroll(dy);
}
},
child: MouseRegion(
@@ -752,7 +748,7 @@ class _RemotePageState extends State<RemotePage> {
void changeTouchMode() {
setState(() => _showEdit = false);
showModalBottomSheet(
backgroundColor: MyTheme.grayBg,
// backgroundColor: MyTheme.grayBg,
isScrollControlled: true,
context: context,
shape: const RoundedRectangleBorder(
@@ -968,7 +964,9 @@ class ImagePainter extends CustomPainter {
}
}
// TODO global widget
class QualityMonitor extends StatelessWidget {
static final textColor = Colors.grey.shade200;
@override
Widget build(BuildContext context) => ChangeNotifierProvider.value(
value: gFFI.qualityMonitorModel,
@@ -985,23 +983,23 @@ class QualityMonitor extends StatelessWidget {
children: [
Text(
"Speed: ${qualityMonitorModel.data.speed ?? ''}",
style: TextStyle(color: MyTheme.grayBg),
style: TextStyle(color: textColor),
),
Text(
"FPS: ${qualityMonitorModel.data.fps ?? ''}",
style: TextStyle(color: MyTheme.grayBg),
style: TextStyle(color: textColor),
),
Text(
"Delay: ${qualityMonitorModel.data.delay ?? ''} ms",
style: TextStyle(color: MyTheme.grayBg),
style: TextStyle(color: textColor),
),
Text(
"Target Bitrate: ${qualityMonitorModel.data.targetBitrate ?? ''}kb",
style: TextStyle(color: MyTheme.grayBg),
style: TextStyle(color: textColor),
),
Text(
"Codec: ${qualityMonitorModel.data.codecFormat ?? ''}",
style: TextStyle(color: MyTheme.grayBg),
style: TextStyle(color: textColor),
),
],
),

View File

@@ -170,7 +170,7 @@ class ServerInfo extends StatelessWidget {
icon: const Icon(Icons.perm_identity),
labelText: translate("ID"),
labelStyle: const TextStyle(
fontWeight: FontWeight.bold, color: MyTheme.accent50),
fontWeight: FontWeight.bold, color: MyTheme.accent80),
),
onSaved: (String? value) {},
),
@@ -185,7 +185,7 @@ class ServerInfo extends StatelessWidget {
icon: const Icon(Icons.lock),
labelText: translate("Password"),
labelStyle: const TextStyle(
fontWeight: FontWeight.bold, color: MyTheme.accent50),
fontWeight: FontWeight.bold, color: MyTheme.accent80),
suffix: isPermanent
? null
: IconButton(
@@ -213,7 +213,7 @@ class ServerInfo extends StatelessWidget {
fontFamily: 'WorkSans',
fontWeight: FontWeight.bold,
fontSize: 18,
color: MyTheme.accent80,
color: MyTheme.accent,
),
))
],
@@ -304,7 +304,8 @@ class _PermissionCheckerState extends State<PermissionChecker> {
softWrap: true,
style: const TextStyle(
fontSize: 14.0,
color: MyTheme.accent50)))
fontWeight: FontWeight.w500,
color: MyTheme.accent80)))
],
)
: const SizedBox.shrink())
@@ -333,8 +334,13 @@ class PermissionRow extends StatelessWidget {
fit: BoxFit.scaleDown,
alignment: Alignment.centerLeft,
child: Text(name,
style: const TextStyle(
fontSize: 16.0, color: MyTheme.accent50)))),
style: TextStyle(
fontSize: 16.0,
fontWeight:
Theme.of(context).brightness == Brightness.dark
? FontWeight.bold
: null,
color: MyTheme.accent80)))),
Expanded(
flex: 2,
child: FittedBox(
@@ -398,7 +404,7 @@ class ConnectionManager extends StatelessWidget {
},
icon: const Icon(
Icons.chat,
color: MyTheme.accent80,
color: MyTheme.accent,
)))
],
),
@@ -406,7 +412,9 @@ class ConnectionManager extends StatelessWidget {
? const SizedBox.shrink()
: Text(
translate("android_new_connection_tip"),
style: const TextStyle(color: Colors.black54),
style: Theme.of(globalKey.currentContext!)
.textTheme
.bodyMedium,
),
client.authorized
? ElevatedButton.icon(
@@ -460,8 +468,8 @@ class PaddingCard extends StatelessWidget {
titleIcon != null
? Padding(
padding: const EdgeInsets.only(right: 10),
child: Icon(titleIcon,
color: MyTheme.accent80, size: 30))
child:
Icon(titleIcon, color: MyTheme.accent, size: 30))
: const SizedBox.shrink(),
Text(
title!,
@@ -469,7 +477,7 @@ class PaddingCard extends StatelessWidget {
fontFamily: 'WorkSans',
fontWeight: FontWeight.bold,
fontSize: 20,
color: MyTheme.accent80,
color: MyTheme.accent,
),
)
],

View File

@@ -2,12 +2,14 @@ import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:http/http.dart' as http;
import 'package:provider/provider.dart';
import 'package:settings_ui/settings_ui.dart';
import 'package:url_launcher/url_launcher.dart';
import '../../common.dart';
import '../../common/widgets/dialog.dart';
import '../../models/model.dart';
import '../../models/platform_model.dart';
import '../widgets/dialog.dart';
@@ -25,14 +27,15 @@ class SettingsPage extends StatefulWidget implements PageShape {
final appBarActions = [ScanButton()];
@override
_SettingsState createState() => _SettingsState();
State<SettingsPage> createState() => _SettingsState();
}
const url = 'https://rustdesk.com/';
final _hasIgnoreBattery = androidVersion >= 26;
var _ignoreBatteryOpt = false;
var _enableAbr = false;
var _isDarkMode = false;
var _denyLANDiscovery = false;
var _onlyWhiteList = false;
class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
String? username;
@@ -60,7 +63,19 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
_enableAbr = enableAbrRes;
}
// _isDarkMode = MyTheme.currentDarkMode(); // TODO
final denyLanDiscovery = !option2bool('enable-lan-discovery',
await bind.mainGetOption(key: 'enable-lan-discovery'));
if (denyLanDiscovery != _denyLANDiscovery) {
update = true;
_denyLANDiscovery = denyLanDiscovery;
}
final onlyWhiteList =
(await bind.mainGetOption(key: 'whitelist')).isNotEmpty;
if (onlyWhiteList != _onlyWhiteList) {
update = true;
_onlyWhiteList = onlyWhiteList;
}
if (update) {
setState(() {});
@@ -100,16 +115,57 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
Provider.of<FfiModel>(context);
final enhancementsTiles = [
SettingsTile.switchTile(
title: Text(translate('Adaptive Bitrate') + ' (beta)'),
title: Text('${translate('Adaptive Bitrate')} (beta)'),
initialValue: _enableAbr,
onToggle: (v) {
bind.mainSetOption(key: "enable-abr", value: v ? "" : "N");
onToggle: (v) async {
await bind.mainSetOption(key: "enable-abr", value: v ? "" : "N");
final newValue = await bind.mainGetOption(key: "enable-abr") != "N";
setState(() {
_enableAbr = !_enableAbr;
_enableAbr = newValue;
});
},
)
];
final shareScreenTiles = [
SettingsTile.switchTile(
title: Text(translate('Deny LAN Discovery')),
initialValue: _denyLANDiscovery,
onToggle: (v) async {
await bind.mainSetOption(
key: "enable-lan-discovery",
value: bool2option("enable-lan-discovery", !v));
final newValue = !option2bool('enable-lan-discovery',
await bind.mainGetOption(key: 'enable-lan-discovery'));
setState(() {
_denyLANDiscovery = newValue;
});
},
),
SettingsTile.switchTile(
title: Row(children: [
Text(translate('Use IP Whitelisting')),
Offstage(
offstage: !_onlyWhiteList,
child: const Icon(Icons.warning_amber_rounded,
color: Color.fromARGB(255, 255, 204, 0)))
.marginOnly(left: 5)
]),
initialValue: _onlyWhiteList,
onToggle: (_) async {
update() async {
final onlyWhiteList =
(await bind.mainGetOption(key: 'whitelist')).isNotEmpty;
if (onlyWhiteList != _onlyWhiteList) {
setState(() {
_onlyWhiteList = onlyWhiteList;
});
}
}
changeWhiteList(callback: update);
},
)
];
if (_hasIgnoreBattery) {
enhancementsTiles.insert(
0,
@@ -152,7 +208,7 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
SettingsTile.navigation(
title: Text(username == null
? translate("Login")
: translate("Logout") + ' ($username)'),
: '${translate("Logout")} ($username)'),
leading: Icon(Icons.person),
onPressed: (context) {
if (username == null) {
@@ -177,18 +233,18 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
onPressed: (context) {
showLanguageSettings(gFFI.dialogManager);
}),
SettingsTile.switchTile(
SettingsTile.navigation(
title: Text(translate('Dark Theme')),
leading: Icon(Icons.dark_mode),
initialValue: _isDarkMode,
onToggle: (v) {
setState(() {
_isDarkMode = !_isDarkMode;
// MyTheme.changeDarkMode(_isDarkMode); // TODO
});
onPressed: (context) {
showThemeSettings(gFFI.dialogManager);
},
)
]),
SettingsSection(
title: Text(translate("Share Screen")),
tiles: shareScreenTiles,
),
SettingsSection(
title: Text(translate("Enhancements")),
tiles: enhancementsTiles,
@@ -232,7 +288,7 @@ void showLanguageSettings(OverlayDialogManager dialogManager) async {
final langs = json.decode(await bind.mainGetLangs()) as List<dynamic>;
var lang = await bind.mainGetLocalOption(key: "lang");
dialogManager.show((setState, close) {
final setLang = (v) {
setLang(v) {
if (lang != v) {
setState(() {
lang = v;
@@ -241,7 +297,8 @@ void showLanguageSettings(OverlayDialogManager dialogManager) async {
HomePage.homeKey.currentState?.refreshPages();
Future.delayed(Duration(milliseconds: 200), close);
}
};
}
return CustomAlertDialog(
title: SizedBox.shrink(),
content: Column(
@@ -257,13 +314,41 @@ void showLanguageSettings(OverlayDialogManager dialogManager) async {
),
actions: []);
}, backDismiss: true, clickMaskDismiss: true);
} catch (_e) {}
} catch (e) {
//
}
}
void showThemeSettings(OverlayDialogManager dialogManager) async {
var themeMode = MyTheme.getThemeModePreference();
dialogManager.show((setState, close) {
setTheme(v) {
if (themeMode != v) {
setState(() {
themeMode = v;
});
MyTheme.changeDarkMode(themeMode);
Future.delayed(Duration(milliseconds: 200), close);
}
}
return CustomAlertDialog(
title: SizedBox.shrink(),
contentPadding: 10,
content: Column(children: [
getRadio('Light', ThemeMode.light, themeMode, setTheme),
getRadio('Dark', ThemeMode.dark, themeMode, setTheme),
getRadio('Follow System', ThemeMode.system, themeMode, setTheme)
]),
actions: []);
}, backDismiss: true, clickMaskDismiss: true);
}
void showAbout(OverlayDialogManager dialogManager) {
dialogManager.show((setState, close) {
return CustomAlertDialog(
title: Text(translate('About') + ' RustDesk'),
title: Text('${translate('About')} RustDesk'),
content: Wrap(direction: Axis.vertical, spacing: 12, children: [
Text('Version: $version'),
InkWell(
@@ -429,7 +514,7 @@ void showLogin(OverlayDialogManager dialogManager) {
),
controller: nameController,
),
PasswordWidget(controller: passwordController),
PasswordWidget(controller: passwordController, autoFocus: false),
]),
actions: (loading
? <Widget>[CircularProgressIndicator()]

View File

@@ -6,8 +6,7 @@ import '../../models/model.dart';
import '../../models/platform_model.dart';
void clientClose(OverlayDialogManager dialogManager) {
msgBox('', 'Close', 'Are you sure to close the connection?',
dialogManager);
msgBox('', 'Close', 'Are you sure to close the connection?', dialogManager);
}
void showSuccess() {
@@ -131,7 +130,7 @@ void setTemporaryPasswordLengthDialog(
if (index < 0) index = 0;
length = lengths[index];
dialogManager.show((setState, close) {
final setLength = (newValue) {
setLength(newValue) {
final oldValue = length;
if (oldValue == newValue) return;
setState(() {
@@ -143,7 +142,8 @@ void setTemporaryPasswordLengthDialog(
close();
showSuccess();
});
};
}
return CustomAlertDialog(
title: Text(translate("Set temporary password length")),
content: Column(
@@ -230,12 +230,14 @@ void wrongPasswordDialog(String id, OverlayDialogManager dialogManager) {
}
class PasswordWidget extends StatefulWidget {
PasswordWidget({Key? key, required this.controller}) : super(key: key);
PasswordWidget({Key? key, required this.controller, this.autoFocus = true})
: super(key: key);
final TextEditingController controller;
final bool autoFocus;
@override
_PasswordWidgetState createState() => _PasswordWidgetState();
State<PasswordWidget> createState() => _PasswordWidgetState();
}
class _PasswordWidgetState extends State<PasswordWidget> {
@@ -245,7 +247,9 @@ class _PasswordWidgetState extends State<PasswordWidget> {
@override
void initState() {
super.initState();
Timer(Duration(milliseconds: 50), () => _focusNode.requestFocus());
if (widget.autoFocus) {
Timer(Duration(milliseconds: 50), () => _focusNode.requestFocus());
}
}
@override

View File

@@ -166,10 +166,12 @@ class ChatModel with ChangeNotifier {
if (_isShowChatPage) {
_isShowChatPage = !_isShowChatPage;
notifyListeners();
await windowManager.setSize(Size(400, 600));
await windowManager.setSize(Size(300, 400));
await windowManager.setAlignment(Alignment.topRight);
} else {
await windowManager.setSize(Size(800, 600));
await windowManager.setSize(Size(600, 400));
await Future.delayed(Duration(milliseconds: 100));
await windowManager.setAlignment(Alignment.topRight);
_isShowChatPage = !_isShowChatPage;
notifyListeners();
}

View File

@@ -436,7 +436,7 @@ class ServerModel with ChangeNotifier {
clientInfo(client),
Text(
translate("android_new_connection_tip"),
style: const TextStyle(color: Colors.black54),
style: Theme.of(globalKey.currentContext!).textTheme.bodyMedium,
),
],
),

View File

@@ -53,13 +53,19 @@ static void my_application_activate(GApplication* application) {
// bdw->setCustomFrame(true); // <-- add this line
gtk_window_set_default_size(window, 800, 600); // <-- comment this line
gtk_widget_show(GTK_WIDGET(window));
gtk_widget_set_opacity(GTK_WIDGET(window), 0);
g_autoptr(FlDartProject) project = fl_dart_project_new();
fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments);
FlView* view = fl_view_new(project);
gtk_widget_show(GTK_WIDGET(view));
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
auto border_frame = gtk_frame_new(nullptr);
gtk_frame_set_shadow_type(GTK_FRAME(border_frame), GTK_SHADOW_ETCHED_IN);
gtk_container_add(GTK_CONTAINER(border_frame), GTK_WIDGET(view));
gtk_widget_show(GTK_WIDGET(border_frame));
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(border_frame));
fl_register_plugins(FL_PLUGIN_REGISTRY(view));

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 383 B

After

Width:  |  Height:  |  Size: 354 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 624 B

After

Width:  |  Height:  |  Size: 569 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1023 B

After

Width:  |  Height:  |  Size: 909 B

View File

@@ -243,8 +243,8 @@ packages:
dependency: "direct main"
description:
path: "."
ref: fee851fa43116e0b91c39acd0ec37063dc6015f8
resolved-ref: fee851fa43116e0b91c39acd0ec37063dc6015f8
ref: "74c10aa49fecc088992a9edef1f6da39f83df505"
resolved-ref: "74c10aa49fecc088992a9edef1f6da39f83df505"
url: "https://github.com/Kingtous/rustdesk_desktop_multi_window"
source: git
version: "0.1.0"

View File

@@ -64,7 +64,7 @@ dependencies:
desktop_multi_window:
git:
url: https://github.com/Kingtous/rustdesk_desktop_multi_window
ref: fee851fa43116e0b91c39acd0ec37063dc6015f8
ref: 74c10aa49fecc088992a9edef1f6da39f83df505
freezed_annotation: ^2.0.3
tray_manager:
git:

View File

@@ -90,12 +90,12 @@ BEGIN
BLOCK "040904e4"
BEGIN
VALUE "CompanyName", "com.carriez" "\0"
VALUE "FileDescription", "flutter_hbb" "\0"
VALUE "FileDescription", "rustdesk" "\0"
VALUE "FileVersion", VERSION_AS_STRING "\0"
VALUE "InternalName", "flutter_hbb" "\0"
VALUE "InternalName", "rustdesk" "\0"
VALUE "LegalCopyright", "Copyright (C) 2022 com.carriez. All rights reserved." "\0"
VALUE "OriginalFilename", "flutter_hbb.exe" "\0"
VALUE "ProductName", "flutter_hbb" "\0"
VALUE "OriginalFilename", "rustdesk.exe" "\0"
VALUE "ProductName", "rustdesk" "\0"
VALUE "ProductVersion", VERSION_AS_STRING "\0"
END
END

View File

@@ -53,7 +53,7 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
FlutterWindow window(project);
Win32Window::Point origin(10, 10);
Win32Window::Size size(800, 600);
if (!window.CreateAndShow(L"flutter_hbb", origin, size))
if (!window.CreateAndShow(L"rustdesk", origin, size))
{
return EXIT_FAILURE;
}