1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324 |
- // original cm window in Sciter version.
- import 'dart:async';
- import 'dart:math';
- import 'package:flutter/material.dart';
- import 'package:flutter_hbb/common/widgets/audio_input.dart';
- import 'package:flutter_hbb/consts.dart';
- import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart';
- import 'package:flutter_hbb/models/chat_model.dart';
- import 'package:flutter_hbb/models/cm_file_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';
- class DesktopServerPage extends StatefulWidget {
- const DesktopServerPage({Key? key}) : super(key: key);
- @override
- State<DesktopServerPage> createState() => _DesktopServerPageState();
- }
- class _DesktopServerPageState extends State<DesktopServerPage>
- with WindowListener, AutomaticKeepAliveClientMixin {
- final tabController = gFFI.serverModel.tabController;
- _DesktopServerPageState() {
- gFFI.ffiModel.updateEventListener(gFFI.sessionId, "");
- Get.put<DesktopTabController>(tabController);
- tabController.onRemoved = (_, id) {
- onRemoveId(id);
- };
- }
- @override
- void initState() {
- windowManager.addListener(this);
- super.initState();
- }
- @override
- void dispose() {
- windowManager.removeListener(this);
- super.dispose();
- }
- @override
- void onWindowClose() {
- Future.wait([gFFI.serverModel.closeAll(), gFFI.close()]).then((_) {
- if (isMacOS) {
- RdPlatformChannel.instance.terminate();
- } else {
- windowManager.setPreventClose(false);
- windowManager.close();
- }
- });
- super.onWindowClose();
- }
- void onRemoveId(String id) {
- if (tabController.state.value.tabs.isEmpty) {
- windowManager.close();
- }
- }
- @override
- Widget build(BuildContext context) {
- super.build(context);
- return MultiProvider(
- providers: [
- ChangeNotifierProvider.value(value: gFFI.serverModel),
- ChangeNotifierProvider.value(value: gFFI.chatModel),
- ],
- child: Consumer<ServerModel>(
- builder: (context, serverModel, child) {
- final body = Scaffold(
- backgroundColor: Theme.of(context).colorScheme.background,
- body: ConnectionManager(),
- );
- return isLinux
- ? buildVirtualWindowFrame(context, body)
- : Container(
- decoration: BoxDecoration(
- border:
- Border.all(color: MyTheme.color(context).border!)),
- child: body,
- );
- },
- ),
- );
- }
- @override
- bool get wantKeepAlive => true;
- }
- class ConnectionManager extends StatefulWidget {
- @override
- State<StatefulWidget> createState() => ConnectionManagerState();
- }
- class ConnectionManagerState extends State<ConnectionManager>
- with WidgetsBindingObserver {
- final RxBool _controlPageBlock = false.obs;
- final RxBool _sidePageBlock = false.obs;
- ConnectionManagerState() {
- gFFI.serverModel.tabController.onSelected = (client_id_str) {
- final client_id = int.tryParse(client_id_str);
- if (client_id != null) {
- final client =
- gFFI.serverModel.clients.firstWhereOrNull((e) => e.id == client_id);
- if (client != null) {
- gFFI.chatModel.changeCurrentKey(MessageKey(client.peerId, client.id));
- if (client.unreadChatMessageCount.value > 0) {
- WidgetsBinding.instance.addPostFrameCallback((_) {
- client.unreadChatMessageCount.value = 0;
- gFFI.chatModel.showChatPage(MessageKey(client.peerId, client.id));
- });
- }
- windowManager.setTitle(getWindowNameWithId(client.peerId));
- gFFI.cmFileModel.updateCurrentClientId(client.id);
- }
- }
- };
- gFFI.chatModel.isConnManager = true;
- }
- @override
- void didChangeAppLifecycleState(AppLifecycleState state) {
- super.didChangeAppLifecycleState(state);
- if (state == AppLifecycleState.resumed) {
- if (!allowRemoteCMModification()) {
- shouldBeBlocked(_controlPageBlock, null);
- shouldBeBlocked(_sidePageBlock, null);
- }
- }
- }
- @override
- void initState() {
- gFFI.serverModel.updateClientState();
- WidgetsBinding.instance.addObserver(this);
- super.initState();
- }
- @override
- void dispose() {
- WidgetsBinding.instance.removeObserver(this);
- super.dispose();
- }
- @override
- Widget build(BuildContext context) {
- final serverModel = Provider.of<ServerModel>(context);
- pointerHandler(PointerEvent e) {
- if (serverModel.cmHiddenTimer != null) {
- serverModel.cmHiddenTimer!.cancel();
- serverModel.cmHiddenTimer = null;
- debugPrint("CM hidden timer has been canceled");
- }
- }
- return serverModel.clients.isEmpty
- ? Column(
- children: [
- buildTitleBar(),
- Expanded(
- child: Center(
- child: Text(translate("Waiting")),
- ),
- ),
- ],
- )
- : Listener(
- onPointerDown: pointerHandler,
- onPointerMove: pointerHandler,
- child: DesktopTab(
- showTitle: false,
- showMaximize: false,
- showMinimize: true,
- showClose: true,
- onWindowCloseButton: handleWindowCloseButton,
- controller: serverModel.tabController,
- selectedBorderColor: MyTheme.accent,
- maxLabelWidth: 100,
- tail: null, //buildScrollJumper(),
- tabBuilder: (key, icon, label, themeConf) {
- final client = serverModel.clients
- .firstWhereOrNull((client) => client.id.toString() == key);
- return Row(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- Tooltip(
- message: key,
- waitDuration: Duration(seconds: 1),
- child: label),
- unreadMessageCountBuilder(client?.unreadChatMessageCount)
- .marginOnly(left: 4),
- ],
- );
- },
- pageViewBuilder: (pageView) => LayoutBuilder(
- builder: (context, constrains) {
- var borderWidth = 0.0;
- if (constrains.maxWidth >
- kConnectionManagerWindowSizeClosedChat.width) {
- borderWidth = kConnectionManagerWindowSizeOpenChat.width -
- constrains.maxWidth;
- } else {
- borderWidth = kConnectionManagerWindowSizeClosedChat.width -
- constrains.maxWidth;
- }
- if (borderWidth < 0 || borderWidth > 50) {
- borderWidth = 0;
- }
- final realClosedWidth =
- kConnectionManagerWindowSizeClosedChat.width -
- borderWidth;
- final realChatPageWidth =
- constrains.maxWidth - realClosedWidth;
- final row = Row(children: [
- if (constrains.maxWidth >
- kConnectionManagerWindowSizeClosedChat.width)
- Consumer<ChatModel>(
- builder: (_, model, child) => SizedBox(
- width: realChatPageWidth,
- child: allowRemoteCMModification()
- ? buildSidePage()
- : buildRemoteBlock(
- child: buildSidePage(),
- block: _sidePageBlock,
- mask: true),
- )),
- SizedBox(
- width: realClosedWidth,
- child: SizedBox(
- width: realClosedWidth,
- child: allowRemoteCMModification()
- ? pageView
- : buildRemoteBlock(
- child: _buildKeyEventBlock(pageView),
- block: _controlPageBlock,
- mask: false,
- ))),
- ]);
- return Container(
- color: Theme.of(context).scaffoldBackgroundColor,
- child: row,
- );
- },
- ),
- ),
- );
- }
- 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 _buildKeyEventBlock(Widget child) {
- return ExcludeFocus(child: child, excluding: true);
- }
- Widget buildTitleBar() {
- return SizedBox(
- height: kDesktopRemoteTabBarHeight,
- child: Row(
- crossAxisAlignment: CrossAxisAlignment.center,
- children: [
- const _AppIcon(),
- Expanded(
- child: GestureDetector(
- onPanStart: (d) {
- windowManager.startDragging();
- },
- child: Container(
- color: Theme.of(context).colorScheme.background,
- ),
- ),
- ),
- const SizedBox(
- width: 4.0,
- ),
- const _CloseButton()
- ],
- ),
- );
- }
- Widget buildScrollJumper() {
- final offstage = gFFI.serverModel.clients.length < 2;
- final sc = gFFI.serverModel.tabController.state.value.scrollController;
- return Offstage(
- offstage: offstage,
- child: Row(
- children: [
- ActionIcon(
- icon: Icons.arrow_left, iconSize: 22, onTap: sc.backward),
- ActionIcon(
- icon: Icons.arrow_right, iconSize: 22, onTap: sc.forward),
- ],
- ));
- }
- Future<bool> handleWindowCloseButton() async {
- var tabController = gFFI.serverModel.tabController;
- final connLength = tabController.length;
- if (connLength <= 1) {
- windowManager.close();
- return true;
- } else {
- final bool res;
- if (!option2bool(kOptionEnableConfirmClosingTabs,
- bind.mainGetLocalOption(key: kOptionEnableConfirmClosingTabs))) {
- res = true;
- } else {
- res = await closeConfirmDialog();
- }
- if (res) {
- windowManager.close();
- }
- return res;
- }
- }
- }
- Widget buildConnectionCard(Client client) {
- return Consumer<ServerModel>(
- builder: (context, value, child) => Column(
- mainAxisAlignment: MainAxisAlignment.start,
- crossAxisAlignment: CrossAxisAlignment.start,
- key: ValueKey(client.id),
- children: [
- _CmHeader(client: client),
- client.type_() != ClientType.remote || client.disconnected
- ? Offstage()
- : _PrivilegeBoard(client: client),
- Expanded(
- child: Align(
- alignment: Alignment.bottomCenter,
- child: _CmControlPanel(client: client),
- ),
- )
- ],
- ).paddingSymmetric(vertical: 4.0, horizontal: 8.0),
- );
- }
- class _AppIcon extends StatelessWidget {
- const _AppIcon({Key? key}) : super(key: key);
- @override
- Widget build(BuildContext context) {
- return Container(
- margin: EdgeInsets.symmetric(horizontal: 4.0),
- child: loadIcon(30),
- );
- }
- }
- class _CloseButton extends StatelessWidget {
- const _CloseButton({Key? key}) : super(key: key);
- @override
- Widget build(BuildContext context) {
- return IconButton(
- onPressed: () {
- windowManager.close();
- },
- icon: const Icon(
- IconFont.close,
- size: 18,
- ),
- splashColor: Colors.transparent,
- hoverColor: Colors.transparent,
- );
- }
- }
- class _CmHeader extends StatefulWidget {
- final Client client;
- const _CmHeader({Key? key, required this.client}) : super(key: key);
- @override
- State<_CmHeader> createState() => _CmHeaderState();
- }
- class _CmHeaderState extends State<_CmHeader>
- with AutomaticKeepAliveClientMixin {
- Client get client => widget.client;
- final _time = 0.obs;
- Timer? _timer;
- @override
- void initState() {
- super.initState();
- _timer = Timer.periodic(Duration(seconds: 1), (_) {
- if (client.authorized && !client.disconnected) {
- _time.value = _time.value + 1;
- }
- });
- // Call onSelected in post frame callback, since we cannot guarantee that the callback will not call setState.
- WidgetsBinding.instance.addPostFrameCallback((_) {
- gFFI.serverModel.tabController.onSelected?.call(client.id.toString());
- });
- }
- @override
- void dispose() {
- _timer?.cancel();
- super.dispose();
- }
- @override
- Widget build(BuildContext context) {
- super.build(context);
- return Container(
- decoration: BoxDecoration(
- borderRadius: BorderRadius.circular(10.0),
- gradient: LinearGradient(
- begin: Alignment.topRight,
- end: Alignment.bottomLeft,
- colors: [
- Color(0xff00bfe1),
- Color(0xff0071ff),
- ],
- ),
- ),
- margin: EdgeInsets.symmetric(horizontal: 5.0, vertical: 10.0),
- padding: EdgeInsets.only(
- top: 10.0,
- bottom: 10.0,
- left: 10.0,
- right: 5.0,
- ),
- child: Row(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Container(
- width: 70,
- height: 70,
- alignment: Alignment.center,
- decoration: BoxDecoration(
- color: str2color(client.name),
- borderRadius: BorderRadius.circular(15.0),
- ),
- child: Text(
- client.name[0],
- style: TextStyle(
- fontWeight: FontWeight.bold,
- color: Colors.white,
- fontSize: 55,
- ),
- ),
- ).marginOnly(right: 10.0),
- Expanded(
- child: Column(
- mainAxisAlignment: MainAxisAlignment.start,
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- FittedBox(
- child: Text(
- client.name,
- style: TextStyle(
- color: Colors.white,
- fontWeight: FontWeight.bold,
- fontSize: 20,
- overflow: TextOverflow.ellipsis,
- ),
- maxLines: 1,
- )),
- FittedBox(
- child: Text(
- "(${client.peerId})",
- style: TextStyle(color: Colors.white, fontSize: 14),
- ),
- ).marginOnly(bottom: 10.0),
- FittedBox(
- child: Row(
- children: [
- Text(
- client.authorized
- ? client.disconnected
- ? translate("Disconnected")
- : translate("Connected")
- : "${translate("Request access to your device")}...",
- style: TextStyle(color: Colors.white),
- ).marginOnly(right: 8.0),
- if (client.authorized)
- Obx(
- () => Text(
- formatDurationToTime(
- Duration(seconds: _time.value),
- ),
- style: TextStyle(color: Colors.white),
- ),
- )
- ],
- ))
- ],
- ),
- ),
- Offstage(
- offstage: !client.authorized ||
- (client.type_() != ClientType.remote &&
- client.type_() != ClientType.file),
- child: IconButton(
- onPressed: () => checkClickTime(client.id, () {
- if (client.type_() == ClientType.file) {
- gFFI.chatModel.toggleCMFilePage();
- } 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,
- ),
- )
- ],
- ),
- );
- }
- @override
- bool get wantKeepAlive => true;
- }
- class _PrivilegeBoard extends StatefulWidget {
- final Client client;
- const _PrivilegeBoard({Key? key, required this.client}) : super(key: key);
- @override
- State<StatefulWidget> createState() => _PrivilegeBoardState();
- }
- class _PrivilegeBoardState extends State<_PrivilegeBoard> {
- late final client = widget.client;
- Widget buildPermissionIcon(bool enabled, IconData iconData,
- Function(bool)? onTap, String tooltipText) {
- return Tooltip(
- message: "$tooltipText: ${enabled ? "ON" : "OFF"}",
- waitDuration: Duration.zero,
- child: Container(
- decoration: BoxDecoration(
- color: enabled ? MyTheme.accent : Colors.grey[700],
- borderRadius: BorderRadius.circular(10.0),
- ),
- padding: EdgeInsets.all(8.0),
- child: InkWell(
- onTap: () =>
- checkClickTime(widget.client.id, () => onTap?.call(!enabled)),
- child: Column(
- mainAxisAlignment: MainAxisAlignment.spaceAround,
- children: [
- Expanded(
- child: Icon(
- iconData,
- color: Colors.white,
- ),
- ),
- ],
- ),
- ),
- ),
- );
- }
- @override
- Widget build(BuildContext context) {
- final crossAxisCount = 4;
- final spacing = 10.0;
- return Container(
- width: double.infinity,
- height: 160.0,
- margin: EdgeInsets.all(5.0),
- padding: EdgeInsets.all(5.0),
- decoration: BoxDecoration(
- borderRadius: BorderRadius.circular(10.0),
- color: Theme.of(context).colorScheme.background,
- boxShadow: [
- BoxShadow(
- color: Colors.black.withOpacity(0.2),
- spreadRadius: 1,
- blurRadius: 1,
- offset: Offset(0, 1.5),
- ),
- ],
- ),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.center,
- children: [
- Text(
- translate("Permissions"),
- style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
- textAlign: TextAlign.center,
- ).marginOnly(left: 4.0, bottom: 8.0),
- Expanded(
- child: GridView.count(
- crossAxisCount: crossAxisCount,
- padding: EdgeInsets.symmetric(horizontal: spacing),
- mainAxisSpacing: spacing,
- crossAxisSpacing: spacing,
- children: [
- buildPermissionIcon(
- client.keyboard,
- Icons.keyboard,
- (enabled) {
- bind.cmSwitchPermission(
- connId: client.id, name: "keyboard", enabled: enabled);
- setState(() {
- client.keyboard = enabled;
- });
- },
- translate('Enable keyboard/mouse'),
- ),
- buildPermissionIcon(
- client.clipboard,
- Icons.assignment_rounded,
- (enabled) {
- bind.cmSwitchPermission(
- connId: client.id, name: "clipboard", enabled: enabled);
- setState(() {
- client.clipboard = enabled;
- });
- },
- translate('Enable clipboard'),
- ),
- buildPermissionIcon(
- client.audio,
- Icons.volume_up_rounded,
- (enabled) {
- bind.cmSwitchPermission(
- connId: client.id, name: "audio", enabled: enabled);
- setState(() {
- client.audio = enabled;
- });
- },
- translate('Enable audio'),
- ),
- buildPermissionIcon(
- client.file,
- Icons.upload_file_rounded,
- (enabled) {
- bind.cmSwitchPermission(
- connId: client.id, name: "file", enabled: enabled);
- setState(() {
- client.file = enabled;
- });
- },
- translate('Enable file copy and paste'),
- ),
- buildPermissionIcon(
- client.restart,
- Icons.restart_alt_rounded,
- (enabled) {
- bind.cmSwitchPermission(
- connId: client.id, name: "restart", enabled: enabled);
- setState(() {
- client.restart = enabled;
- });
- },
- translate('Enable remote restart'),
- ),
- buildPermissionIcon(
- client.recording,
- Icons.videocam_rounded,
- (enabled) {
- bind.cmSwitchPermission(
- connId: client.id, name: "recording", enabled: enabled);
- setState(() {
- client.recording = enabled;
- });
- },
- translate('Enable recording session'),
- ),
- // only windows support block input
- if (isWindows)
- buildPermissionIcon(
- client.blockInput,
- Icons.block,
- (enabled) {
- bind.cmSwitchPermission(
- connId: client.id,
- name: "block_input",
- enabled: enabled);
- setState(() {
- client.blockInput = enabled;
- });
- },
- translate('Enable blocking user input'),
- )
- ],
- ),
- ),
- ],
- ),
- );
- }
- }
- const double buttonBottomMargin = 8;
- class _CmControlPanel extends StatelessWidget {
- final Client client;
- const _CmControlPanel({Key? key, required this.client}) : super(key: key);
- @override
- Widget build(BuildContext context) {
- return client.authorized
- ? client.disconnected
- ? buildDisconnected(context)
- : buildAuthorized(context)
- : buildUnAuthorized(context);
- }
- buildAuthorized(BuildContext context) {
- final bool canElevate = bind.cmCanElevate();
- final model = Provider.of<ServerModel>(context);
- final showElevation = canElevate &&
- model.showElevation &&
- client.type_() == ClientType.remote;
- return Column(
- mainAxisAlignment: MainAxisAlignment.end,
- children: [
- Offstage(
- offstage: !client.inVoiceCall,
- child: Row(
- children: [
- Expanded(
- child: buildButton(context,
- color: MyTheme.accent,
- onClick: null, onTapDown: (details) async {
- final devicesInfo =
- await AudioInput.getDevicesInfo(true, true);
- List<String> devices = devicesInfo['devices'] as List<String>;
- if (devices.isEmpty) {
- msgBox(
- gFFI.sessionId,
- 'custom-nocancel-info',
- 'Prompt',
- 'no_audio_input_device_tip',
- '',
- gFFI.dialogManager,
- );
- return;
- }
- String currentDevice = devicesInfo['current'] as String;
- final x = details.globalPosition.dx;
- final y = details.globalPosition.dy;
- final position = RelativeRect.fromLTRB(x, y, x, y);
- showMenu(
- context: context,
- position: position,
- items: devices
- .map((d) => PopupMenuItem<String>(
- value: d,
- height: 18,
- padding: EdgeInsets.zero,
- onTap: () => AudioInput.setDevice(d, true, true),
- child: IgnorePointer(
- child: RadioMenuButton(
- value: d,
- groupValue: currentDevice,
- onChanged: (v) {
- if (v != null)
- AudioInput.setDevice(v, true, true);
- },
- child: Container(
- child: Text(
- d,
- overflow: TextOverflow.ellipsis,
- maxLines: 1,
- ),
- constraints: BoxConstraints(
- maxWidth:
- kConnectionManagerWindowSizeClosedChat
- .width -
- 80),
- ),
- )),
- ))
- .toList(),
- );
- },
- icon: Icon(
- Icons.call_rounded,
- color: Colors.white,
- size: 14,
- ),
- text: "Audio input",
- textColor: Colors.white),
- ),
- Expanded(
- child: buildButton(
- context,
- color: Colors.red,
- onClick: () => closeVoiceCall(),
- icon: Icon(
- Icons.call_end_rounded,
- color: Colors.white,
- size: 14,
- ),
- text: "Stop voice call",
- textColor: Colors.white,
- ),
- )
- ],
- ),
- ),
- Offstage(
- offstage: !client.incomingVoiceCall,
- child: Row(
- children: [
- Expanded(
- child: buildButton(context,
- color: MyTheme.accent,
- onClick: () => handleVoiceCall(true),
- icon: Icon(
- Icons.call_rounded,
- color: Colors.white,
- size: 14,
- ),
- text: "Accept",
- textColor: Colors.white),
- ),
- Expanded(
- child: buildButton(
- context,
- color: Colors.red,
- onClick: () => handleVoiceCall(false),
- icon: Icon(
- Icons.phone_disabled_rounded,
- color: Colors.white,
- size: 14,
- ),
- text: "Dismiss",
- textColor: Colors.white,
- ),
- )
- ],
- ),
- ),
- Offstage(
- offstage: !client.fromSwitch,
- child: buildButton(context,
- color: Colors.purple,
- onClick: () => handleSwitchBack(context),
- icon: Icon(Icons.reply, color: Colors.white),
- text: "Switch Sides",
- textColor: Colors.white),
- ),
- Offstage(
- offstage: !showElevation,
- child: buildButton(
- context,
- color: MyTheme.accent,
- onClick: () {
- handleElevate(context);
- windowManager.minimize();
- },
- icon: Icon(
- Icons.security_rounded,
- color: Colors.white,
- size: 14,
- ),
- text: 'Elevate',
- textColor: Colors.white,
- ),
- ),
- Row(
- children: [
- Expanded(
- child: buildButton(context,
- color: Colors.redAccent,
- onClick: handleDisconnect,
- text: 'Disconnect',
- icon: Icon(
- Icons.link_off_rounded,
- color: Colors.white,
- size: 14,
- ),
- textColor: Colors.white),
- ),
- ],
- )
- ],
- ).marginOnly(bottom: buttonBottomMargin);
- }
- buildDisconnected(BuildContext context) {
- return Row(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- Expanded(
- child: buildButton(context,
- color: MyTheme.accent,
- onClick: handleClose,
- text: 'Close',
- textColor: Colors.white)),
- ],
- ).marginOnly(bottom: buttonBottomMargin);
- }
- buildUnAuthorized(BuildContext context) {
- final bool canElevate = bind.cmCanElevate();
- final model = Provider.of<ServerModel>(context);
- final showElevation = canElevate &&
- model.showElevation &&
- client.type_() == ClientType.remote;
- final showAccept = model.approveMode != 'password';
- return Column(
- mainAxisAlignment: MainAxisAlignment.end,
- children: [
- Offstage(
- offstage: !showElevation || !showAccept,
- child: buildButton(context, color: Colors.green[700], onClick: () {
- handleAccept(context);
- handleElevate(context);
- windowManager.minimize();
- },
- text: 'Accept and Elevate',
- icon: Icon(
- Icons.security_rounded,
- color: Colors.white,
- size: 14,
- ),
- textColor: Colors.white,
- tooltip: 'accept_and_elevate_btn_tooltip'),
- ),
- Row(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- if (showAccept)
- Expanded(
- child: Column(
- children: [
- buildButton(
- context,
- color: MyTheme.accent,
- onClick: () {
- handleAccept(context);
- windowManager.minimize();
- },
- text: 'Accept',
- textColor: Colors.white,
- ),
- ],
- ),
- ),
- Expanded(
- child: buildButton(
- context,
- color: Colors.transparent,
- border: Border.all(color: Colors.grey),
- onClick: handleDisconnect,
- text: 'Cancel',
- textColor: null,
- ),
- ),
- ],
- ),
- ],
- ).marginOnly(bottom: buttonBottomMargin);
- }
- Widget buildButton(BuildContext context,
- {required Color? color,
- GestureTapCallback? onClick,
- Widget? icon,
- BoxBorder? border,
- required String text,
- required Color? textColor,
- String? tooltip,
- GestureTapDownCallback? onTapDown}) {
- assert(!(onClick == null && onTapDown == null));
- Widget textWidget;
- if (icon != null) {
- textWidget = Text(
- translate(text),
- style: TextStyle(color: textColor),
- textAlign: TextAlign.center,
- );
- } else {
- textWidget = Expanded(
- child: Text(
- translate(text),
- style: TextStyle(color: textColor),
- textAlign: TextAlign.center,
- ),
- );
- }
- final borderRadius = BorderRadius.circular(10.0);
- final btn = Container(
- height: 28,
- decoration: BoxDecoration(
- color: color, borderRadius: borderRadius, border: border),
- child: InkWell(
- borderRadius: borderRadius,
- onTap: () {
- if (onClick == null) return;
- checkClickTime(client.id, onClick);
- },
- onTapDown: (details) {
- if (onTapDown == null) return;
- checkClickTime(client.id, () {
- onTapDown.call(details);
- });
- },
- child: Row(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- Offstage(offstage: icon == null, child: icon).marginOnly(right: 5),
- textWidget,
- ],
- ),
- ),
- );
- return (tooltip != null
- ? Tooltip(
- message: translate(tooltip),
- child: btn,
- )
- : btn)
- .marginAll(4);
- }
- void handleDisconnect() {
- bind.cmCloseConnection(connId: client.id);
- }
- void handleAccept(BuildContext context) {
- final model = Provider.of<ServerModel>(context, listen: false);
- model.sendLoginResponse(client, true);
- }
- void handleElevate(BuildContext context) {
- final model = Provider.of<ServerModel>(context, listen: false);
- model.setShowElevation(false);
- bind.cmElevatePortable(connId: client.id);
- }
- void handleClose() async {
- await bind.cmRemoveDisconnectedConnection(connId: client.id);
- if (await bind.cmGetClientsLength() == 0) {
- windowManager.close();
- }
- }
- void handleSwitchBack(BuildContext context) {
- bind.cmSwitchBack(connId: client.id);
- }
- void handleVoiceCall(bool accept) {
- bind.cmHandleIncomingVoiceCall(id: client.id, accept: accept);
- }
- void closeVoiceCall() {
- bind.cmCloseVoiceCall(id: client.id);
- }
- }
- void checkClickTime(int id, Function() callback) async {
- if (allowRemoteCMModification()) {
- callback();
- return;
- }
- var clickCallbackTime = DateTime.now().millisecondsSinceEpoch;
- await bind.cmCheckClickTime(connId: id);
- Timer(const Duration(milliseconds: 120), () async {
- var d = clickCallbackTime - await bind.cmGetClickTime();
- if (d > 120) callback();
- });
- }
- bool allowRemoteCMModification() {
- return option2bool(kOptionAllowRemoteCmModification,
- bind.mainGetLocalOption(key: kOptionAllowRemoteCmModification));
- }
- 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,
- );
- }
- iconLabel(CmFileLog item) {
- switch (item.action) {
- case CmFileAction.none:
- return Container();
- case CmFileAction.localToRemote:
- case CmFileAction.remoteToLocal:
- return Column(
- children: [
- Transform.rotate(
- angle: item.action == CmFileAction.remoteToLocal ? 0 : pi,
- child: SvgPicture.asset(
- "assets/arrow.svg",
- colorFilter: svgColor(Theme.of(context).tabBarTheme.labelColor),
- ),
- ),
- Text(item.action == CmFileAction.remoteToLocal
- ? translate('Send')
- : translate('Receive'))
- ],
- );
- case CmFileAction.remove:
- return Column(
- children: [
- Icon(
- Icons.delete,
- color: Theme.of(context).tabBarTheme.labelColor,
- ),
- Text(translate('Delete'))
- ],
- );
- case CmFileAction.createDir:
- return Column(
- children: [
- Icon(
- Icons.create_new_folder,
- color: Theme.of(context).tabBarTheme.labelColor,
- ),
- Text(translate('Create Folder'))
- ],
- );
- case CmFileAction.rename:
- return Column(
- children: [
- Icon(
- Icons.drive_file_move_outlined,
- color: Theme.of(context).tabBarTheme.labelColor,
- ),
- Text(translate('Rename'))
- ],
- );
- }
- }
- Widget statusList() {
- return PreferredSize(
- preferredSize: const Size(200, double.infinity),
- child: Container(
- padding: const EdgeInsets.all(12.0),
- child: Obx(
- () {
- final jobTable = gFFI.cmFileModel.currentJobTable;
- statusListView(List<CmFileLog> 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: iconLabel(item),
- ).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.isTransfer() &&
- 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",
- colorFilter: svgColor(
- Theme.of(context).tabBarTheme.labelColor),
- height: 40,
- ).paddingOnly(bottom: 10),
- Text(
- translate("No transfers in progress"),
- textAlign: TextAlign.center,
- textScaler: TextScaler.linear(1.20),
- style: TextStyle(
- color:
- Theme.of(context).tabBarTheme.labelColor),
- ),
- ],
- ),
- ),
- )
- : statusListView(jobTable);
- },
- )),
- );
- }
- }
|