feat(录音): 容器接 Web触摸观察(方案A onTouch)+录音浮层+SHOW_RECORDING+计时/上滑取消/太短决策
- 补 import: AudioEvents, ShowRecordingRequest, RecordResult (from common) + RecordingOverlay
- 新增字段: recordOverlayVisible/recordState/recordResultEvent/touchDown/touchStartY/
recordingActive/wantCancel/recordSeconds/recordTimer
- subscribeEvents() 追加 SHOW_RECORDING → onShowRecording
- onShowRecording: 手指未按时立即 cancel 回投; 否则显浮层+启计时器
- onWebTouch(TouchEvent): Down记起点/Move判上滑(>100vp)/Up调 finishRecording
- finishRecording: <0.8s→tooShort延1.3s撤层; 上滑→cancel; 否则→send; 一次性清 resultEvent
- 大厅/子游戏 Web 各加 .onTouch((e)=>this.onWebTouch(e)) (方案A, 不消费触摸)
- Stack 顶层(ShareGuideDialog 之后)追加 RecordingOverlay({ state })
- aboutToDisappear 清 recordTimer
对应 §10 录音浮层设计; 手势观察接线
This commit is contained in:
@@ -6,11 +6,13 @@ import { ConfigManager, ResourceManager, AppDataInjector, AppDataValues, RemoteC
|
||||
import { KvStore, LocalUploadServer } from 'platform';
|
||||
import { OutboundHandlers } from 'contracts';
|
||||
import { AppEnv, EventBus, EventPayload, NavEvents, OpenGenericWebPayload, SwitchGamePayload,
|
||||
BackGamePayload, ShareEvents, SharePanelRequest, SharePanelResult, ShareGuideRequest, ShareGuideResult, Logger } from 'common';
|
||||
BackGamePayload, ShareEvents, SharePanelRequest, SharePanelResult, ShareGuideRequest, ShareGuideResult, Logger,
|
||||
AudioEvents, ShowRecordingRequest, RecordResult } from 'common';
|
||||
import { BridgeGameParams, GenericWebParams, GenericWebResult, RouteName, AppEvents } from '../routes/AppRoutes';
|
||||
import { WebSlot } from '../web/WebSlot';
|
||||
import { SharePanel } from '../components/SharePanel';
|
||||
import { ShareGuideDialog } from '../components/ShareGuideDialog';
|
||||
import { RecordingOverlay } from '../components/RecordingOverlay';
|
||||
import { KEY_SPLASH_VISIBLE } from './Index';
|
||||
|
||||
/**
|
||||
@@ -59,6 +61,19 @@ export struct BridgeGameContainer {
|
||||
private shareGuideTimeline: boolean = false;
|
||||
/** 当前指引窗的一次性回投事件名(用户选动作后 emit 回 ShareProvider)。 */
|
||||
private shareGuideResultEvent: string = '';
|
||||
/** 录音浮层显隐与状态('recording'|'cancel'|'tooShort')。 */
|
||||
@State private recordOverlayVisible: boolean = false;
|
||||
@State private recordState: string = 'recording';
|
||||
/** 当前录音的一次性回投事件名。 */
|
||||
private recordResultEvent: string = '';
|
||||
/** 触摸跟踪(始终更新,供录音激活时读取)。 */
|
||||
private touchDown: boolean = false;
|
||||
private touchStartY: number = 0;
|
||||
/** 录音激活中(收到 SHOW_RECORDING 且手指在按)。 */
|
||||
private recordingActive: boolean = false;
|
||||
private wantCancel: boolean = false;
|
||||
private recordSeconds: number = 0;
|
||||
private recordTimer: number = -1;
|
||||
|
||||
aboutToAppear(): void {
|
||||
if (AppEnv.isDebug()) {
|
||||
@@ -72,6 +87,10 @@ export struct BridgeGameContainer {
|
||||
cancel();
|
||||
}
|
||||
this.cancels = [];
|
||||
if (this.recordTimer >= 0) {
|
||||
clearInterval(this.recordTimer);
|
||||
this.recordTimer = -1;
|
||||
}
|
||||
this.slotS?.dispose();
|
||||
this.slotL?.dispose();
|
||||
this.uploadServer.stop();
|
||||
@@ -128,6 +147,83 @@ export struct BridgeGameContainer {
|
||||
this.cancels.push(EventBus.on(NavEvents.BACK_GAME, (p?: EventPayload) => this.backToLobby(p)));
|
||||
this.cancels.push(EventBus.on(ShareEvents.SHOW_PANEL, (p?: EventPayload) => this.showSharePanel(p)));
|
||||
this.cancels.push(EventBus.on(ShareEvents.SHOW_GUIDE, (p?: EventPayload) => this.showShareGuide(p)));
|
||||
this.cancels.push(EventBus.on(AudioEvents.SHOW_RECORDING, (p?: EventPayload) => this.onShowRecording(p)));
|
||||
}
|
||||
|
||||
/** 录音器已起:手指仍在按则显示浮层+计时;已松开(异步起录音期间抬手)则立即按取消回投。 */
|
||||
private onShowRecording(p?: EventPayload): void {
|
||||
if (p === undefined) {
|
||||
return;
|
||||
}
|
||||
const req = p as ShowRecordingRequest;
|
||||
this.recordResultEvent = req.resultEvent;
|
||||
if (!this.touchDown) {
|
||||
this.emitRecordResult('cancel', 0);
|
||||
return;
|
||||
}
|
||||
this.recordingActive = true;
|
||||
this.wantCancel = false;
|
||||
this.recordSeconds = 0;
|
||||
this.recordState = 'recording';
|
||||
this.recordOverlayVisible = true;
|
||||
this.recordTimer = setInterval(() => {
|
||||
this.recordSeconds += 0.1;
|
||||
}, 100);
|
||||
}
|
||||
|
||||
/** Web 触摸观察(不消费):Down 记起点 / Move 判上滑取消 / Up 结束决策。 */
|
||||
private onWebTouch(e: TouchEvent): void {
|
||||
if (e.type === TouchType.Down) {
|
||||
this.touchDown = true;
|
||||
this.touchStartY = e.touches.length > 0 ? e.touches[0].y : 0;
|
||||
if (this.recordingActive) {
|
||||
this.wantCancel = false;
|
||||
this.recordState = 'recording';
|
||||
}
|
||||
} else if (e.type === TouchType.Move) {
|
||||
if (this.recordingActive && e.touches.length > 0) {
|
||||
const dy: number = this.touchStartY - e.touches[0].y;
|
||||
this.wantCancel = dy > 100;
|
||||
this.recordState = this.wantCancel ? 'cancel' : 'recording';
|
||||
}
|
||||
} else if (e.type === TouchType.Up || e.type === TouchType.Cancel) {
|
||||
this.touchDown = false;
|
||||
if (this.recordingActive) {
|
||||
this.finishRecording();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 抬手:按时长/上滑决定 send/cancel/tooShort,撤浮层并回投。 */
|
||||
private finishRecording(): void {
|
||||
this.recordingActive = false;
|
||||
if (this.recordTimer >= 0) {
|
||||
clearInterval(this.recordTimer);
|
||||
this.recordTimer = -1;
|
||||
}
|
||||
const elapsed: number = this.recordSeconds;
|
||||
if (elapsed < 0.8) {
|
||||
this.recordState = 'tooShort';
|
||||
setTimeout(() => {
|
||||
this.recordOverlayVisible = false;
|
||||
}, 1300);
|
||||
this.emitRecordResult('tooShort', elapsed);
|
||||
} else if (this.wantCancel) {
|
||||
this.recordOverlayVisible = false;
|
||||
this.emitRecordResult('cancel', elapsed);
|
||||
} else {
|
||||
this.recordOverlayVisible = false;
|
||||
this.emitRecordResult('send', elapsed);
|
||||
}
|
||||
}
|
||||
|
||||
private emitRecordResult(action: string, timeSec: number): void {
|
||||
const ev: string = this.recordResultEvent;
|
||||
this.recordResultEvent = '';
|
||||
if (ev !== '') {
|
||||
const r: RecordResult = { action: action, timeSec: timeSec };
|
||||
EventBus.emit(ev, r);
|
||||
}
|
||||
}
|
||||
|
||||
/** 收到 ShareProvider 的 SHOW_PANEL:记下一次性回投事件名并弹出面板。 */
|
||||
@@ -355,6 +451,8 @@ export struct BridgeGameContainer {
|
||||
// 铺满系统安全区(消除底部导航手势区白边),与原 Android 全屏 WebView 一致
|
||||
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
|
||||
.visibility(this.subgameUrl !== '' ? Visibility.Hidden : Visibility.Visible)
|
||||
// 录音手势观察(方案A,不消费触摸)
|
||||
.onTouch((e: TouchEvent) => this.onWebTouch(e))
|
||||
.onControllerAttached(() => this.setupLobby())
|
||||
.onLoadIntercept((event: OnLoadInterceptEvent) =>
|
||||
this.slotL !== undefined ? this.slotL.onLoadIntercept(event.data.getRequestUrl()) : false)
|
||||
@@ -379,6 +477,8 @@ export struct BridgeGameContainer {
|
||||
.geolocationAccess(true).zoomAccess(false)
|
||||
.width('100%').height('100%')
|
||||
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
|
||||
// 录音手势观察(方案A,不消费触摸)
|
||||
.onTouch((e: TouchEvent) => this.onWebTouch(e))
|
||||
.onControllerAttached(() => this.setupSubgame())
|
||||
.onLoadIntercept((event: OnLoadInterceptEvent) =>
|
||||
this.slotS !== undefined ? this.slotS.onLoadIntercept(event.data.getRequestUrl()) : false)
|
||||
@@ -435,6 +535,10 @@ export struct BridgeGameContainer {
|
||||
onPick: (action: string) => this.emitShareGuideResult(action),
|
||||
})
|
||||
}
|
||||
// 录音浮层(顶层叠加,触摸穿透到下方 Web 由 onTouch 观察)
|
||||
if (this.recordOverlayVisible) {
|
||||
RecordingOverlay({ state: this.recordState })
|
||||
}
|
||||
}
|
||||
.width('100%').height('100%')
|
||||
// 垫白底:Web 首帧前/页面切换间隙显白色(与启动页白底无缝),避免黑屏闪烁
|
||||
|
||||
Reference in New Issue
Block a user