diff --git a/flutter/android/app/src/main/AndroidManifest.xml b/flutter/android/app/src/main/AndroidManifest.xml index 51015f74a..47533612b 100644 --- a/flutter/android/app/src/main/AndroidManifest.xml +++ b/flutter/android/app/src/main/AndroidManifest.xml @@ -15,6 +15,13 @@ + + + + + + + ? get subWindowManagerEnableResizeEdges => isWindows void earlyAssert() { assert('\1' == '1'); } + +void checkUpdate() { + if (isDesktop || isAndroid) { + if (!bind.isCustomClient()) { + platformFFI.registerEventHandler( + kCheckSoftwareUpdateFinish, kCheckSoftwareUpdateFinish, + (Map evt) async { + if (evt['url'] is String) { + stateGlobal.updateUrl.value = evt['url']; + } + }); + Timer(const Duration(seconds: 1), () async { + bind.mainGetSoftwareUpdateUrl(); + }); + } + } +} diff --git a/flutter/lib/desktop/pages/desktop_home_page.dart b/flutter/lib/desktop/pages/desktop_home_page.dart index 04a186b84..754efbd5a 100644 --- a/flutter/lib/desktop/pages/desktop_home_page.dart +++ b/flutter/lib/desktop/pages/desktop_home_page.dart @@ -14,6 +14,7 @@ import 'package:flutter_hbb/desktop/pages/desktop_setting_page.dart'; import 'package:flutter_hbb/desktop/pages/desktop_tab_page.dart'; import 'package:flutter_hbb/models/platform_model.dart'; import 'package:flutter_hbb/models/server_model.dart'; +import 'package:flutter_hbb/models/state_model.dart'; import 'package:flutter_hbb/plugin/ui_manager.dart'; import 'package:flutter_hbb/utils/multi_window_manager.dart'; import 'package:get/get.dart'; @@ -39,7 +40,6 @@ class _DesktopHomePageState extends State @override bool get wantKeepAlive => true; - var updateUrl = ''; var systemError = ''; StreamSubscription? _uniLinksSubscription; var svcStopped = false.obs; @@ -86,7 +86,8 @@ class _DesktopHomePageState extends State if (!isOutgoingOnly) buildIDBoard(context), if (!isOutgoingOnly) buildPasswordBoard(context), FutureBuilder( - future: buildHelpCards(), + future: Future.value( + Obx(() => buildHelpCards(stateGlobal.updateUrl.value))), builder: (_, data) { if (data.hasData) { if (isIncomingOnly) { @@ -415,7 +416,7 @@ class _DesktopHomePageState extends State ); } - Future buildHelpCards() async { + Widget buildHelpCards(String updateUrl) { if (!bind.isCustomClient() && updateUrl.isNotEmpty && !isCardClosed && @@ -669,20 +670,6 @@ class _DesktopHomePageState extends State @override void initState() { super.initState(); - if (!bind.isCustomClient()) { - platformFFI.registerEventHandler( - kCheckSoftwareUpdateFinish, kCheckSoftwareUpdateFinish, - (Map evt) async { - if (evt['url'] is String) { - setState(() { - updateUrl = evt['url']; - }); - } - }); - Timer(const Duration(seconds: 1), () async { - bind.mainGetSoftwareUpdateUrl(); - }); - } _updateTimer = periodic_immediate(const Duration(seconds: 1), () async { await gFFI.serverModel.fetchID(); final error = await bind.mainGetError(); diff --git a/flutter/lib/desktop/pages/desktop_setting_page.dart b/flutter/lib/desktop/pages/desktop_setting_page.dart index 6f220a35f..44cdbcbb8 100644 --- a/flutter/lib/desktop/pages/desktop_setting_page.dart +++ b/flutter/lib/desktop/pages/desktop_setting_page.dart @@ -14,6 +14,7 @@ import 'package:flutter_hbb/desktop/pages/desktop_tab_page.dart'; import 'package:flutter_hbb/mobile/widgets/dialog.dart'; import 'package:flutter_hbb/models/platform_model.dart'; import 'package:flutter_hbb/models/server_model.dart'; +import 'package:flutter_hbb/models/state_model.dart'; import 'package:flutter_hbb/plugin/manager.dart'; import 'package:flutter_hbb/plugin/widgets/desktop_settings.dart'; import 'package:get/get.dart'; diff --git a/flutter/lib/main.dart b/flutter/lib/main.dart index 3176bfb86..301a9f25c 100644 --- a/flutter/lib/main.dart +++ b/flutter/lib/main.dart @@ -120,6 +120,7 @@ Future initEnv(String appType) async { void runMainApp(bool startService) async { // register uni links await initEnv(kAppTypeMain); + checkUpdate(); // trigger connection status updater await bind.mainCheckConnectStatus(); if (startService) { @@ -156,6 +157,7 @@ void runMainApp(bool startService) async { void runMobileApp() async { await initEnv(kAppTypeMain); + checkUpdate(); if (isAndroid) androidChannelInit(); if (isAndroid) platformFFI.syncAndroidServiceAppDirConfigPath(); draggablePositions.load(); diff --git a/flutter/lib/mobile/pages/connection_page.dart b/flutter/lib/mobile/pages/connection_page.dart index de68aa510..49e3b2c91 100644 --- a/flutter/lib/mobile/pages/connection_page.dart +++ b/flutter/lib/mobile/pages/connection_page.dart @@ -4,6 +4,7 @@ import 'package:auto_size_text_field/auto_size_text_field.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hbb/common/formatter/id_formatter.dart'; import 'package:flutter_hbb/common/widgets/connection_page_title.dart'; +import 'package:flutter_hbb/models/state_model.dart'; import 'package:get/get.dart'; import 'package:provider/provider.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -40,8 +41,6 @@ class _ConnectionPageState extends State { final _idController = IDTextEditingController(); final RxBool _idEmpty = true.obs; - /// Update url. If it's not null, means an update is available. - var _updateUrl = ''; List peers = []; bool isPeersLoading = false; @@ -72,22 +71,6 @@ class _ConnectionPageState extends State { } }); } - if (isAndroid) { - if (!bind.isCustomClient()) { - platformFFI.registerEventHandler( - kCheckSoftwareUpdateFinish, kCheckSoftwareUpdateFinish, - (Map evt) async { - if (evt['url'] is String) { - setState(() { - _updateUrl = evt['url']; - }); - } - }); - Timer(const Duration(seconds: 1), () async { - bind.mainGetSoftwareUpdateUrl(); - }); - } - } } @override @@ -97,7 +80,8 @@ class _ConnectionPageState extends State { slivers: [ SliverList( delegate: SliverChildListDelegate([ - if (!bind.isCustomClient()) _buildUpdateUI(), + if (!bind.isCustomClient()) + Obx(() => _buildUpdateUI(stateGlobal.updateUrl.value)), _buildRemoteIDTextField(), ])), SliverFillRemaining( @@ -116,13 +100,21 @@ class _ConnectionPageState extends State { } /// UI for software update. - /// If [_updateUrl] is not empty, shows a button to update the software. - Widget _buildUpdateUI() { - return _updateUrl.isEmpty + /// If _updateUrl] is not empty, shows a button to update the software. + Widget _buildUpdateUI(String updateUrl) { + return updateUrl.isEmpty ? const SizedBox(height: 0) : InkWell( onTap: () async { final url = 'https://rustdesk.com/download'; + // https://pub.dev/packages/url_launcher#configuration + // https://developer.android.com/training/package-visibility/use-cases#open-urls-custom-tabs + // + // `await launchUrl(Uri.parse(url))` can also run if skip + // 1. The following check + // 2. `` in AndroidManifest.xml + // + // But it is better to add the check. if (await canLaunchUrl(Uri.parse(url))) { await launchUrl(Uri.parse(url)); } diff --git a/flutter/lib/mobile/pages/settings_page.dart b/flutter/lib/mobile/pages/settings_page.dart index 9e265810f..ede66d78a 100644 --- a/flutter/lib/mobile/pages/settings_page.dart +++ b/flutter/lib/mobile/pages/settings_page.dart @@ -5,6 +5,7 @@ import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter_hbb/common/widgets/setting_widgets.dart'; import 'package:flutter_hbb/desktop/pages/desktop_setting_page.dart'; +import 'package:flutter_hbb/models/state_model.dart'; import 'package:get/get.dart'; import 'package:provider/provider.dart'; import 'package:settings_ui/settings_ui.dart'; @@ -70,6 +71,7 @@ class _SettingsState extends State with WidgetsBindingObserver { false; //androidVersion >= 26; // remove because not work on every device var _ignoreBatteryOpt = false; var _enableStartOnBoot = false; + var _checkUpdateOnStartup = false; var _floatingWindowDisabled = false; var _keepScreenOn = KeepScreenOn.duringControlled; // relay on floating window var _enableAbr = false; @@ -154,6 +156,13 @@ class _SettingsState extends State with WidgetsBindingObserver { _enableStartOnBoot = enableStartOnBoot; } + var checkUpdateOnStartup = + mainGetLocalBoolOptionSync(kOptionEnableCheckUpdate); + if (checkUpdateOnStartup != _checkUpdateOnStartup) { + update = true; + _checkUpdateOnStartup = checkUpdateOnStartup; + } + var floatingWindowDisabled = bind.mainGetLocalOption(key: kOptionDisableFloatingWindow) == "Y" || !await AndroidPermissionManager.check(kSystemAlertWindow); @@ -552,6 +561,22 @@ class _SettingsState extends State with WidgetsBindingObserver { gFFI.invokeMethod(AndroidChannel.kSetStartOnBootOpt, toValue); })); + if (!bind.isCustomClient()) { + enhancementsTiles.add( + SettingsTile.switchTile( + initialValue: _checkUpdateOnStartup, + title: + Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + Text(translate('Check for software update on startup')), + ]), + onToggle: (bool toValue) async { + await mainSetLocalBoolOption(kOptionEnableCheckUpdate, toValue); + setState(() => _checkUpdateOnStartup = toValue); + }, + ), + ); + } + onFloatingWindowChanged(bool toValue) async { if (toValue) { if (!await AndroidPermissionManager.check(kSystemAlertWindow)) { diff --git a/flutter/lib/models/state_model.dart b/flutter/lib/models/state_model.dart index f8f06cc3f..f09603649 100644 --- a/flutter/lib/models/state_model.dart +++ b/flutter/lib/models/state_model.dart @@ -25,6 +25,8 @@ class StateGlobal { final isPortrait = false.obs; + final updateUrl = ''.obs; + String _inputSource = ''; // Use for desktop -> remote toolbar -> resolution diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index 4630ac333..5b6c48b95 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -1418,7 +1418,8 @@ pub fn main_get_last_remote_id() -> String { } pub fn main_get_software_update_url() { - if get_local_option("enable-check-update".to_string()) != "N" { + let opt = get_local_option(config::keys::OPTION_ENABLE_CHECK_UPDATE.to_string()); + if config::option2bool(config::keys::OPTION_ENABLE_CHECK_UPDATE, &opt) { crate::common::check_software_update(); } }