From ba1b5e1ce597fb2e2d74acc2279f0cbe261bbd08 Mon Sep 17 00:00:00 2001 From: SWangHash <88996709+SWangHash@users.noreply.github.com> Date: Fri, 5 Jun 2026 10:42:49 +0800 Subject: [PATCH 1/6] feat(ohos): refactor app update logic with strategy pattern - Add UpdateStrategy interface with DialogUpdateStrategy (dialog + store redirect) and SilentUpdateStrategy (silent download + restart prompt, TODO) - Add AppUpdater to delegate update check to current strategy - Simplify EntryAbility to a single appUpdater.check() call --- .../main/ets/entryability/EntryAbility.ets | 3 ++ .../entry/src/main/ets/utils/AppUpdater.ets | 36 +++++++++++++++++++ .../src/main/ets/utils/UpdateStrategy.ets | 27 ++++++++++++++ 3 files changed, 66 insertions(+) create mode 100644 src/apps/ohos/entry/src/main/ets/utils/AppUpdater.ets create mode 100644 src/apps/ohos/entry/src/main/ets/utils/UpdateStrategy.ets diff --git a/src/apps/ohos/entry/src/main/ets/entryability/EntryAbility.ets b/src/apps/ohos/entry/src/main/ets/entryability/EntryAbility.ets index 70c7e8b733..28a7945ac4 100644 --- a/src/apps/ohos/entry/src/main/ets/entryability/EntryAbility.ets +++ b/src/apps/ohos/entry/src/main/ets/entryability/EntryAbility.ets @@ -16,6 +16,7 @@ import { uniformTypeDescriptor } from '@kit.ArkData'; import { calendarManager } from '@kit.CalendarKit'; import { BusinessError } from '@kit.BasicServicesKit'; import RustModule from 'libbitfun_desktop_lib.so'; +import { AppUpdater } from '../utils/AppUpdater'; import { CommonUtils } from '../utils/CommonUtils'; import { runDeveco } from '../utils/DevecoStart'; @@ -27,6 +28,7 @@ export default class EntryAbility extends RustAbility { public defaultPage: boolean = true; public commonEventListener: CommonEventListener | undefined = undefined; public remoteUrl: string = ""; + private appUpdater: AppUpdater = new AppUpdater(); public shareStatus: boolean = false; async onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): Promise { @@ -191,6 +193,7 @@ export default class EntryAbility extends RustAbility { } return ''; }); + this.appUpdater.check(this.context); return super.onWindowStageCreate(windowStage); } diff --git a/src/apps/ohos/entry/src/main/ets/utils/AppUpdater.ets b/src/apps/ohos/entry/src/main/ets/utils/AppUpdater.ets new file mode 100644 index 0000000000..4dbcacfa67 --- /dev/null +++ b/src/apps/ohos/entry/src/main/ets/utils/AppUpdater.ets @@ -0,0 +1,36 @@ +import { common } from '@kit.AbilityKit'; +import { BusinessError } from '@kit.BasicServicesKit'; +import { updateManager } from '@kit.AppGalleryKit'; +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { UpdateStrategy, DialogUpdateStrategy } from './UpdateStrategy'; + +export class AppUpdater { + private strategy: UpdateStrategy; + + constructor(strategy?: UpdateStrategy) { + this.strategy = strategy ?? new DialogUpdateStrategy(); + } + + setStrategy(strategy: UpdateStrategy): void { + this.strategy = strategy; + } + + check(context: common.UIAbilityContext): void { + try { + updateManager.checkAppUpdate(context) + .then((checkResult: updateManager.CheckUpdateResult) => { + if (checkResult.updateAvailable === updateManager.UpdateAvailableCode.LATER_VERSION_EXIST) { + hilog.info(0x0000, 'vnext', 'New version available'); + this.strategy.onUpdateAvailable(context); + } else { + hilog.info(0x0000, 'vnext', 'No update available'); + } + }) + .catch((error: BusinessError) => { + hilog.error(0x0000, 'vnext', 'checkAppUpdate error: ' + JSON.stringify(error)); + }); + } catch (error) { + hilog.error(0x0000, 'vnext', 'checkAppUpdate exception: ' + JSON.stringify(error)); + } + } +} diff --git a/src/apps/ohos/entry/src/main/ets/utils/UpdateStrategy.ets b/src/apps/ohos/entry/src/main/ets/utils/UpdateStrategy.ets new file mode 100644 index 0000000000..68c6f6634e --- /dev/null +++ b/src/apps/ohos/entry/src/main/ets/utils/UpdateStrategy.ets @@ -0,0 +1,27 @@ +import { common } from '@kit.AbilityKit'; +import { BusinessError } from '@kit.BasicServicesKit'; +import { updateManager } from '@kit.AppGalleryKit'; +import { hilog } from '@kit.PerformanceAnalysisKit'; + +export interface UpdateStrategy { + onUpdateAvailable(context: common.UIAbilityContext): void; +} + +export class DialogUpdateStrategy implements UpdateStrategy { + onUpdateAvailable(context: common.UIAbilityContext): void { + updateManager.showUpdateDialog(context) + .then((resultCode: updateManager.ShowUpdateResultCode) => { + hilog.info(0x0000, 'vnext', 'showUpdateDialog result: ' + resultCode); + }) + .catch((error: BusinessError) => { + hilog.error(0x0000, 'vnext', 'showUpdateDialog error: ' + JSON.stringify(error)); + }); + } +} + +export class SilentUpdateStrategy implements UpdateStrategy { + onUpdateAvailable(context: common.UIAbilityContext): void { + hilog.info(0x0000, 'vnext', 'SilentUpdateStrategy: download in background'); + // TODO: implement silent download + restart prompt + } +} From 39182eb9754bcee08d81635a062cec5af886e6d0 Mon Sep 17 00:00:00 2001 From: SWangHash <88996709+SWangHash@users.noreply.github.com> Date: Fri, 5 Jun 2026 11:41:20 +0800 Subject: [PATCH 2/6] feat(ohos): wire AppGallery update check into AboutDialog via Rust-ArkTS bridge - Register check_app_update_ohos ArkTS function using @kit.AppGalleryKit - Add Rust ohos/update.rs bridge following open_browser pattern - Add check_for_updates_ohos Tauri command (double-gated by target_env) - Add checkForUpdatesOhos() in SystemAPI.ts (try OHOS first, fallback to Tauri) - Fix isTauriRuntime import to use @/infrastructure/runtime for correct OHOS detection - Remove unused AboutPage.ets --- src/apps/desktop/src/api/ohos/mod.rs | 3 +- src/apps/desktop/src/api/ohos/update.rs | 23 ++++++++++++ src/apps/desktop/src/api/system_api.rs | 13 +++++++ src/apps/desktop/src/lib.rs | 1 + .../main/ets/entryability/EntryAbility.ets | 14 ++++++++ .../components/AboutDialog/AboutDialog.tsx | 35 +++++++++++++------ .../api/service-api/SystemAPI.ts | 10 ++++++ 7 files changed, 87 insertions(+), 12 deletions(-) create mode 100644 src/apps/desktop/src/api/ohos/update.rs diff --git a/src/apps/desktop/src/api/ohos/mod.rs b/src/apps/desktop/src/api/ohos/mod.rs index 6eedc89d3c..bf91ed2105 100644 --- a/src/apps/desktop/src/api/ohos/mod.rs +++ b/src/apps/desktop/src/api/ohos/mod.rs @@ -1,3 +1,4 @@ pub mod ohos_file_system; pub mod window; -pub mod browser; \ No newline at end of file +pub mod browser; +pub mod update; \ No newline at end of file diff --git a/src/apps/desktop/src/api/ohos/update.rs b/src/apps/desktop/src/api/ohos/update.rs new file mode 100644 index 0000000000..595ed42a32 --- /dev/null +++ b/src/apps/desktop/src/api/ohos/update.rs @@ -0,0 +1,23 @@ +use bitfun_core::util::JS_THREADSAFE_FUNCTION; +use log::info; + +pub async fn check_app_update_ohos() -> Result { + let function = { + let lock = JS_THREADSAFE_FUNCTION.read(); + lock.get("check_app_update_ohos").cloned() + }; + let Some(function) = function else { + return Err("The Arkts has not register the function".to_owned()); + }; + let res = function.call_async(Ok(String::new())).await; + match res { + Ok(res) => match res.await { + Ok(result) => { + info!("check_app_update_ohos successfully"); + Ok(result) + } + Err(err) => Err(err.to_string()), + }, + Err(err) => Err(err.to_string()), + } +} diff --git a/src/apps/desktop/src/api/system_api.rs b/src/apps/desktop/src/api/system_api.rs index ff06e66821..201f113d23 100644 --- a/src/apps/desktop/src/api/system_api.rs +++ b/src/apps/desktop/src/api/system_api.rs @@ -340,6 +340,19 @@ pub async fn open_external_ohos(url: String) -> Result<(), String> { Err("open_external is only supported on ohos".to_string()) } } +#[tauri::command] +pub async fn check_for_updates_ohos() -> Result { + #[cfg(target_env = "ohos")] + { + use crate::api::ohos::update::check_app_update_ohos; + check_app_update_ohos().await + } + #[cfg(not(target_env = "ohos"))] + { + Err("check_for_updates_ohos is only supported on ohos".to_string()) + } +} + #[tauri::command] pub async fn set_macos_edit_menu_mode( state: State<'_, AppState>, diff --git a/src/apps/desktop/src/lib.rs b/src/apps/desktop/src/lib.rs index 1c17ad0470..955dd52db0 100644 --- a/src/apps/desktop/src/lib.rs +++ b/src/apps/desktop/src/lib.rs @@ -1178,6 +1178,7 @@ pub async fn _run() { install_update, api::system_api::open_html_file_in_browser, restart_app, + check_for_updates_ohos, send_system_notification, api::system_api::quit_app, api::system_api::minimize_to_tray, diff --git a/src/apps/ohos/entry/src/main/ets/entryability/EntryAbility.ets b/src/apps/ohos/entry/src/main/ets/entryability/EntryAbility.ets index 28a7945ac4..f4f819c9f3 100644 --- a/src/apps/ohos/entry/src/main/ets/entryability/EntryAbility.ets +++ b/src/apps/ohos/entry/src/main/ets/entryability/EntryAbility.ets @@ -15,6 +15,7 @@ import { fileIo } from '@kit.CoreFileKit'; import { uniformTypeDescriptor } from '@kit.ArkData'; import { calendarManager } from '@kit.CalendarKit'; import { BusinessError } from '@kit.BasicServicesKit'; +import { updateManager } from '@kit.AppGalleryKit'; import RustModule from 'libbitfun_desktop_lib.so'; import { AppUpdater } from '../utils/AppUpdater'; import { CommonUtils } from '../utils/CommonUtils'; @@ -80,6 +81,19 @@ export default class EntryAbility extends RustAbility { return 'false'; } }); + RustModule.registerArktsFunction('check_app_update_ohos', async (err: Error, _arg: string): Promise => { + try { + const checkResult = await updateManager.checkAppUpdate(this.context); + if (checkResult.updateAvailable === updateManager.UpdateAvailableCode.LATER_VERSION_EXIST) { + await updateManager.showUpdateDialog(this.context); + return '{"updateAvailable":true}'; + } + return '{"updateAvailable":false}'; + } catch (error) { + hilog.error(0x0000, 'vnext', 'check_app_update_ohos error: ' + JSON.stringify(error)); + return '{"updateAvailable":false,"error":"' + (error.message || 'unknown error') + '"}'; + } + }); RustModule.registerArktsFunction('open_browser', async (err: Error, url: string): Promise => { try { let want : Want = { diff --git a/src/web-ui/src/app/components/AboutDialog/AboutDialog.tsx b/src/web-ui/src/app/components/AboutDialog/AboutDialog.tsx index ff98d90bd4..d304bb6e84 100644 --- a/src/web-ui/src/app/components/AboutDialog/AboutDialog.tsx +++ b/src/web-ui/src/app/components/AboutDialog/AboutDialog.tsx @@ -16,7 +16,7 @@ import { import { createLogger } from '@/shared/utils/logger'; import { systemAPI } from '@/infrastructure/api'; import type { CheckForUpdatesResponse } from '@/infrastructure/api/service-api/SystemAPI'; -import { isTauriRuntime } from '@/infrastructure/update/tauriEnv'; +import { isTauriRuntime } from '@/infrastructure/runtime'; import { UpdateAvailableDialog } from '@/infrastructure/update/UpdateAvailableDialog'; import { useUpdateInstallStore } from '@/infrastructure/update/updateInstallStore'; import { formatUpdateInstallError } from '@/infrastructure/update/updateErrorMessage'; @@ -140,18 +140,31 @@ export const AboutDialog: React.FC = ({ setManualCheckErrorMessage(null); setManualCheckBusy(true); try { - const res = await systemAPI.checkForUpdates(); - if (!res.updateAvailable) { + // Try OHOS native update path first + const ohosRes = await systemAPI.checkForUpdatesOhos(); + if (ohosRes.error) { + setManualCheckErrorMessage(String(ohosRes.error)); + setManualCheckStatus('error'); + } else if (!ohosRes.updateAvailable) { setManualCheckStatus('latest'); - } else { - setManualData(res); - setManualOpen(true); } - } catch (e) { - log.error('check_for_updates failed', e); - const msg = e instanceof Error ? e.message : String(e); - setManualCheckErrorMessage(formatUpdateInstallError(msg, t)); - setManualCheckStatus('error'); + // If updateAvailable is true, the native dialog was already shown + } catch { + // Not OHOS runtime, fall back to Tauri updater + try { + const res = await systemAPI.checkForUpdates(); + if (!res.updateAvailable) { + setManualCheckStatus('latest'); + } else { + setManualData(res); + setManualOpen(true); + } + } catch (e) { + log.error('check_for_updates failed', e); + const msg = e instanceof Error ? e.message : String(e); + setManualCheckErrorMessage(formatUpdateInstallError(msg, t)); + setManualCheckStatus('error'); + } } finally { setManualCheckBusy(false); } diff --git a/src/web-ui/src/infrastructure/api/service-api/SystemAPI.ts b/src/web-ui/src/infrastructure/api/service-api/SystemAPI.ts index 0b402d961a..5b4542e9bf 100644 --- a/src/web-ui/src/infrastructure/api/service-api/SystemAPI.ts +++ b/src/web-ui/src/infrastructure/api/service-api/SystemAPI.ts @@ -60,6 +60,16 @@ export class SystemAPI { } } + /** OHOS only: check for update via ArkTS AppGallery Kit, shows system dialog if available. */ + async checkForUpdatesOhos(): Promise<{ updateAvailable: boolean; error?: string }> { + try { + const raw: string = await api.invoke('check_for_updates_ohos', {}); + return JSON.parse(raw) as { updateAvailable: boolean; error?: string }; + } catch (error) { + throw createTauriCommandError('check_for_updates_ohos', error); + } + } + /** Desktop only: download and install update after user confirms (calls updater again). */ async installUpdate(): Promise { try { From fa9c65280451e5e9462722f99c02e7d737518a5a Mon Sep 17 00:00:00 2001 From: hanlinyu1030 Date: Wed, 15 Jul 2026 11:14:59 +0800 Subject: [PATCH 3/6] fix(web-ui): restore AboutDialog footer link styles in isolated stylesheet - Add AboutDialogLinks.scss with .bitfun-about-dialog__links/__link/__link-sep rules lost during the 861deb97 merge - Import new stylesheet from AboutDialog.tsx so footer buttons render as link text instead of default button chrome - Isolate footer link styles from AboutDialog.scss to prevent future merges from silently dropping them --- .../components/AboutDialog/AboutDialog.tsx | 1 + .../AboutDialog/AboutDialogLinks.scss | 40 +++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 src/web-ui/src/app/components/AboutDialog/AboutDialogLinks.scss diff --git a/src/web-ui/src/app/components/AboutDialog/AboutDialog.tsx b/src/web-ui/src/app/components/AboutDialog/AboutDialog.tsx index d304bb6e84..007b9711e6 100644 --- a/src/web-ui/src/app/components/AboutDialog/AboutDialog.tsx +++ b/src/web-ui/src/app/components/AboutDialog/AboutDialog.tsx @@ -21,6 +21,7 @@ import { UpdateAvailableDialog } from '@/infrastructure/update/UpdateAvailableDi import { useUpdateInstallStore } from '@/infrastructure/update/updateInstallStore'; import { formatUpdateInstallError } from '@/infrastructure/update/updateErrorMessage'; import './AboutDialog.scss'; +import './AboutDialogLinks.scss'; const log = createLogger('AboutDialog'); diff --git a/src/web-ui/src/app/components/AboutDialog/AboutDialogLinks.scss b/src/web-ui/src/app/components/AboutDialog/AboutDialogLinks.scss new file mode 100644 index 0000000000..e7a7d47eec --- /dev/null +++ b/src/web-ui/src/app/components/AboutDialog/AboutDialogLinks.scss @@ -0,0 +1,40 @@ +/** + * About dialog footer link styles. + * Isolated into its own stylesheet so merges that re-resolve AboutDialog.scss + * do not silently drop the footer link styling (which previously caused the + * open-source / user-agreement buttons to fall back to default button chrome). + */ + +@use '../../../component-library/styles/tokens' as *; + +// ==================== Footer links ==================== + +.bitfun-about-dialog__links { + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + margin-bottom: 10px; +} + +.bitfun-about-dialog__link { + font-size: 11px; + color: var(--color-text-muted); + text-decoration: none; + cursor: pointer; + transition: color 0.2s ease; + background: none; + border: none; + padding: 0; + font-family: inherit; + + &:hover { + color: var(--color-accent-400); + text-decoration: underline; + } +} + +.bitfun-about-dialog__link-sep { + font-size: 11px; + color: var(--border-subtle); +} From 389046c9fa9f749a529abf806bab5b67617651a1 Mon Sep 17 00:00:00 2001 From: hanlinyu1030 Date: Wed, 15 Jul 2026 14:27:48 +0800 Subject: [PATCH 4/6] feat(ohos): gate AppGallery auto-update check on app.auto_update config - Add sync #[napi] get_app_config_bool(path) -> bool in system_api.rs, driven via a cached current-thread tokio runtime over get_global_config_service + ConfigService::get_config::; defaults to false on missing/unavailable (napi-derive-ohos 1.1 has no async-fn support) - Declare getAppConfigBool(path): boolean in libbitfun_desktop_lib Index.d.ts - Call RustModule.getAppConfigBool('app.auto_update') before appUpdater.check in EntryAbility.onWindowStageCreate so the startup update check respects the BasicsConfig toggle --- src/apps/desktop/src/api/system_api.rs | 19 +++++++++++++++++++ .../types/libbitfun_desktop_lib/Index.d.ts | 3 ++- .../main/ets/entryability/EntryAbility.ets | 6 +++++- 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/apps/desktop/src/api/system_api.rs b/src/apps/desktop/src/api/system_api.rs index 201f113d23..43e15fcb86 100644 --- a/src/apps/desktop/src/api/system_api.rs +++ b/src/apps/desktop/src/api/system_api.rs @@ -504,6 +504,25 @@ pub fn ohos_mark_clean_shutdown() { crate::perform_process_exit_cleanup(); } +#[cfg(target_env = "ohos")] +#[napi] +pub fn get_app_config_bool(path: String) -> bool { + use std::sync::OnceLock; + static RUNTIME: OnceLock = OnceLock::new(); + let runtime = RUNTIME.get_or_init(|| { + tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .expect("failed to build napi config runtime") + }); + runtime.block_on(async { + let Ok(service) = bitfun_core::service::config::get_global_config_service().await else { + return false; + }; + service.get_config::(Some(&path)).await.unwrap_or(false) + }) +} + /// Hide the main window so it lives only in the system tray (used by the "ask" /// dialog when the user chooses to minimize instead of quitting). #[tauri::command] diff --git a/src/apps/ohos/entry/src/main/cpp/types/libbitfun_desktop_lib/Index.d.ts b/src/apps/ohos/entry/src/main/cpp/types/libbitfun_desktop_lib/Index.d.ts index e287faceaa..2a868b5cd4 100644 --- a/src/apps/ohos/entry/src/main/cpp/types/libbitfun_desktop_lib/Index.d.ts +++ b/src/apps/ohos/entry/src/main/cpp/types/libbitfun_desktop_lib/Index.d.ts @@ -1,3 +1,4 @@ export declare function registerArktsFunction(funcName: string, callback: ((err: Error | null, arg: string) => Promise)): void; export declare function setBuildResult(msg: string): void; -export declare function ohosMarkCleanShutdown(): void; \ No newline at end of file +export declare function ohosMarkCleanShutdown(): void; +export declare function getAppConfigBool(path: string): boolean; \ No newline at end of file diff --git a/src/apps/ohos/entry/src/main/ets/entryability/EntryAbility.ets b/src/apps/ohos/entry/src/main/ets/entryability/EntryAbility.ets index f4f819c9f3..e78138a4b5 100644 --- a/src/apps/ohos/entry/src/main/ets/entryability/EntryAbility.ets +++ b/src/apps/ohos/entry/src/main/ets/entryability/EntryAbility.ets @@ -207,7 +207,11 @@ export default class EntryAbility extends RustAbility { } return ''; }); - this.appUpdater.check(this.context); + const autoUpdateEnabled = RustModule.getAppConfigBool('app.auto_update'); + hilog.info(0x0000, 'vnext', 'autoUpdateEnabled: ' + autoUpdateEnabled); + if (autoUpdateEnabled) { + this.appUpdater.check(this.context); + } return super.onWindowStageCreate(windowStage); } From 6f2e10935482932c5d219d47d32915322f51a879 Mon Sep 17 00:00:00 2001 From: hanlinyu1030 Date: Wed, 15 Jul 2026 17:50:35 +0800 Subject: [PATCH 5/6] fix(web-ui): restore AboutDialog open-source sub-page styles in isolated stylesheet - Add AboutDialogOpenSource.scss with sub-content/sub-desc/sub-category*/sub-footnote layout (lost in the 861deb97 merge) and the redesigned dependencies-grid/dependency-* rules (gradient cards, hover lift, dependency-name button reset) so the open-source dialog no longer falls back to default chrome - Import new stylesheet from AboutDialog.tsx after the main scss so its rules override the stale legacy dependency-* definitions via source order - Isolate open-source sub-page styles from AboutDialog.scss to prevent future merges from silently dropping them, mirroring the AboutDialogLinks.scss approach --- .../components/AboutDialog/AboutDialog.tsx | 1 + .../AboutDialog/AboutDialogOpenSource.scss | 219 ++++++++++++++++++ 2 files changed, 220 insertions(+) create mode 100644 src/web-ui/src/app/components/AboutDialog/AboutDialogOpenSource.scss diff --git a/src/web-ui/src/app/components/AboutDialog/AboutDialog.tsx b/src/web-ui/src/app/components/AboutDialog/AboutDialog.tsx index 007b9711e6..c9dd74a51e 100644 --- a/src/web-ui/src/app/components/AboutDialog/AboutDialog.tsx +++ b/src/web-ui/src/app/components/AboutDialog/AboutDialog.tsx @@ -22,6 +22,7 @@ import { useUpdateInstallStore } from '@/infrastructure/update/updateInstallStor import { formatUpdateInstallError } from '@/infrastructure/update/updateErrorMessage'; import './AboutDialog.scss'; import './AboutDialogLinks.scss'; +import './AboutDialogOpenSource.scss'; const log = createLogger('AboutDialog'); diff --git a/src/web-ui/src/app/components/AboutDialog/AboutDialogOpenSource.scss b/src/web-ui/src/app/components/AboutDialog/AboutDialogOpenSource.scss new file mode 100644 index 0000000000..f4e5520cd6 --- /dev/null +++ b/src/web-ui/src/app/components/AboutDialog/AboutDialogOpenSource.scss @@ -0,0 +1,219 @@ +/** + * About dialog open-source sub-page styles. + * Isolated into its own stylesheet so merges that re-resolve AboutDialog.scss + * do not silently drop the sub-page styling. The 861deb97 merge previously + * discarded the 63065594 redesign of this sub-page (sub-* layout classes and + * the redesigned dependency-* cards), causing the open-source dialog to fall + * back to default chrome (unpadded content, default h3 headings, bare count + * numbers, and dependency-name - - - + {SHOW_BROWSER_ENTRY && ( + + + + )}