enable group, show accessible users and peers

Signed-off-by: 21pages <pages21@163.com>
This commit is contained in:
21pages
2023-09-14 10:17:03 +08:00
parent 09d380ba8f
commit b2a4f11e0b
53 changed files with 568 additions and 273 deletions

View File

@@ -2480,3 +2480,59 @@ String toCapitalized(String s) {
}
return s.substring(0, 1).toUpperCase() + s.substring(1);
}
Widget buildErrorBanner(BuildContext context,
{required RxBool loading,
required RxString err,
required Function? retry,
required Function close}) {
const double height = 25;
return Obx(() => Offstage(
offstage: !(!loading.value && err.value.isNotEmpty),
child: Center(
child: Container(
height: height,
color: MyTheme.color(context).errorBannerBg,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
FittedBox(
child: Icon(
Icons.info,
color: Color.fromARGB(255, 249, 81, 81),
),
).marginAll(4),
Flexible(
child: Align(
alignment: Alignment.centerLeft,
child: Tooltip(
message: translate(err.value),
child: Text(
translate(err.value),
overflow: TextOverflow.ellipsis,
),
)).marginSymmetric(vertical: 2),
),
if (retry != null)
InkWell(
onTap: () {
retry.call();
},
child: Text(
translate("Retry"),
style: TextStyle(color: MyTheme.accent),
)).marginSymmetric(horizontal: 5),
FittedBox(
child: InkWell(
onTap: () {
close.call();
},
child: Icon(Icons.close).marginSymmetric(horizontal: 5),
),
).marginAll(4)
],
),
)).marginOnly(bottom: 14),
));
}

View File

@@ -1,5 +1,6 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_hbb/consts.dart';
import 'package:flutter_hbb/models/peer_model.dart';
@@ -48,11 +49,18 @@ class UserPayload {
};
return map;
}
Map<String, dynamic> toGroupCacheJson() {
final Map<String, dynamic> map = {
'name': name,
};
return map;
}
}
class PeerPayload {
String id = '';
String info = '';
Map<String, dynamic> info = {};
int? status;
String user = '';
String user_name = '';
@@ -67,7 +75,38 @@ class PeerPayload {
note = json['note'] ?? '';
static Peer toPeer(PeerPayload p) {
return Peer.fromJson({"id": p.id, "username": p.user_name});
return Peer.fromJson({
"id": p.id,
'loginName': p.user_name,
"username": p.info['username'] ?? '',
"platform": _platform(p.info['os']),
"hostname": p.info['device_name'],
});
}
static String? _platform(dynamic field) {
if (field == null) {
return null;
}
final fieldStr = field.toString();
List<String> list = fieldStr.split(' / ');
if (list.isEmpty) return null;
final os = list[0];
switch (os.toLowerCase()) {
case 'windows':
return kPeerPlatformWindows;
case 'linux':
return kPeerPlatformLinux;
case 'macos':
return kPeerPlatformMacOS;
case 'android':
return kPeerPlatformAndroid;
default:
if (fieldStr.toLowerCase().contains('linux')) {
return kPeerPlatformLinux;
}
return null;
}
}
}

View File

@@ -35,7 +35,7 @@ class _AddressBookState extends State<AddressBook> {
@override
Widget build(BuildContext context) => Obx(() {
if (gFFI.userModel.userName.value.isEmpty) {
if (!gFFI.userModel.isLogin) {
return Center(
child: ElevatedButton(
onPressed: loginDialog, child: Text(translate("Login"))));
@@ -49,11 +49,13 @@ class _AddressBookState extends State<AddressBook> {
children: [
// NOT use Offstage to wrap LinearProgressIndicator
if (gFFI.abModel.retrying.value) LinearProgressIndicator(),
_buildErrorBanner(
buildErrorBanner(context,
loading: gFFI.abModel.abLoading,
err: gFFI.abModel.pullError,
retry: null,
close: () => gFFI.abModel.pullError.value = ''),
_buildErrorBanner(
buildErrorBanner(context,
loading: gFFI.abModel.abLoading,
err: gFFI.abModel.pushError,
retry: () => gFFI.abModel.pushAb(isRetry: true),
close: () => gFFI.abModel.pushError.value = ''),
@@ -66,61 +68,6 @@ class _AddressBookState extends State<AddressBook> {
}
});
Widget _buildErrorBanner(
{required RxString err,
required Function? retry,
required Function close}) {
const double height = 25;
return Obx(() => Offstage(
offstage: !(!gFFI.abModel.abLoading.value && err.value.isNotEmpty),
child: Center(
child: Container(
height: height,
color: MyTheme.color(context).errorBannerBg,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
FittedBox(
child: Icon(
Icons.info,
color: Color.fromARGB(255, 249, 81, 81),
),
).marginAll(4),
Flexible(
child: Align(
alignment: Alignment.centerLeft,
child: Tooltip(
message: translate(err.value),
child: Text(
translate(err.value),
overflow: TextOverflow.ellipsis,
),
)).marginSymmetric(vertical: 2),
),
if (retry != null)
InkWell(
onTap: () {
retry.call();
},
child: Text(
translate("Retry"),
style: TextStyle(color: MyTheme.accent),
)).marginSymmetric(horizontal: 5),
FittedBox(
child: InkWell(
onTap: () {
close.call();
},
child: Icon(Icons.close).marginSymmetric(horizontal: 5),
),
).marginAll(4)
],
),
)).marginOnly(bottom: 14),
));
}
Widget _buildAddressBookDesktop() {
return Row(
children: [
@@ -230,11 +177,10 @@ class _AddressBookState extends State<AddressBook> {
return Expanded(
child: Align(
alignment: Alignment.topLeft,
child: Obx(() => AddressBookPeersView(
menuPadding: widget.menuPadding,
// ignore: invalid_use_of_protected_member
initPeers: gFFI.abModel.peers.value,
))),
child: AddressBookPeersView(
menuPadding: widget.menuPadding,
initPeers: gFFI.abModel.peers,
)),
);
}

View File

@@ -29,49 +29,28 @@ class _MyGroupState extends State<MyGroup> {
@override
Widget build(BuildContext context) {
return Obx(() {
// use username to be same with ab
if (gFFI.userModel.userName.value.isEmpty) {
if (!gFFI.userModel.isLogin) {
return Center(
child: ElevatedButton(
onPressed: loginDialog, child: Text(translate("Login"))));
}
return buildBody(context);
});
}
Widget buildBody(BuildContext context) {
return Obx(() {
if (gFFI.groupModel.groupLoading.value) {
} else if (gFFI.groupModel.groupLoading.value && gFFI.groupModel.emtpy) {
return const Center(
child: CircularProgressIndicator(),
);
}
if (gFFI.groupModel.groupLoadError.isNotEmpty) {
return _buildShowError(gFFI.groupModel.groupLoadError.value);
}
if (isDesktop) {
return _buildDesktop();
} else {
return _buildMobile();
}
return Column(
children: [
buildErrorBanner(context,
loading: gFFI.groupModel.groupLoading,
err: gFFI.groupModel.groupLoadError,
retry: null,
close: () => gFFI.groupModel.groupLoadError.value = ''),
Expanded(child: isDesktop ? _buildDesktop() : _buildMobile())
],
);
});
}
Widget _buildShowError(String error) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(translate(error)),
TextButton(
onPressed: () {
gFFI.groupModel.pull();
},
child: Text(translate("Retry")))
],
));
}
Widget _buildDesktop() {
return Row(
children: [
@@ -100,10 +79,9 @@ class _MyGroupState extends State<MyGroup> {
Expanded(
child: Align(
alignment: Alignment.topLeft,
child: Obx(() => MyGroupPeerView(
child: MyGroupPeerView(
menuPadding: widget.menuPadding,
// ignore: invalid_use_of_protected_member
initPeers: gFFI.groupModel.peersShow.value))),
initPeers: gFFI.groupModel.peers)),
)
],
);
@@ -133,10 +111,9 @@ class _MyGroupState extends State<MyGroup> {
Expanded(
child: Align(
alignment: Alignment.topLeft,
child: Obx(() => MyGroupPeerView(
child: MyGroupPeerView(
menuPadding: widget.menuPadding,
// ignore: invalid_use_of_protected_member
initPeers: gFFI.groupModel.peersShow.value))),
initPeers: gFFI.groupModel.peers)),
)
],
);
@@ -195,6 +172,7 @@ class _MyGroupState extends State<MyGroup> {
}, child: Obx(
() {
bool selected = selectedUser.value == username;
final isMe = username == gFFI.userModel.userName.value;
return Container(
decoration: BoxDecoration(
color: selected ? MyTheme.color(context).highlight : null,
@@ -208,7 +186,7 @@ class _MyGroupState extends State<MyGroup> {
children: [
Icon(Icons.person_rounded, color: Colors.grey, size: 16)
.marginOnly(right: 4),
Expanded(child: Text(username)),
Expanded(child: Text(isMe ? translate('Me') : username)),
],
).paddingSymmetric(vertical: 4),
),

View File

@@ -1093,7 +1093,7 @@ class MyGroupPeerCard extends BasePeerCard {
menuItems.add(_tcpTunnelingAction(context, peer.id));
}
// menuItems.add(await _openNewConnInOptAction(peer.id));
menuItems.add(await _forceAlwaysRelayAction(peer.id));
// menuItems.add(await _forceAlwaysRelayAction(peer.id));
if (peer.platform == 'Windows') {
menuItems.add(_rdpAction(context, peer.id));
}
@@ -1101,9 +1101,14 @@ class MyGroupPeerCard extends BasePeerCard {
menuItems.add(_createShortCutAction(peer.id));
}
menuItems.add(MenuEntryDivider());
menuItems.add(_renameAction(peer.id));
if (await bind.mainPeerHasPassword(id: peer.id)) {
menuItems.add(_unrememberPasswordAction(peer.id));
// menuItems.add(_renameAction(peer.id));
// if (await bind.mainPeerHasPassword(id: peer.id)) {
// menuItems.add(_unrememberPasswordAction(peer.id));
// }
if (gFFI.userModel.userName.isNotEmpty) {
if (!gFFI.abModel.idContainBy(peer.id)) {
menuItems.add(_addToAb(peer));
}
}
return menuItems;
}

View File

@@ -111,7 +111,11 @@ class _PeerTabPageState extends State<PeerTabPage>
child:
visibleContextMenuListener(_createSwitchBar(context))),
const PeerSearchBar().marginOnly(right: isMobile ? 0 : 13),
_createRefresh(),
_createRefresh(
index: PeerTabIndex.ab, loading: gFFI.abModel.abLoading),
_createRefresh(
index: PeerTabIndex.group,
loading: gFFI.groupModel.groupLoading),
_createMultiSelection(),
Offstage(
offstage: !isDesktop,
@@ -170,12 +174,12 @@ class _PeerTabPageState extends State<PeerTabPage>
));
return Obx(() => InkWell(
child: Container(
decoration:
selected ? decoBorder : (hover.value ? deco : null),
decoration: (hover.value
? (selected ? decoBorder : deco)
: (selected ? decoBorder : null)),
child: Tooltip(
preferBelow: false,
message:
model.tabTooltip(t, gFFI.groupModel.groupName.value),
message: model.tabTooltip(t),
onTriggered: isMobile ? mobileShowTabVisibilityMenu : null,
child: Icon(model.tabIcon(t), color: color),
).paddingSymmetric(horizontal: 4),
@@ -212,17 +216,19 @@ class _PeerTabPageState extends State<PeerTabPage>
child: child.marginSymmetric(vertical: isDesktop ? 12.0 : 6.0));
}
Widget _createRefresh() {
Widget _createRefresh(
{required PeerTabIndex index, required RxBool loading}) {
final model = Provider.of<PeerTabModel>(context);
final textColor = Theme.of(context).textTheme.titleLarge?.color;
return Offstage(
offstage: gFFI.peerTabModel.currentTab != PeerTabIndex.ab.index,
offstage: model.currentTab != index.index,
child: RefreshWidget(
onPressed: () {
if (gFFI.peerTabModel.currentTab < entries.length) {
entries[gFFI.peerTabModel.currentTab].load();
}
},
spinning: gFFI.abModel.abLoading,
spinning: loading,
child: RotatedBox(
quarterTurns: 2,
child: Tooltip(
@@ -297,9 +303,7 @@ class _PeerTabPageState extends State<PeerTabPage>
Navigator.pop(context);
}
}),
Expanded(
child:
Text(model.tabTooltip(i, gFFI.groupModel.groupName.value))),
Expanded(child: Text(model.tabTooltip(i))),
],
),
));
@@ -348,7 +352,7 @@ class _PeerTabPageState extends State<PeerTabPage>
for (int i = 0; i < model.tabNames.length; i++) {
menu.add(MenuEntrySwitch(
switchType: SwitchType.scheckbox,
text: model.tabTooltip(i, gFFI.groupModel.groupName.value),
text: model.tabTooltip(i),
getter: () async {
return model.isVisible[i];
},
@@ -388,6 +392,9 @@ class _PeerTabPageState extends State<PeerTabPage>
Widget deleteSelection() {
final model = Provider.of<PeerTabModel>(context);
if (model.currentTab == PeerTabIndex.group.index) {
return Offstage();
}
return _hoverAction(
context: context,
onTap: () {

View File

@@ -35,6 +35,7 @@ class LoadEvent {
static const String favorite = 'load_fav_peers';
static const String lan = 'load_lan_peers';
static const String addressBook = 'load_address_book_peers';
static const String group = 'load_group_peers';
}
/// for peer search text, global obs value
@@ -312,7 +313,7 @@ abstract class BasePeersView extends StatelessWidget {
final String loadEvent;
final PeerFilter? peerFilter;
final PeerCardBuilder peerCardBuilder;
final List<Peer> initPeers;
final RxList<Peer>? initPeers;
const BasePeersView({
Key? key,
@@ -326,7 +327,7 @@ abstract class BasePeersView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return _PeersView(
peers: Peers(name: name, loadEvent: loadEvent, peers: initPeers),
peers: Peers(name: name, loadEvent: loadEvent, initPeers: initPeers),
peerFilter: peerFilter,
peerCardBuilder: peerCardBuilder);
}
@@ -343,7 +344,7 @@ class RecentPeersView extends BasePeersView {
peer: peer,
menuPadding: menuPadding,
),
initPeers: [],
initPeers: null,
);
@override
@@ -365,7 +366,7 @@ class FavoritePeersView extends BasePeersView {
peer: peer,
menuPadding: menuPadding,
),
initPeers: [],
initPeers: null,
);
@override
@@ -387,7 +388,7 @@ class DiscoveredPeersView extends BasePeersView {
peer: peer,
menuPadding: menuPadding,
),
initPeers: [],
initPeers: null,
);
@override
@@ -403,7 +404,7 @@ class AddressBookPeersView extends BasePeersView {
{Key? key,
EdgeInsets? menuPadding,
ScrollController? scrollController,
required List<Peer> initPeers})
required RxList<Peer> initPeers})
: super(
key: key,
name: 'address book peer',
@@ -435,11 +436,11 @@ class MyGroupPeerView extends BasePeersView {
{Key? key,
EdgeInsets? menuPadding,
ScrollController? scrollController,
required List<Peer> initPeers})
required RxList<Peer> initPeers})
: super(
key: key,
name: 'my group peer',
loadEvent: 'load_my_group_peers',
name: 'group peer',
loadEvent: LoadEvent.group,
peerFilter: filter,
peerCardBuilder: (Peer peer) => MyGroupPeerCard(
peer: peer,
@@ -450,12 +451,12 @@ class MyGroupPeerView extends BasePeersView {
static bool filter(Peer peer) {
if (gFFI.groupModel.searchUserText.isNotEmpty) {
if (!peer.username.contains(gFFI.groupModel.searchUserText)) {
if (!peer.loginName.contains(gFFI.groupModel.searchUserText)) {
return false;
}
}
if (gFFI.groupModel.selectedUser.isNotEmpty) {
if (gFFI.groupModel.selectedUser.value != peer.username) {
if (gFFI.groupModel.selectedUser.value != peer.loginName) {
return false;
}
}

View File

@@ -126,6 +126,7 @@ void runMainApp(bool startService) async {
bind.pluginListReload();
}
gFFI.abModel.loadCache();
gFFI.groupModel.loadCache();
gFFI.userModel.refreshCurrentUser();
runApp(App());
// Set window option.
@@ -154,6 +155,7 @@ void runMobileApp() async {
if (isAndroid) androidChannelInit();
platformFFI.syncAndroidServiceAppDirConfigPath();
gFFI.abModel.loadCache();
gFFI.groupModel.loadCache();
gFFI.userModel.refreshCurrentUser();
runApp(App());
}

View File

@@ -3,6 +3,7 @@ import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_hbb/common/widgets/peers_view.dart';
import 'package:flutter_hbb/models/model.dart';
import 'package:flutter_hbb/models/peer_model.dart';
import 'package:flutter_hbb/models/peer_tab_model.dart';
@@ -115,9 +116,10 @@ class AbModel {
_timerCounter = 0;
if (pullError.isNotEmpty) {
if (statusCode == 401) {
gFFI.userModel.reset(clearAbCache: true);
gFFI.userModel.reset(resetOther: true);
}
}
platformFFI.tryHandle({'name': LoadEvent.addressBook});
}
}
@@ -241,7 +243,8 @@ class AbModel {
ret = true;
_saveCache();
} else {
Map<String, dynamic> json = _jsonDecodeResp(resp.body, resp.statusCode);
Map<String, dynamic> json =
_jsonDecodeResp(utf8.decode(resp.bodyBytes), resp.statusCode);
if (json.containsKey('error')) {
throw json['error'];
} else if (resp.statusCode == 200) {
@@ -479,11 +482,12 @@ class AbModel {
loadCache() async {
try {
if (_cacheLoadOnceFlag || abLoading.value) return;
if (_cacheLoadOnceFlag || abLoading.value || initialized) return;
_cacheLoadOnceFlag = true;
final access_token = bind.mainGetLocalOption(key: 'access_token');
if (access_token.isEmpty) return;
final cache = await bind.mainLoadAb();
if (abLoading.value) return;
final data = jsonDecode(cache);
if (data == null || data['access_token'] != access_token) return;
_deserialize(data);
@@ -561,4 +565,12 @@ class AbModel {
}
});
}
reset() async {
pullError.value = '';
pushError.value = '';
tags.clear();
peers.clear();
await bind.mainClearAb();
}
}

View File

@@ -1,6 +1,7 @@
import 'package:flutter/widgets.dart';
import 'package:flutter_hbb/common.dart';
import 'package:flutter_hbb/common/hbbs/hbbs.dart';
import 'package:flutter_hbb/common/widgets/peers_view.dart';
import 'package:flutter_hbb/models/model.dart';
import 'package:flutter_hbb/models/peer_model.dart';
import 'package:flutter_hbb/models/platform_model.dart';
@@ -11,57 +12,74 @@ import 'package:http/http.dart' as http;
class GroupModel {
final RxBool groupLoading = false.obs;
final RxString groupLoadError = "".obs;
final RxString groupId = ''.obs;
RxString groupName = ''.obs;
final RxList<UserPayload> users = RxList.empty(growable: true);
final RxList<Peer> peersShow = RxList.empty(growable: true);
final RxList<Peer> peers = RxList.empty(growable: true);
final RxString selectedUser = ''.obs;
final RxString searchUserText = ''.obs;
WeakReference<FFI> parent;
var initialized = false;
var _cacheLoadOnceFlag = false;
var _statusCode = 200;
bool get emtpy => users.isEmpty && peers.isEmpty;
GroupModel(this.parent);
reset() {
groupName.value = '';
groupId.value = '';
users.clear();
peersShow.clear();
initialized = false;
}
Future<void> pull({force = true, quiet = false}) async {
/*
if (!gFFI.userModel.isLogin || groupLoading.value) return;
if (!force && initialized) return;
if (!quiet) {
groupLoading.value = true;
groupLoadError.value = "";
}
await _pull();
try {
await _pull();
} catch (_) {}
groupLoading.value = false;
initialized = true;
*/
platformFFI.tryHandle({'name': LoadEvent.group});
if (_statusCode == 401) {
gFFI.userModel.reset(resetOther: true);
} else {
_saveCache();
}
}
Future<void> _pull() async {
reset();
if (bind.mainGetLocalOption(key: 'access_token') == '') {
List<UserPayload> tmpUsers = List.empty(growable: true);
if (!await _getUsers(tmpUsers)) {
return;
}
try {
if (!await _getGroup()) {
reset();
return;
}
} catch (e) {
debugPrint('$e');
reset();
List<Peer> tmpPeers = List.empty(growable: true);
if (!await _getPeers(tmpPeers)) {
return;
}
// me first
var index = tmpUsers
.indexWhere((user) => user.name == gFFI.userModel.userName.value);
if (index != -1) {
var user = tmpUsers.removeAt(index);
tmpUsers.insert(0, user);
}
users.value = tmpUsers;
if (!users.any((u) => u.name == selectedUser.value)) {
selectedUser.value = '';
}
// recover online
final oldOnlineIDs = peers.where((e) => e.online).map((e) => e.id).toList();
peers.value = tmpPeers;
peers
.where((e) => oldOnlineIDs.contains(e.id))
.map((e) => e.online = true)
.toList();
groupLoadError.value = '';
}
Future<bool> _getUsers(List<UserPayload> tmpUsers) async {
final api = "${await bind.mainGetApiServer()}/api/users";
try {
var uri0 = Uri.parse(api);
final pageSize = 20;
final pageSize = 100;
var total = 0;
int current = 0;
do {
@@ -74,84 +92,63 @@ class GroupModel {
queryParameters: {
'current': current.toString(),
'pageSize': pageSize.toString(),
if (gFFI.userModel.isAdmin.isFalse) 'grp': groupId.value,
'accessible': '',
'status': '1',
});
final resp = await http.get(uri, headers: getHttpHeaders());
if (resp.body.isNotEmpty && resp.body.toLowerCase() != "null") {
Map<String, dynamic> json = jsonDecode(utf8.decode(resp.bodyBytes));
if (json.containsKey('error')) {
throw json['error'];
_statusCode = resp.statusCode;
Map<String, dynamic> json =
_jsonDecodeResp(utf8.decode(resp.bodyBytes), resp.statusCode);
if (json.containsKey('error')) {
if (json['error'] == 'Admin required!') {
throw translate('upgrade_rustdesk_server_pro_to_{1.1.10}_tip');
} else {
if (json.containsKey('total')) {
if (total == 0) total = json['total'];
if (json.containsKey('data')) {
final data = json['data'];
if (data is List) {
for (final user in data) {
final u = UserPayload.fromJson(user);
if (!users.any((e) => e.name == u.name)) {
users.add(u);
}
}
throw json['error'];
}
}
if (resp.statusCode != 200) {
throw 'HTTP ${resp.statusCode}';
}
if (json.containsKey('total')) {
if (total == 0) total = json['total'];
if (json.containsKey('data')) {
final data = json['data'];
if (data is List) {
for (final user in data) {
final u = UserPayload.fromJson(user);
int index = tmpUsers.indexWhere((e) => e.name == u.name);
if (index < 0) {
tmpUsers.add(u);
} else {
tmpUsers[index] = u;
}
}
}
}
}
} while (current * pageSize < total);
return true;
} catch (err) {
debugPrint('$err');
debugPrint('get accessible users: $err');
groupLoadError.value = err.toString();
} finally {
_pullUserPeers();
}
}
Future<bool> _getGroup() async {
final url = await bind.mainGetApiServer();
final body = {
'id': await bind.mainGetMyId(),
'uuid': await bind.mainGetUuid()
};
try {
final response = await http.post(Uri.parse('$url/api/currentGroup'),
headers: getHttpHeaders(), body: json.encode(body));
final status = response.statusCode;
if (status == 401 || status == 400) {
return false;
}
final data = json.decode(utf8.decode(response.bodyBytes));
final error = data['error'];
if (error != null) {
throw error;
}
groupName.value = data['name'] ?? '';
groupId.value = data['guid'] ?? '';
return groupId.value.isNotEmpty && groupName.isNotEmpty;
} catch (e) {
debugPrint('$e');
groupLoadError.value = e.toString();
} finally {}
return false;
}
Future<void> _pullUserPeers() async {
peersShow.clear();
final api = "${await bind.mainGetApiServer()}/api/peers";
Future<bool> _getPeers(List<Peer> tmpPeers) async {
try {
final api = "${await bind.mainGetApiServer()}/api/peers";
var uri0 = Uri.parse(api);
final pageSize =
20; // ????????????????????????????????????????????????????? stupid stupis, how about >20 peers
final pageSize = 100;
var total = 0;
int current = 0;
var queryParameters = {
'current': current.toString(),
'pageSize': pageSize.toString(),
'accessible': '',
'status': '1',
'user_status': '1',
};
if (!gFFI.userModel.isAdmin.value) {
queryParameters.addAll({'grp': groupId.value});
}
do {
current += 1;
var uri = Uri(
@@ -161,32 +158,107 @@ class GroupModel {
port: uri0.port,
queryParameters: queryParameters);
final resp = await http.get(uri, headers: getHttpHeaders());
if (resp.body.isNotEmpty && resp.body.toLowerCase() != "null") {
Map<String, dynamic> json = jsonDecode(utf8.decode(resp.bodyBytes));
if (json.containsKey('error')) {
throw json['error'];
} else {
if (json.containsKey('total')) {
if (total == 0) total = json['total'];
if (json.containsKey('data')) {
final data = json['data'];
if (data is List) {
for (final p in data) {
final peerPayload = PeerPayload.fromJson(p);
final peer = PeerPayload.toPeer(peerPayload);
if (!peersShow.any((e) => e.id == peer.id)) {
peersShow.add(peer);
}
}
_statusCode = resp.statusCode;
Map<String, dynamic> json =
_jsonDecodeResp(utf8.decode(resp.bodyBytes), resp.statusCode);
if (json.containsKey('error')) {
throw json['error'];
}
if (resp.statusCode != 200) {
throw 'HTTP ${resp.statusCode}';
}
if (json.containsKey('total')) {
if (total == 0) total = json['total'];
if (total > 1000) {
total = 1000;
}
if (json.containsKey('data')) {
final data = json['data'];
if (data is List) {
for (final p in data) {
final peerPayload = PeerPayload.fromJson(p);
final peer = PeerPayload.toPeer(peerPayload);
int index = tmpPeers.indexWhere((e) => e.id == peer.id);
if (index < 0) {
tmpPeers.add(peer);
} else {
tmpPeers[index] = peer;
}
if (tmpPeers.length >= 1000) {
break;
}
}
}
}
}
} while (current * pageSize < total);
return true;
} catch (err) {
debugPrint('$err');
debugPrint('get accessible peers: $err');
groupLoadError.value = err.toString();
} finally {}
}
return false;
}
Map<String, dynamic> _jsonDecodeResp(String body, int statusCode) {
try {
Map<String, dynamic> json = jsonDecode(body);
return json;
} catch (e) {
final err = body.isNotEmpty && body.length < 128 ? body : e.toString();
if (statusCode != 200) {
throw 'HTTP $statusCode, $err';
}
throw err;
}
}
void _saveCache() {
try {
final map = (<String, dynamic>{
"access_token": bind.mainGetLocalOption(key: 'access_token'),
"users": users.map((e) => e.toGroupCacheJson()).toList(),
'peers': peers.map((e) => e.toGroupCacheJson()).toList()
});
bind.mainSaveGroup(json: jsonEncode(map));
} catch (e) {
debugPrint('group save:$e');
}
}
loadCache() async {
try {
if (_cacheLoadOnceFlag || groupLoading.value || initialized) return;
_cacheLoadOnceFlag = true;
final access_token = bind.mainGetLocalOption(key: 'access_token');
if (access_token.isEmpty) return;
final cache = await bind.mainLoadGroup();
if (groupLoading.value) return;
final data = jsonDecode(cache);
if (data == null || data['access_token'] != access_token) return;
users.clear();
peers.clear();
if (data['users'] is List) {
for (var u in data['users']) {
users.add(UserPayload.fromJson(u));
}
}
if (data['peers'] is List) {
for (final peer in data['peers']) {
peers.add(Peer.fromJson(peer));
}
}
} catch (e) {
debugPrint("load group cache: $e");
}
}
reset() async {
groupLoadError.value = '';
users.clear();
peers.clear();
selectedUser.value = '';
await bind.mainClearGroup();
}
}

View File

@@ -98,7 +98,8 @@ class PlatformFFI {
int getRgbaSize(SessionID sessionId) =>
_ffiBind.sessionGetRgbaSize(sessionId: sessionId);
void nextRgba(SessionID sessionId) => _ffiBind.sessionNextRgba(sessionId: sessionId);
void nextRgba(SessionID sessionId) =>
_ffiBind.sessionNextRgba(sessionId: sessionId);
void registerTexture(SessionID sessionId, int ptr) =>
_ffiBind.sessionRegisterTexture(sessionId: sessionId, ptr: ptr);
@@ -198,7 +199,7 @@ class PlatformFFI {
version = await getVersion();
}
Future<bool> _tryHandle(Map<String, dynamic> evt) async {
Future<bool> tryHandle(Map<String, dynamic> evt) async {
final name = evt['name'];
if (name != null) {
final handlers = _eventHandlers[name];
@@ -216,14 +217,15 @@ class PlatformFFI {
/// Start listening to the Rust core's events and frames.
void _startListenEvent(RustdeskImpl rustdeskImpl) {
final appType = _appType == kAppTypeDesktopRemote ? '$_appType,$kWindowId' : _appType;
final appType =
_appType == kAppTypeDesktopRemote ? '$_appType,$kWindowId' : _appType;
var sink = rustdeskImpl.startGlobalEventStream(appType: appType);
sink.listen((message) {
() async {
try {
Map<String, dynamic> event = json.decode(message);
// _tryHandle here may be more flexible than _eventCallback
if (!await _tryHandle(event)) {
if (!await tryHandle(event)) {
if (_eventCallback != null) {
await _eventCallback!(event);
}

View File

@@ -1,5 +1,6 @@
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:get/get.dart';
import 'platform_model.dart';
// ignore: depend_on_referenced_packages
import 'package:collection/collection.dart';
@@ -7,7 +8,7 @@ import 'package:collection/collection.dart';
class Peer {
final String id;
String hash;
String username;
String username; // pc username
String hostname;
String platform;
String alias;
@@ -16,6 +17,7 @@ class Peer {
String rdpPort;
String rdpUsername;
bool online = false;
String loginName; //login username
String getId() {
if (alias != '') {
@@ -34,7 +36,8 @@ class Peer {
tags = json['tags'] ?? [],
forceAlwaysRelay = json['forceAlwaysRelay'] == 'true',
rdpPort = json['rdpPort'] ?? '',
rdpUsername = json['rdpUsername'] ?? '';
rdpUsername = json['rdpUsername'] ?? '',
loginName = json['loginName'] ?? '';
Map<String, dynamic> toJson() {
return <String, dynamic>{
@@ -48,6 +51,7 @@ class Peer {
"forceAlwaysRelay": forceAlwaysRelay.toString(),
"rdpPort": rdpPort,
"rdpUsername": rdpUsername,
'loginName': loginName,
};
}
@@ -63,6 +67,16 @@ class Peer {
};
}
Map<String, dynamic> toGroupCacheJson() {
return <String, dynamic>{
"id": id,
"username": username,
"hostname": hostname,
"platform": platform,
"login_name": loginName,
};
}
Peer({
required this.id,
required this.hash,
@@ -74,6 +88,7 @@ class Peer {
required this.forceAlwaysRelay,
required this.rdpPort,
required this.rdpUsername,
required this.loginName,
});
Peer.loading()
@@ -88,6 +103,7 @@ class Peer {
forceAlwaysRelay: false,
rdpPort: '',
rdpUsername: '',
loginName: '',
);
bool equal(Peer other) {
return id == other.id &&
@@ -99,21 +115,24 @@ class Peer {
tags.equals(other.tags) &&
forceAlwaysRelay == other.forceAlwaysRelay &&
rdpPort == other.rdpPort &&
rdpUsername == other.rdpUsername;
rdpUsername == other.rdpUsername &&
loginName == other.loginName;
}
Peer.copy(Peer other)
: this(
id: other.id,
hash: other.hash,
username: other.username,
hostname: other.hostname,
platform: other.platform,
alias: other.alias,
tags: other.tags.toList(),
forceAlwaysRelay: other.forceAlwaysRelay,
rdpPort: other.rdpPort,
rdpUsername: other.rdpUsername);
id: other.id,
hash: other.hash,
username: other.username,
hostname: other.hostname,
platform: other.platform,
alias: other.alias,
tags: other.tags.toList(),
forceAlwaysRelay: other.forceAlwaysRelay,
rdpPort: other.rdpPort,
rdpUsername: other.rdpUsername,
loginName: other.loginName,
);
}
enum UpdateEvent { online, load }
@@ -121,11 +140,14 @@ enum UpdateEvent { online, load }
class Peers extends ChangeNotifier {
final String name;
final String loadEvent;
List<Peer> peers;
List<Peer> peers = List.empty(growable: true);
final RxList<Peer>? initPeers;
UpdateEvent event = UpdateEvent.load;
static const _cbQueryOnlines = 'callback_query_onlines';
Peers({required this.name, required this.peers, required this.loadEvent}) {
Peers(
{required this.name, required this.initPeers, required this.loadEvent}) {
peers = initPeers ?? [];
platformFFI.registerEventHandler(_cbQueryOnlines, name, (evt) async {
_updateOnlineState(evt);
});
@@ -176,7 +198,11 @@ class Peers extends ChangeNotifier {
void _updatePeers(Map<String, dynamic> evt) {
final onlineStates = _getOnlineStates();
peers = _decodePeers(evt['peers']);
if (initPeers != null) {
peers = initPeers!;
} else {
peers = _decodePeers(evt['peers']);
}
for (var peer in peers) {
final state = onlineStates[peer.id];
peer.online = state != null && state != false;

View File

@@ -17,8 +17,6 @@ enum PeerTabIndex {
group,
}
const String defaultGroupTabname = 'Group';
class PeerTabModel with ChangeNotifier {
WeakReference<FFI> parent;
int get currentTab => _currentTab;
@@ -28,7 +26,7 @@ class PeerTabModel with ChangeNotifier {
'Favorites',
'Discovered',
'Address Book',
//defaultGroupTabname,
'Group',
];
final List<IconData> icons = [
Icons.access_time_filled,
@@ -37,7 +35,7 @@ class PeerTabModel with ChangeNotifier {
IconFont.addressBook,
Icons.group,
];
final List<bool> _isVisible = List.filled(4, true, growable: false);
final List<bool> _isVisible = List.filled(5, true, growable: false);
List<bool> get isVisible => _isVisible;
List<int> get indexs => List.generate(tabNames.length, (index) => index);
List<int> get visibleIndexs => indexs.where((e) => _isVisible[e]).toList();
@@ -85,17 +83,9 @@ class PeerTabModel with ChangeNotifier {
}
}
String tabTooltip(int index, String groupName) {
String tabTooltip(int index) {
if (index >= 0 && index < tabNames.length) {
if (index == PeerTabIndex.group.index) {
if (gFFI.userModel.isAdmin.value || groupName.isEmpty) {
return translate(defaultGroupTabname);
} else {
return '${translate('Group')}: $groupName';
}
} else {
return translate(tabNames[index]);
}
return translate(tabNames[index]);
}
assert(false);
return index.toString();

View File

@@ -45,7 +45,7 @@ class UserModel {
refreshingUser = false;
final status = response.statusCode;
if (status == 401 || status == 400) {
reset(clearAbCache: status == 401);
reset(resetOther: status == 401);
return;
}
final data = json.decode(utf8.decode(response.bodyBytes));
@@ -84,11 +84,13 @@ class UserModel {
}
}
Future<void> reset({bool clearAbCache = false}) async {
Future<void> reset({bool resetOther = false}) async {
await bind.mainSetLocalOption(key: 'access_token', value: '');
await bind.mainSetLocalOption(key: 'user_info', value: '');
if (clearAbCache) await bind.mainClearAb();
await gFFI.groupModel.reset();
if (resetOther) {
await gFFI.abModel.reset();
await gFFI.groupModel.reset();
}
userName.value = '';
}
@@ -120,7 +122,7 @@ class UserModel {
} catch (e) {
debugPrint("request /api/logout failed: err=$e");
} finally {
await reset(clearAbCache: true);
await reset(resetOther: true);
gFFI.dialogManager.dismissByTag(tag);
}
}