install_page.dart 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. import 'dart:convert';
  2. import 'package:file_picker/file_picker.dart';
  3. import 'package:flutter/material.dart';
  4. import 'package:flutter_hbb/common.dart';
  5. import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart';
  6. import 'package:flutter_hbb/models/platform_model.dart';
  7. import 'package:flutter_hbb/models/state_model.dart';
  8. import 'package:get/get.dart';
  9. import 'package:path/path.dart';
  10. import 'package:url_launcher/url_launcher_string.dart';
  11. import 'package:window_manager/window_manager.dart';
  12. class InstallPage extends StatefulWidget {
  13. const InstallPage({Key? key}) : super(key: key);
  14. @override
  15. State<InstallPage> createState() => _InstallPageState();
  16. }
  17. class _InstallPageState extends State<InstallPage> {
  18. final tabController = DesktopTabController(tabType: DesktopTabType.main);
  19. _InstallPageState() {
  20. Get.put<DesktopTabController>(tabController);
  21. const label = "install";
  22. tabController.add(TabInfo(
  23. key: label,
  24. label: label,
  25. closable: false,
  26. page: _InstallPageBody(
  27. key: const ValueKey(label),
  28. )));
  29. }
  30. @override
  31. void dispose() {
  32. super.dispose();
  33. Get.delete<DesktopTabController>();
  34. }
  35. @override
  36. Widget build(BuildContext context) {
  37. return DragToResizeArea(
  38. resizeEdgeSize: stateGlobal.resizeEdgeSize.value,
  39. enableResizeEdges: windowManagerEnableResizeEdges,
  40. child: Container(
  41. child: Scaffold(
  42. backgroundColor: Theme.of(context).colorScheme.background,
  43. body: DesktopTab(controller: tabController)),
  44. ),
  45. );
  46. }
  47. }
  48. class _InstallPageBody extends StatefulWidget {
  49. const _InstallPageBody({Key? key}) : super(key: key);
  50. @override
  51. State<_InstallPageBody> createState() => _InstallPageBodyState();
  52. }
  53. class _InstallPageBodyState extends State<_InstallPageBody>
  54. with WindowListener {
  55. late final TextEditingController controller;
  56. final RxBool startmenu = true.obs;
  57. final RxBool desktopicon = true.obs;
  58. final RxBool showProgress = false.obs;
  59. final RxBool btnEnabled = true.obs;
  60. // todo move to theme.
  61. final buttonStyle = OutlinedButton.styleFrom(
  62. textStyle: TextStyle(fontSize: 14, fontWeight: FontWeight.normal),
  63. padding: EdgeInsets.symmetric(vertical: 15, horizontal: 12),
  64. );
  65. _InstallPageBodyState() {
  66. controller = TextEditingController(text: bind.installInstallPath());
  67. final installOptions = jsonDecode(bind.installInstallOptions());
  68. startmenu.value = installOptions['STARTMENUSHORTCUTS'] != '0';
  69. desktopicon.value = installOptions['DESKTOPSHORTCUTS'] != '0';
  70. }
  71. @override
  72. void initState() {
  73. windowManager.addListener(this);
  74. super.initState();
  75. }
  76. @override
  77. void dispose() {
  78. windowManager.removeListener(this);
  79. super.dispose();
  80. }
  81. @override
  82. void onWindowClose() {
  83. gFFI.close();
  84. super.onWindowClose();
  85. windowManager.setPreventClose(false);
  86. windowManager.close();
  87. }
  88. InkWell Option(RxBool option, {String label = ''}) {
  89. return InkWell(
  90. // todo mouseCursor: "SystemMouseCursors.forbidden" or no cursor on btnEnabled == false
  91. borderRadius: BorderRadius.circular(6),
  92. onTap: () => btnEnabled.value ? option.value = !option.value : null,
  93. child: Row(
  94. children: [
  95. Obx(
  96. () => Checkbox(
  97. visualDensity: VisualDensity(horizontal: -4, vertical: -4),
  98. value: option.value,
  99. onChanged: (v) =>
  100. btnEnabled.value ? option.value = !option.value : null,
  101. ).marginOnly(right: 8),
  102. ),
  103. Expanded(
  104. child: Text(translate(label)),
  105. ),
  106. ],
  107. ),
  108. );
  109. }
  110. @override
  111. Widget build(BuildContext context) {
  112. final double em = 13;
  113. final isDarkTheme = MyTheme.currentThemeMode() == ThemeMode.dark;
  114. return Scaffold(
  115. backgroundColor: null,
  116. body: SingleChildScrollView(
  117. child: Column(
  118. crossAxisAlignment: CrossAxisAlignment.start,
  119. children: [
  120. Text(translate('Installation'),
  121. style: Theme.of(context).textTheme.headlineMedium),
  122. Row(
  123. children: [
  124. Text('${translate('Installation Path')}:')
  125. .marginOnly(right: 10),
  126. Expanded(
  127. child: TextField(
  128. controller: controller,
  129. readOnly: true,
  130. decoration: InputDecoration(
  131. contentPadding: EdgeInsets.all(0.75 * em),
  132. ),
  133. ).workaroundFreezeLinuxMint().marginOnly(right: 10),
  134. ),
  135. Obx(
  136. () => OutlinedButton.icon(
  137. icon: Icon(Icons.folder_outlined, size: 16),
  138. onPressed: btnEnabled.value ? selectInstallPath : null,
  139. style: buttonStyle,
  140. label: Text(translate('Change Path')),
  141. ),
  142. )
  143. ],
  144. ).marginSymmetric(vertical: 2 * em),
  145. Option(startmenu, label: 'Create start menu shortcuts')
  146. .marginOnly(bottom: 7),
  147. Option(desktopicon, label: 'Create desktop icon'),
  148. Container(
  149. padding: EdgeInsets.all(12),
  150. decoration: BoxDecoration(
  151. color: isDarkTheme
  152. ? Color.fromARGB(135, 87, 87, 90)
  153. : Colors.grey[100],
  154. borderRadius: BorderRadius.circular(8),
  155. border: Border.all(color: Colors.grey),
  156. ),
  157. child: Row(
  158. children: [
  159. Icon(Icons.info_outline_rounded, size: 32)
  160. .marginOnly(right: 16),
  161. Column(
  162. crossAxisAlignment: CrossAxisAlignment.start,
  163. children: [
  164. Text(translate('agreement_tip'))
  165. .marginOnly(bottom: em),
  166. InkWell(
  167. hoverColor: Colors.transparent,
  168. onTap: () => launchUrlString(
  169. 'https://rustdesk.com/privacy.html'),
  170. child: Tooltip(
  171. message: 'https://rustdesk.com/privacy.html',
  172. child: Row(children: [
  173. Icon(Icons.launch_outlined, size: 16)
  174. .marginOnly(right: 5),
  175. Text(
  176. translate('End-user license agreement'),
  177. style: const TextStyle(
  178. decoration: TextDecoration.underline),
  179. )
  180. ]),
  181. ),
  182. ),
  183. ],
  184. )
  185. ],
  186. )).marginSymmetric(vertical: 2 * em),
  187. Row(
  188. children: [
  189. Expanded(
  190. // NOT use Offstage to wrap LinearProgressIndicator
  191. child: Obx(() => showProgress.value
  192. ? LinearProgressIndicator().marginOnly(right: 10)
  193. : Offstage()),
  194. ),
  195. Obx(
  196. () => OutlinedButton.icon(
  197. icon: Icon(Icons.close_rounded, size: 16),
  198. label: Text(translate('Cancel')),
  199. onPressed:
  200. btnEnabled.value ? () => windowManager.close() : null,
  201. style: buttonStyle,
  202. ).marginOnly(right: 10),
  203. ),
  204. Obx(
  205. () => ElevatedButton.icon(
  206. icon: Icon(Icons.done_rounded, size: 16),
  207. label: Text(translate('Accept and Install')),
  208. onPressed: btnEnabled.value ? install : null,
  209. style: buttonStyle,
  210. ),
  211. ),
  212. Offstage(
  213. offstage: bind.installShowRunWithoutInstall(),
  214. child: Obx(
  215. () => OutlinedButton.icon(
  216. icon: Icon(Icons.screen_share_outlined, size: 16),
  217. label: Text(translate('Run without install')),
  218. onPressed: btnEnabled.value
  219. ? () => bind.installRunWithoutInstall()
  220. : null,
  221. style: buttonStyle,
  222. ).marginOnly(left: 10),
  223. ),
  224. ),
  225. ],
  226. )
  227. ],
  228. ).paddingSymmetric(horizontal: 4 * em, vertical: 3 * em),
  229. ));
  230. }
  231. void install() {
  232. do_install() {
  233. btnEnabled.value = false;
  234. showProgress.value = true;
  235. String args = '';
  236. if (startmenu.value) args += ' startmenu';
  237. if (desktopicon.value) args += ' desktopicon';
  238. bind.installInstallMe(options: args, path: controller.text);
  239. }
  240. do_install();
  241. }
  242. void selectInstallPath() async {
  243. String? install_path = await FilePicker.platform
  244. .getDirectoryPath(initialDirectory: controller.text);
  245. if (install_path != null) {
  246. controller.text = join(install_path, await bind.mainGetAppName());
  247. }
  248. }
  249. }