1. update DesktopTabBar for cm.
2. refactor server_model clients map -> list. 3. update tab changing events.
This commit is contained in:
parent
b33d1f216f
commit
14b8140e45
@ -195,7 +195,7 @@ closeConnection({String? id}) {
|
||||
if (isAndroid || isIOS) {
|
||||
Navigator.popUntil(globalKey.currentContext!, ModalRoute.withName("/"));
|
||||
} else {
|
||||
closeTab(id);
|
||||
DesktopTabBar.close(id);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -33,6 +33,7 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
||||
_ConnectionTabPageState(Map<String, dynamic> params) {
|
||||
if (params['id'] != null) {
|
||||
tabs.add(TabInfo(
|
||||
key: params['id'],
|
||||
label: params['id'],
|
||||
selectedIcon: selectedIcon,
|
||||
unselectedIcon: unselectedIcon));
|
||||
@ -53,6 +54,7 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
||||
DesktopTabBar.onAdd(
|
||||
tabs,
|
||||
TabInfo(
|
||||
key: id,
|
||||
label: id,
|
||||
selectedIcon: selectedIcon,
|
||||
unselectedIcon: unselectedIcon));
|
||||
|
@ -22,6 +22,7 @@ class _DesktopTabPageState extends State<DesktopTabPage> {
|
||||
super.initState();
|
||||
tabs = RxList.from([
|
||||
TabInfo(
|
||||
key: kTabLabelHomePage,
|
||||
label: kTabLabelHomePage,
|
||||
selectedIcon: Icons.home_sharp,
|
||||
unselectedIcon: Icons.home_outlined,
|
||||
@ -70,6 +71,7 @@ class _DesktopTabPageState extends State<DesktopTabPage> {
|
||||
DesktopTabBar.onAdd(
|
||||
tabs,
|
||||
TabInfo(
|
||||
key: kTabLabelSettingPage,
|
||||
label: kTabLabelSettingPage,
|
||||
selectedIcon: Icons.build_sharp,
|
||||
unselectedIcon: Icons.build_outlined));
|
||||
|
@ -29,6 +29,7 @@ class _FileManagerTabPageState extends State<FileManagerTabPage> {
|
||||
_FileManagerTabPageState(Map<String, dynamic> params) {
|
||||
if (params['id'] != null) {
|
||||
tabs.add(TabInfo(
|
||||
key: params['id'],
|
||||
label: params['id'],
|
||||
selectedIcon: selectedIcon,
|
||||
unselectedIcon: unselectedIcon));
|
||||
@ -49,6 +50,7 @@ class _FileManagerTabPageState extends State<FileManagerTabPage> {
|
||||
DesktopTabBar.onAdd(
|
||||
tabs,
|
||||
TabInfo(
|
||||
key: id,
|
||||
label: id,
|
||||
selectedIcon: selectedIcon,
|
||||
unselectedIcon: unselectedIcon));
|
||||
|
@ -1,6 +1,9 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart';
|
||||
import 'package:flutter_hbb/mobile/pages/chat_page.dart';
|
||||
import 'package:flutter_hbb/models/chat_model.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
@ -24,8 +27,11 @@ class _DesktopServerPageState extends State<DesktopServerPage>
|
||||
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
return ChangeNotifierProvider.value(
|
||||
value: gFFI.serverModel,
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
ChangeNotifierProvider.value(value: gFFI.serverModel),
|
||||
ChangeNotifierProvider.value(value: gFFI.chatModel),
|
||||
],
|
||||
child: Consumer<ServerModel>(
|
||||
builder: (context, serverModel, child) => Material(
|
||||
child: Center(
|
||||
@ -44,14 +50,28 @@ class _DesktopServerPageState extends State<DesktopServerPage>
|
||||
bool get wantKeepAlive => true;
|
||||
}
|
||||
|
||||
class ConnectionManager extends StatelessWidget {
|
||||
class ConnectionManager extends StatefulWidget {
|
||||
@override
|
||||
State<StatefulWidget> createState() => ConnectionManagerState();
|
||||
}
|
||||
|
||||
class ConnectionManagerState extends State<ConnectionManager> {
|
||||
@override
|
||||
void initState() {
|
||||
gFFI.serverModel.updateClientState();
|
||||
// test
|
||||
// gFFI.serverModel.clients.forEach((client) {
|
||||
// DesktopTabBar.onAdd(
|
||||
// gFFI.serverModel.tabs,
|
||||
// TabInfo(
|
||||
// key: client.id.toString(), label: client.name, closable: false));
|
||||
// });
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final serverModel = Provider.of<ServerModel>(context);
|
||||
// test case:
|
||||
// serverModel.clients.clear();
|
||||
// serverModel.clients[0] = Client(
|
||||
// false, false, "Readmi-M21sdfsdf", "123123123", true, false, false);
|
||||
return serverModel.clients.isEmpty
|
||||
? Column(
|
||||
children: [
|
||||
@ -63,27 +83,37 @@ class ConnectionManager extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
)
|
||||
: DefaultTabController(
|
||||
length: serverModel.clients.length,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
height: kTextTabBarHeight,
|
||||
child: buildTitleBar(TabBar(
|
||||
isScrollable: true,
|
||||
tabs: serverModel.clients.entries
|
||||
.map((entry) => buildTab(entry))
|
||||
.toList(growable: false))),
|
||||
),
|
||||
Expanded(
|
||||
child: TabBarView(
|
||||
children: serverModel.clients.entries
|
||||
.map((entry) => buildConnectionCard(entry))
|
||||
.toList(growable: false)),
|
||||
)
|
||||
],
|
||||
),
|
||||
: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
height: kTextTabBarHeight,
|
||||
child: Obx(() => DesktopTabBar(
|
||||
dark: isDarkTheme(),
|
||||
mainTab: true,
|
||||
tabs: serverModel.tabs,
|
||||
showTitle: false,
|
||||
showMaximize: false,
|
||||
showMinimize: false,
|
||||
onSelected: (index) => gFFI.chatModel
|
||||
.changeCurrentID(serverModel.clients[index].id),
|
||||
)),
|
||||
),
|
||||
Expanded(
|
||||
child: Row(children: [
|
||||
Expanded(
|
||||
child: PageView(
|
||||
controller: DesktopTabBar.controller.value,
|
||||
children: serverModel.clients
|
||||
.map((client) => buildConnectionCard(client))
|
||||
.toList(growable: false))),
|
||||
Consumer<ChatModel>(
|
||||
builder: (_, model, child) => model.isShowChatPage
|
||||
? Expanded(child: Scaffold(body: ChatPage()))
|
||||
: Offstage())
|
||||
]),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@ -106,12 +136,11 @@ class ConnectionManager extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildConnectionCard(MapEntry<int, Client> entry) {
|
||||
final client = entry.value;
|
||||
Widget buildConnectionCard(Client client) {
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
key: ValueKey(entry.key),
|
||||
key: ValueKey(client.id),
|
||||
children: [
|
||||
_CmHeader(client: client),
|
||||
client.isFileTransfer ? Offstage() : _PrivilegeBoard(client: client),
|
||||
@ -124,14 +153,14 @@ class ConnectionManager extends StatelessWidget {
|
||||
).paddingSymmetric(vertical: 8.0, horizontal: 8.0);
|
||||
}
|
||||
|
||||
Widget buildTab(MapEntry<int, Client> entry) {
|
||||
Widget buildTab(Client client) {
|
||||
return Tab(
|
||||
child: Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 80,
|
||||
child: Text(
|
||||
"${entry.value.name}",
|
||||
"${client.name}",
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
textAlign: TextAlign.center,
|
||||
@ -261,7 +290,7 @@ class _CmHeaderState extends State<_CmHeader>
|
||||
Offstage(
|
||||
offstage: client.isFileTransfer,
|
||||
child: IconButton(
|
||||
onPressed: handleSendMsg,
|
||||
onPressed: () => gFFI.chatModel.toggleCMChatPage(client.id),
|
||||
icon: Icon(Icons.message_outlined),
|
||||
),
|
||||
)
|
||||
@ -269,8 +298,6 @@ class _CmHeaderState extends State<_CmHeader>
|
||||
);
|
||||
}
|
||||
|
||||
void handleSendMsg() {}
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
}
|
||||
|
@ -14,36 +14,19 @@ const double _kTabBarHeight = kDesktopRemoteTabBarHeight;
|
||||
const double _kIconSize = 18;
|
||||
const double _kDividerIndent = 10;
|
||||
const double _kActionIconSize = 12;
|
||||
final _tabBarKey = GlobalKey();
|
||||
|
||||
void closeTab(String? id) {
|
||||
final tabBar = _tabBarKey.currentWidget as _ListView?;
|
||||
if (tabBar == null) return;
|
||||
final tabs = tabBar.tabs;
|
||||
if (id == null) {
|
||||
if (tabBar.selected.value < tabs.length) {
|
||||
tabs[tabBar.selected.value].onClose();
|
||||
}
|
||||
} else {
|
||||
for (final tab in tabs) {
|
||||
if (tab.label == id) {
|
||||
tab.onClose();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TabInfo {
|
||||
late final String key;
|
||||
late final String label;
|
||||
late final IconData selectedIcon;
|
||||
late final IconData unselectedIcon;
|
||||
late final IconData? selectedIcon;
|
||||
late final IconData? unselectedIcon;
|
||||
late final bool closable;
|
||||
|
||||
TabInfo(
|
||||
{required this.label,
|
||||
required this.selectedIcon,
|
||||
required this.unselectedIcon,
|
||||
{required this.key,
|
||||
required this.label,
|
||||
this.selectedIcon,
|
||||
this.unselectedIcon,
|
||||
this.closable = true});
|
||||
}
|
||||
|
||||
@ -53,20 +36,33 @@ class DesktopTabBar extends StatelessWidget {
|
||||
late final bool dark;
|
||||
late final _Theme _theme;
|
||||
late final bool mainTab;
|
||||
late final Function()? onAddSetting;
|
||||
late final bool showLogo;
|
||||
late final bool showTitle;
|
||||
late final bool showMinimize;
|
||||
late final bool showMaximize;
|
||||
late final bool showClose;
|
||||
late final void Function()? onAddSetting;
|
||||
late final void Function(int)? onSelected;
|
||||
final ScrollPosController scrollController =
|
||||
ScrollPosController(itemCount: 0);
|
||||
static final Rx<PageController> controller = PageController().obs;
|
||||
static final Rx<int> selected = 0.obs;
|
||||
static final _tabBarListViewKey = GlobalKey();
|
||||
|
||||
DesktopTabBar({
|
||||
Key? key,
|
||||
required this.tabs,
|
||||
this.onTabClose,
|
||||
required this.dark,
|
||||
required this.mainTab,
|
||||
this.onAddSetting,
|
||||
}) : _theme = dark ? _Theme.dark() : _Theme.light(),
|
||||
DesktopTabBar(
|
||||
{Key? key,
|
||||
required this.tabs,
|
||||
this.onTabClose,
|
||||
required this.dark,
|
||||
required this.mainTab,
|
||||
this.onAddSetting,
|
||||
this.onSelected,
|
||||
this.showLogo = true,
|
||||
this.showTitle = true,
|
||||
this.showMinimize = true,
|
||||
this.showMaximize = true,
|
||||
this.showClose = true})
|
||||
: _theme = dark ? _Theme.dark() : _Theme.light(),
|
||||
super(key: key) {
|
||||
scrollController.itemCount = tabs.length;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
@ -88,22 +84,23 @@ class DesktopTabBar extends StatelessWidget {
|
||||
Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
Offstage(
|
||||
offstage: !mainTab,
|
||||
child: Row(children: [
|
||||
Image.asset(
|
||||
'assets/logo.ico',
|
||||
width: 20,
|
||||
height: 20,
|
||||
),
|
||||
Text(
|
||||
"RustDesk",
|
||||
style: TextStyle(fontSize: 13),
|
||||
).marginOnly(left: 2),
|
||||
]).marginOnly(
|
||||
left: 5,
|
||||
right: 10,
|
||||
),
|
||||
Row(children: [
|
||||
Offstage(
|
||||
offstage: !showLogo,
|
||||
child: Image.asset(
|
||||
'assets/logo.ico',
|
||||
width: 20,
|
||||
height: 20,
|
||||
)),
|
||||
Offstage(
|
||||
offstage: !showTitle,
|
||||
child: Text(
|
||||
"RustDesk",
|
||||
style: TextStyle(fontSize: 13),
|
||||
).marginOnly(left: 2))
|
||||
]).marginOnly(
|
||||
left: 5,
|
||||
right: 10,
|
||||
),
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
@ -116,13 +113,14 @@ class DesktopTabBar extends StatelessWidget {
|
||||
}
|
||||
},
|
||||
child: _ListView(
|
||||
key: _tabBarKey,
|
||||
key: _tabBarListViewKey,
|
||||
controller: controller,
|
||||
scrollController: scrollController,
|
||||
tabInfos: tabs,
|
||||
selected: selected,
|
||||
onTabClose: onTabClose,
|
||||
theme: _theme)),
|
||||
theme: _theme,
|
||||
onSelected: onSelected)),
|
||||
),
|
||||
Offstage(
|
||||
offstage: mainTab,
|
||||
@ -146,6 +144,9 @@ class DesktopTabBar extends StatelessWidget {
|
||||
WindowActionPanel(
|
||||
mainTab: mainTab,
|
||||
theme: _theme,
|
||||
showMinimize: showMinimize,
|
||||
showMaximize: showMaximize,
|
||||
showClose: showClose,
|
||||
)
|
||||
],
|
||||
),
|
||||
@ -160,7 +161,7 @@ class DesktopTabBar extends StatelessWidget {
|
||||
}
|
||||
|
||||
static onAdd(RxList<TabInfo> tabs, TabInfo tab) {
|
||||
int index = tabs.indexWhere((e) => e.label == tab.label);
|
||||
int index = tabs.indexWhere((e) => e.key == tab.key);
|
||||
if (index >= 0) {
|
||||
selected.value = index;
|
||||
} else {
|
||||
@ -168,86 +169,148 @@ class DesktopTabBar extends StatelessWidget {
|
||||
selected.value = tabs.length - 1;
|
||||
assert(selected.value >= 0);
|
||||
}
|
||||
try {
|
||||
controller.value.jumpToPage(selected.value);
|
||||
} catch (e) {
|
||||
// call before binding controller will throw
|
||||
debugPrint("Failed to jumpToPage: $e");
|
||||
}
|
||||
}
|
||||
|
||||
static remove(RxList<TabInfo> tabs, int index) {
|
||||
if (index < 0) return;
|
||||
if (index == tabs.length - 1) {
|
||||
selected.value = max(0, selected.value - 1);
|
||||
} else if (index < tabs.length - 1 && index < selected.value) {
|
||||
selected.value = max(0, selected.value - 1);
|
||||
}
|
||||
tabs.removeAt(index);
|
||||
controller.value.jumpToPage(selected.value);
|
||||
}
|
||||
|
||||
static void jumpTo(RxList<TabInfo> tabs, int index) {
|
||||
if (index < 0 || index >= tabs.length) return;
|
||||
selected.value = index;
|
||||
controller.value.jumpToPage(selected.value);
|
||||
}
|
||||
|
||||
static void close(String? key) {
|
||||
final tabBar = _tabBarListViewKey.currentWidget as _ListView?;
|
||||
if (tabBar == null) return;
|
||||
final tabs = tabBar.tabs;
|
||||
if (key == null) {
|
||||
if (tabBar.selected.value < tabs.length) {
|
||||
tabs[tabBar.selected.value].onClose();
|
||||
}
|
||||
} else {
|
||||
for (final tab in tabs) {
|
||||
if (tab.key == key) {
|
||||
tab.onClose();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class WindowActionPanel extends StatelessWidget {
|
||||
final bool mainTab;
|
||||
final _Theme theme;
|
||||
|
||||
final bool showMinimize;
|
||||
final bool showMaximize;
|
||||
final bool showClose;
|
||||
|
||||
const WindowActionPanel(
|
||||
{Key? key, required this.mainTab, required this.theme})
|
||||
{Key? key,
|
||||
required this.mainTab,
|
||||
required this.theme,
|
||||
this.showMinimize = true,
|
||||
this.showMaximize = true,
|
||||
this.showClose = true})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
_ActionIcon(
|
||||
message: 'Minimize',
|
||||
icon: IconFont.min,
|
||||
theme: theme,
|
||||
onTap: () {
|
||||
if (mainTab) {
|
||||
windowManager.minimize();
|
||||
} else {
|
||||
WindowController.fromWindowId(windowId!).minimize();
|
||||
}
|
||||
},
|
||||
is_close: false,
|
||||
),
|
||||
FutureBuilder(builder: (context, snapshot) {
|
||||
RxBool is_maximized = false.obs;
|
||||
if (mainTab) {
|
||||
windowManager.isMaximized().then((maximized) {
|
||||
is_maximized.value = maximized;
|
||||
});
|
||||
} else {
|
||||
final wc = WindowController.fromWindowId(windowId!);
|
||||
wc.isMaximized().then((maximized) {
|
||||
is_maximized.value = maximized;
|
||||
});
|
||||
}
|
||||
return Obx(
|
||||
() => _ActionIcon(
|
||||
message: is_maximized.value ? "Restore" : "Maximize",
|
||||
icon: is_maximized.value ? IconFont.restore : IconFont.max,
|
||||
Offstage(
|
||||
offstage: !showMinimize,
|
||||
child: _ActionIcon(
|
||||
message: 'Minimize',
|
||||
icon: IconFont.min,
|
||||
theme: theme,
|
||||
onTap: () {
|
||||
if (mainTab) {
|
||||
if (is_maximized.value) {
|
||||
windowManager.unmaximize();
|
||||
} else {
|
||||
windowManager.maximize();
|
||||
}
|
||||
windowManager.minimize();
|
||||
} else {
|
||||
final wc = WindowController.fromWindowId(windowId!);
|
||||
if (is_maximized.value) {
|
||||
wc.unmaximize();
|
||||
} else {
|
||||
wc.maximize();
|
||||
}
|
||||
WindowController.fromWindowId(windowId!).minimize();
|
||||
}
|
||||
is_maximized.value = !is_maximized.value;
|
||||
},
|
||||
is_close: false,
|
||||
),
|
||||
);
|
||||
}),
|
||||
_ActionIcon(
|
||||
message: 'Close',
|
||||
icon: IconFont.close,
|
||||
theme: theme,
|
||||
onTap: () {
|
||||
if (mainTab) {
|
||||
windowManager.close();
|
||||
} else {
|
||||
WindowController.fromWindowId(windowId!).close();
|
||||
}
|
||||
},
|
||||
is_close: true,
|
||||
),
|
||||
)),
|
||||
Offstage(
|
||||
offstage: !showMaximize,
|
||||
child: FutureBuilder(builder: (context, snapshot) {
|
||||
RxBool is_maximized = false.obs;
|
||||
if (mainTab) {
|
||||
windowManager.isMaximized().then((maximized) {
|
||||
is_maximized.value = maximized;
|
||||
});
|
||||
} else {
|
||||
final wc = WindowController.fromWindowId(windowId!);
|
||||
wc.isMaximized().then((maximized) {
|
||||
is_maximized.value = maximized;
|
||||
});
|
||||
}
|
||||
return Obx(
|
||||
() => _ActionIcon(
|
||||
message: is_maximized.value ? "Restore" : "Maximize",
|
||||
icon: is_maximized.value ? IconFont.restore : IconFont.max,
|
||||
theme: theme,
|
||||
onTap: () {
|
||||
if (mainTab) {
|
||||
if (is_maximized.value) {
|
||||
windowManager.unmaximize();
|
||||
} else {
|
||||
WindowController.fromWindowId(windowId!).minimize();
|
||||
}
|
||||
} else {
|
||||
final wc = WindowController.fromWindowId(windowId!);
|
||||
if (is_maximized.value) {
|
||||
wc.unmaximize();
|
||||
} else {
|
||||
final wc = WindowController.fromWindowId(windowId!);
|
||||
wc.isMaximized().then((maximized) {
|
||||
if (maximized) {
|
||||
wc.unmaximize();
|
||||
} else {
|
||||
wc.maximize();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
is_maximized.value = !is_maximized.value;
|
||||
},
|
||||
is_close: false,
|
||||
),
|
||||
);
|
||||
})),
|
||||
Offstage(
|
||||
offstage: !showClose,
|
||||
child: _ActionIcon(
|
||||
message: 'Close',
|
||||
icon: IconFont.close,
|
||||
theme: theme,
|
||||
onTap: () {
|
||||
if (mainTab) {
|
||||
windowManager.close();
|
||||
} else {
|
||||
WindowController.fromWindowId(windowId!).close();
|
||||
}
|
||||
},
|
||||
is_close: true,
|
||||
)),
|
||||
],
|
||||
);
|
||||
}
|
||||
@ -259,19 +322,21 @@ class _ListView extends StatelessWidget {
|
||||
final ScrollPosController scrollController;
|
||||
final RxList<TabInfo> tabInfos;
|
||||
final Rx<int> selected;
|
||||
final Function(String label)? onTabClose;
|
||||
final Function(String key)? onTabClose;
|
||||
final _Theme _theme;
|
||||
late List<_Tab> tabs;
|
||||
late final void Function(int)? onSelected;
|
||||
|
||||
_ListView({
|
||||
Key? key,
|
||||
required this.controller,
|
||||
required this.scrollController,
|
||||
required this.tabInfos,
|
||||
required this.selected,
|
||||
required this.onTabClose,
|
||||
required _Theme theme,
|
||||
}) : _theme = theme,
|
||||
_ListView(
|
||||
{Key? key,
|
||||
required this.controller,
|
||||
required this.scrollController,
|
||||
required this.tabInfos,
|
||||
required this.selected,
|
||||
required this.onTabClose,
|
||||
required _Theme theme,
|
||||
this.onSelected})
|
||||
: _theme = theme,
|
||||
super(key: key);
|
||||
|
||||
@override
|
||||
@ -279,17 +344,16 @@ class _ListView extends StatelessWidget {
|
||||
return Obx(() {
|
||||
tabs = tabInfos.asMap().entries.map((e) {
|
||||
int index = e.key;
|
||||
String label = e.value.label;
|
||||
return _Tab(
|
||||
index: index,
|
||||
label: label,
|
||||
label: e.value.label,
|
||||
selectedIcon: e.value.selectedIcon,
|
||||
unselectedIcon: e.value.unselectedIcon,
|
||||
closable: e.value.closable,
|
||||
selected: selected.value,
|
||||
onClose: () {
|
||||
tabInfos.removeWhere((tab) => tab.label == label);
|
||||
onTabClose?.call(label);
|
||||
tabInfos.removeWhere((tab) => tab.key == e.value.key);
|
||||
onTabClose?.call(e.value.key);
|
||||
if (index <= selected.value) {
|
||||
selected.value = max(0, selected.value - 1);
|
||||
}
|
||||
@ -305,6 +369,7 @@ class _ListView extends StatelessWidget {
|
||||
selected.value = index;
|
||||
scrollController.scrollToItem(index, center: true, animate: true);
|
||||
controller.value.jumpToPage(index);
|
||||
onSelected?.call(selected.value);
|
||||
},
|
||||
theme: _theme,
|
||||
);
|
||||
@ -322,8 +387,8 @@ class _ListView extends StatelessWidget {
|
||||
class _Tab extends StatelessWidget {
|
||||
late final int index;
|
||||
late final String label;
|
||||
late final IconData selectedIcon;
|
||||
late final IconData unselectedIcon;
|
||||
late final IconData? selectedIcon;
|
||||
late final IconData? unselectedIcon;
|
||||
late final bool closable;
|
||||
late final int selected;
|
||||
late final Function() onClose;
|
||||
@ -335,8 +400,8 @@ class _Tab extends StatelessWidget {
|
||||
{Key? key,
|
||||
required this.index,
|
||||
required this.label,
|
||||
required this.selectedIcon,
|
||||
required this.unselectedIcon,
|
||||
this.selectedIcon,
|
||||
this.unselectedIcon,
|
||||
required this.closable,
|
||||
required this.selected,
|
||||
required this.onClose,
|
||||
@ -346,6 +411,7 @@ class _Tab extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
bool show_icon = selectedIcon != null && unselectedIcon != null;
|
||||
bool is_selected = index == selected;
|
||||
bool show_divider = index != selected - 1 && index != selected;
|
||||
return Ink(
|
||||
@ -362,13 +428,15 @@ class _Tab extends StatelessWidget {
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
is_selected ? selectedIcon : unselectedIcon,
|
||||
size: _kIconSize,
|
||||
color: is_selected
|
||||
? theme.selectedtabIconColor
|
||||
: theme.unSelectedtabIconColor,
|
||||
).paddingOnly(right: 5),
|
||||
Offstage(
|
||||
offstage: !show_icon,
|
||||
child: Icon(
|
||||
is_selected ? selectedIcon : unselectedIcon,
|
||||
size: _kIconSize,
|
||||
color: is_selected
|
||||
? theme.selectedtabIconColor
|
||||
: theme.unSelectedtabIconColor,
|
||||
).paddingOnly(right: 5)),
|
||||
Text(
|
||||
translate(label),
|
||||
textAlign: TextAlign.center,
|
||||
|
@ -359,12 +359,12 @@ class ConnectionManager extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final serverModel = Provider.of<ServerModel>(context);
|
||||
return Column(
|
||||
children: serverModel.clients.entries
|
||||
.map((entry) => PaddingCard(
|
||||
title: translate(entry.value.isFileTransfer
|
||||
children: serverModel.clients
|
||||
.map((client) => PaddingCard(
|
||||
title: translate(client.isFileTransfer
|
||||
? "File Connection"
|
||||
: "Screen Connection"),
|
||||
titleIcon: entry.value.isFileTransfer
|
||||
titleIcon: client.isFileTransfer
|
||||
? Icons.folder_outlined
|
||||
: Icons.mobile_screen_share,
|
||||
child: Column(
|
||||
@ -373,16 +373,14 @@ class ConnectionManager extends StatelessWidget {
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(child: clientInfo(entry.value)),
|
||||
Expanded(child: clientInfo(client)),
|
||||
Expanded(
|
||||
flex: -1,
|
||||
child: entry.value.isFileTransfer ||
|
||||
!entry.value.authorized
|
||||
child: client.isFileTransfer || !client.authorized
|
||||
? SizedBox.shrink()
|
||||
: IconButton(
|
||||
onPressed: () {
|
||||
gFFI.chatModel
|
||||
.changeCurrentID(entry.value.id);
|
||||
gFFI.chatModel.changeCurrentID(client.id);
|
||||
final bar =
|
||||
navigationBarKey.currentWidget;
|
||||
if (bar != null) {
|
||||
@ -396,37 +394,35 @@ class ConnectionManager extends StatelessWidget {
|
||||
)))
|
||||
],
|
||||
),
|
||||
entry.value.authorized
|
||||
client.authorized
|
||||
? SizedBox.shrink()
|
||||
: Text(
|
||||
translate("android_new_connection_tip"),
|
||||
style: TextStyle(color: Colors.black54),
|
||||
),
|
||||
entry.value.authorized
|
||||
client.authorized
|
||||
? ElevatedButton.icon(
|
||||
style: ButtonStyle(
|
||||
backgroundColor:
|
||||
MaterialStateProperty.all(Colors.red)),
|
||||
icon: Icon(Icons.close),
|
||||
onPressed: () {
|
||||
bind.cmCloseConnection(connId: entry.key);
|
||||
bind.cmCloseConnection(connId: client.id);
|
||||
gFFI.invokeMethod(
|
||||
"cancel_notification", entry.key);
|
||||
"cancel_notification", client.id);
|
||||
},
|
||||
label: Text(translate("Close")))
|
||||
: Row(children: [
|
||||
TextButton(
|
||||
child: Text(translate("Dismiss")),
|
||||
onPressed: () {
|
||||
serverModel.sendLoginResponse(
|
||||
entry.value, false);
|
||||
serverModel.sendLoginResponse(client, false);
|
||||
}),
|
||||
SizedBox(width: 20),
|
||||
ElevatedButton(
|
||||
child: Text(translate("Accept")),
|
||||
onPressed: () {
|
||||
serverModel.sendLoginResponse(
|
||||
entry.value, true);
|
||||
serverModel.sendLoginResponse(client, true);
|
||||
}),
|
||||
]),
|
||||
],
|
||||
|
@ -4,9 +4,11 @@ import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hbb/models/platform_model.dart';
|
||||
import 'package:get/get_rx/src/rx_types/rx_types.dart';
|
||||
import 'package:wakelock/wakelock.dart';
|
||||
|
||||
import '../common.dart';
|
||||
import '../desktop/widgets/tabbar_widget.dart';
|
||||
import '../mobile/pages/server_page.dart';
|
||||
import 'model.dart';
|
||||
|
||||
@ -30,7 +32,9 @@ class ServerModel with ChangeNotifier {
|
||||
late final TextEditingController _serverId;
|
||||
final _serverPasswd = TextEditingController(text: "");
|
||||
|
||||
Map<int, Client> _clients = {};
|
||||
RxList<TabInfo> tabs = RxList<TabInfo>.empty(growable: true);
|
||||
|
||||
List<Client> _clients = [];
|
||||
|
||||
bool get isStart => _isStart;
|
||||
|
||||
@ -76,7 +80,7 @@ class ServerModel with ChangeNotifier {
|
||||
|
||||
TextEditingController get serverPasswd => _serverPasswd;
|
||||
|
||||
Map<int, Client> get clients => _clients;
|
||||
List<Client> get clients => _clients;
|
||||
|
||||
final controller = ScrollController();
|
||||
|
||||
@ -338,6 +342,7 @@ class ServerModel with ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// force
|
||||
updateClientState([String? json]) async {
|
||||
var res = await bind.mainGetClientsState();
|
||||
try {
|
||||
@ -347,9 +352,16 @@ class ServerModel with ChangeNotifier {
|
||||
exit(0);
|
||||
}
|
||||
_clients.clear();
|
||||
tabs.clear();
|
||||
for (var clientJson in clientsJson) {
|
||||
final client = Client.fromJson(clientJson);
|
||||
_clients[client.id] = client;
|
||||
_clients.add(client);
|
||||
DesktopTabBar.onAdd(
|
||||
tabs,
|
||||
TabInfo(
|
||||
key: client.id.toString(),
|
||||
label: client.name,
|
||||
closable: false));
|
||||
}
|
||||
notifyListeners();
|
||||
} catch (e) {
|
||||
@ -360,10 +372,14 @@ class ServerModel with ChangeNotifier {
|
||||
void loginRequest(Map<String, dynamic> evt) {
|
||||
try {
|
||||
final client = Client.fromJson(jsonDecode(evt["client"]));
|
||||
if (_clients.containsKey(client.id)) {
|
||||
if (_clients.any((c) => c.id == client.id)) {
|
||||
return;
|
||||
}
|
||||
_clients[client.id] = client;
|
||||
_clients.add(client);
|
||||
DesktopTabBar.onAdd(
|
||||
tabs,
|
||||
TabInfo(
|
||||
key: client.id.toString(), label: client.name, closable: false));
|
||||
scrollToBottom();
|
||||
notifyListeners();
|
||||
if (isAndroid) showLoginDialog(client);
|
||||
@ -419,6 +435,7 @@ class ServerModel with ChangeNotifier {
|
||||
}
|
||||
|
||||
scrollToBottom() {
|
||||
if (isDesktop) return;
|
||||
Future.delayed(Duration(milliseconds: 200), () {
|
||||
controller.animateTo(controller.position.maxScrollExtent,
|
||||
duration: Duration(milliseconds: 200),
|
||||
@ -433,12 +450,14 @@ class ServerModel with ChangeNotifier {
|
||||
parent.target?.invokeMethod("start_capture");
|
||||
}
|
||||
parent.target?.invokeMethod("cancel_notification", client.id);
|
||||
_clients[client.id]?.authorized = true;
|
||||
client.authorized = true;
|
||||
notifyListeners();
|
||||
} else {
|
||||
bind.cmLoginRes(connId: client.id, res: res);
|
||||
parent.target?.invokeMethod("cancel_notification", client.id);
|
||||
_clients.remove(client.id);
|
||||
final index = _clients.indexOf(client);
|
||||
DesktopTabBar.remove(tabs, index);
|
||||
_clients.remove(client);
|
||||
}
|
||||
}
|
||||
|
||||
@ -446,7 +465,11 @@ class ServerModel with ChangeNotifier {
|
||||
try {
|
||||
final client = Client.fromJson(jsonDecode(evt['client']));
|
||||
parent.target?.dialogManager.dismissByTag(getLoginDialogTag(client.id));
|
||||
_clients[client.id] = client;
|
||||
_clients.add(client);
|
||||
DesktopTabBar.onAdd(
|
||||
tabs,
|
||||
TabInfo(
|
||||
key: client.id.toString(), label: client.name, closable: false));
|
||||
scrollToBottom();
|
||||
notifyListeners();
|
||||
} catch (e) {}
|
||||
@ -455,8 +478,10 @@ class ServerModel with ChangeNotifier {
|
||||
void onClientRemove(Map<String, dynamic> evt) {
|
||||
try {
|
||||
final id = int.parse(evt['id'] as String);
|
||||
if (_clients.containsKey(id)) {
|
||||
_clients.remove(id);
|
||||
if (_clients.any((c) => c.id == id)) {
|
||||
final index = _clients.indexWhere((client) => client.id == id);
|
||||
_clients.removeAt(index);
|
||||
DesktopTabBar.remove(tabs, index);
|
||||
parent.target?.dialogManager.dismissByTag(getLoginDialogTag(id));
|
||||
parent.target?.invokeMethod("cancel_notification", id);
|
||||
}
|
||||
@ -467,10 +492,11 @@ class ServerModel with ChangeNotifier {
|
||||
}
|
||||
|
||||
closeAll() {
|
||||
_clients.forEach((id, client) {
|
||||
bind.cmCloseConnection(connId: id);
|
||||
_clients.forEach((client) {
|
||||
bind.cmCloseConnection(connId: client.id);
|
||||
});
|
||||
_clients.clear();
|
||||
tabs.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@ -486,7 +512,7 @@ class Client {
|
||||
bool file = false;
|
||||
bool restart = false;
|
||||
|
||||
Client(this.authorized, this.isFileTransfer, this.name, this.peerId,
|
||||
Client(this.id, this.authorized, this.isFileTransfer, this.name, this.peerId,
|
||||
this.keyboard, this.clipboard, this.audio);
|
||||
|
||||
Client.fromJson(Map<String, dynamic> json) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user