278 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
			
		
		
	
	
			278 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
| import 'package:debounce_throttle/debounce_throttle.dart';
 | |
| import 'package:flutter/material.dart';
 | |
| import 'package:flutter/services.dart';
 | |
| import 'package:flutter_hbb/common.dart';
 | |
| import 'package:flutter_hbb/models/platform_model.dart';
 | |
| import 'package:get/get.dart';
 | |
| 
 | |
| customImageQualityWidget(
 | |
|     {required double initQuality,
 | |
|     required double initFps,
 | |
|     required Function(double) setQuality,
 | |
|     required Function(double) setFps,
 | |
|     required bool showFps}) {
 | |
|   final qualityValue = initQuality.obs;
 | |
|   final fpsValue = initFps.obs;
 | |
| 
 | |
|   final RxBool moreQualityChecked = RxBool(qualityValue.value > 100);
 | |
|   final debouncerQuality = Debouncer<double>(
 | |
|     Duration(milliseconds: 1000),
 | |
|     onChanged: (double v) {
 | |
|       setQuality(v);
 | |
|     },
 | |
|     initialValue: qualityValue.value,
 | |
|   );
 | |
|   final debouncerFps = Debouncer<double>(
 | |
|     Duration(milliseconds: 1000),
 | |
|     onChanged: (double v) {
 | |
|       setFps(v);
 | |
|     },
 | |
|     initialValue: fpsValue.value,
 | |
|   );
 | |
| 
 | |
|   onMoreChanged(bool? value) {
 | |
|     if (value == null) return;
 | |
|     moreQualityChecked.value = value;
 | |
|     if (!value && qualityValue.value > 100) {
 | |
|       qualityValue.value = 100;
 | |
|     }
 | |
|     debouncerQuality.value = qualityValue.value;
 | |
|   }
 | |
| 
 | |
|   return Column(
 | |
|     children: [
 | |
|       Obx(() => Row(
 | |
|             children: [
 | |
|               Expanded(
 | |
|                 flex: 3,
 | |
|                 child: Slider(
 | |
|                   value: qualityValue.value,
 | |
|                   min: 10.0,
 | |
|                   max: moreQualityChecked.value ? 2000 : 100,
 | |
|                   divisions: moreQualityChecked.value ? 199 : 18,
 | |
|                   onChanged: (double value) async {
 | |
|                     qualityValue.value = value;
 | |
|                     debouncerQuality.value = value;
 | |
|                   },
 | |
|                 ),
 | |
|               ),
 | |
|               Expanded(
 | |
|                   flex: 1,
 | |
|                   child: Text(
 | |
|                     '${qualityValue.value.round()}%',
 | |
|                     style: const TextStyle(fontSize: 15),
 | |
|                   )),
 | |
|               Expanded(
 | |
|                   flex: isMobile ? 2 : 1,
 | |
|                   child: Text(
 | |
|                     translate('Bitrate'),
 | |
|                     style: const TextStyle(fontSize: 15),
 | |
|                   )),
 | |
|               // mobile doesn't have enough space
 | |
|               if (!isMobile)
 | |
|                 Expanded(
 | |
|                     flex: 1,
 | |
|                     child: Row(
 | |
|                       children: [
 | |
|                         Checkbox(
 | |
|                           value: moreQualityChecked.value,
 | |
|                           onChanged: onMoreChanged,
 | |
|                         ),
 | |
|                         Expanded(
 | |
|                           child: Text(translate('More')),
 | |
|                         )
 | |
|                       ],
 | |
|                     ))
 | |
|             ],
 | |
|           )),
 | |
|       if (isMobile)
 | |
|         Obx(() => Row(
 | |
|               children: [
 | |
|                 Expanded(
 | |
|                   child: Align(
 | |
|                     alignment: Alignment.centerRight,
 | |
|                     child: Checkbox(
 | |
|                       value: moreQualityChecked.value,
 | |
|                       onChanged: onMoreChanged,
 | |
|                     ),
 | |
|                   ),
 | |
|                 ),
 | |
|                 Expanded(
 | |
|                   child: Text(translate('More')),
 | |
|                 )
 | |
|               ],
 | |
|             )),
 | |
|       if (showFps)
 | |
|         Obx(() => Row(
 | |
|               children: [
 | |
|                 Expanded(
 | |
|                   flex: 3,
 | |
|                   child: Slider(
 | |
|                     value: fpsValue.value,
 | |
|                     min: 5.0,
 | |
|                     max: 120.0,
 | |
|                     divisions: 23,
 | |
|                     onChanged: (double value) async {
 | |
|                       fpsValue.value = value;
 | |
|                       debouncerFps.value = value;
 | |
|                     },
 | |
|                   ),
 | |
|                 ),
 | |
|                 Expanded(
 | |
|                     flex: 1,
 | |
|                     child: Text(
 | |
|                       '${fpsValue.value.round()}',
 | |
|                       style: const TextStyle(fontSize: 15),
 | |
|                     )),
 | |
|                 Expanded(
 | |
|                     flex: 2,
 | |
|                     child: Text(
 | |
|                       translate('FPS'),
 | |
|                       style: const TextStyle(fontSize: 15),
 | |
|                     ))
 | |
|               ],
 | |
|             )),
 | |
|     ],
 | |
|   );
 | |
| }
 | |
| 
 | |
| customImageQualitySetting() {
 | |
|   final qualityKey = 'custom_image_quality';
 | |
|   final fpsKey = 'custom-fps';
 | |
| 
 | |
|   var initQuality =
 | |
|       (double.tryParse(bind.mainGetUserDefaultOption(key: qualityKey)) ?? 50.0);
 | |
|   if (initQuality < 10 || initQuality > 2000) {
 | |
|     initQuality = 50;
 | |
|   }
 | |
|   var initFps =
 | |
|       (double.tryParse(bind.mainGetUserDefaultOption(key: fpsKey)) ?? 30.0);
 | |
|   if (initFps < 5 || initFps > 120) {
 | |
|     initFps = 30;
 | |
|   }
 | |
| 
 | |
|   return customImageQualityWidget(
 | |
|       initQuality: initQuality,
 | |
|       initFps: initFps,
 | |
|       setQuality: (v) {
 | |
|         bind.mainSetUserDefaultOption(key: qualityKey, value: v.toString());
 | |
|       },
 | |
|       setFps: (v) {
 | |
|         bind.mainSetUserDefaultOption(key: fpsKey, value: v.toString());
 | |
|       },
 | |
|       showFps: true);
 | |
| }
 | |
| 
 | |
| Future<bool> setServerConfig(
 | |
|   List<TextEditingController> controllers,
 | |
|   List<RxString> errMsgs,
 | |
|   ServerConfig config,
 | |
| ) async {
 | |
|   config.idServer = config.idServer.trim();
 | |
|   config.relayServer = config.relayServer.trim();
 | |
|   config.apiServer = config.apiServer.trim();
 | |
|   config.key = config.key.trim();
 | |
|   // id
 | |
|   if (config.idServer.isNotEmpty) {
 | |
|     errMsgs[0].value =
 | |
|         translate(await bind.mainTestIfValidServer(server: config.idServer));
 | |
|     if (errMsgs[0].isNotEmpty) {
 | |
|       return false;
 | |
|     }
 | |
|   }
 | |
|   // relay
 | |
|   if (config.relayServer.isNotEmpty) {
 | |
|     errMsgs[1].value =
 | |
|         translate(await bind.mainTestIfValidServer(server: config.relayServer));
 | |
|     if (errMsgs[1].isNotEmpty) {
 | |
|       return false;
 | |
|     }
 | |
|   }
 | |
|   // api
 | |
|   if (config.apiServer.isNotEmpty) {
 | |
|     if (!config.apiServer.startsWith('http://') &&
 | |
|         !config.apiServer.startsWith('https://')) {
 | |
|       errMsgs[2].value =
 | |
|           '${translate("API Server")}: ${translate("invalid_http")}';
 | |
|       return false;
 | |
|     }
 | |
|   }
 | |
|   final oldApiServer = await bind.mainGetApiServer();
 | |
| 
 | |
|   // should set one by one
 | |
|   await bind.mainSetOption(
 | |
|       key: 'custom-rendezvous-server', value: config.idServer);
 | |
|   await bind.mainSetOption(key: 'relay-server', value: config.relayServer);
 | |
|   await bind.mainSetOption(key: 'api-server', value: config.apiServer);
 | |
|   await bind.mainSetOption(key: 'key', value: config.key);
 | |
| 
 | |
|   final newApiServer = await bind.mainGetApiServer();
 | |
|   if (oldApiServer.isNotEmpty &&
 | |
|       oldApiServer != newApiServer &&
 | |
|       gFFI.userModel.isLogin) {
 | |
|     gFFI.userModel.logOut(apiServer: oldApiServer);
 | |
|   }
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| List<Widget> ServerConfigImportExportWidgets(
 | |
|   List<TextEditingController> controllers,
 | |
|   List<RxString> errMsgs,
 | |
| ) {
 | |
|   import() {
 | |
|     Clipboard.getData(Clipboard.kTextPlain).then((value) {
 | |
|       final text = value?.text;
 | |
|       if (text != null && text.isNotEmpty) {
 | |
|         try {
 | |
|           final sc = ServerConfig.decode(text);
 | |
|           if (sc.idServer.isNotEmpty) {
 | |
|             controllers[0].text = sc.idServer;
 | |
|             controllers[1].text = sc.relayServer;
 | |
|             controllers[2].text = sc.apiServer;
 | |
|             controllers[3].text = sc.key;
 | |
|             Future<bool> success = setServerConfig(controllers, errMsgs, sc);
 | |
|             success.then((value) {
 | |
|               if (value) {
 | |
|                 showToast(
 | |
|                     translate('Import server configuration successfully'));
 | |
|               } else {
 | |
|                 showToast(translate('Invalid server configuration'));
 | |
|               }
 | |
|             });
 | |
|           } else {
 | |
|             showToast(translate('Invalid server configuration'));
 | |
|           }
 | |
|         } catch (e) {
 | |
|           showToast(translate('Invalid server configuration'));
 | |
|         }
 | |
|       } else {
 | |
|         showToast(translate('Clipboard is empty'));
 | |
|       }
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   export() {
 | |
|     final text = ServerConfig(
 | |
|             idServer: controllers[0].text.trim(),
 | |
|             relayServer: controllers[1].text.trim(),
 | |
|             apiServer: controllers[2].text.trim(),
 | |
|             key: controllers[3].text.trim())
 | |
|         .encode();
 | |
|     debugPrint("ServerConfig export: $text");
 | |
|     Clipboard.setData(ClipboardData(text: text));
 | |
|     showToast(translate('Export server configuration successfully'));
 | |
|   }
 | |
| 
 | |
|   return [
 | |
|     Tooltip(
 | |
|       message: translate('Import Server Config'),
 | |
|       child: IconButton(
 | |
|           icon: Icon(Icons.paste, color: Colors.grey), onPressed: import),
 | |
|     ),
 | |
|     Tooltip(
 | |
|         message: translate('Export Server Config'),
 | |
|         child: IconButton(
 | |
|             icon: Icon(Icons.copy, color: Colors.grey), onPressed: export))
 | |
|   ];
 | |
| }
 |