content: // 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)
                : workaroundWindowBorder(
                    context,
                    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.file ||
                  client.type_() == ClientType.portForward ||
                  client.type_() == ClientType.terminal ||
                  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),
                    ),
                  ),
                  if (client.type_() == ClientType.terminal)
                    FittedBox(
                      child: Text(
                        translate("Terminal"),
                        style: TextStyle(color: Colors.white70, fontSize: 12),
                      ),
                    ),
                  if (client.type_() == ClientType.file)
                    FittedBox(
                      child: Text(
                        translate("File Transfer"),
                        style: TextStyle(color: Colors.white70, fontSize: 12),
                      ),
                    ),
                  if (client.type_() == ClientType.camera)
                    FittedBox(
                      child: Text(
                        translate("View Camera"),
                        style: TextStyle(color: Colors.white70, fontSize: 12),
                      ),
                    ),
                  if (client.portForward.isNotEmpty)
                    FittedBox(
                      child: Text(
                        "Port Forward: ${client.portForward}",
                        style: TextStyle(color: Colors.white70, fontSize: 12),
                      ),
                    ),
                  SizedBox(height: 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 &&
                      client.type_() != ClientType.camera),
              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: client.type_() == ClientType.camera
                    ? [
                        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.recording,
                          Icons.videocam_rounded,
                          (enabled) {
                            bind.cmSwitchPermission(
                                connId: client.id,
                                name: "recording",
                                enabled: enabled);
                            setState(() {
                              client.recording = enabled;
                            });
                          },
                          translate('Enable recording session'),
                        ),
                      ]
                    : [
                        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);
              },
            )),
      );
    }
  }
  