mirrored_repositories/rustdesk/flutter/lib/plugin
import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hbb/common.dart';
import 'package:flutter_hbb/models/model.dart';
import 'package:provider/provider.dart';
import 'package:get/get.dart';
// to-do: do not depend on desktop
import 'package:flutter_hbb/desktop/widgets/remote_toolbar.dart';
import 'package:flutter_hbb/models/platform_model.dart';

import '../manager.dart';
import '../model.dart';
import '../common.dart';

// dup to flutter\lib\desktop\pages\desktop_setting_page.dart
const double _kCheckBoxLeftMargin = 10;

class LocationItem extends StatelessWidget {
  final String peerId;
  final FFI ffi;
  final String location;
  final LocationModel locationModel;
  final bool isMenu;

  LocationItem({
    Key? key,
    required this.peerId,
    required this.ffi,
    required this.location,
    required this.locationModel,
    required this.isMenu,
  }) : super(key: key);

  bool get isEmpty => locationModel.isEmpty;

  static Widget createLocationItem(
      String peerId, FFI ffi, String location, bool isMenu) {
    final model = getLocationModel(location);
    return model == null
        ? Container()
        : LocationItem(
            peerId: peerId,
            ffi: ffi,
            location: location,
            locationModel: model,
            isMenu: isMenu,
          );
  }

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider.value(
      value: locationModel,
      child: Consumer<LocationModel>(builder: (context, model, child) {
        return Column(
          children: model.pluginModels.entries
              .map((entry) => _buildPluginItem(entry.key, entry.value))
              .toList(),
        );
      }),
    );
  }

  Widget _buildPluginItem(PluginId id, PluginModel model) => PluginItem(
        pluginId: id,
        peerId: peerId,
        ffi: ffi,
        location: location,
        pluginModel: model,
        isMenu: isMenu,
      );
}

class PluginItem extends StatelessWidget {
  final PluginId pluginId;
  final String peerId;
  final FFI? ffi;
  final String location;
  final PluginModel pluginModel;
  final bool isMenu;

  PluginItem({
    Key? key,
    required this.pluginId,
    required this.peerId,
    this.ffi,
    required this.location,
    required this.pluginModel,
    required this.isMenu,
  }) : super(key: key);

  bool get isEmpty => pluginModel.isEmpty;

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider.value(
      value: pluginModel,
      child: Consumer<PluginModel>(
        builder: (context, pluginModel, child) {
          return Column(
            children: pluginModel.uiList.map((ui) => _buildItem(ui)).toList(),
          );
        },
      ),
    );
  }

  Widget _buildItem(UiType ui) {
    Widget? child;
    switch (ui.runtimeType) {
      case UiButton:
        if (isMenu) {
          if (ffi != null) {
            child = _buildMenuButton(ui as UiButton, ffi!);
          }
        } else {
          child = _buildButton(ui as UiButton);
        }
        break;
      case UiCheckbox:
        if (isMenu) {
          if (ffi != null) {
            child = _buildCheckboxMenuButton(ui as UiCheckbox, ffi!);
          }
        } else {
          child = _buildCheckbox(ui as UiCheckbox);
        }
        break;
      default:
        break;
    }
    // to-do: add plugin icon and tooltip
    return child ?? Container();
  }

  Widget _buildButton(UiButton ui) {
    return TextButton(
      onPressed: () => bind.pluginEvent(
        id: pluginId,
        peer: peerId,
        event: _makeEvent(ui.key),
      ),
      child: Text(ui.text),
    );
  }

  Widget _buildCheckbox(UiCheckbox ui) {
    getChild(OptionModel model) {
      final v = _getOption(model, ui.key);
      if (v == null) {
        // session or plugin not found
        return Container();
      }

      onChanged(bool value) {
        bind.pluginEvent(
          id: pluginId,
          peer: peerId,
          event: _makeEvent(ui.key, v: value),
        );
      }

      final value = ConfigItem.isTrue(v);
      return GestureDetector(
        child: Row(
          children: [
            Checkbox(
              value: value,
              onChanged: (_) => onChanged(!value),
            ).marginOnly(right: 5),
            Expanded(
              child: Text(translate(ui.text)),
            )
          ],
        ).marginOnly(left: _kCheckBoxLeftMargin),
        onTap: () => onChanged(!value),
      );
    }

    return ChangeNotifierProvider.value(
      value: getOptionModel(location, pluginId, peerId, ui.key),
      child: Consumer<OptionModel>(
        builder: (context, model, child) => getChild(model),
      ),
    );
  }

  Widget _buildCheckboxMenuButton(UiCheckbox ui, FFI ffi) {
    getChild(OptionModel model) {
      final v = _getOption(model, ui.key);
      if (v == null) {
        // session or plugin not found
        return Container();
      }
      return CkbMenuButton(
        value: ConfigItem.isTrue(v),
        onChanged: (v) {
          if (v != null) {
            bind.pluginEvent(
              id: pluginId,
              peer: peerId,
              event: _makeEvent(ui.key, v: v),
            );
          }
        },
        // to-do: RustDesk translate or plugin translate ?
        child: Text(ui.text),
        ffi: ffi,
      );
    }

    return ChangeNotifierProvider.value(
      value: getOptionModel(location, pluginId, peerId, ui.key),
      child: Consumer<OptionModel>(
        builder: (context, model, child) => getChild(model),
      ),
    );
  }

  Widget _buildMenuButton(UiButton ui, FFI ffi) {
    return MenuButton(
      onPressed: () => bind.pluginEvent(
        id: pluginId,
        peer: peerId,
        event: _makeEvent(ui.key),
      ),
      // to-do: support trailing icon, but it will cause tree shake error.
      // ```
      // This application cannot tree shake icons fonts. It has non-constant instances of IconData at the following locations:
      // Target release_macos_bundle_flutter_assets failed: Exception: Avoid non-constant invocations of IconData or try to build again with --no-tree-shake-icons.
      // ```
      //
      // trailingIcon: Icon(
      //     IconData(int.parse(ui.icon, radix: 16), fontFamily: 'MaterialIcons')),
      //
      // to-do: RustDesk translate or plugin translate ?
      child: Text(ui.text),
      ffi: ffi,
    );
  }

  Uint8List _makeEvent(
    String key, {
    bool? v,
  }) {
    final event = MsgFromUi(
      id: pluginId,
      name: pluginManager.getPlugin(pluginId)?.meta.name ?? '',
      location: location,
      key: key,
      value:
          v != null ? (v ? ConfigItem.trueValue : ConfigItem.falseValue) : '',
      action: '',
    );
    return Uint8List.fromList(event.toString().codeUnits);
  }

  String? _getOption(OptionModel model, String key) {
    var v = model.value;
    if (v == null) {
      try {
        if (peerId.isEmpty) {
          v = bind.pluginGetSharedOption(id: pluginId, key: key);
        } else {
          v = bind.pluginGetSessionOption(id: pluginId, peer: peerId, key: key);
        }
      } catch (e) {
        debugPrint('Failed to get option "$key", $e');
        v = null;
      }
    }
    return v;
  }
}

void handleReloading(Map<String, dynamic> evt) {
  if (evt['id'] == null || evt['location'] == null) {
    return;
  }
  try {
    final uiList = <UiType>[];
    for (var e in json.decode(evt['ui'] as String)) {
      final ui = UiType.create(e);
      if (ui != null) {
        uiList.add(ui);
      }
    }
    if (uiList.isNotEmpty) {
      addLocationUi(evt['location']!, evt['id']!, uiList);
    }
  } catch (e) {
    debugPrint('Failed handleReloading, json decode of ui, $e ');
  }
}

void handleOption(Map<String, dynamic> evt) {
  updateOption(
      evt['location'], evt['id'], evt['peer'] ?? '', evt['key'], evt['value']);
}
