setting_widgets.dart 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  1. import 'package:debounce_throttle/debounce_throttle.dart';
  2. import 'package:flutter/material.dart';
  3. import 'package:flutter/services.dart';
  4. import 'package:flutter_hbb/common.dart';
  5. import 'package:flutter_hbb/consts.dart';
  6. import 'package:flutter_hbb/models/platform_model.dart';
  7. import 'package:get/get.dart';
  8. customImageQualityWidget(
  9. {required double initQuality,
  10. required double initFps,
  11. required Function(double)? setQuality,
  12. required Function(double)? setFps,
  13. required bool showFps,
  14. required bool showMoreQuality}) {
  15. if (initQuality < kMinQuality ||
  16. initQuality > (showMoreQuality ? kMaxMoreQuality : kMaxQuality)) {
  17. initQuality = kDefaultQuality;
  18. }
  19. if (initFps < kMinFps || initFps > kMaxFps) {
  20. initFps = kDefaultFps;
  21. }
  22. final qualityValue = initQuality.obs;
  23. final fpsValue = initFps.obs;
  24. final RxBool moreQualityChecked = RxBool(qualityValue.value > kMaxQuality);
  25. final debouncerQuality = Debouncer<double>(
  26. Duration(milliseconds: 1000),
  27. onChanged: setQuality,
  28. initialValue: qualityValue.value,
  29. );
  30. final debouncerFps = Debouncer<double>(
  31. Duration(milliseconds: 1000),
  32. onChanged: setFps,
  33. initialValue: fpsValue.value,
  34. );
  35. onMoreChanged(bool? value) {
  36. if (value == null) return;
  37. moreQualityChecked.value = value;
  38. if (!value && qualityValue.value > 100) {
  39. qualityValue.value = 100;
  40. }
  41. debouncerQuality.value = qualityValue.value;
  42. }
  43. return Column(
  44. children: [
  45. Obx(() => Row(
  46. children: [
  47. Expanded(
  48. flex: 3,
  49. child: Slider(
  50. value: qualityValue.value,
  51. min: kMinQuality,
  52. max: moreQualityChecked.value ? kMaxMoreQuality : kMaxQuality,
  53. divisions: moreQualityChecked.value
  54. ? ((kMaxMoreQuality - kMinQuality) / 10).round()
  55. : ((kMaxQuality - kMinQuality) / 5).round(),
  56. onChanged: setQuality == null
  57. ? null
  58. : (double value) async {
  59. qualityValue.value = value;
  60. debouncerQuality.value = value;
  61. },
  62. ),
  63. ),
  64. Expanded(
  65. flex: 1,
  66. child: Text(
  67. '${qualityValue.value.round()}%',
  68. style: const TextStyle(fontSize: 15),
  69. )),
  70. Expanded(
  71. flex: isMobile ? 2 : 1,
  72. child: Text(
  73. translate('Bitrate'),
  74. style: const TextStyle(fontSize: 15),
  75. )),
  76. // mobile doesn't have enough space
  77. if (showMoreQuality && !isMobile)
  78. Expanded(
  79. flex: 1,
  80. child: Row(
  81. children: [
  82. Checkbox(
  83. value: moreQualityChecked.value,
  84. onChanged: onMoreChanged,
  85. ),
  86. Expanded(
  87. child: Text(translate('More')),
  88. )
  89. ],
  90. ))
  91. ],
  92. )),
  93. if (showMoreQuality && isMobile)
  94. Obx(() => Row(
  95. children: [
  96. Expanded(
  97. child: Align(
  98. alignment: Alignment.centerRight,
  99. child: Checkbox(
  100. value: moreQualityChecked.value,
  101. onChanged: onMoreChanged,
  102. ),
  103. ),
  104. ),
  105. Expanded(
  106. child: Text(translate('More')),
  107. )
  108. ],
  109. )),
  110. if (showFps)
  111. Obx(() => Row(
  112. children: [
  113. Expanded(
  114. flex: 3,
  115. child: Slider(
  116. value: fpsValue.value,
  117. min: kMinFps,
  118. max: kMaxFps,
  119. divisions: ((kMaxFps - kMinFps) / 5).round(),
  120. onChanged: setFps == null
  121. ? null
  122. : (double value) async {
  123. fpsValue.value = value;
  124. debouncerFps.value = value;
  125. },
  126. ),
  127. ),
  128. Expanded(
  129. flex: 1,
  130. child: Text(
  131. '${fpsValue.value.round()}',
  132. style: const TextStyle(fontSize: 15),
  133. )),
  134. Expanded(
  135. flex: 2,
  136. child: Text(
  137. translate('FPS'),
  138. style: const TextStyle(fontSize: 15),
  139. ))
  140. ],
  141. )),
  142. ],
  143. );
  144. }
  145. customImageQualitySetting() {
  146. final qualityKey = 'custom_image_quality';
  147. final fpsKey = 'custom-fps';
  148. final initQuality =
  149. (double.tryParse(bind.mainGetUserDefaultOption(key: qualityKey)) ??
  150. kDefaultQuality);
  151. final isQuanlityFixed = isOptionFixed(qualityKey);
  152. final initFps =
  153. (double.tryParse(bind.mainGetUserDefaultOption(key: fpsKey)) ??
  154. kDefaultFps);
  155. final isFpsFixed = isOptionFixed(fpsKey);
  156. return customImageQualityWidget(
  157. initQuality: initQuality,
  158. initFps: initFps,
  159. setQuality: isQuanlityFixed
  160. ? null
  161. : (v) {
  162. bind.mainSetUserDefaultOption(
  163. key: qualityKey, value: v.toString());
  164. },
  165. setFps: isFpsFixed
  166. ? null
  167. : (v) {
  168. bind.mainSetUserDefaultOption(key: fpsKey, value: v.toString());
  169. },
  170. showFps: true,
  171. showMoreQuality: true);
  172. }
  173. List<Widget> ServerConfigImportExportWidgets(
  174. List<TextEditingController> controllers,
  175. List<RxString> errMsgs,
  176. ) {
  177. import() {
  178. Clipboard.getData(Clipboard.kTextPlain).then((value) {
  179. importConfig(controllers, errMsgs, value?.text);
  180. });
  181. }
  182. export() {
  183. final text = ServerConfig(
  184. idServer: controllers[0].text.trim(),
  185. relayServer: controllers[1].text.trim(),
  186. apiServer: controllers[2].text.trim(),
  187. key: controllers[3].text.trim())
  188. .encode();
  189. debugPrint("ServerConfig export: $text");
  190. Clipboard.setData(ClipboardData(text: text));
  191. showToast(translate('Export server configuration successfully'));
  192. }
  193. return [
  194. Tooltip(
  195. message: translate('Import server config'),
  196. child: IconButton(
  197. icon: Icon(Icons.paste, color: Colors.grey), onPressed: import),
  198. ),
  199. Tooltip(
  200. message: translate('Export Server Config'),
  201. child: IconButton(
  202. icon: Icon(Icons.copy, color: Colors.grey), onPressed: export))
  203. ];
  204. }
  205. List<(String, String)> otherDefaultSettings() {
  206. List<(String, String)> v = [
  207. ('View Mode', kOptionViewOnly),
  208. if ((isDesktop || isWebDesktop))
  209. ('show_monitors_tip', kKeyShowMonitorsToolbar),
  210. if ((isDesktop || isWebDesktop))
  211. ('Collapse toolbar', kOptionCollapseToolbar),
  212. ('Show remote cursor', kOptionShowRemoteCursor),
  213. ('Follow remote cursor', kOptionFollowRemoteCursor),
  214. ('Follow remote window focus', kOptionFollowRemoteWindow),
  215. if ((isDesktop || isWebDesktop)) ('Zoom cursor', kOptionZoomCursor),
  216. ('Show quality monitor', kOptionShowQualityMonitor),
  217. ('Mute', kOptionDisableAudio),
  218. if (isDesktop) ('Enable file copy and paste', kOptionEnableFileCopyPaste),
  219. ('Disable clipboard', kOptionDisableClipboard),
  220. ('Lock after session end', kOptionLockAfterSessionEnd),
  221. ('Privacy mode', kOptionPrivacyMode),
  222. if (isMobile) ('Touch mode', kOptionTouchMode),
  223. ('True color (4:4:4)', kOptionI444),
  224. ('Reverse mouse wheel', kKeyReverseMouseWheel),
  225. ('swap-left-right-mouse', kOptionSwapLeftRightMouse),
  226. if (isDesktop)
  227. (
  228. 'Show displays as individual windows',
  229. kKeyShowDisplaysAsIndividualWindows
  230. ),
  231. if (isDesktop)
  232. (
  233. 'Use all my displays for the remote session',
  234. kKeyUseAllMyDisplaysForTheRemoteSession
  235. )
  236. ];
  237. return v;
  238. }
  239. class TrackpadSpeedWidget extends StatefulWidget {
  240. final SimpleWrapper<int> value;
  241. // If null, no debouncer will be applied.
  242. final Function(int)? onDebouncer;
  243. TrackpadSpeedWidget({Key? key, required this.value, this.onDebouncer});
  244. @override
  245. TrackpadSpeedWidgetState createState() => TrackpadSpeedWidgetState();
  246. }
  247. class TrackpadSpeedWidgetState extends State<TrackpadSpeedWidget> {
  248. final TextEditingController _controller = TextEditingController();
  249. late final Debouncer<int> debouncerSpeed;
  250. set value(int v) => widget.value.value = v;
  251. int get value => widget.value.value;
  252. void updateValue(int newValue) {
  253. setState(() {
  254. value = newValue.clamp(kMinTrackpadSpeed, kMaxTrackpadSpeed);
  255. // Scale the trackpad speed value to a percentage for display purposes.
  256. _controller.text = value.toString();
  257. if (widget.onDebouncer != null) {
  258. debouncerSpeed.setValue(value);
  259. }
  260. });
  261. }
  262. @override
  263. void initState() {
  264. super.initState();
  265. debouncerSpeed = Debouncer<int>(
  266. Duration(milliseconds: 1000),
  267. onChanged: widget.onDebouncer,
  268. initialValue: widget.value.value,
  269. );
  270. }
  271. @override
  272. Widget build(BuildContext context) {
  273. if (_controller.text.isEmpty) {
  274. _controller.text = value.toString();
  275. }
  276. return Row(
  277. children: [
  278. Expanded(
  279. flex: 3,
  280. child: Slider(
  281. value: value.toDouble(),
  282. min: kMinTrackpadSpeed.toDouble(),
  283. max: kMaxTrackpadSpeed.toDouble(),
  284. divisions: ((kMaxTrackpadSpeed - kMinTrackpadSpeed) / 10).round(),
  285. onChanged: (double v) => updateValue(v.round()),
  286. ),
  287. ),
  288. Expanded(
  289. flex: 1,
  290. child: Row(
  291. children: [
  292. SizedBox(
  293. width: 56,
  294. child: TextField(
  295. controller: _controller,
  296. keyboardType: TextInputType.number,
  297. textAlign: TextAlign.center,
  298. onSubmitted: (text) {
  299. int? v = int.tryParse(text);
  300. if (v != null) {
  301. updateValue(v);
  302. }
  303. },
  304. style: const TextStyle(fontSize: 13),
  305. decoration: InputDecoration(
  306. contentPadding:
  307. EdgeInsets.symmetric(vertical: 8.0, horizontal: 12.0),
  308. ),
  309. ),
  310. ).marginOnly(right: 8.0),
  311. Text(
  312. '%',
  313. style: const TextStyle(fontSize: 15),
  314. )
  315. ],
  316. )),
  317. ],
  318. );
  319. }
  320. }