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)) | ||
|  |   ]; | ||
|  | } |