desc_ui.dart 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. import 'dart:convert';
  2. import 'package:flutter/material.dart';
  3. import 'package:flutter/services.dart';
  4. import 'package:flutter_hbb/common.dart';
  5. import 'package:flutter_hbb/models/model.dart';
  6. import 'package:provider/provider.dart';
  7. import 'package:get/get.dart';
  8. // to-do: do not depend on desktop
  9. import 'package:flutter_hbb/desktop/widgets/remote_toolbar.dart';
  10. import 'package:flutter_hbb/models/platform_model.dart';
  11. import '../manager.dart';
  12. import '../model.dart';
  13. import '../common.dart';
  14. // dup to flutter\lib\desktop\pages\desktop_setting_page.dart
  15. const double _kCheckBoxLeftMargin = 10;
  16. class LocationItem extends StatelessWidget {
  17. final String peerId;
  18. final FFI ffi;
  19. final String location;
  20. final LocationModel locationModel;
  21. final bool isMenu;
  22. LocationItem({
  23. Key? key,
  24. required this.peerId,
  25. required this.ffi,
  26. required this.location,
  27. required this.locationModel,
  28. required this.isMenu,
  29. }) : super(key: key);
  30. bool get isEmpty => locationModel.isEmpty;
  31. static Widget createLocationItem(
  32. String peerId, FFI ffi, String location, bool isMenu) {
  33. final model = getLocationModel(location);
  34. return model == null
  35. ? Container()
  36. : LocationItem(
  37. peerId: peerId,
  38. ffi: ffi,
  39. location: location,
  40. locationModel: model,
  41. isMenu: isMenu,
  42. );
  43. }
  44. @override
  45. Widget build(BuildContext context) {
  46. return ChangeNotifierProvider.value(
  47. value: locationModel,
  48. child: Consumer<LocationModel>(builder: (context, model, child) {
  49. return Column(
  50. children: model.pluginModels.entries
  51. .map((entry) => _buildPluginItem(entry.key, entry.value))
  52. .toList(),
  53. );
  54. }),
  55. );
  56. }
  57. Widget _buildPluginItem(PluginId id, PluginModel model) => PluginItem(
  58. pluginId: id,
  59. peerId: peerId,
  60. ffi: ffi,
  61. location: location,
  62. pluginModel: model,
  63. isMenu: isMenu,
  64. );
  65. }
  66. class PluginItem extends StatelessWidget {
  67. final PluginId pluginId;
  68. final String peerId;
  69. final FFI? ffi;
  70. final String location;
  71. final PluginModel pluginModel;
  72. final bool isMenu;
  73. PluginItem({
  74. Key? key,
  75. required this.pluginId,
  76. required this.peerId,
  77. this.ffi,
  78. required this.location,
  79. required this.pluginModel,
  80. required this.isMenu,
  81. }) : super(key: key);
  82. bool get isEmpty => pluginModel.isEmpty;
  83. @override
  84. Widget build(BuildContext context) {
  85. return ChangeNotifierProvider.value(
  86. value: pluginModel,
  87. child: Consumer<PluginModel>(
  88. builder: (context, pluginModel, child) {
  89. return Column(
  90. children: pluginModel.uiList.map((ui) => _buildItem(ui)).toList(),
  91. );
  92. },
  93. ),
  94. );
  95. }
  96. Widget _buildItem(UiType ui) {
  97. Widget? child;
  98. switch (ui.runtimeType) {
  99. case UiButton:
  100. if (isMenu) {
  101. if (ffi != null) {
  102. child = _buildMenuButton(ui as UiButton, ffi!);
  103. }
  104. } else {
  105. child = _buildButton(ui as UiButton);
  106. }
  107. break;
  108. case UiCheckbox:
  109. if (isMenu) {
  110. if (ffi != null) {
  111. child = _buildCheckboxMenuButton(ui as UiCheckbox, ffi!);
  112. }
  113. } else {
  114. child = _buildCheckbox(ui as UiCheckbox);
  115. }
  116. break;
  117. default:
  118. break;
  119. }
  120. // to-do: add plugin icon and tooltip
  121. return child ?? Container();
  122. }
  123. Widget _buildButton(UiButton ui) {
  124. return TextButton(
  125. onPressed: () => bind.pluginEvent(
  126. id: pluginId,
  127. peer: peerId,
  128. event: _makeEvent(ui.key),
  129. ),
  130. child: Text(ui.text),
  131. );
  132. }
  133. Widget _buildCheckbox(UiCheckbox ui) {
  134. getChild(OptionModel model) {
  135. final v = _getOption(model, ui.key);
  136. if (v == null) {
  137. // session or plugin not found
  138. return Container();
  139. }
  140. onChanged(bool value) {
  141. bind.pluginEvent(
  142. id: pluginId,
  143. peer: peerId,
  144. event: _makeEvent(ui.key, v: value),
  145. );
  146. }
  147. final value = ConfigItem.isTrue(v);
  148. return GestureDetector(
  149. child: Row(
  150. children: [
  151. Checkbox(
  152. value: value,
  153. onChanged: (_) => onChanged(!value),
  154. ).marginOnly(right: 5),
  155. Expanded(
  156. child: Text(translate(ui.text)),
  157. )
  158. ],
  159. ).marginOnly(left: _kCheckBoxLeftMargin),
  160. onTap: () => onChanged(!value),
  161. );
  162. }
  163. return ChangeNotifierProvider.value(
  164. value: getOptionModel(location, pluginId, peerId, ui.key),
  165. child: Consumer<OptionModel>(
  166. builder: (context, model, child) => getChild(model),
  167. ),
  168. );
  169. }
  170. Widget _buildCheckboxMenuButton(UiCheckbox ui, FFI ffi) {
  171. getChild(OptionModel model) {
  172. final v = _getOption(model, ui.key);
  173. if (v == null) {
  174. // session or plugin not found
  175. return Container();
  176. }
  177. return CkbMenuButton(
  178. value: ConfigItem.isTrue(v),
  179. onChanged: (v) {
  180. if (v != null) {
  181. bind.pluginEvent(
  182. id: pluginId,
  183. peer: peerId,
  184. event: _makeEvent(ui.key, v: v),
  185. );
  186. }
  187. },
  188. // to-do: RustDesk translate or plugin translate ?
  189. child: Text(ui.text),
  190. ffi: ffi,
  191. );
  192. }
  193. return ChangeNotifierProvider.value(
  194. value: getOptionModel(location, pluginId, peerId, ui.key),
  195. child: Consumer<OptionModel>(
  196. builder: (context, model, child) => getChild(model),
  197. ),
  198. );
  199. }
  200. Widget _buildMenuButton(UiButton ui, FFI ffi) {
  201. return MenuButton(
  202. onPressed: () => bind.pluginEvent(
  203. id: pluginId,
  204. peer: peerId,
  205. event: _makeEvent(ui.key),
  206. ),
  207. // to-do: support trailing icon, but it will cause tree shake error.
  208. // ```
  209. // This application cannot tree shake icons fonts. It has non-constant instances of IconData at the following locations:
  210. // Target release_macos_bundle_flutter_assets failed: Exception: Avoid non-constant invocations of IconData or try to build again with --no-tree-shake-icons.
  211. // ```
  212. //
  213. // trailingIcon: Icon(
  214. // IconData(int.parse(ui.icon, radix: 16), fontFamily: 'MaterialIcons')),
  215. //
  216. // to-do: RustDesk translate or plugin translate ?
  217. child: Text(ui.text),
  218. ffi: ffi,
  219. );
  220. }
  221. Uint8List _makeEvent(
  222. String key, {
  223. bool? v,
  224. }) {
  225. final event = MsgFromUi(
  226. id: pluginId,
  227. name: pluginManager.getPlugin(pluginId)?.meta.name ?? '',
  228. location: location,
  229. key: key,
  230. value:
  231. v != null ? (v ? ConfigItem.trueValue : ConfigItem.falseValue) : '',
  232. action: '',
  233. );
  234. return Uint8List.fromList(event.toString().codeUnits);
  235. }
  236. String? _getOption(OptionModel model, String key) {
  237. var v = model.value;
  238. if (v == null) {
  239. try {
  240. if (peerId.isEmpty) {
  241. v = bind.pluginGetSharedOption(id: pluginId, key: key);
  242. } else {
  243. v = bind.pluginGetSessionOption(id: pluginId, peer: peerId, key: key);
  244. }
  245. } catch (e) {
  246. debugPrint('Failed to get option "$key", $e');
  247. v = null;
  248. }
  249. }
  250. return v;
  251. }
  252. }
  253. void handleReloading(Map<String, dynamic> evt) {
  254. if (evt['id'] == null || evt['location'] == null) {
  255. return;
  256. }
  257. try {
  258. final uiList = <UiType>[];
  259. for (var e in json.decode(evt['ui'] as String)) {
  260. final ui = UiType.create(e);
  261. if (ui != null) {
  262. uiList.add(ui);
  263. }
  264. }
  265. if (uiList.isNotEmpty) {
  266. addLocationUi(evt['location']!, evt['id']!, uiList);
  267. }
  268. } catch (e) {
  269. debugPrint('Failed handleReloading, json decode of ui, $e ');
  270. }
  271. }
  272. void handleOption(Map<String, dynamic> evt) {
  273. updateOption(
  274. evt['location'], evt['id'], evt['peer'] ?? '', evt['key'], evt['value']);
  275. }