mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-10-25 22:56:23 +08:00
[feat] add configurable landscape entry for mobile sessions
This commit is contained in:
@@ -9,6 +9,8 @@
|
||||
|
||||
两者仍为开发版。已完成的模拟器验证及未完成的真机验收见各端说明。
|
||||
|
||||
手机控制端的“设置 → 屏幕方向 → 连接后自动横屏”默认开启并保存在本机。连接后先保持横屏,检测到手机实际横过来后恢复跟随设备方向;竖持、平放或斜持不会解除初始横屏。关闭后沿用自动旋转,平板和多窗口不受此选项影响。退出会话恢复首页方向,返回同一后台会话不重复锁定已解锁的方向;重力传感器不可用时沿用自动旋转。
|
||||
|
||||
## 构建
|
||||
|
||||
在仓库根目录执行,当前构建脚本面向 macOS:
|
||||
|
||||
@@ -50,6 +50,7 @@ export class MobileState {
|
||||
muted: boolean = false;
|
||||
previews: boolean = false;
|
||||
videoPreference: number = 1;
|
||||
autoLandscape: boolean = true;
|
||||
version: string = '0.1.0';
|
||||
update: string = '';
|
||||
updateStatus: string = '';
|
||||
@@ -122,6 +123,7 @@ export class MobileController {
|
||||
this.state.consent = this.store.flag('consent'); this.state.server = this.store.text('server', 'api.crossdesk.cn');
|
||||
this.state.port = this.store.text('port', '9099'); this.state.remote = this.store.text('lastRemote');
|
||||
this.state.relative = this.store.flag('relative', true); this.state.videoPreference = this.store.number('videoPreference', 1);
|
||||
this.state.autoLandscape = this.store.flag('autoLandscape', true);
|
||||
this.state.previews = this.store.flag('previews');
|
||||
try { this.state.version = bundleManager.getBundleInfoForSelfSync(bundleManager.BundleFlag.GET_BUNDLE_INFO_DEFAULT).versionName; } catch {}
|
||||
try {
|
||||
@@ -280,6 +282,7 @@ export class MobileController {
|
||||
this.settingsTimeout = setTimeout(() => { this.video.expire(id); this.syncVideo(); }, 5000); this.syncVideo();
|
||||
}
|
||||
setPreference(value: number): void { this.store.put('videoPreference', value); this.state.videoPreference = value; }
|
||||
setAutoLandscape(value: boolean): void { this.store.put('autoLandscape', value); this.state.autoLandscape = value; }
|
||||
setPreviews(value: boolean): void {
|
||||
this.store.put('previews', value); this.state.previews = value;
|
||||
if (!value) { this.send({ op: 'cancelPreview' }); this.previewEligible = false; this.store.clearPreviews(); this.history(); }
|
||||
@@ -427,7 +430,7 @@ export class MobileController {
|
||||
this.history();
|
||||
}
|
||||
}).catch(() => this.toast('密码保存失败,本次连接仍可使用'));
|
||||
this.platform.connected();
|
||||
this.platform.connected(this.state.autoLandscape);
|
||||
if (this.video.supported && this.video.requestId === 0) this.updateVideo(0, this.video.selection.quality);
|
||||
if (this.receivedHostInfo) this.checkRemote(this.remoteVersion);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { PiPWindow, window, display } from '@kit.ArkUI';
|
||||
import { backgroundTaskManager } from '@kit.BackgroundTasksKit';
|
||||
import { picker, fileIo as fs, fileUri } from '@kit.CoreFileKit';
|
||||
import { http } from '@kit.NetworkKit';
|
||||
import { sensor } from '@kit.SensorServiceKit';
|
||||
import { isAnnouncementURL } from './AnnouncementText';
|
||||
|
||||
export class MobileLifecycle {
|
||||
@@ -21,6 +22,19 @@ export class MobilePlatform {
|
||||
private generation: number = 0;
|
||||
private backgroundActive: boolean = false;
|
||||
private active: boolean = false;
|
||||
private waitingForLandscape: boolean = false;
|
||||
private observingOrientation: boolean = false;
|
||||
private orientationRevision: number = 0;
|
||||
private orientationQueue: Promise<void> = Promise.resolve();
|
||||
private gravityChanged: (data: sensor.GravityResponse) => void = (data: sensor.GravityResponse) => {
|
||||
if (!this.active || !this.foreground || !this.waitingForLandscape || !this.observingOrientation) return;
|
||||
// Gravity is in device coordinates, independent of programmatic window rotation.
|
||||
// Reject flat and diagonal poses before handing rotation back to the system.
|
||||
const x = Math.abs(data.x), y = Math.abs(data.y), z = Math.abs(data.z);
|
||||
if (x >= 6 && x >= y * 1.7 && x >= z) {
|
||||
this.waitingForLandscape = false; this.stopOrientationSensor(); this.orientation(true);
|
||||
}
|
||||
};
|
||||
private foreground: boolean = true;
|
||||
choosing: boolean = false;
|
||||
pipActive: boolean = false;
|
||||
@@ -37,7 +51,9 @@ export class MobilePlatform {
|
||||
this.context = context; this.ended = ended; this.release = release; this.feedback = feedback;
|
||||
try { backgroundTaskManager.on('continuousTaskCancel', this.backgroundCancelled); } catch {}
|
||||
}
|
||||
async connected(): Promise<void> {
|
||||
async connected(autoLandscape: boolean = true): Promise<void> {
|
||||
if (this.active) return;
|
||||
this.waitingForLandscape = autoLandscape && deviceInfo.deviceType === 'phone';
|
||||
this.active = true; const generation = ++this.generation;
|
||||
this.muted = false; this.orientation(true);
|
||||
this.backgroundQueue = this.backgroundQueue.then(async () => {
|
||||
@@ -90,7 +106,7 @@ export class MobilePlatform {
|
||||
}
|
||||
videoSize(width: number, height: number): void { try { this.pip?.updateContentSize(width, height); } catch { /* Not supported on this device. */ } }
|
||||
async background(): Promise<void> {
|
||||
this.foreground = false; this.release(); this.cancelRequests();
|
||||
this.foreground = false; this.stopOrientationSensor(); this.release(); this.cancelRequests();
|
||||
this.onVisibility(this.pipActive);
|
||||
if (!this.active) return;
|
||||
const generation = this.generation;
|
||||
@@ -109,6 +125,7 @@ export class MobilePlatform {
|
||||
}
|
||||
dispose(): void { this.disconnect(); try { backgroundTaskManager.off('continuousTaskCancel', this.backgroundCancelled); } catch {} }
|
||||
disconnect(): void {
|
||||
this.waitingForLandscape = false; this.stopOrientationSensor();
|
||||
this.active = false; this.choosing = false; this.generation++; this.cancelRequests();
|
||||
const pip = this.pip; this.pip = undefined; this.pipActive = false;
|
||||
if (pip) { try { pip.setAutoStartEnabled(false); pip.stopPiP().catch(() => {}); } catch { /* Already stopped. */ } }
|
||||
@@ -117,18 +134,46 @@ export class MobilePlatform {
|
||||
});
|
||||
this.orientation(false);
|
||||
}
|
||||
private async orientation(session: boolean): Promise<void> {
|
||||
const generation = this.generation;
|
||||
private stopOrientationSensor(): void {
|
||||
if (!this.observingOrientation) return;
|
||||
this.observingOrientation = false;
|
||||
try { sensor.off(sensor.SensorId.GRAVITY, this.gravityChanged); } catch {}
|
||||
}
|
||||
private startOrientationSensor(): void {
|
||||
if (!this.waitingForLandscape || !this.foreground || this.observingOrientation) return;
|
||||
try {
|
||||
const win = await window.getLastWindow(this.context);
|
||||
if (generation !== this.generation) return;
|
||||
await win.setWindowKeepScreenOn(session);
|
||||
if (generation !== this.generation) return;
|
||||
// Tablets and resizable windows follow the user orientation in both views.
|
||||
const phone = deviceInfo.deviceType === 'phone';
|
||||
const narrow = win.getWindowProperties().windowRect.width / display.getDefaultDisplaySync().densityPixels < 600;
|
||||
await win.setPreferredOrientation(!session && phone && narrow ? window.Orientation.PORTRAIT : window.Orientation.AUTO_ROTATION);
|
||||
} catch { /* Window may already be going away. */ }
|
||||
sensor.getSingleSensorSync(sensor.SensorId.GRAVITY);
|
||||
sensor.on(sensor.SensorId.GRAVITY, this.gravityChanged, { interval: 100000000 });
|
||||
this.observingOrientation = true;
|
||||
} catch {
|
||||
// A phone without a usable sensor keeps the previous system-following policy.
|
||||
this.waitingForLandscape = false;
|
||||
}
|
||||
}
|
||||
private orientation(session: boolean): void {
|
||||
const generation = this.generation;
|
||||
const revision = ++this.orientationRevision;
|
||||
// Serialize window requests so an earlier landscape request cannot win after
|
||||
// the sensor has released it or the session has disconnected.
|
||||
this.orientationQueue = this.orientationQueue.then(async () => {
|
||||
if (generation !== this.generation || revision !== this.orientationRevision) return;
|
||||
try {
|
||||
const win = await window.getLastWindow(this.context);
|
||||
if (generation !== this.generation || revision !== this.orientationRevision) return;
|
||||
await win.setWindowKeepScreenOn(session);
|
||||
if (generation !== this.generation || revision !== this.orientationRevision) return;
|
||||
// Tablets and resizable windows follow the user orientation in both views.
|
||||
const phone = deviceInfo.deviceType === 'phone';
|
||||
const rect = win.getWindowProperties().windowRect;
|
||||
const narrow = Math.min(rect.width, rect.height) / display.getDefaultDisplaySync().densityPixels < 600;
|
||||
const fullScreen = win.getWindowStatus() === window.WindowStatusType.FULL_SCREEN;
|
||||
if (!phone || !narrow || !fullScreen) { this.waitingForLandscape = false; this.stopOrientationSensor(); }
|
||||
if (session) this.startOrientationSensor();
|
||||
const orientation = !session && phone && narrow && fullScreen ? window.Orientation.PORTRAIT :
|
||||
session && this.waitingForLandscape ? window.Orientation.AUTO_ROTATION_LANDSCAPE : window.Orientation.AUTO_ROTATION;
|
||||
await win.setPreferredOrientation(orientation);
|
||||
} catch { /* Window may already be going away. */ }
|
||||
});
|
||||
}
|
||||
private setChoosing(value: boolean): void {
|
||||
this.choosing = value; this.release();
|
||||
|
||||
@@ -267,6 +267,15 @@ struct Mobile {
|
||||
MobileSegment({ labels: ['帧率优先', '画质优先', '平衡'], selection: this.state.videoPreference,
|
||||
changed: (i: number) => this.controller?.setPreference(i) })
|
||||
}.padding(12).width('100%').backgroundColor(Color.White).borderRadius(26).border({ width: 0.5, color: MobileTheme.line })
|
||||
this.SectionTitle('屏幕方向')
|
||||
Row() {
|
||||
Text('连接后自动横屏').fontSize(16).fontColor(MobileTheme.ink).layoutWeight(1)
|
||||
Toggle({ type: ToggleType.Switch, isOn: this.state.autoLandscape }).selectedColor(MobileTheme.green)
|
||||
.onChange((value: boolean) => this.controller?.setAutoLandscape(value)).accessibilityText('连接后自动横屏')
|
||||
}.padding({ left: 16, right: 16, top: 10, bottom: 10 }).width('100%').backgroundColor(Color.White)
|
||||
.borderRadius(26).border({ width: 0.5, color: MobileTheme.line })
|
||||
Text('手机连接后先进入横屏,将手机横过来后恢复跟随设备方向。关闭后始终跟随设备方向。平板和多窗口不受影响。')
|
||||
.fontSize(12).fontColor(MobileTheme.secondary).width('100%').padding({ left: 16, right: 16, top: 8, bottom: 12 })
|
||||
Column() {
|
||||
this.SettingRow('服务器', this.customServer ? '自定义' : '默认', () => this.serverExpanded = !this.serverExpanded)
|
||||
if (this.serverExpanded) {
|
||||
|
||||
Reference in New Issue
Block a user