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
40 changes: 30 additions & 10 deletions entry/src/main/ets/components/CustomKeyOverlay.ets
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ export struct CustomKeyOverlay {
@State private qrError: string = '';
@State private shareJson: string = '';
@State private qrDataLen: number = 0;
@State private canScanQrCode: boolean = true;
/** 扫码导入预览 */
@State private importPreviewName: string = '';
@State private importPreviewCount: number = 0;
Expand Down Expand Up @@ -237,6 +238,7 @@ export struct CustomKeyOverlay {
await CustomKeyStore.init();
this.keys = await CustomKeyStore.getKeys();
this.activeProfileName = await CustomKeyStore.getActiveProfileName();
this.canScanQrCode = QrShareService.isScanQrCodeSupported();

// 延迟刷新命中测试区域(与 VirtualControllerOverlay 同理)
setTimeout(() => {
Expand Down Expand Up @@ -2112,6 +2114,15 @@ export struct CustomKeyOverlay {

/** 调用系统相机扫码导入 */
private async scanAndImport(): Promise<void> {
if (!QrShareService.isScanQrCodeSupported()) {
ToastQueue.show({
message: QrShareService.getScanUnsupportedMessage() + ',请使用剪贴板导入',
duration: 2500
});
this.canScanQrCode = false;
return;
}

const result = await QrShareService.scanQrCode();
if (!result) {
ToastQueue.show({ message: '扫码取消或失败', duration: 1500 });
Expand Down Expand Up @@ -2453,12 +2464,14 @@ export struct CustomKeyOverlay {
Text('导入配置')
.fontSize(12).fontColor(OV_TEXT_MEDIUM)

Text('扫码导入')
.fontSize(13).fontColor(OV_TEXT_WHITE).fontWeight(FontWeight.Medium)
.width(130).height(36).textAlign(TextAlign.Center)
.borderRadius(18).backgroundColor('#6098C0')
.clickEffect({ level: ClickEffectLevel.LIGHT, scale: 0.9 })
.onClick(() => { this.scanAndImport(); })
if (this.canScanQrCode) {
Text('扫码导入')
.fontSize(13).fontColor(OV_TEXT_WHITE).fontWeight(FontWeight.Medium)
.width(130).height(36).textAlign(TextAlign.Center)
.borderRadius(18).backgroundColor('#6098C0')
.clickEffect({ level: ClickEffectLevel.LIGHT, scale: 0.9 })
.onClick(() => { this.scanAndImport(); })
}

Text('从剪贴板导入')
.fontSize(13).fontColor(OV_TEXT_WHITE).fontWeight(FontWeight.Medium)
Expand All @@ -2469,10 +2482,17 @@ export struct CustomKeyOverlay {

Divider().color('#20FFFFFF').width(100).margin({ top: 4, bottom: 4 })

Text('扫描其他设备上的')
.fontSize(10).fontColor(OV_TEXT_DIM)
Text('配置二维码即可导入')
.fontSize(10).fontColor(OV_TEXT_DIM)
if (this.canScanQrCode) {
Text('扫描其他设备上的')
.fontSize(10).fontColor(OV_TEXT_DIM)
Text('配置二维码即可导入')
.fontSize(10).fontColor(OV_TEXT_DIM)
} else {
Text('当前设备可使用')
.fontSize(10).fontColor(OV_TEXT_DIM)
Text('剪贴板导入配置')
.fontSize(10).fontColor(OV_TEXT_DIM)
}
}
}
.padding({ left: 16, right: 16, top: 8, bottom: 16 })
Expand Down
37 changes: 21 additions & 16 deletions entry/src/main/ets/components/PcListTitleBar.ets
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ export interface TitleBarConfig {
onlineCount: number;
/** 总设备数量 */
totalCount: number;
/** 是否显示扫码导入入口 */
showScanImport?: boolean;
/** 关于按钮点击回调 */
onAboutClick: () => void;
/** 扫码导入按钮点击回调 */
Expand All @@ -39,6 +41,7 @@ export struct PcListTitleBar {
@Prop config: TitleBarConfig = {
onlineCount: 0,
totalCount: 0,
showScanImport: true,
onAboutClick: () => {},
onScanImport: () => {},
onSettingsClick: () => {}
Expand Down Expand Up @@ -111,23 +114,25 @@ export struct PcListTitleBar {
this.config.onAboutClick();
})

// 扫码导入按钮
Button({ type: ButtonType.Circle }) {
Image($r('app.media.ic_scan'))
.width(AppSizes.IconMedium)
.height(AppSizes.IconMedium)
.fillColor(AppColors.Primary)
if (this.config.showScanImport !== false) {
// 扫码导入按钮
Button({ type: ButtonType.Circle }) {
Image($r('app.media.ic_scan'))
.width(AppSizes.IconMedium)
.height(AppSizes.IconMedium)
.fillColor(AppColors.Primary)
}
.width(44)
.height(44)
.backgroundColor(AppColors.Surface)
.borderWidth(1)
.borderColor(AppColors.CardBorder)
.flexShrink(0)
.margin({ right: AppSpacing.Small })
.onClick(() => {
this.config.onScanImport();
})
}
.width(44)
.height(44)
.backgroundColor(AppColors.Surface)
.borderWidth(1)
.borderColor(AppColors.CardBorder)
.flexShrink(0)
.margin({ right: AppSpacing.Small })
.onClick(() => {
this.config.onScanImport();
})

// 设置按钮
Button({ type: ButtonType.Circle }) {
Expand Down
12 changes: 12 additions & 0 deletions entry/src/main/ets/pages/PcListPageV2.ets
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ struct PcListPageV2 {
@State showAboutDialog: boolean = false;
@State isLandscape: boolean = false; // 横屏检测
@State screenWidth: number = 0; // 屏幕宽度
@State canScanQrCode: boolean = true; // 当前设备是否支持系统扫码
@StorageProp('topSafeHeight') topSafeHeight: number = 56; // 动态状态栏安全区高度

// LazyForEach 数据源
Expand Down Expand Up @@ -130,6 +131,7 @@ struct PcListPageV2 {
// 先设置 context 以便后续使用本地缓存
const context = getContext(this) as common.UIAbilityContext;
this.backgroundUtil.setContext(context);
this.canScanQrCode = QrShareService.isScanQrCodeSupported();

// 初始化业务逻辑处理类
this.actions = new PcListActions({
Expand Down Expand Up @@ -461,6 +463,7 @@ struct PcListPageV2 {
config: {
onlineCount: this.viewModel.onlineCount,
totalCount: this.viewModel.computerCount,
showScanImport: this.canScanQrCode,
onAboutClick: () => { this.showAboutDialog = true; },
onScanImport: () => { this.scanAndImportKeys(); },
onSettingsClick: () => { router.pushUrl({ url: 'pages/SettingsPageV2' }); }
Expand Down Expand Up @@ -840,6 +843,15 @@ struct PcListPageV2 {

/** 扫码:自动识别配对 URL 或按键配置 */
private async scanAndImportKeys(): Promise<void> {
if (!QrShareService.isScanQrCodeSupported()) {
ToastQueue.show({
message: QrShareService.getScanUnsupportedMessage() + ',请使用扫描网络或手动添加',
duration: 2500
});
this.canScanQrCode = false;
return;
}

const result = await QrShareService.scanQrCode();
if (!result) {
ToastQueue.show({ message: '扫码取消或失败', duration: 1500 });
Expand Down
26 changes: 26 additions & 0 deletions entry/src/main/ets/service/network/QrShareService.ets
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,11 @@ const QR_MAX_CHARS = 4000;
/** 压缩数据前缀 — Moonlight Compressed */
const COMPRESSED_PREFIX = 'MLC:';

/** 系统扫码 UI 能力。HarmonyOS PC/2in1 只提供生成二维码能力,不提供 ScanBarcode。 */
const SCAN_BARCODE_SYSCAP = 'SystemCapability.Multimedia.Scan.ScanBarcode';

const SCAN_UNSUPPORTED_MESSAGE = '当前设备不支持扫码';

// =============================================================================
// zlib 压缩 / 解压
// =============================================================================
Expand Down Expand Up @@ -548,6 +553,22 @@ function fromCompactV1(raw: Record<string, Object>): CustomKeyDef {

export class QrShareService {

/**
* 当前设备是否支持拉起系统扫码 UI。
* 例如 MateBook/2in1 设备缺少 ScanBarcode syscap,直接调用会返回取消或失败。
*/
static isScanQrCodeSupported(): boolean {
try {
return canIUse(SCAN_BARCODE_SYSCAP);
} catch (_e) {
return false;
}
}

static getScanUnsupportedMessage(): string {
return SCAN_UNSUPPORTED_MESSAGE;
}

/**
* 将配置序列化为 v3 管道格式(QR/压缩用,最紧凑)
*/
Expand Down Expand Up @@ -697,6 +718,11 @@ export class QrShareService {
* @returns 扫码内容字符串,失败返回 null
*/
static async scanQrCode(): Promise<string | null> {
if (!QrShareService.isScanQrCodeSupported()) {
console.warn('[QrShareService] 当前设备不支持 ScanBarcode 系统能力');
return null;
}

try {
const options: scanBarcode.ScanOptions = {
scanTypes: [scanCore.ScanType.QR_CODE],
Expand Down
Loading