feat: Android, opt, check update on startup (#10165)
* feat: Android, opt, check update on startup Signed-off-by: fufesou <linlong1266@gmail.com> * refact: check update only on startup Signed-off-by: fufesou <linlong1266@gmail.com> * fix: Android, "Download new version" Signed-off-by: fufesou <linlong1266@gmail.com> --------- Signed-off-by: fufesou <linlong1266@gmail.com>
This commit is contained in:
parent
3d17bf4990
commit
a23822074e
@ -15,6 +15,13 @@
|
|||||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
|
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
|
||||||
|
|
||||||
|
<queries>
|
||||||
|
<intent>
|
||||||
|
<!-- https://developer.android.com/training/package-visibility/use-cases#open-urls-custom-tabs -->
|
||||||
|
<action android:name="android.support.customtabs.action.CustomTabsService" />
|
||||||
|
</intent>
|
||||||
|
</queries>
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="RustDesk"
|
android:label="RustDesk"
|
||||||
|
@ -3633,3 +3633,20 @@ List<SubWindowResizeEdge>? get subWindowManagerEnableResizeEdges => isWindows
|
|||||||
void earlyAssert() {
|
void earlyAssert() {
|
||||||
assert('\1' == '1');
|
assert('\1' == '1');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void checkUpdate() {
|
||||||
|
if (isDesktop || isAndroid) {
|
||||||
|
if (!bind.isCustomClient()) {
|
||||||
|
platformFFI.registerEventHandler(
|
||||||
|
kCheckSoftwareUpdateFinish, kCheckSoftwareUpdateFinish,
|
||||||
|
(Map<String, dynamic> evt) async {
|
||||||
|
if (evt['url'] is String) {
|
||||||
|
stateGlobal.updateUrl.value = evt['url'];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Timer(const Duration(seconds: 1), () async {
|
||||||
|
bind.mainGetSoftwareUpdateUrl();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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/desktop/pages/desktop_tab_page.dart';
|
||||||
import 'package:flutter_hbb/models/platform_model.dart';
|
import 'package:flutter_hbb/models/platform_model.dart';
|
||||||
import 'package:flutter_hbb/models/server_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/plugin/ui_manager.dart';
|
||||||
import 'package:flutter_hbb/utils/multi_window_manager.dart';
|
import 'package:flutter_hbb/utils/multi_window_manager.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
@ -39,7 +40,6 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool get wantKeepAlive => true;
|
bool get wantKeepAlive => true;
|
||||||
var updateUrl = '';
|
|
||||||
var systemError = '';
|
var systemError = '';
|
||||||
StreamSubscription? _uniLinksSubscription;
|
StreamSubscription? _uniLinksSubscription;
|
||||||
var svcStopped = false.obs;
|
var svcStopped = false.obs;
|
||||||
@ -86,7 +86,8 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
|||||||
if (!isOutgoingOnly) buildIDBoard(context),
|
if (!isOutgoingOnly) buildIDBoard(context),
|
||||||
if (!isOutgoingOnly) buildPasswordBoard(context),
|
if (!isOutgoingOnly) buildPasswordBoard(context),
|
||||||
FutureBuilder<Widget>(
|
FutureBuilder<Widget>(
|
||||||
future: buildHelpCards(),
|
future: Future.value(
|
||||||
|
Obx(() => buildHelpCards(stateGlobal.updateUrl.value))),
|
||||||
builder: (_, data) {
|
builder: (_, data) {
|
||||||
if (data.hasData) {
|
if (data.hasData) {
|
||||||
if (isIncomingOnly) {
|
if (isIncomingOnly) {
|
||||||
@ -415,7 +416,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Widget> buildHelpCards() async {
|
Widget buildHelpCards(String updateUrl) {
|
||||||
if (!bind.isCustomClient() &&
|
if (!bind.isCustomClient() &&
|
||||||
updateUrl.isNotEmpty &&
|
updateUrl.isNotEmpty &&
|
||||||
!isCardClosed &&
|
!isCardClosed &&
|
||||||
@ -669,20 +670,6 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
if (!bind.isCustomClient()) {
|
|
||||||
platformFFI.registerEventHandler(
|
|
||||||
kCheckSoftwareUpdateFinish, kCheckSoftwareUpdateFinish,
|
|
||||||
(Map<String, dynamic> 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 {
|
_updateTimer = periodic_immediate(const Duration(seconds: 1), () async {
|
||||||
await gFFI.serverModel.fetchID();
|
await gFFI.serverModel.fetchID();
|
||||||
final error = await bind.mainGetError();
|
final error = await bind.mainGetError();
|
||||||
|
@ -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/mobile/widgets/dialog.dart';
|
||||||
import 'package:flutter_hbb/models/platform_model.dart';
|
import 'package:flutter_hbb/models/platform_model.dart';
|
||||||
import 'package:flutter_hbb/models/server_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/manager.dart';
|
||||||
import 'package:flutter_hbb/plugin/widgets/desktop_settings.dart';
|
import 'package:flutter_hbb/plugin/widgets/desktop_settings.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
@ -120,6 +120,7 @@ Future<void> initEnv(String appType) async {
|
|||||||
void runMainApp(bool startService) async {
|
void runMainApp(bool startService) async {
|
||||||
// register uni links
|
// register uni links
|
||||||
await initEnv(kAppTypeMain);
|
await initEnv(kAppTypeMain);
|
||||||
|
checkUpdate();
|
||||||
// trigger connection status updater
|
// trigger connection status updater
|
||||||
await bind.mainCheckConnectStatus();
|
await bind.mainCheckConnectStatus();
|
||||||
if (startService) {
|
if (startService) {
|
||||||
@ -156,6 +157,7 @@ void runMainApp(bool startService) async {
|
|||||||
|
|
||||||
void runMobileApp() async {
|
void runMobileApp() async {
|
||||||
await initEnv(kAppTypeMain);
|
await initEnv(kAppTypeMain);
|
||||||
|
checkUpdate();
|
||||||
if (isAndroid) androidChannelInit();
|
if (isAndroid) androidChannelInit();
|
||||||
if (isAndroid) platformFFI.syncAndroidServiceAppDirConfigPath();
|
if (isAndroid) platformFFI.syncAndroidServiceAppDirConfigPath();
|
||||||
draggablePositions.load();
|
draggablePositions.load();
|
||||||
|
@ -4,6 +4,7 @@ import 'package:auto_size_text_field/auto_size_text_field.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hbb/common/formatter/id_formatter.dart';
|
import 'package:flutter_hbb/common/formatter/id_formatter.dart';
|
||||||
import 'package:flutter_hbb/common/widgets/connection_page_title.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:get/get.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
@ -40,8 +41,6 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
|||||||
final _idController = IDTextEditingController();
|
final _idController = IDTextEditingController();
|
||||||
final RxBool _idEmpty = true.obs;
|
final RxBool _idEmpty = true.obs;
|
||||||
|
|
||||||
/// Update url. If it's not null, means an update is available.
|
|
||||||
var _updateUrl = '';
|
|
||||||
List<Peer> peers = [];
|
List<Peer> peers = [];
|
||||||
|
|
||||||
bool isPeersLoading = false;
|
bool isPeersLoading = false;
|
||||||
@ -72,22 +71,6 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (isAndroid) {
|
|
||||||
if (!bind.isCustomClient()) {
|
|
||||||
platformFFI.registerEventHandler(
|
|
||||||
kCheckSoftwareUpdateFinish, kCheckSoftwareUpdateFinish,
|
|
||||||
(Map<String, dynamic> evt) async {
|
|
||||||
if (evt['url'] is String) {
|
|
||||||
setState(() {
|
|
||||||
_updateUrl = evt['url'];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
Timer(const Duration(seconds: 1), () async {
|
|
||||||
bind.mainGetSoftwareUpdateUrl();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -97,7 +80,8 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
|||||||
slivers: [
|
slivers: [
|
||||||
SliverList(
|
SliverList(
|
||||||
delegate: SliverChildListDelegate([
|
delegate: SliverChildListDelegate([
|
||||||
if (!bind.isCustomClient()) _buildUpdateUI(),
|
if (!bind.isCustomClient())
|
||||||
|
Obx(() => _buildUpdateUI(stateGlobal.updateUrl.value)),
|
||||||
_buildRemoteIDTextField(),
|
_buildRemoteIDTextField(),
|
||||||
])),
|
])),
|
||||||
SliverFillRemaining(
|
SliverFillRemaining(
|
||||||
@ -116,13 +100,21 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// UI for software update.
|
/// UI for software update.
|
||||||
/// If [_updateUrl] is not empty, shows a button to update the software.
|
/// If _updateUrl] is not empty, shows a button to update the software.
|
||||||
Widget _buildUpdateUI() {
|
Widget _buildUpdateUI(String updateUrl) {
|
||||||
return _updateUrl.isEmpty
|
return updateUrl.isEmpty
|
||||||
? const SizedBox(height: 0)
|
? const SizedBox(height: 0)
|
||||||
: InkWell(
|
: InkWell(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
final url = 'https://rustdesk.com/download';
|
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. `<action android:name="android.support.customtabs.action.CustomTabsService" />` in AndroidManifest.xml
|
||||||
|
//
|
||||||
|
// But it is better to add the check.
|
||||||
if (await canLaunchUrl(Uri.parse(url))) {
|
if (await canLaunchUrl(Uri.parse(url))) {
|
||||||
await launchUrl(Uri.parse(url));
|
await launchUrl(Uri.parse(url));
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import 'dart:typed_data';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hbb/common/widgets/setting_widgets.dart';
|
import 'package:flutter_hbb/common/widgets/setting_widgets.dart';
|
||||||
import 'package:flutter_hbb/desktop/pages/desktop_setting_page.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:get/get.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:settings_ui/settings_ui.dart';
|
import 'package:settings_ui/settings_ui.dart';
|
||||||
@ -70,6 +71,7 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
|
|||||||
false; //androidVersion >= 26; // remove because not work on every device
|
false; //androidVersion >= 26; // remove because not work on every device
|
||||||
var _ignoreBatteryOpt = false;
|
var _ignoreBatteryOpt = false;
|
||||||
var _enableStartOnBoot = false;
|
var _enableStartOnBoot = false;
|
||||||
|
var _checkUpdateOnStartup = false;
|
||||||
var _floatingWindowDisabled = false;
|
var _floatingWindowDisabled = false;
|
||||||
var _keepScreenOn = KeepScreenOn.duringControlled; // relay on floating window
|
var _keepScreenOn = KeepScreenOn.duringControlled; // relay on floating window
|
||||||
var _enableAbr = false;
|
var _enableAbr = false;
|
||||||
@ -154,6 +156,13 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
|
|||||||
_enableStartOnBoot = enableStartOnBoot;
|
_enableStartOnBoot = enableStartOnBoot;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var checkUpdateOnStartup =
|
||||||
|
mainGetLocalBoolOptionSync(kOptionEnableCheckUpdate);
|
||||||
|
if (checkUpdateOnStartup != _checkUpdateOnStartup) {
|
||||||
|
update = true;
|
||||||
|
_checkUpdateOnStartup = checkUpdateOnStartup;
|
||||||
|
}
|
||||||
|
|
||||||
var floatingWindowDisabled =
|
var floatingWindowDisabled =
|
||||||
bind.mainGetLocalOption(key: kOptionDisableFloatingWindow) == "Y" ||
|
bind.mainGetLocalOption(key: kOptionDisableFloatingWindow) == "Y" ||
|
||||||
!await AndroidPermissionManager.check(kSystemAlertWindow);
|
!await AndroidPermissionManager.check(kSystemAlertWindow);
|
||||||
@ -552,6 +561,22 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
|
|||||||
gFFI.invokeMethod(AndroidChannel.kSetStartOnBootOpt, toValue);
|
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 {
|
onFloatingWindowChanged(bool toValue) async {
|
||||||
if (toValue) {
|
if (toValue) {
|
||||||
if (!await AndroidPermissionManager.check(kSystemAlertWindow)) {
|
if (!await AndroidPermissionManager.check(kSystemAlertWindow)) {
|
||||||
|
@ -25,6 +25,8 @@ class StateGlobal {
|
|||||||
|
|
||||||
final isPortrait = false.obs;
|
final isPortrait = false.obs;
|
||||||
|
|
||||||
|
final updateUrl = ''.obs;
|
||||||
|
|
||||||
String _inputSource = '';
|
String _inputSource = '';
|
||||||
|
|
||||||
// Use for desktop -> remote toolbar -> resolution
|
// Use for desktop -> remote toolbar -> resolution
|
||||||
|
@ -1418,7 +1418,8 @@ pub fn main_get_last_remote_id() -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn main_get_software_update_url() {
|
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();
|
crate::common::check_software_update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user