/*
* Copyright (c) Huawei Device Co., Ltd. 2024-2025. All rights reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { BackGestureModelCallBack } from './BackGestureModelCallBack';
import { BackEventViewModelCallBack } from './BackEventViewModelCallBack';
import { LogDomain, LogHelper, } from '@ohos/basicutils';
import { EvtBus, HiSysBackEventData, RotateChangeEvent } from '@ohos/frameworkwrapper';
import { scbEventExclusiveManager, EventType } from '@ohos/windowscene';
import { MetaBallUtils } from '../../metaball/common/MetaBallUtils';
import { GestureBackConstants } from '../../common/GestureBackConstants';
import { gestureBackSettings } from '../../common/GestureBackSettings';
const TAG = 'BackGestureModel';
const log: LogHelper = LogHelper.getLogHelper(LogDomain.GESTURE, TAG);
export class BackGestureModel {
private isIgnoreBackGesture: boolean = false;
private viewModelCallBack: BackEventViewModelCallBack | null = null;
private callBack: BackGestureModelCallBack | null = null;
constructor(callBack: BackGestureModelCallBack, viewModelCallBack: BackEventViewModelCallBack | null) {
this.callBack = callBack;
this.viewModelCallBack = viewModelCallBack;
}
public backGestureStart(event: GestureEvent, isLeftArea: boolean, startY: number) {
log.showInfo(`backGestureStart, startY:${startY}`);
if (scbEventExclusiveManager.isExclusive(EventType.GESTURE_BACK)) {
if (event.fingerList.length === 1) {
scbEventExclusiveManager.restoreAllEventExclusive(EventType.GESTURE_BACK);
}
this.isIgnoreBackGesture = true;
return;
}
this.viewModelCallBack?.onBackGestureStart(event, isLeftArea, startY);
}
public backGestureUpdate(event: GestureEvent, isLeftArea: boolean) {
if (this.isIgnoreBackGesture) {
return;
}
// if recognized as back gesture, update meta ball animation
let animProcess = MetaBallUtils.calculateAnimProcess(isLeftArea ? event.offsetX : -event.offsetX);
this.viewModelCallBack?.onBackGestureUpdate(isLeftArea, event, animProcess);
}
public backGestureEnd(event: GestureEvent, startX: number, startTime: number, isLeftArea: boolean) {
log.showInfo('backGestureEnd');
if (this.isIgnoreBackGesture) {
this.isIgnoreBackGesture = false;
this.callBack?.onIgnoreBackGesture();
return;
}
this.viewModelCallBack?.onBackGestureEnd();
const backDirection = isLeftArea ? HiSysBackEventData.LEFT : HiSysBackEventData.RIGHT
if (this.checkAccidentalTouch(event, isLeftArea, startX, startTime)) {
this.callBack?.onBackGestureCancelForUser(backDirection);
return;
}
// 当执行back手势时会打断当前旋转过程
let rotateEvent: RotateChangeEvent = RotateChangeEvent.create(RotateChangeEvent.ROTATE_STATUS_CANCEL);
EvtBus.post<RotateChangeEvent>(RotateChangeEvent, rotateEvent);
log.showInfo(`on pan gesture to send back. isLeftArea=${isLeftArea}`);
this.callBack?.onSendGestureBackTo(event);
}
// 抬手防back手势误触
protected checkAccidentalTouch(event: GestureEvent, isLeftArea: boolean, startX: number, startTime: number): boolean {
const duration = (event.timestamp - startTime) / GestureBackConstants.NS_TO_MS;
// 1. 如果抬手坐标在热区内,则back失败;
if (this.isInBackResponseRegion(event, isLeftArea, startX)) {
log.showInfo('Up antiAccidentalTouch, in ResponseRegion');
return true;
}
// 2. 如果手势时间小于120ms,滑动距离小于64px,则back失败
if (duration < GestureBackConstants.BACK_TIME_THRESHOLD &&
Math.abs(event.offsetX) < px2vp(GestureBackConstants.CANCEL_BACK_DISTANCE_THRESHOLD_120)) {
log.showInfo(`Up antiAccidentalTouch, less than 64px in ${gestureBackSettings.getBackTimeThreshold()}ms.`);
return true;
}
// 3. 如果手势时间大于150ms,滑动距离小于32vp,则back失败
if (duration > GestureBackConstants.CANCEL_BACK_TIME_THRESHOLD &&
Math.abs(event.offsetX) < GestureBackConstants.CANCEL_BACK_DISTANCE_THRESHOLD) {
log.showInfo(`Up antiAccidentalTouch, is less than 32vp over 150ms.`);
return true;
}
return false;
}
/**
* 是否在back手势热区内
*
* @param event
*/
public isInBackResponseRegion(event: GestureEvent, isLeftArea: boolean, startX: number): boolean {
log.showInfo(`startX:${vp2px(startX)}, event.offsetX:${vp2px(event.offsetX)}, isInBackResponseRegion:${vp2px(gestureBackSettings.getBackResponseRegionWidth())}`);
let isInBackResponseRegion = false;
if (isLeftArea) {
isInBackResponseRegion = startX + event.offsetX <= gestureBackSettings.getBackResponseRegionWidth();
} else {
isInBackResponseRegion = startX + event.offsetX > 0;
}
return isInBackResponseRegion;
}
public backGestureCancel() {
log.showInfo('backGestureCancel');
if (this.isIgnoreBackGesture) {
this.isIgnoreBackGesture = false;
this.callBack?.onIgnoreBackGesture();
return;
}
this.callBack?.onBackGestureCancel();
}
}