Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/apps/desktop/src/api/ohos/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod ohos_file_system;
pub mod window;
pub mod browser;
pub mod browser;
pub mod update;
23 changes: 23 additions & 0 deletions src/apps/desktop/src/api/ohos/update.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use bitfun_core::util::JS_THREADSAFE_FUNCTION;
use log::info;

pub async fn check_app_update_ohos() -> Result<String, String> {
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()),
}
}
32 changes: 32 additions & 0 deletions src/apps/desktop/src/api/system_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, String> {
#[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>,
Expand Down Expand Up @@ -491,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<tokio::runtime::Runtime> = 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::<bool>(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]
Expand Down
1 change: 1 addition & 0 deletions src/apps/desktop/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export declare function registerArktsFunction(funcName: string, callback: ((err: Error | null, arg: string) => Promise<string>)): void;
export declare function setBuildResult(msg: string): void;
export declare function ohosMarkCleanShutdown(): void;
export declare function ohosMarkCleanShutdown(): void;
export declare function getAppConfigBool(path: string): boolean;
21 changes: 21 additions & 0 deletions src/apps/ohos/entry/src/main/ets/entryability/EntryAbility.ets
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ 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';
import { runDeveco } from '../utils/DevecoStart';

Expand All @@ -27,6 +29,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<void> {
Expand Down Expand Up @@ -78,6 +81,19 @@ export default class EntryAbility extends RustAbility {
return 'false';
}
});
RustModule.registerArktsFunction('check_app_update_ohos', async (err: Error, _arg: string): Promise<string> => {
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<string> => {
try {
let want : Want = {
Expand Down Expand Up @@ -191,6 +207,11 @@ export default class EntryAbility extends RustAbility {
}
return '';
});
const autoUpdateEnabled = RustModule.getAppConfigBool('app.auto_update');
hilog.info(0x0000, 'vnext', 'autoUpdateEnabled: ' + autoUpdateEnabled);
if (autoUpdateEnabled) {
this.appUpdater.check(this.context);
}
return super.onWindowStageCreate(windowStage);
}

Expand Down
36 changes: 36 additions & 0 deletions src/apps/ohos/entry/src/main/ets/utils/AppUpdater.ets
Original file line number Diff line number Diff line change
@@ -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));
}
}
}
27 changes: 27 additions & 0 deletions src/apps/ohos/entry/src/main/ets/utils/UpdateStrategy.ets
Original file line number Diff line number Diff line change
@@ -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
}
}
37 changes: 26 additions & 11 deletions src/web-ui/src/app/components/AboutDialog/AboutDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@ 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';
import './AboutDialog.scss';
import './AboutDialogLinks.scss';
import './AboutDialogOpenSource.scss';

const log = createLogger('AboutDialog');

Expand Down Expand Up @@ -140,18 +142,31 @@ export const AboutDialog: React.FC<AboutDialogProps> = ({
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);
}
Expand Down
40 changes: 40 additions & 0 deletions src/web-ui/src/app/components/AboutDialog/AboutDialogLinks.scss
Original file line number Diff line number Diff line change
@@ -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);
}
Loading
Loading