Merge remote-tracking branch 'github/master' into sigma

This commit is contained in:
sjpark
2023-02-28 16:00:31 +09:00
87 changed files with 3236 additions and 584 deletions

View File

@@ -151,10 +151,7 @@ class _ConnectionPageState extends State<ConnectionPage>
/// Connects to the selected peer.
void onConnect({bool isFileTransfer = false}) {
var id = _idController.id;
var forceRelay = id.endsWith(r'/r');
if (forceRelay) id = id.substring(0, id.length - 2);
connect(context, id,
isFileTransfer: isFileTransfer, forceRelay: forceRelay);
connect(context, id, isFileTransfer: isFileTransfer);
}
/// UI for the remote ID TextField.

View File

@@ -75,7 +75,7 @@ class _DesktopTabPageState extends State<DesktopTabPage> {
isClose: false,
),
)));
return Platform.isMacOS
return Platform.isMacOS || kUseCompatibleUiMode
? tabWidget
: Obx(
() => DragToResizeArea(

View File

@@ -567,161 +567,187 @@ class _FileManagerPageState extends State<FileManagerPage>
return false;
}
Widget generateCard(Widget child) {
return Container(
decoration: BoxDecoration(
color: Theme.of(context).cardColor,
borderRadius: BorderRadius.all(
Radius.circular(15.0),
),
),
child: child,
);
}
/// transfer status list
/// watch transfer status
Widget statusList() {
return PreferredSize(
preferredSize: const Size(200, double.infinity),
preferredSize: const Size(200, double.infinity),
child: Container(
margin: const EdgeInsets.only(top: 16.0, bottom: 16.0, right: 16.0),
padding: const EdgeInsets.all(8.0),
child: model.jobTable.isEmpty
? Center(child: Text(translate("Empty")))
: Container(
margin:
const EdgeInsets.only(top: 16.0, bottom: 16.0, right: 16.0),
padding: const EdgeInsets.all(8.0),
child: Obx(
() => ListView.builder(
controller: ScrollController(),
itemBuilder: (BuildContext context, int index) {
final item = model.jobTable[index];
return Padding(
padding: const EdgeInsets.only(bottom: 5),
child: Container(
decoration: BoxDecoration(
color: Theme.of(context).cardColor,
borderRadius: BorderRadius.all(
Radius.circular(15.0),
),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Transform.rotate(
angle: item.isRemote ? pi : 0,
child: SvgPicture.asset(
"assets/arrow.svg",
color: Theme.of(context)
.tabBarTheme
.labelColor,
),
).paddingOnly(left: 15),
const SizedBox(
width: 16.0,
? generateCard(
Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SvgPicture.asset(
"assets/transfer.svg",
color: Theme.of(context).tabBarTheme.labelColor,
height: 40,
).paddingOnly(bottom: 10),
Text(
translate("No transfers in progress"),
textAlign: TextAlign.center,
textScaleFactor: 1.20,
style: TextStyle(
color: Theme.of(context).tabBarTheme.labelColor),
),
],
),
),
)
: Obx(
() => ListView.builder(
controller: ScrollController(),
itemBuilder: (BuildContext context, int index) {
final item = model.jobTable[index];
return Padding(
padding: const EdgeInsets.only(bottom: 5),
child: generateCard(
Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Transform.rotate(
angle: item.isRemote ? pi : 0,
child: SvgPicture.asset(
"assets/arrow.svg",
color: Theme.of(context)
.tabBarTheme
.labelColor,
),
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Tooltip(
waitDuration:
Duration(milliseconds: 500),
message: item.jobName,
child: Text(
item.jobName,
maxLines: 1,
overflow: TextOverflow.ellipsis,
).paddingSymmetric(vertical: 10),
).paddingOnly(left: 15),
const SizedBox(
width: 16.0,
),
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Tooltip(
waitDuration:
Duration(milliseconds: 500),
message: item.jobName,
child: Text(
item.jobName,
maxLines: 1,
overflow: TextOverflow.ellipsis,
).paddingSymmetric(vertical: 10),
),
Text(
'${translate("Total")} ${readableFileSize(item.totalSize.toDouble())}',
style: TextStyle(
fontSize: 12,
color: MyTheme.darkGray,
),
Text(
'${translate("Total")} ${readableFileSize(item.totalSize.toDouble())}',
),
Offstage(
offstage:
item.state != JobState.inProgress,
child: Text(
'${translate("Speed")} ${readableFileSize(item.speed)}/s',
style: TextStyle(
fontSize: 12,
color: MyTheme.darkGray,
),
),
Offstage(
offstage:
item.state != JobState.inProgress,
child: Text(
'${translate("Speed")} ${readableFileSize(item.speed)}/s',
style: TextStyle(
fontSize: 12,
color: MyTheme.darkGray,
),
),
),
Offstage(
offstage:
item.state == JobState.inProgress,
child: Text(
translate(
item.display(),
),
style: TextStyle(
fontSize: 12,
color: MyTheme.darkGray,
),
),
),
Offstage(
offstage:
item.state != JobState.inProgress,
child: LinearPercentIndicator(
padding: EdgeInsets.only(right: 15),
animateFromLastPercent: true,
center: Text(
'${(item.finishedSize / item.totalSize * 100).toStringAsFixed(0)}%',
),
barRadius: Radius.circular(15),
percent: item.finishedSize /
item.totalSize,
progressColor: MyTheme.accent,
backgroundColor:
Theme.of(context).hoverColor,
lineHeight:
kDesktopFileTransferRowHeight,
).paddingSymmetric(vertical: 15),
),
],
),
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
),
Offstage(
offstage: item.state != JobState.paused,
child: MenuButton(
onPressed: () {
model.resumeJob(item.id);
},
child: SvgPicture.asset(
"assets/refresh.svg",
color: Colors.white,
offstage:
item.state == JobState.inProgress,
child: Text(
translate(
item.display(),
),
style: TextStyle(
fontSize: 12,
color: MyTheme.darkGray,
),
color: MyTheme.accent,
hoverColor: MyTheme.accent80,
),
),
MenuButton(
padding: EdgeInsets.only(right: 15),
child: SvgPicture.asset(
"assets/close.svg",
color: Colors.white,
),
onPressed: () {
model.jobTable.removeAt(index);
model.cancelJob(item.id);
},
color: MyTheme.accent,
hoverColor: MyTheme.accent80,
Offstage(
offstage:
item.state != JobState.inProgress,
child: LinearPercentIndicator(
padding: EdgeInsets.only(right: 15),
animateFromLastPercent: true,
center: Text(
'${(item.finishedSize / item.totalSize * 100).toStringAsFixed(0)}%',
),
barRadius: Radius.circular(15),
percent: item.finishedSize /
item.totalSize,
progressColor: MyTheme.accent,
backgroundColor:
Theme.of(context).hoverColor,
lineHeight:
kDesktopFileTransferRowHeight,
).paddingSymmetric(vertical: 15),
),
],
),
],
),
],
).paddingSymmetric(vertical: 10),
),
);
},
itemCount: model.jobTable.length,
),
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Offstage(
offstage: item.state != JobState.paused,
child: MenuButton(
onPressed: () {
model.resumeJob(item.id);
},
child: SvgPicture.asset(
"assets/refresh.svg",
color: Colors.white,
),
color: MyTheme.accent,
hoverColor: MyTheme.accent80,
),
),
MenuButton(
padding: EdgeInsets.only(right: 15),
child: SvgPicture.asset(
"assets/close.svg",
color: Colors.white,
),
onPressed: () {
model.jobTable.removeAt(index);
model.cancelJob(item.id);
},
color: MyTheme.accent,
hoverColor: MyTheme.accent80,
),
],
),
],
),
],
).paddingSymmetric(vertical: 10),
),
);
},
itemCount: model.jobTable.length,
),
));
),
),
);
}
Widget headTools(bool isLocal) {
@@ -1028,7 +1054,9 @@ class _FileManagerPageState extends State<FileManagerPage>
textAlign: TextAlign.right,
style: TextStyle(
color: selectedItems.length == 0
? MyTheme.darkGray
? Theme.of(context).brightness == Brightness.light
? MyTheme.grayBg
: MyTheme.darkGray
: Colors.white,
),
)
@@ -1037,7 +1065,9 @@ class _FileManagerPageState extends State<FileManagerPage>
child: SvgPicture.asset(
"assets/arrow.svg",
color: selectedItems.length == 0
? MyTheme.darkGray
? Theme.of(context).brightness == Brightness.light
? MyTheme.grayBg
: MyTheme.darkGray
: Colors.white,
alignment: Alignment.bottomRight,
),
@@ -1046,14 +1076,18 @@ class _FileManagerPageState extends State<FileManagerPage>
? SvgPicture.asset(
"assets/arrow.svg",
color: selectedItems.length == 0
? MyTheme.darkGray
? Theme.of(context).brightness == Brightness.light
? MyTheme.grayBg
: MyTheme.darkGray
: Colors.white,
)
: Text(
translate('Receive'),
style: TextStyle(
color: selectedItems.length == 0
? MyTheme.darkGray
? Theme.of(context).brightness == Brightness.light
? MyTheme.grayBg
: MyTheme.darkGray
: Colors.white,
),
),

View File

@@ -98,7 +98,7 @@ class _FileManagerTabPageState extends State<FileManagerTabPage> {
labelGetter: DesktopTab.labelGetterAlias,
)),
);
return Platform.isMacOS
return Platform.isMacOS || kUseCompatibleUiMode
? tabWidget
: SubWindowDragToResizeArea(
child: tabWidget,

View File

@@ -46,15 +46,19 @@ class _InstallPageState extends State<InstallPage> with WindowListener {
final double em = 13;
final btnFontSize = 0.9 * em;
final double button_radius = 6;
final isDarkTheme = MyTheme.currentThemeMode() == ThemeMode.dark;
final buttonStyle = OutlinedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(button_radius)),
));
final inputBorder = OutlineInputBorder(
borderRadius: BorderRadius.zero,
borderSide: BorderSide(color: Colors.black12));
borderSide:
BorderSide(color: isDarkTheme ? Colors.white70 : Colors.black12));
final textColor = isDarkTheme ? null : Colors.black87;
final dividerColor = isDarkTheme ? Colors.white70 : Colors.black87;
return Scaffold(
backgroundColor: Colors.white,
backgroundColor: null,
body: SingleChildScrollView(
child: Column(
children: [
@@ -91,8 +95,7 @@ class _InstallPageState extends State<InstallPage> with WindowListener {
style: buttonStyle,
child: Text(translate('Change Path'),
style: TextStyle(
color: Colors.black87,
fontSize: btnFontSize)))
color: textColor, fontSize: btnFontSize)))
.marginOnly(left: em))
],
).marginSymmetric(vertical: 2 * em),
@@ -127,8 +130,7 @@ class _InstallPageState extends State<InstallPage> with WindowListener {
)).marginOnly(top: 2 * em),
Row(children: [Text(translate('agreement_tip'))])
.marginOnly(top: em),
Divider(color: Colors.black87)
.marginSymmetric(vertical: 0.5 * em),
Divider(color: dividerColor).marginSymmetric(vertical: 0.5 * em),
Row(
children: [
Expanded(
@@ -143,8 +145,7 @@ class _InstallPageState extends State<InstallPage> with WindowListener {
style: buttonStyle,
child: Text(translate('Cancel'),
style: TextStyle(
color: Colors.black87,
fontSize: btnFontSize)))
color: textColor, fontSize: btnFontSize)))
.marginOnly(right: 2 * em)),
Obx(() => ElevatedButton(
onPressed: btnEnabled.value ? install : null,
@@ -167,8 +168,7 @@ class _InstallPageState extends State<InstallPage> with WindowListener {
style: buttonStyle,
child: Text(translate('Run without install'),
style: TextStyle(
color: Colors.black87,
fontSize: btnFontSize)))
color: textColor, fontSize: btnFontSize)))
.marginOnly(left: 2 * em)),
),
],

View File

@@ -107,13 +107,15 @@ class _PortForwardTabPageState extends State<PortForwardTabPage> {
labelGetter: DesktopTab.labelGetterAlias,
)),
);
return Platform.isMacOS
return Platform.isMacOS || kUseCompatibleUiMode
? tabWidget
: SubWindowDragToResizeArea(
child: tabWidget,
resizeEdgeSize: stateGlobal.resizeEdgeSize.value,
windowId: stateGlobal.windowId,
);
: Obx(
() => SubWindowDragToResizeArea(
child: tabWidget,
resizeEdgeSize: stateGlobal.resizeEdgeSize.value,
windowId: stateGlobal.windowId,
),
);
}
void onRemoveId(String id) {

View File

@@ -205,11 +205,13 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
),
),
);
return Platform.isMacOS
return Platform.isMacOS || kUseCompatibleUiMode
? tabWidget
: Obx(() => SubWindowDragToResizeArea(
key: contentKey,
child: tabWidget,
// Specially configured for a better resize area and remote control.
childPadding: kDragToResizeAreaPadding,
resizeEdgeSize: stateGlobal.resizeEdgeSize.value,
windowId: stateGlobal.windowId,
));

View File

@@ -1,3 +1,5 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_hbb/common.dart';
import 'package:flutter_hbb/desktop/pages/remote_tab_page.dart';
@@ -26,6 +28,9 @@ class DesktopRemoteScreen extends StatelessWidget {
ChangeNotifierProvider.value(value: gFFI.canvasModel),
],
child: Scaffold(
// Set transparent background for padding the resize area out of the flutter view.
// This allows the wallpaper goes through our resize area. (Linux only now).
backgroundColor: Platform.isLinux ? Colors.transparent : null,
body: ConnectionTabPage(
params: params,
),

View File

@@ -37,7 +37,7 @@ class _MenuButtonState extends State<MenuButton> {
message: widget.tooltip,
child: Material(
type: MaterialType.transparency,
child: Ink(
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(_borderRadius),
color: _isHover ? widget.hoverColor : widget.color,

View File

@@ -23,6 +23,10 @@ import '../../common/shared_state.dart';
import './popup_menu.dart';
import './kb_layout_type_chooser.dart';
const _kKeyLegacyMode = 'legacy';
const _kKeyMapMode = 'map';
const _kKeyTranslateMode = 'translate';
class MenubarState {
final kStoreKey = 'remoteMenubarState';
late RxBool show;
@@ -598,6 +602,7 @@ class _ControlMenu extends StatelessWidget {
hoverColor: _MenubarTheme.hoverBlueColor,
ffi: ffi,
menuChildren: [
requestElevation(),
osPassword(),
transferFile(context),
tcpTunneling(context),
@@ -605,12 +610,22 @@ class _ControlMenu extends StatelessWidget {
Divider(),
ctrlAltDel(),
restart(),
insertLock(),
blockUserInput(),
switchSides(),
refresh(),
]);
}
requestElevation() {
final visible = ffi.elevationModel.showRequestMenu;
if (!visible) return Offstage();
return _MenuItemButton(
child: Text(translate('Request Elevation')),
ffi: ffi,
onPressed: () => showRequestElevationDialog(id, ffi.dialogManager));
}
osPassword() {
return _MenuItemButton(
child: Text(translate('OS Password')),
@@ -779,6 +794,16 @@ class _ControlMenu extends StatelessWidget {
onPressed: () => showRestartRemoteDevice(pi, id, ffi.dialogManager));
}
insertLock() {
final perms = ffi.ffiModel.permissions;
final visible = perms['keyboard'] != false;
if (!visible) return Offstage();
return _MenuItemButton(
child: Text(translate('Insert Lock')),
ffi: ffi,
onPressed: () => bind.sessionLockScreen(id: id));
}
blockUserInput() {
final perms = ffi.ffiModel.permissions;
final pi = ffi.ffiModel.pi;
@@ -1092,7 +1117,8 @@ class _DisplayMenuState extends State<_DisplayMenu> {
await bind.sessionSetImageQuality(id: widget.id, value: value);
}
return SubmenuButton(
return _SubmenuButton(
ffi: widget.ffi,
child: Text(translate('Image Quality')),
menuChildren: [
_RadioMenuButton<String>(
@@ -1126,7 +1152,7 @@ class _DisplayMenuState extends State<_DisplayMenu> {
},
ffi: widget.ffi,
),
].map((e) => _buildPointerTrackWidget(e, widget.ffi)).toList(),
],
);
});
}
@@ -1301,7 +1327,8 @@ class _DisplayMenuState extends State<_DisplayMenu> {
bind.sessionChangePreferCodec(id: widget.id);
}
return SubmenuButton(
return _SubmenuButton(
ffi: widget.ffi,
child: Text(translate('Codec')),
menuChildren: [
_RadioMenuButton<String>(
@@ -1332,7 +1359,7 @@ class _DisplayMenuState extends State<_DisplayMenu> {
onChanged: onChanged,
ffi: widget.ffi,
),
].map((e) => _buildPointerTrackWidget(e, widget.ffi)).toList());
]);
});
}
@@ -1364,7 +1391,8 @@ class _DisplayMenuState extends State<_DisplayMenu> {
}
}
return SubmenuButton(
return _SubmenuButton(
ffi: widget.ffi,
menuChildren: resolutions
.map((e) => _RadioMenuButton(
value: '${e.width}x${e.height}',
@@ -1372,8 +1400,6 @@ class _DisplayMenuState extends State<_DisplayMenu> {
onChanged: onChanged,
ffi: widget.ffi,
child: Text('${e.width}x${e.height}')))
.toList()
.map((e) => _buildPointerTrackWidget(e, widget.ffi))
.toList(),
child: Text(translate("Resolution")));
}
@@ -1534,11 +1560,16 @@ class _KeyboardMenu extends StatelessWidget {
@override
Widget build(BuildContext context) {
var ffiModel = Provider.of<FfiModel>(context);
if (ffiModel.permissions['keyboard'] == false) return Offstage();
// Do not support peer 1.1.9.
// Do not check permission here?
// var ffiModel = Provider.of<FfiModel>(context);
// if (ffiModel.permissions['keyboard'] == false) return Offstage();
if (stateGlobal.grabKeyboard) {
bind.sessionSetKeyboardMode(id: id, value: 'map');
if (bind.sessionIsKeyboardModeSupported(id: id, mode: _kKeyMapMode)) {
bind.sessionSetKeyboardMode(id: id, value: _kKeyMapMode);
} else if (bind.sessionIsKeyboardModeSupported(
id: id, mode: _kKeyLegacyMode)) {
bind.sessionSetKeyboardMode(id: id, value: _kKeyLegacyMode);
}
return Offstage();
}
return _IconSubmenuButton(
@@ -1551,13 +1582,13 @@ class _KeyboardMenu extends StatelessWidget {
mode() {
return futureBuilder(future: () async {
return await bind.sessionGetKeyboardMode(id: id) ?? 'legacy';
return await bind.sessionGetKeyboardMode(id: id) ?? _kKeyLegacyMode;
}(), hasData: (data) {
final groupValue = data as String;
List<KeyboardModeMenu> modes = [
KeyboardModeMenu(key: 'legacy', menu: 'Legacy mode'),
KeyboardModeMenu(key: 'map', menu: 'Map mode'),
KeyboardModeMenu(key: 'translate', menu: 'Translate mode'),
KeyboardModeMenu(key: _kKeyLegacyMode, menu: 'Legacy mode'),
KeyboardModeMenu(key: _kKeyMapMode, menu: 'Map mode'),
KeyboardModeMenu(key: _kKeyTranslateMode, menu: 'Translate mode'),
];
List<_RadioMenuButton> list = [];
onChanged(String? value) async {
@@ -1567,13 +1598,13 @@ class _KeyboardMenu extends StatelessWidget {
for (KeyboardModeMenu mode in modes) {
if (bind.sessionIsKeyboardModeSupported(id: id, mode: mode.key)) {
if (mode.key == 'translate') {
if (mode.key == _kKeyTranslateMode) {
if (Platform.isLinux || pi.platform == kPeerPlatformLinux) {
continue;
}
}
var text = translate(mode.menu);
if (mode.key == 'translate') {
if (mode.key == _kKeyTranslateMode) {
text = '$text beta';
}
list.add(_RadioMenuButton<String>(
@@ -1877,6 +1908,28 @@ class _IconSubmenuButtonState extends State<_IconSubmenuButton> {
}
}
class _SubmenuButton extends StatelessWidget {
final List<Widget> menuChildren;
final Widget? child;
final FFI ffi;
const _SubmenuButton({
Key? key,
required this.menuChildren,
required this.child,
required this.ffi,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return SubmenuButton(
key: key,
child: child,
menuChildren:
menuChildren.map((e) => _buildPointerTrackWidget(e, ffi)).toList(),
);
}
}
class _MenuItemButton extends StatelessWidget {
final VoidCallback? onPressed;
final Widget? trailingIcon;

View File

@@ -523,12 +523,18 @@ class WindowActionPanelState extends State<WindowActionPanel>
super.dispose();
}
void _setMaximize(bool maximize) {
stateGlobal.setMaximize(maximize);
setState(() {});
}
@override
void onWindowMaximize() {
// catch maximize from system
if (!widget.isMaximized.value) {
widget.isMaximized.value = true;
}
_setMaximize(true);
super.onWindowMaximize();
}
@@ -538,6 +544,7 @@ class WindowActionPanelState extends State<WindowActionPanel>
if (widget.isMaximized.value) {
widget.isMaximized.value = false;
}
_setMaximize(false);
super.onWindowUnmaximize();
}