Merge pull request #5623 from 21pages/cm_file

add file log page to cm
This commit is contained in:
RustDesk
2023-09-07 20:33:00 +08:00
committed by GitHub
14 changed files with 519 additions and 39 deletions

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1694049173782" class="icon" viewBox="0 0 1024 1024" width="24" height="24" fill="#fff" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="992" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32"><path d="M891.64 184.73H620.41c-27.41 0-54.41-7.77-77.32-22.5L428.13 87.36C402.77 71 372.91 62 342.64 62H131.95C93.5 62 62 93.5 62 132.36v759.68C62 930.91 93.5 962 131.95 962h759.68c38.86 0 70.36-31.09 70.36-69.96V255.09c0.01-38.86-31.49-70.36-70.35-70.36zM480.5 753.77c0 16.77-13.5 30.68-30.68 30.68-16.77 0-30.68-13.91-30.68-30.68V523.04l-31.91 55.64c-8.59 14.32-27.41 19.64-42.14 11.04-14.32-8.59-19.64-27.41-11.05-41.73l89.18-154.64c6.96-12.27 21.27-18 34.77-14.32 13.09 3.27 22.5 15.55 22.5 29.45v345.29z m209.04-139.5l-89.18 154.64c-5.32 9.82-15.55 15.55-26.59 15.55-2.46 0-5.32-0.41-7.77-1.23-13.5-3.68-22.91-15.55-22.91-29.46V408.5c0-16.77 13.91-30.68 30.68-30.68 17.18 0 30.68 13.91 30.68 30.68v230.73l31.91-55.64c8.59-14.73 27.41-19.64 42.14-11.05 14.73 8.6 19.64 27.01 11.04 41.73z" p-id="993"></path></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -2,6 +2,7 @@
import 'dart:async';
import 'dart:io';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_hbb/consts.dart';
@@ -9,12 +10,14 @@ import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart';
import 'package:flutter_hbb/models/chat_model.dart';
import 'package:flutter_hbb/utils/platform_channel.dart';
import 'package:get/get.dart';
import 'package:percent_indicator/linear_percent_indicator.dart';
import 'package:provider/provider.dart';
import 'package:window_manager/window_manager.dart';
import 'package:flutter_svg/flutter_svg.dart';
import '../../common.dart';
import '../../common/widgets/chat_page.dart';
import '../../models/file_model.dart';
import '../../models/platform_model.dart';
import '../../models/server_model.dart';
@@ -32,6 +35,7 @@ class _DesktopServerPageState extends State<DesktopServerPage>
void initState() {
gFFI.ffiModel.updateEventListener(gFFI.sessionId, "");
windowManager.addListener(this);
Get.put(tabController);
tabController.onRemoved = (_, id) {
onRemoveId(id);
};
@@ -111,6 +115,7 @@ class ConnectionManagerState extends State<ConnectionManager> {
});
}
windowManager.setTitle(getWindowNameWithId(client.peerId));
gFFI.cmFileModel.updateCurrentClientId(client.id);
}
}
};
@@ -173,7 +178,7 @@ class ConnectionManagerState extends State<ConnectionManager> {
pageViewBuilder: (pageView) => Row(
children: [
Consumer<ChatModel>(
builder: (_, model, child) => model.isShowCMChatPage
builder: (_, model, child) => model.isShowCMSidePage
? Expanded(
child: buildRemoteBlock(
child: Container(
@@ -182,8 +187,7 @@ class ConnectionManagerState extends State<ConnectionManager> {
right: BorderSide(
color: Theme.of(context)
.dividerColor))),
child:
ChatPage(type: ChatPageType.desktopCM)),
child: buildSidePage()),
),
flex: (kConnectionManagerWindowSizeOpenChat.width -
kConnectionManagerWindowSizeClosedChat
@@ -204,6 +208,19 @@ class ConnectionManagerState extends State<ConnectionManager> {
);
}
Widget buildSidePage() {
final selected = gFFI.serverModel.tabController.state.value.selected;
if (selected < 0 || selected >= gFFI.serverModel.clients.length) {
return Offstage();
}
final clientType = gFFI.serverModel.clients[selected].type_();
if (clientType == ClientType.file) {
return _FileTransferLogPage();
} else {
return ChatPage(type: ChatPageType.desktopCM);
}
}
Widget buildTitleBar() {
return SizedBox(
height: kDesktopRemoteTabBarHeight,
@@ -447,14 +464,21 @@ class _CmHeaderState extends State<_CmHeader>
),
),
Offstage(
offstage: !client.authorized || client.type_() != ClientType.remote,
offstage: !client.authorized ||
(client.type_() != ClientType.remote &&
client.type_() != ClientType.file),
child: IconButton(
onPressed: () => checkClickTime(
client.id,
() => gFFI.chatModel
.toggleCMChatPage(MessageKey(client.peerId, client.id)),
),
icon: SvgPicture.asset('assets/chat2.svg'),
onPressed: () => checkClickTime(client.id, () {
if (client.type_() != ClientType.file) {
gFFI.chatModel.toggleCMSidePage();
} else {
gFFI.chatModel
.toggleCMChatPage(MessageKey(client.peerId, client.id));
}
}),
icon: SvgPicture.asset(client.type_() == ClientType.file
? 'assets/file_transfer.svg'
: 'assets/chat2.svg'),
splashRadius: kDesktopIconButtonSplashRadius,
),
)
@@ -912,3 +936,182 @@ void checkClickTime(int id, Function() callback) async {
if (d > 120) callback();
});
}
class _FileTransferLogPage extends StatefulWidget {
_FileTransferLogPage({Key? key}) : super(key: key);
@override
State<_FileTransferLogPage> createState() => __FileTransferLogPageState();
}
class __FileTransferLogPageState extends State<_FileTransferLogPage> {
@override
Widget build(BuildContext context) {
return statusList();
}
Widget generateCard(Widget child) {
return Container(
decoration: BoxDecoration(
color: Theme.of(context).cardColor,
borderRadius: BorderRadius.all(
Radius.circular(15.0),
),
),
child: child,
);
}
Widget statusList() {
return PreferredSize(
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: Obx(
() {
final jobTable = gFFI.cmFileModel.currentJobTable;
statusListView(List<JobProgress> jobs) => ListView.builder(
controller: ScrollController(),
itemBuilder: (BuildContext context, int index) {
final item = jobs[index];
return Padding(
padding: const EdgeInsets.only(bottom: 5),
child: generateCard(
Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(
width: 50,
child: Column(
children: [
Transform.rotate(
angle: item.isRemoteToLocal ? 0 : pi,
child: SvgPicture.asset(
"assets/arrow.svg",
color: Theme.of(context)
.tabBarTheme
.labelColor,
),
),
Text(item.isRemoteToLocal
? translate('Send')
: translate('Receive'))
],
),
).paddingOnly(left: 15),
const SizedBox(
width: 16.0,
),
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
item.fileName,
).paddingSymmetric(vertical: 10),
if (item.totalSize > 0)
Text(
'${translate("Total")} ${readableFileSize(item.totalSize.toDouble())}',
style: TextStyle(
fontSize: 12,
color: MyTheme.darkGray,
),
),
if (item.totalSize > 0)
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,
),
),
),
if (item.totalSize > 0)
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: [],
),
],
),
],
).paddingSymmetric(vertical: 10),
),
);
},
itemCount: jobTable.length,
);
return jobTable.isEmpty
? 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),
),
],
),
),
)
: statusListView(jobTable);
},
)),
);
}
}

View File

@@ -93,13 +93,13 @@ class ChatModel with ChangeNotifier {
late final Map<MessageKey, MessageBody> _messages = {};
MessageKey _currentKey = MessageKey('', -2); // -2 is invalid value
late bool _isShowCMChatPage = false;
late bool _isShowCMSidePage = false;
Map<MessageKey, MessageBody> get messages => _messages;
MessageKey get currentKey => _currentKey;
bool get isShowCMChatPage => _isShowCMChatPage;
bool get isShowCMSidePage => _isShowCMSidePage;
void setOverlayState(BlockableOverlayState blockableOverlayState) {
_blockableOverlayState = blockableOverlayState;
@@ -262,7 +262,7 @@ class ChatModel with ChangeNotifier {
showChatPage(MessageKey key) async {
if (isDesktop) {
if (isConnManager) {
if (!_isShowCMChatPage) {
if (!_isShowCMSidePage) {
await toggleCMChatPage(key);
}
} else {
@@ -279,12 +279,26 @@ class ChatModel with ChangeNotifier {
}
}
showSidePage() async {
if (isDesktop) {
if (isConnManager) {
if (!_isShowCMSidePage) {
await toggleCMSidePage();
}
}
}
}
toggleCMChatPage(MessageKey key) async {
if (gFFI.chatModel.currentKey != key) {
gFFI.chatModel.changeCurrentKey(key);
}
if (_isShowCMChatPage) {
_isShowCMChatPage = !_isShowCMChatPage;
await toggleCMSidePage();
}
toggleCMSidePage() async {
if (_isShowCMSidePage) {
_isShowCMSidePage = !_isShowCMSidePage;
notifyListeners();
await windowManager.show();
await windowManager.setSizeAlignment(
@@ -294,7 +308,7 @@ class ChatModel with ChangeNotifier {
await windowManager.show();
await windowManager.setSizeAlignment(
kConnectionManagerWindowSizeOpenChat, Alignment.topRight);
_isShowCMChatPage = !_isShowCMChatPage;
_isShowCMSidePage = !_isShowCMSidePage;
notifyListeners();
}
}

View File

@@ -0,0 +1,154 @@
import 'dart:collection';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_hbb/common.dart';
import 'package:flutter_hbb/models/model.dart';
import 'package:flutter_hbb/models/server_model.dart';
import 'package:get/get.dart';
import 'file_model.dart';
class CmFileModel {
final WeakReference<FFI> parent;
final currentJobTable = RxList<JobProgress>();
final _jobTables = HashMap<int, RxList<JobProgress>>.fromEntries([]);
Stopwatch stopwatch = Stopwatch();
int _lastElapsed = 0;
bool _jobAdded = false;
bool _showing = false;
CmFileModel(this.parent);
void updateCurrentClientId(int id) {
if (_jobTables[id] == null) {
_jobTables[id] = RxList<JobProgress>();
}
Future.delayed(Duration.zero, () {
currentJobTable.value = _jobTables[id]!;
});
}
onFileTransferLog(dynamic log) {
try {
dynamic d = jsonDecode(log);
if (!stopwatch.isRunning) stopwatch.start();
bool calcSpeed = stopwatch.elapsedMilliseconds - _lastElapsed >= 1000;
if (calcSpeed) {
_lastElapsed = stopwatch.elapsedMilliseconds;
}
if (d is List<dynamic>) {
for (var l in d) {
_dealOneJob(l, calcSpeed);
}
} else {
_dealOneJob(d, calcSpeed);
}
currentJobTable.refresh();
Future.delayed(Duration.zero, () async {
if (_jobAdded) {
_jobAdded = false;
if (!_showing) {
_showing = true;
await gFFI.chatModel.showSidePage();
_showing = false;
}
}
});
} catch (e) {
debugPrint("onFileTransferLog:$e");
}
}
_dealOneJob(dynamic l, bool calcSpeed) {
final data = TransferJobSerdeData.fromJson(l);
Client? client =
gFFI.serverModel.clients.firstWhereOrNull((e) => e.id == data.connId);
var jobTable = _jobTables[data.connId];
if (jobTable == null) {
debugPrint("jobTable should not be null");
return;
}
JobProgress? job = jobTable.firstWhereOrNull((e) => e.id == data.id);
if (job == null) {
job = JobProgress();
jobTable.add(job);
_jobAdded = true;
final currentSelectedTab =
gFFI.serverModel.tabController.state.value.selectedTabInfo;
if (currentSelectedTab.key != data.connId.toString()) {
client?.unreadChatMessageCount.value += 1;
}
}
job.id = data.id;
job.isRemoteToLocal = data.isRemote;
job.fileName = data.path;
job.totalSize = data.totalSize;
job.finishedSize = data.finishedSize;
if (job.finishedSize > data.totalSize) {
job.finishedSize = data.totalSize;
}
job.isRemoteToLocal = data.isRemote;
if (job.finishedSize > 0) {
if (job.finishedSize < job.totalSize) {
job.state = JobState.inProgress;
} else {
job.state = JobState.done;
}
}
if (data.done) {
job.state = JobState.done;
} else if (data.cancel || data.error == 'skipped') {
job.state = JobState.done;
job.err = 'skipped';
} else if (data.error.isNotEmpty) {
job.state = JobState.error;
job.err = data.error;
}
if (calcSpeed) {
job.speed = (data.transferred - job.lastTransferredSize) * 1.0;
job.lastTransferredSize = data.transferred;
}
jobTable.refresh();
}
}
class TransferJobSerdeData {
int connId;
int id;
String path;
bool isRemote;
int totalSize;
int finishedSize;
int transferred;
bool done;
bool cancel;
String error;
TransferJobSerdeData({
required this.connId,
required this.id,
required this.path,
required this.isRemote,
required this.totalSize,
required this.finishedSize,
required this.transferred,
required this.done,
required this.cancel,
required this.error,
});
TransferJobSerdeData.fromJson(dynamic d)
: this(
connId: d['connId'] ?? 0,
id: int.tryParse(d['id'].toString()) ?? 0,
path: d['path'] ?? '',
isRemote: d['isRemote'] ?? false,
totalSize: d['totalSize'] ?? 0,
finishedSize: d['finishedSize'] ?? 0,
transferred: d['transferred'] ?? 0,
done: d['done'] ?? false,
cancel: d['cancel'] ?? false,
error: d['error'] ?? '',
);
}

View File

@@ -1029,6 +1029,7 @@ class JobProgress {
var to = "";
var showHidden = false;
var err = "";
int lastTransferredSize = 0;
clear() {
state = JobState.none;

View File

@@ -11,6 +11,7 @@ import 'package:flutter_hbb/consts.dart';
import 'package:flutter_hbb/generated_bridge.dart';
import 'package:flutter_hbb/models/ab_model.dart';
import 'package:flutter_hbb/models/chat_model.dart';
import 'package:flutter_hbb/models/cm_file_model.dart';
import 'package:flutter_hbb/models/file_model.dart';
import 'package:flutter_hbb/models/group_model.dart';
import 'package:flutter_hbb/models/peer_tab_model.dart';
@@ -317,6 +318,10 @@ class FfiModel with ChangeNotifier {
}
}
}
} else if (name == "cm_file_transfer_log") {
if (isDesktop) {
gFFI.cmFileModel.onFileTransferLog(evt['log']);
}
} else {
debugPrint('Unknown event name: $name');
}
@@ -1699,6 +1704,7 @@ class FFI {
late final RecordingModel recordingModel; // session
late final InputModel inputModel; // session
late final ElevationModel elevationModel; // session
late final CmFileModel cmFileModel; // cm
FFI(SessionID? sId) {
sessionId = sId ?? (isDesktop ? Uuid().v4obj() : _constSessionId);
@@ -1717,6 +1723,7 @@ class FFI {
recordingModel = RecordingModel(WeakReference(this));
inputModel = InputModel(WeakReference(this));
elevationModel = ElevationModel(WeakReference(this));
cmFileModel = CmFileModel(WeakReference(this));
}
/// Mobile reuse FFI