[fix] make mobile landscape locking opt-in

This commit is contained in:
dijunkun
2026-10-27 03:55:17 +08:00
parent 0e052e6d7a
commit a9e3178042
12 changed files with 57 additions and 154 deletions
+1 -1
View File
@@ -9,7 +9,7 @@
两者仍为开发版。已完成的模拟器验证及未完成的真机验收见各端说明。
手机控制端的“设置 → 屏幕方向 → 连接后自动横屏”默认开启并保存在本机。连接后先保持横屏,检测到手机实际横过来后恢复跟随设备方向;竖持、平放或斜持不会解除初始横屏。关闭后沿用自动旋转,平板和多窗口不受此选项影响。退出会话恢复首页方向,返回同一后台会话不重复锁定已解锁的方向;重力传感器不可用时沿用自动旋转。
手机控制端的“设置 → 屏幕方向 → 连接建立后锁定横屏”默认关闭并保存在本机。开启后,手机连接时进入并保持横屏;关闭时跟随设备方向。平板和多窗口不受此选项影响。退出会话恢复首页方向,返回后台会话沿用该会话的方向设置。已移除检测手机姿态后解除初始横屏的逻辑。
## 构建
@@ -51,7 +51,7 @@ export class MobileState {
muted: boolean = false;
previews: boolean = false;
videoPreference: number = 1;
autoLandscape: boolean = true;
autoLandscape: boolean = false;
version: string = '0.1.0';
update: string = '';
updateStatus: string = '';
@@ -132,7 +132,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.autoLandscape = this.store.flag('autoLandscape', false);
this.state.previews = this.store.flag('previews');
try { this.state.version = bundleManager.getBundleInfoForSelfSync(bundleManager.BundleFlag.GET_BUNDLE_INFO_DEFAULT).versionName; } catch {}
try {
@@ -4,7 +4,6 @@ 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 {
@@ -22,19 +21,9 @@ export class MobilePlatform {
private generation: number = 0;
private backgroundActive: boolean = false;
private active: boolean = false;
private waitingForLandscape: boolean = false;
private observingOrientation: boolean = false;
private autoLandscape: 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;
@@ -51,9 +40,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(autoLandscape: boolean = true): Promise<void> {
async connected(autoLandscape: boolean = false): Promise<void> {
if (this.active) return;
this.waitingForLandscape = autoLandscape && deviceInfo.deviceType === 'phone';
this.autoLandscape = autoLandscape;
this.active = true; const generation = ++this.generation;
this.muted = false; this.orientation(true);
this.backgroundQueue = this.backgroundQueue.then(async () => {
@@ -106,7 +95,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.stopOrientationSensor(); this.release(); this.cancelRequests();
this.foreground = false; this.release(); this.cancelRequests();
this.onVisibility(this.pipActive);
if (!this.active) return;
const generation = this.generation;
@@ -125,7 +114,7 @@ export class MobilePlatform {
}
dispose(): void { this.disconnect(); try { backgroundTaskManager.off('continuousTaskCancel', this.backgroundCancelled); } catch {} }
disconnect(): void {
this.waitingForLandscape = false; this.stopOrientationSensor();
this.autoLandscape = false;
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. */ } }
@@ -134,27 +123,10 @@ export class MobilePlatform {
});
this.orientation(false);
}
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 {
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.
// Serialize window requests so the latest policy wins after a lifecycle change.
this.orientationQueue = this.orientationQueue.then(async () => {
if (generation !== this.generation || revision !== this.orientationRevision) return;
try {
@@ -167,10 +139,9 @@ export class MobilePlatform {
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;
session && this.autoLandscape && phone && narrow && fullScreen ?
window.Orientation.AUTO_ROTATION_LANDSCAPE : window.Orientation.AUTO_ROTATION;
await win.setPreferredOrientation(orientation);
} catch { /* Window may already be going away. */ }
});
@@ -269,12 +269,12 @@ struct Mobile {
}.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)
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('连接后自动横屏')
.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('手机连接后先进入横屏,将手机横过来后恢复跟随设备方向。关闭后始终跟随设备方向。平板和多窗口不受影响。')
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)