refact: web ui (#9217)

* refact: web ui

Signed-off-by: fufesou <linlong1266@gmail.com>

* refact: remove AppBar shadow

Signed-off-by: fufesou <linlong1266@gmail.com>

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
This commit is contained in:
fufesou 2024-09-01 00:30:07 +08:00 committed by GitHub
parent bf390611ab
commit ae339f039d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 188 additions and 126 deletions

View file

@ -350,6 +350,9 @@ class MyTheme {
hoverColor: Color.fromARGB(255, 224, 224, 224), hoverColor: Color.fromARGB(255, 224, 224, 224),
scaffoldBackgroundColor: Colors.white, scaffoldBackgroundColor: Colors.white,
dialogBackgroundColor: Colors.white, dialogBackgroundColor: Colors.white,
appBarTheme: AppBarTheme(
shadowColor: Colors.transparent,
),
dialogTheme: DialogTheme( dialogTheme: DialogTheme(
elevation: 15, elevation: 15,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
@ -445,6 +448,9 @@ class MyTheme {
hoverColor: Color.fromARGB(255, 45, 46, 53), hoverColor: Color.fromARGB(255, 45, 46, 53),
scaffoldBackgroundColor: Color(0xFF18191E), scaffoldBackgroundColor: Color(0xFF18191E),
dialogBackgroundColor: Color(0xFF18191E), dialogBackgroundColor: Color(0xFF18191E),
appBarTheme: AppBarTheme(
shadowColor: Colors.transparent,
),
dialogTheme: DialogTheme( dialogTheme: DialogTheme(
elevation: 15, elevation: 15,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
@ -550,7 +556,7 @@ class MyTheme {
static void changeDarkMode(ThemeMode mode) async { static void changeDarkMode(ThemeMode mode) async {
Get.changeThemeMode(mode); Get.changeThemeMode(mode);
if (desktopType == DesktopType.main || isAndroid || isIOS) { if (desktopType == DesktopType.main || isAndroid || isIOS || isWeb) {
if (mode == ThemeMode.system) { if (mode == ThemeMode.system) {
await bind.mainSetLocalOption( await bind.mainSetLocalOption(
key: kCommConfKeyTheme, value: defaultOptionTheme); key: kCommConfKeyTheme, value: defaultOptionTheme);
@ -558,7 +564,7 @@ class MyTheme {
await bind.mainSetLocalOption( await bind.mainSetLocalOption(
key: kCommConfKeyTheme, value: mode.toShortString()); key: kCommConfKeyTheme, value: mode.toShortString());
} }
await bind.mainChangeTheme(dark: mode.toShortString()); if (!isWeb) await bind.mainChangeTheme(dark: mode.toShortString());
// Synchronize the window theme of the system. // Synchronize the window theme of the system.
updateSystemWindowTheme(); updateSystemWindowTheme();
} }

View file

@ -61,7 +61,8 @@ class DesktopSettingPage extends StatefulWidget {
final SettingsTabKey initialTabkey; final SettingsTabKey initialTabkey;
static final List<SettingsTabKey> tabKeys = [ static final List<SettingsTabKey> tabKeys = [
SettingsTabKey.general, SettingsTabKey.general,
if (!bind.isOutgoingOnly() && if (!isWeb &&
!bind.isOutgoingOnly() &&
!bind.isDisableSettings() && !bind.isDisableSettings() &&
bind.mainGetBuildinOption(key: kOptionHideSecuritySetting) != 'Y') bind.mainGetBuildinOption(key: kOptionHideSecuritySetting) != 'Y')
SettingsTabKey.safety, SettingsTabKey.safety,
@ -216,7 +217,7 @@ class _DesktopSettingPageState extends State<DesktopSettingPage>
width: _kTabWidth, width: _kTabWidth,
child: Column( child: Column(
children: [ children: [
_header(), _header(context),
Flexible(child: _listView(tabs: _settingTabs())), Flexible(child: _listView(tabs: _settingTabs())),
], ],
), ),
@ -239,21 +240,40 @@ class _DesktopSettingPageState extends State<DesktopSettingPage>
); );
} }
Widget _header() { Widget _header(BuildContext context) {
final settingsText = Text(
translate('Settings'),
textAlign: TextAlign.left,
style: const TextStyle(
color: _accentColor,
fontSize: _kTitleFontSize,
fontWeight: FontWeight.w400,
),
);
return Row( return Row(
children: [ children: [
SizedBox( if (isWeb)
height: 62, IconButton(
child: Text( onPressed: () {
translate('Settings'), if (Navigator.canPop(context)) {
textAlign: TextAlign.left, Navigator.pop(context);
style: const TextStyle( }
color: _accentColor, },
fontSize: _kTitleFontSize, icon: Icon(Icons.arrow_back),
fontWeight: FontWeight.w400, ).marginOnly(left: 5),
if (isWeb)
SizedBox(
height: 62,
child: Align(
alignment: Alignment.center,
child: settingsText,
), ),
), ).marginOnly(left: 20),
).marginOnly(left: 20, top: 10), if (!isWeb)
SizedBox(
height: 62,
child: settingsText,
).marginOnly(left: 20, top: 10),
const Spacer(), const Spacer(),
], ],
); );
@ -322,7 +342,8 @@ class _General extends StatefulWidget {
} }
class _GeneralState extends State<_General> { class _GeneralState extends State<_General> {
final RxBool serviceStop = Get.find<RxBool>(tag: 'stop-service'); final RxBool serviceStop =
isWeb ? RxBool(false) : Get.find<RxBool>(tag: 'stop-service');
RxBool serviceBtnEnabled = true.obs; RxBool serviceBtnEnabled = true.obs;
@override @override
@ -334,13 +355,13 @@ class _GeneralState extends State<_General> {
physics: DraggableNeverScrollableScrollPhysics(), physics: DraggableNeverScrollableScrollPhysics(),
controller: scrollController, controller: scrollController,
children: [ children: [
service(), if (!isWeb) service(),
theme(), theme(),
_Card(title: 'Language', children: [language()]), _Card(title: 'Language', children: [language()]),
hwcodec(), if (!isWeb) hwcodec(),
audio(context), if (!isWeb) audio(context),
record(context), if (!isWeb) record(context),
WaylandCard(), if (!isWeb) WaylandCard(),
other() other()
], ],
).marginOnly(bottom: _kListViewBottomMargin)); ).marginOnly(bottom: _kListViewBottomMargin));
@ -394,13 +415,13 @@ class _GeneralState extends State<_General> {
Widget other() { Widget other() {
final children = <Widget>[ final children = <Widget>[
if (!bind.isIncomingOnly()) if (!isWeb && !bind.isIncomingOnly())
_OptionCheckBox(context, 'Confirm before closing multiple tabs', _OptionCheckBox(context, 'Confirm before closing multiple tabs',
kOptionEnableConfirmClosingTabs, kOptionEnableConfirmClosingTabs,
isServer: false), isServer: false),
_OptionCheckBox(context, 'Adaptive bitrate', kOptionEnableAbr), _OptionCheckBox(context, 'Adaptive bitrate', kOptionEnableAbr),
wallpaper(), if (!isWeb) wallpaper(),
if (!bind.isIncomingOnly()) ...[ if (!isWeb && !bind.isIncomingOnly()) ...[
_OptionCheckBox( _OptionCheckBox(
context, context,
'Open connection in new tab', 'Open connection in new tab',
@ -417,18 +438,19 @@ class _GeneralState extends State<_General> {
kOptionAllowAlwaysSoftwareRender, kOptionAllowAlwaysSoftwareRender,
), ),
), ),
Tooltip( if (!isWeb)
message: translate('texture_render_tip'), Tooltip(
child: _OptionCheckBox( message: translate('texture_render_tip'),
context, child: _OptionCheckBox(
"Use texture rendering", context,
kOptionTextureRender, "Use texture rendering",
optGetter: bind.mainGetUseTextureRender, kOptionTextureRender,
optSetter: (k, v) async => optGetter: bind.mainGetUseTextureRender,
await bind.mainSetLocalOption(key: k, value: v ? 'Y' : 'N'), optSetter: (k, v) async =>
await bind.mainSetLocalOption(key: k, value: v ? 'Y' : 'N'),
),
), ),
), if (!isWeb && !bind.isCustomClient())
if (!bind.isCustomClient())
_OptionCheckBox( _OptionCheckBox(
context, context,
'Check for software update on startup', 'Check for software update on startup',
@ -443,7 +465,7 @@ class _GeneralState extends State<_General> {
) )
], ],
]; ];
if (bind.mainShowOption(key: kOptionAllowLinuxHeadless)) { if (!isWeb && bind.mainShowOption(key: kOptionAllowLinuxHeadless)) {
children.add(_OptionCheckBox( children.add(_OptionCheckBox(
context, 'Allow linux headless', kOptionAllowLinuxHeadless)); context, 'Allow linux headless', kOptionAllowLinuxHeadless));
} }
@ -641,8 +663,9 @@ class _GeneralState extends State<_General> {
initialKey: currentKey, initialKey: currentKey,
onChanged: (key) async { onChanged: (key) async {
await bind.mainSetLocalOption(key: kCommConfKeyLang, value: key); await bind.mainSetLocalOption(key: kCommConfKeyLang, value: key);
reloadAllWindows(); if (isWeb) reloadCurrentWindow();
bind.mainChangeLanguage(lang: key); if (!isWeb) reloadAllWindows();
if (!isWeb) bind.mainChangeLanguage(lang: key);
}, },
enabled: !isOptFixed, enabled: !isOptFixed,
).marginOnly(left: _kContentHMargin); ).marginOnly(left: _kContentHMargin);
@ -1337,7 +1360,7 @@ class _Network extends StatefulWidget {
class _NetworkState extends State<_Network> with AutomaticKeepAliveClientMixin { class _NetworkState extends State<_Network> with AutomaticKeepAliveClientMixin {
@override @override
bool get wantKeepAlive => true; bool get wantKeepAlive => true;
bool locked = bind.mainIsInstalled(); bool locked = !isWeb && bind.mainIsInstalled();
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -1346,8 +1369,9 @@ class _NetworkState extends State<_Network> with AutomaticKeepAliveClientMixin {
final scrollController = ScrollController(); final scrollController = ScrollController();
final hideServer = final hideServer =
bind.mainGetBuildinOption(key: kOptionHideServerSetting) == 'Y'; bind.mainGetBuildinOption(key: kOptionHideServerSetting) == 'Y';
// TODO: support web proxy
final hideProxy = final hideProxy =
bind.mainGetBuildinOption(key: kOptionHideProxySetting) == 'Y'; isWeb || bind.mainGetBuildinOption(key: kOptionHideProxySetting) == 'Y';
return DesktopScrollWrapper( return DesktopScrollWrapper(
scrollController: scrollController, scrollController: scrollController,
child: ListView( child: ListView(
@ -1467,7 +1491,7 @@ class _DisplayState extends State<_Display> {
scrollStyle(context), scrollStyle(context),
imageQuality(context), imageQuality(context),
codec(context), codec(context),
privacyModeImpl(context), if (!isWeb) privacyModeImpl(context),
other(context), other(context),
]).marginOnly(bottom: _kListViewBottomMargin)); ]).marginOnly(bottom: _kListViewBottomMargin));
} }

View file

@ -9,19 +9,16 @@ import 'package:url_launcher/url_launcher.dart';
import 'package:flutter_hbb/models/peer_model.dart'; import 'package:flutter_hbb/models/peer_model.dart';
import '../../common.dart'; import '../../common.dart';
import '../../common/widgets/login.dart';
import '../../common/widgets/peer_tab_page.dart'; import '../../common/widgets/peer_tab_page.dart';
import '../../common/widgets/autocomplete.dart'; import '../../common/widgets/autocomplete.dart';
import '../../consts.dart'; import '../../consts.dart';
import '../../models/model.dart'; import '../../models/model.dart';
import '../../models/platform_model.dart'; import '../../models/platform_model.dart';
import 'home_page.dart'; import 'home_page.dart';
import 'scan_page.dart';
import 'settings_page.dart';
/// Connection page for connecting to a remote peer. /// Connection page for connecting to a remote peer.
class ConnectionPage extends StatefulWidget implements PageShape { class ConnectionPage extends StatefulWidget implements PageShape {
ConnectionPage({Key? key}) : super(key: key); ConnectionPage({Key? key, required this.appBarActions}) : super(key: key);
@override @override
final icon = const Icon(Icons.connected_tv); final icon = const Icon(Icons.connected_tv);
@ -30,7 +27,7 @@ class ConnectionPage extends StatefulWidget implements PageShape {
final title = translate("Connection"); final title = translate("Connection");
@override @override
final appBarActions = isWeb ? <Widget>[const WebMenu()] : <Widget>[]; final List<Widget> appBarActions;
@override @override
State<ConnectionPage> createState() => _ConnectionPageState(); State<ConnectionPage> createState() => _ConnectionPageState();
@ -356,73 +353,3 @@ class _ConnectionPageState extends State<ConnectionPage> {
super.dispose(); super.dispose();
} }
} }
class WebMenu extends StatefulWidget {
const WebMenu({Key? key}) : super(key: key);
@override
State<WebMenu> createState() => _WebMenuState();
}
class _WebMenuState extends State<WebMenu> {
@override
Widget build(BuildContext context) {
Provider.of<FfiModel>(context);
return PopupMenuButton<String>(
tooltip: "",
icon: const Icon(Icons.more_vert),
itemBuilder: (context) {
return (isIOS
? [
const PopupMenuItem(
value: "scan",
child: Icon(Icons.qr_code_scanner, color: Colors.black),
)
]
: <PopupMenuItem<String>>[]) +
[
PopupMenuItem(
value: "server",
child: Text(translate('ID/Relay Server')),
)
] +
[
PopupMenuItem(
value: "login",
child: Text(gFFI.userModel.userName.value.isEmpty
? translate("Login")
: '${translate("Logout")} (${gFFI.userModel.userName.value})'),
)
] +
[
PopupMenuItem(
value: "about",
child: Text(translate('About RustDesk')),
)
];
},
onSelected: (value) {
if (value == 'server') {
showServerSettings(gFFI.dialogManager);
}
if (value == 'about') {
showAbout(gFFI.dialogManager);
}
if (value == 'login') {
if (gFFI.userModel.userName.value.isEmpty) {
loginDialog();
} else {
logOutConfirmDialog();
}
}
if (value == 'scan') {
Navigator.push(
context,
MaterialPageRoute(
builder: (BuildContext context) => ScanPage(),
),
);
}
});
}
}

View file

@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hbb/mobile/pages/server_page.dart'; import 'package:flutter_hbb/mobile/pages/server_page.dart';
import 'package:flutter_hbb/mobile/pages/settings_page.dart'; import 'package:flutter_hbb/mobile/pages/settings_page.dart';
import 'package:flutter_hbb/web/settings_page.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../../common.dart'; import '../../common.dart';
import '../../common/widgets/chat_page.dart'; import '../../common/widgets/chat_page.dart';
@ -45,7 +46,10 @@ class HomePageState extends State<HomePage> {
void initPages() { void initPages() {
_pages.clear(); _pages.clear();
if (!bind.isIncomingOnly()) _pages.add(ConnectionPage()); if (!bind.isIncomingOnly())
_pages.add(ConnectionPage(
appBarActions: [],
));
if (isAndroid && !bind.isOutgoingOnly()) { if (isAndroid && !bind.isOutgoingOnly()) {
_chatPageTabIndex = _pages.length; _chatPageTabIndex = _pages.length;
_pages.addAll([ChatPage(type: ChatPageType.mobileMain), ServerPage()]); _pages.addAll([ChatPage(type: ChatPageType.mobileMain), ServerPage()]);
@ -149,7 +153,8 @@ class HomePageState extends State<HomePage> {
} }
class WebHomePage extends StatelessWidget { class WebHomePage extends StatelessWidget {
final connectionPage = ConnectionPage(); final connectionPage =
ConnectionPage(appBarActions: <Widget>[const WebSettingsPage()]);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {

View file

@ -736,7 +736,8 @@ class RustdeskImpl {
} }
Future<String> mainGetLicense({dynamic hint}) { Future<String> mainGetLicense({dynamic hint}) {
throw UnimplementedError(); // TODO: implement
return Future(() => '');
} }
Future<String> mainGetVersion({dynamic hint}) { Future<String> mainGetVersion({dynamic hint}) {
@ -975,10 +976,11 @@ class RustdeskImpl {
Future<void> mainSetUserDefaultOption( Future<void> mainSetUserDefaultOption(
{required String key, required String value, dynamic hint}) { {required String key, required String value, dynamic hint}) {
return js.context.callMethod('getByName', [ js.context.callMethod('setByName', [
'option:user:default', 'option:user:default',
jsonEncode({'name': key, 'value': value}) jsonEncode({'name': key, 'value': value})
]); ]);
return Future.value();
} }
String mainGetUserDefaultOption({required String key, dynamic hint}) { String mainGetUserDefaultOption({required String key, dynamic hint}) {
@ -1052,7 +1054,7 @@ class RustdeskImpl {
} }
Future<String> mainGetLangs({dynamic hint}) { Future<String> mainGetLangs({dynamic hint}) {
throw UnimplementedError(); return Future(() => js.context.callMethod('getByName', ['langs']));
} }
Future<String> mainGetTemporaryPassword({dynamic hint}) { Future<String> mainGetTemporaryPassword({dynamic hint}) {
@ -1064,7 +1066,8 @@ class RustdeskImpl {
} }
Future<String> mainGetFingerprint({dynamic hint}) { Future<String> mainGetFingerprint({dynamic hint}) {
throw UnimplementedError(); // TODO: implement
return Future.value('');
} }
Future<String> cmGetClientsState({dynamic hint}) { Future<String> cmGetClientsState({dynamic hint}) {
@ -1106,7 +1109,7 @@ class RustdeskImpl {
} }
String mainSupportedHwdecodings({dynamic hint}) { String mainSupportedHwdecodings({dynamic hint}) {
throw UnimplementedError(); return '{}';
} }
Future<bool> mainIsRoot({dynamic hint}) { Future<bool> mainIsRoot({dynamic hint}) {
@ -1295,8 +1298,7 @@ class RustdeskImpl {
} }
Future<String> mainGetBuildDate({dynamic hint}) { Future<String> mainGetBuildDate({dynamic hint}) {
// TODO return Future(() => js.context.callMethod('getByName', ['build_date']));
throw UnimplementedError();
} }
String translate( String translate(

View file

@ -0,0 +1,98 @@
import 'package:flutter/material.dart';
import 'package:flutter_hbb/desktop/pages/desktop_setting_page.dart';
import 'package:flutter_hbb/mobile/pages/scan_page.dart';
import 'package:flutter_hbb/mobile/pages/settings_page.dart';
import 'package:provider/provider.dart';
import '../../common.dart';
import '../../common/widgets/login.dart';
import '../../models/model.dart';
class WebSettingsPage extends StatelessWidget {
const WebSettingsPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
if (isWebDesktop) {
return _buildDesktopButton(context);
} else {
return _buildMobileMenu(context);
}
}
Widget _buildDesktopButton(BuildContext context) {
return IconButton(
icon: const Icon(Icons.more_vert),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (BuildContext context) =>
DesktopSettingPage(initialTabkey: SettingsTabKey.general),
),
);
},
);
}
Widget _buildMobileMenu(BuildContext context) {
Provider.of<FfiModel>(context);
return PopupMenuButton<String>(
tooltip: "",
icon: const Icon(Icons.more_vert),
itemBuilder: (context) {
return (isIOS
? [
const PopupMenuItem(
value: "scan",
child: Icon(Icons.qr_code_scanner, color: Colors.black),
)
]
: <PopupMenuItem<String>>[]) +
[
PopupMenuItem(
value: "server",
child: Text(translate('ID/Relay Server')),
)
] +
[
PopupMenuItem(
value: "login",
child: Text(gFFI.userModel.userName.value.isEmpty
? translate("Login")
: '${translate("Logout")} (${gFFI.userModel.userName.value})'),
)
] +
[
PopupMenuItem(
value: "about",
child: Text(translate('About RustDesk')),
)
];
},
onSelected: (value) {
if (value == 'server') {
showServerSettings(gFFI.dialogManager);
}
if (value == 'about') {
showAbout(gFFI.dialogManager);
}
if (value == 'login') {
if (gFFI.userModel.userName.value.isEmpty) {
loginDialog();
} else {
logOutConfirmDialog();
}
}
if (value == 'scan') {
Navigator.push(
context,
MaterialPageRoute(
builder: (BuildContext context) => ScanPage(),
),
);
}
});
}
}