|
|
@@ -6,6 +6,7 @@ import { ImagePickerUtil } from '../common/util/ImagePickerUtil';
|
|
|
import Logger from '../common/util/Logger';
|
|
|
import { ConfigurationConstant, Context } from '@kit.AbilityKit';
|
|
|
import { BaiduConstants } from '../common/constants/BaiduConstants';
|
|
|
+import { BaiduDeviceTokenResult, pollDeviceToken as pollBaiduDeviceToken, requestDeviceCode as requestBaiduDeviceCode } from '../common/network/BaiduPanClient';
|
|
|
import { webview } from '@kit.ArkWeb';
|
|
|
|
|
|
interface ParsedConnectionParts {
|
|
|
@@ -17,6 +18,14 @@ interface ParsedConnectionParts {
|
|
|
path?: string;
|
|
|
}
|
|
|
|
|
|
+interface BaiduTokenResultShape {
|
|
|
+ access_token?: string;
|
|
|
+ refresh_token?: string;
|
|
|
+ expires_in?: number;
|
|
|
+ error?: string;
|
|
|
+ error_description?: string;
|
|
|
+}
|
|
|
+
|
|
|
// 工具函数:将 #RRGGBB 字符串和透明度转为 'rgba(r,g,b,a)' 字符串,深色模式下可返回深色变体
|
|
|
function themeColorWithAlpha(themeColor: string, alpha: number, isDarkMode: boolean = false): string {
|
|
|
if (isDarkMode) {
|
|
|
@@ -59,6 +68,14 @@ export struct RemoteDriveAccountDialog {
|
|
|
@State showBaiduAuthDialog: boolean = false;
|
|
|
@State baiduAuthorizeUrl: string = '';
|
|
|
@State baiduAuthProgress: number = 0;
|
|
|
+ @State baiduAuthMode: 'web' | 'device' = 'web';
|
|
|
+ @State baiduDeviceCode: string = '';
|
|
|
+ @State baiduDeviceUserCode: string = '';
|
|
|
+ @State baiduDeviceVerifyUrl: string = '';
|
|
|
+ @State baiduDeviceQrUrl: string = '';
|
|
|
+ @State baiduDeviceExpireAt: number = 0;
|
|
|
+ @State baiduDeviceInterval: number = 5;
|
|
|
+ @State baiduDevicePolling: boolean = false;
|
|
|
@StorageProp('themeColor') themeColor: string = CommonConstants.DEFAULT_THEME_COLOR;
|
|
|
@StorageProp('currentColorMode') @Watch('onColorModeChange') currentMode: number =
|
|
|
ConfigurationConstant.ColorMode.COLOR_MODE_LIGHT;
|
|
|
@@ -69,6 +86,7 @@ export struct RemoteDriveAccountDialog {
|
|
|
private navBasePathCustomized: boolean = false;
|
|
|
private baiduAuthStateToken: string = '';
|
|
|
private baiduWebController: webview.WebviewController = new webview.WebviewController();
|
|
|
+ private baiduDevicePollTimer: number = 0;
|
|
|
|
|
|
onColorModeChange() {
|
|
|
this.isDarkMode = this.currentMode === ConfigurationConstant.ColorMode.COLOR_MODE_DARK
|
|
|
@@ -807,6 +825,13 @@ export struct RemoteDriveAccountDialog {
|
|
|
.alignSelf(ItemAlign.Start);
|
|
|
|
|
|
Row({ space: 8 }) {
|
|
|
+ Button('扫码授权')
|
|
|
+ .type(ButtonType.Capsule)
|
|
|
+ .backgroundColor(this.isDarkMode ? '#2C2C2E' : $r('app.color.input_background'))
|
|
|
+ .fontColor($r('app.color.index_tab_font_color'))
|
|
|
+ .onClick(() => {
|
|
|
+ this.handleBaiduDeviceAuthorize();
|
|
|
+ });
|
|
|
Button('打开百度授权页')
|
|
|
.type(ButtonType.Capsule)
|
|
|
.backgroundColor(this.themeColor)
|
|
|
@@ -829,7 +854,7 @@ export struct RemoteDriveAccountDialog {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- Text('系统将打开百度网盘授权网页,请登录并允许访问网盘文件。')
|
|
|
+ Text('可扫码授权或打开百度网盘授权网页,请登录并允许访问网盘文件。')
|
|
|
.fontSize(13)
|
|
|
.fontColor(this.isDarkMode ? '#C7C7CC' : '#666666')
|
|
|
.maxLines(2);
|
|
|
@@ -879,33 +904,39 @@ export struct RemoteDriveAccountDialog {
|
|
|
.alignItems(VerticalAlign.Center)
|
|
|
.padding({ bottom: 4 });
|
|
|
|
|
|
+ this.buildBaiduAuthModeSwitch();
|
|
|
+
|
|
|
Divider()
|
|
|
.color(this.isDarkMode ? '#3A3A3C' : '#E5E5EA');
|
|
|
|
|
|
- if (this.baiduAuthProgress > 0 && this.baiduAuthProgress < 100) {
|
|
|
- Text(`页面加载中 ${this.baiduAuthProgress}%`)
|
|
|
- .fontSize(12)
|
|
|
- .fontColor(this.isDarkMode ? '#C7C7CC' : '#666666');
|
|
|
- }
|
|
|
+ if (this.baiduAuthMode === 'device') {
|
|
|
+ this.buildBaiduDeviceAuthContent();
|
|
|
+ } else {
|
|
|
+ if (this.baiduAuthProgress > 0 && this.baiduAuthProgress < 100) {
|
|
|
+ Text(`页面加载中 ${this.baiduAuthProgress}%`)
|
|
|
+ .fontSize(12)
|
|
|
+ .fontColor(this.isDarkMode ? '#C7C7CC' : '#666666');
|
|
|
+ }
|
|
|
|
|
|
- Web({
|
|
|
- src: this.baiduAuthorizeUrl,
|
|
|
- controller: this.baiduWebController
|
|
|
- })
|
|
|
- .layoutWeight(1)
|
|
|
- .width('100%')
|
|
|
- .javaScriptAccess(true)
|
|
|
- .domStorageAccess(true)
|
|
|
- .onPageEnd((event) => {
|
|
|
- if (event && event.url) {
|
|
|
- this.handleBaiduAuthNavigation(event.url);
|
|
|
- }
|
|
|
+ Web({
|
|
|
+ src: this.baiduAuthorizeUrl,
|
|
|
+ controller: this.baiduWebController
|
|
|
})
|
|
|
- .onProgressChange((event) => {
|
|
|
- if (event) {
|
|
|
- this.baiduAuthProgress = event.newProgress;
|
|
|
- }
|
|
|
- });
|
|
|
+ .layoutWeight(1)
|
|
|
+ .width('100%')
|
|
|
+ .javaScriptAccess(true)
|
|
|
+ .domStorageAccess(true)
|
|
|
+ .onPageEnd((event) => {
|
|
|
+ if (event && event.url) {
|
|
|
+ this.handleBaiduAuthNavigation(event.url);
|
|
|
+ }
|
|
|
+ })
|
|
|
+ .onProgressChange((event) => {
|
|
|
+ if (event) {
|
|
|
+ this.baiduAuthProgress = event.newProgress;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
}
|
|
|
.width('92%')
|
|
|
.height('85%')
|
|
|
@@ -917,6 +948,98 @@ export struct RemoteDriveAccountDialog {
|
|
|
.height('100%');
|
|
|
}
|
|
|
|
|
|
+ @Builder
|
|
|
+ private buildBaiduAuthModeSwitch() {
|
|
|
+ Row({ space: 8 }) {
|
|
|
+ this.buildBaiduAuthModeChip('网页登录', 'web');
|
|
|
+ this.buildBaiduAuthModeChip('扫码登录', 'device');
|
|
|
+ }
|
|
|
+ .width('100%')
|
|
|
+ .padding({ top: 4, bottom: 4 });
|
|
|
+ }
|
|
|
+
|
|
|
+ @Builder
|
|
|
+ private buildBaiduAuthModeChip(label: string, mode: 'web' | 'device') {
|
|
|
+ Button(label)
|
|
|
+ .type(ButtonType.Capsule)
|
|
|
+ .backgroundColor(this.baiduAuthMode === mode ? this.themeColor : (this.isDarkMode ? '#2C2C2E' : $r('app.color.input_background')))
|
|
|
+ .fontColor(this.baiduAuthMode === mode ? Color.White : $r('app.color.index_tab_font_color'))
|
|
|
+ .onClick(() => {
|
|
|
+ this.switchBaiduAuthMode(mode);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ @Builder
|
|
|
+ private buildBaiduDeviceAuthContent() {
|
|
|
+ Column({ space: 12 }) {
|
|
|
+ if (this.baiduDeviceQrUrl) {
|
|
|
+ Image(this.baiduDeviceQrUrl)
|
|
|
+ .width(220)
|
|
|
+ .height(220)
|
|
|
+ .borderRadius(12)
|
|
|
+ .objectFit(ImageFit.Contain)
|
|
|
+ .alignSelf(ItemAlign.Center);
|
|
|
+ } else {
|
|
|
+ Column() {
|
|
|
+ Text('正在获取二维码...')
|
|
|
+ .fontColor(this.isDarkMode ? '#C7C7CC' : '#666666');
|
|
|
+ }
|
|
|
+ .width(220)
|
|
|
+ .height(220)
|
|
|
+ .alignSelf(ItemAlign.Center)
|
|
|
+ .backgroundColor(this.isDarkMode ? '#2C2C2E' : $r('app.color.input_background'))
|
|
|
+ .borderRadius(12)
|
|
|
+ .justifyContent(FlexAlign.Center)
|
|
|
+ .alignItems(HorizontalAlign.Center);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this.baiduDeviceUserCode) {
|
|
|
+ Text(`设备码: ${this.baiduDeviceUserCode}`)
|
|
|
+ .fontSize(14)
|
|
|
+ .fontWeight(FontWeight.Medium)
|
|
|
+ .fontColor($r('app.color.index_tab_font_color'));
|
|
|
+ }
|
|
|
+ if (this.baiduDeviceVerifyUrl) {
|
|
|
+ Text(`或在浏览器访问 ${this.baiduDeviceVerifyUrl} 输入设备码完成授权`)
|
|
|
+ .fontSize(12)
|
|
|
+ .fontColor(this.isDarkMode ? '#C7C7CC' : '#666666')
|
|
|
+ .maxLines(2);
|
|
|
+ }
|
|
|
+ if (this.baiduDeviceExpireAt) {
|
|
|
+ Text(`二维码将于 ${this.getBaiduDeviceRemainText()} 过期`)
|
|
|
+ .fontSize(12)
|
|
|
+ .fontColor(this.isDarkMode ? '#C7C7CC' : '#666666');
|
|
|
+ }
|
|
|
+ if (this.baiduAuthStatus) {
|
|
|
+ Text(this.baiduAuthStatus)
|
|
|
+ .fontSize(13)
|
|
|
+ .fontColor(this.isDarkMode ? '#EBEBF5' : '#666666')
|
|
|
+ .maxLines(2);
|
|
|
+ }
|
|
|
+
|
|
|
+ Row({ space: 12 }) {
|
|
|
+ Button('刷新二维码')
|
|
|
+ .type(ButtonType.Capsule)
|
|
|
+ .backgroundColor(this.themeColor)
|
|
|
+ .fontColor(Color.White)
|
|
|
+ .onClick(() => {
|
|
|
+ this.handleBaiduDeviceAuthorize();
|
|
|
+ });
|
|
|
+ Button('改用网页登录')
|
|
|
+ .type(ButtonType.Capsule)
|
|
|
+ .backgroundColor(this.isDarkMode ? '#2C2C2E' : $r('app.color.input_background'))
|
|
|
+ .fontColor($r('app.color.index_tab_font_color'))
|
|
|
+ .onClick(() => {
|
|
|
+ this.handleBaiduImplicitAuthorize();
|
|
|
+ });
|
|
|
+ }
|
|
|
+ .width('100%')
|
|
|
+ .justifyContent(FlexAlign.Start);
|
|
|
+ }
|
|
|
+ .layoutWeight(1)
|
|
|
+ .width('100%');
|
|
|
+ }
|
|
|
+
|
|
|
private getBaiduExpireText(): string {
|
|
|
if (!this.baiduTokenExpiresAt) {
|
|
|
return '';
|
|
|
@@ -927,7 +1050,137 @@ export struct RemoteDriveAccountDialog {
|
|
|
return hours > 0 ? `${hours}小时${minutes}分后` : `${minutes}分钟后`;
|
|
|
}
|
|
|
|
|
|
+ private switchBaiduAuthMode(mode: 'web' | 'device'): void {
|
|
|
+ if (mode === this.baiduAuthMode && this.showBaiduAuthDialog) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (mode === 'device') {
|
|
|
+ this.handleBaiduDeviceAuthorize();
|
|
|
+ } else {
|
|
|
+ this.handleBaiduImplicitAuthorize();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private async handleBaiduDeviceAuthorize(): Promise<void> {
|
|
|
+ this.stopBaiduDevicePolling();
|
|
|
+ this.baiduAuthMode = 'device';
|
|
|
+ this.resetBaiduDeviceState(false);
|
|
|
+ this.baiduAuthorizeUrl = '';
|
|
|
+ this.baiduAuthProgress = 0;
|
|
|
+ this.baiduAuthStateToken = '';
|
|
|
+ this.baiduAuthStatus = '正在获取扫码授权信息...';
|
|
|
+ try {
|
|
|
+ const codeInfo = await requestBaiduDeviceCode();
|
|
|
+ this.baiduDeviceCode = codeInfo.device_code;
|
|
|
+ this.baiduDeviceUserCode = codeInfo.user_code;
|
|
|
+ this.baiduDeviceVerifyUrl = codeInfo.verification_url;
|
|
|
+ this.baiduDeviceQrUrl = codeInfo.qrcode_url ?? '';
|
|
|
+ this.baiduDeviceExpireAt = Date.now() + (codeInfo.expires_in ?? 0) * 1000;
|
|
|
+ this.baiduDeviceInterval = Math.max(3, codeInfo.interval ?? 5);
|
|
|
+ this.baiduAuthStatus = '请在百度网盘App中扫码确认或输入设备码授权';
|
|
|
+ this.showBaiduAuthDialog = true;
|
|
|
+ this.startBaiduDevicePolling();
|
|
|
+ } catch (error) {
|
|
|
+ const message = (error as Error).message ?? '请求失败';
|
|
|
+ this.baiduAuthStatus = `获取设备码失败: ${message}`;
|
|
|
+ ToastUtil.showToast('获取百度扫码授权失败');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private startBaiduDevicePolling(): void {
|
|
|
+ this.stopBaiduDevicePolling();
|
|
|
+ if (!this.baiduDeviceCode) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const intervalMs = Math.max(3000, this.baiduDeviceInterval * 1000);
|
|
|
+ this.baiduDevicePollTimer = setInterval(() => {
|
|
|
+ this.queryBaiduDeviceToken();
|
|
|
+ }, intervalMs) as number;
|
|
|
+ }
|
|
|
+
|
|
|
+ private stopBaiduDevicePolling(): void {
|
|
|
+ if (this.baiduDevicePollTimer) {
|
|
|
+ clearInterval(this.baiduDevicePollTimer);
|
|
|
+ this.baiduDevicePollTimer = 0;
|
|
|
+ }
|
|
|
+ this.baiduDevicePolling = false;
|
|
|
+ }
|
|
|
+
|
|
|
+ private async queryBaiduDeviceToken(): Promise<void> {
|
|
|
+ if (!this.baiduDeviceCode || this.baiduDevicePolling) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.baiduDevicePolling = true;
|
|
|
+ try {
|
|
|
+ const result: BaiduDeviceTokenResult = await pollBaiduDeviceToken(this.baiduDeviceCode);
|
|
|
+ const tokenResult = result as BaiduTokenResultShape;
|
|
|
+ if (tokenResult.access_token !== undefined && tokenResult.access_token.length > 0) {
|
|
|
+ this.baiduAccessToken = tokenResult.access_token;
|
|
|
+ this.baiduRefreshToken = tokenResult.refresh_token ?? '';
|
|
|
+ this.baiduTokenExpiresAt = tokenResult.expires_in ? Date.now() + tokenResult.expires_in * 1000 : 0;
|
|
|
+ this.baiduAuthStatus = '授权成功,可保存账户';
|
|
|
+ ToastUtil.showToast('百度授权成功');
|
|
|
+ this.closeBaiduAuthDialog();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const errorResult = result as BaiduTokenResultShape;
|
|
|
+ if (errorResult.error === undefined) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ switch (errorResult.error) {
|
|
|
+ case 'authorization_pending':
|
|
|
+ this.baiduAuthStatus = '请在百度网盘App扫码并确认';
|
|
|
+ break;
|
|
|
+ case 'slow_down':
|
|
|
+ this.baiduAuthStatus = '请求过于频繁,稍后自动重试';
|
|
|
+ this.baiduDeviceInterval += 2;
|
|
|
+ this.startBaiduDevicePolling();
|
|
|
+ break;
|
|
|
+ case 'expired_token':
|
|
|
+ case 'invalid_grant':
|
|
|
+ this.baiduAuthStatus = '二维码已过期,请刷新重新扫码';
|
|
|
+ this.stopBaiduDevicePolling();
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ this.baiduAuthStatus = `授权失败: ${errorResult.error_description ?? errorResult.error}`;
|
|
|
+ this.stopBaiduDevicePolling();
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ const message = (error as Error).message ?? '未知错误';
|
|
|
+ this.baiduAuthStatus = `授权轮询失败: ${message}`;
|
|
|
+ } finally {
|
|
|
+ this.baiduDevicePolling = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private getBaiduDeviceRemainText(): string {
|
|
|
+ if (!this.baiduDeviceExpireAt) {
|
|
|
+ return '稍后';
|
|
|
+ }
|
|
|
+ const remain = Math.max(0, this.baiduDeviceExpireAt - Date.now());
|
|
|
+ const minutes = Math.floor(remain / 60000);
|
|
|
+ const seconds = Math.floor((remain % 60000) / 1000);
|
|
|
+ return minutes > 0 ? `${minutes}分${seconds}秒后` : `${seconds}秒后`;
|
|
|
+ }
|
|
|
+
|
|
|
+ private resetBaiduDeviceState(resetMode: boolean = true): void {
|
|
|
+ if (resetMode) {
|
|
|
+ this.baiduAuthMode = 'web';
|
|
|
+ }
|
|
|
+ this.baiduDeviceCode = '';
|
|
|
+ this.baiduDeviceUserCode = '';
|
|
|
+ this.baiduDeviceVerifyUrl = '';
|
|
|
+ this.baiduDeviceQrUrl = '';
|
|
|
+ this.baiduDeviceExpireAt = 0;
|
|
|
+ this.baiduDeviceInterval = 5;
|
|
|
+ this.baiduDevicePolling = false;
|
|
|
+ }
|
|
|
+
|
|
|
private handleBaiduImplicitAuthorize(): void {
|
|
|
+ this.stopBaiduDevicePolling();
|
|
|
+ this.resetBaiduDeviceState(false);
|
|
|
+ this.baiduAuthMode = 'web';
|
|
|
this.baiduAuthStatus = '正在打开百度授权页面...';
|
|
|
this.baiduAuthStateToken = `${Date.now()}`;
|
|
|
this.baiduAuthProgress = 0;
|
|
|
@@ -940,6 +1193,8 @@ export struct RemoteDriveAccountDialog {
|
|
|
this.baiduAuthorizeUrl = '';
|
|
|
this.baiduAuthProgress = 0;
|
|
|
this.baiduAuthStateToken = '';
|
|
|
+ this.stopBaiduDevicePolling();
|
|
|
+ this.resetBaiduDeviceState();
|
|
|
}
|
|
|
|
|
|
private buildBaiduAuthorizeUrl(state: string): string {
|