/*
* 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 lazy { HiLog } from '../../utils/HiLog';
import lazy { OhCombinedState, Unsubscribe, Dispatch } from '../../redux';
import lazy { Action } from '../../redux/actions/Action';
import lazy { RecordingState } from '../../function/recordcontrol/RecordAction';
import lazy { DirectionToAngleUtil } from '../../utils/DirectionToAngleUtil';
import lazy { ModeType } from '../../mode/ModeType';
import animator, { AnimatorOptions, AnimatorResult } from '@ohos.animator';
import lazy { ChangeState, getStates, reduxSubscribe } from '../../redux';
import lazy { PositionType } from '../../utils/types';
import lazy { DeviceInfo } from '../deviceinfo/DeviceInfo';
import lazy { ModeListManager } from '../../mode/ModeListManager';
import lazy { AccessibilityUtils } from '../../utils/AccessibilityUtils';
import ResGetter from '../../utils/ResGetter';
import lazy { PickerUtils } from '../../utils/PickerUtils';
import lazy { WindowDirection } from '../../utils/WindowDirection';
import lazy { DirectionCorrectionService } from '../../service/direction/DirectionCorrectionService';
import lazy { SystemLanguageUtil } from '../../utils/SystemLanguageUtil';
import lazy { WindowService } from '../../service/window/WindowService';
import window from '@ohos.window';
/* instrument ignore file */
const TAG: string = 'BigText';
class StateStruct {
public mode: ModeType = ModeType.NONE;
public isRecordingPaused: boolean = false;
public videoState: RecordingState = RecordingState.READY;
public direction: number = 0;
}
class BigTextDispatcher {
private mDispatch: Dispatch = (data) => data;
public setDispatch(dispatch: Dispatch) {
this.mDispatch = dispatch;
}
public updateShowBigTextFlag(flag: boolean): void {
this.mDispatch(Action.updateShowBigTextFlag(flag));
};
}
const BIG_TEXT_POSITION_Y: number = 64;
const BIG_TEXT_POSITION_Y_TABLET: number = 268;
const BIG_TEXT_LINE_HEIGHT: string = '64vp';
export const BIG_TEXT_HEIGHT = 96;
@Component
export struct BigText {
@State state: StateStruct = new StateStruct();
@State bigTextOpacity: number = 0;
@State isShowSemiCollapsed: boolean = false;
@State isShowLandscape: boolean = false;
private mAction: BigTextDispatcher = new BigTextDispatcher();
private currentRotate: number = 0;
private mSubscriber: Unsubscribe | null = null;
private bigTextAppearAnimator: AnimatorResult | null = null; // 显示动效
private bigTextDisAppearAnimator: AnimatorResult | null = null; // 隐藏动效
@State semiComponentWidth: number = 0;
@State semiComponentHeight: number = 0;
@State isLockRotation: boolean = true;
@State isShowTricollaps: boolean = false;
@StorageLink('enableScreenReader') enableScreenReader: boolean = false;
@StorageLink('isOpenTouchGuide') isOpenTouchGuide: boolean = false;
@State isVdeSemiCollapsed: boolean = false;
@State isShowLowAngleShotView: boolean = false;
@State isShowRingLightView: boolean = false;
@Prop isHdr: boolean = false;
public aboutToAppear() {
HiLog.i(TAG, 'aboutToAppear E.');
this.mSubscriber = reduxSubscribe((state: OhCombinedState, changeState: ChangeState<StateStruct>) => {
// 模式不一致时,更新大字体内容
if (this.state.mode != state.get<ModeType>('modeReducer', 'mode') && this.state.mode != ModeType.NONE) {
this.bigTextAppearAnimator?.pause();
this.bigTextDisAppearAnimator?.pause();
this.bigTextAppearAnim();
}
changeState(this.state, {
mode: state.get<ModeType>('modeReducer', 'mode'),
isRecordingPaused: state.get<RecordingState>('recordReducer', 'recordingState') === RecordingState.PAUSED,
videoState: state.get<RecordingState>('recordReducer', 'recordingState'),
direction: state.get<WindowDirection>('contextReducer', 'direction'),
});
this.semiComponentWidth = state.get<number>('previewReducer', 'xComponentWidth');
this.semiComponentHeight = state.get<number>('previewReducer', 'xComponentHeight');
this.isVdeSemiCollapsed = state.get<boolean>('collapsReducer', 'isVdeSemiCollapsed');
this.isShowSemiCollapsed = state.get<boolean>('collapsReducer', 'isShowSemiCollapsed');
this.isShowLandscape = state.get<boolean>('collapsReducer', 'isShowLandscape');
this.isShowTricollaps = state.get<boolean>('collapsReducer', 'isShowTricollaps');
this.isLockRotation = state.get<boolean>('windowReducer', 'isLockRotation');
this.currentRotate = state.get<number>('contextReducer', 'rotateAngle');
this.isShowRingLightView = state.get<boolean>('ringLightReducer', 'isShowRingLightView');
this.isShowLowAngleShotView = state.get<boolean>('collapsReducer', 'isShowLowAngleShotView');
}, (dispatch: Dispatch): void => {
this.mAction.setDispatch(dispatch);
});
let windowStatus: window.WindowStatusType = WindowService.getInstance().getWindowStatus();
if (this.isSubscriptionNeeded() && ! DeviceInfo.isPc() &&
(windowStatus != window.WindowStatusType.FLOATING && windowStatus != window.WindowStatusType.SPLIT_SCREEN)) {
const direction: WindowDirection = DirectionCorrectionService.directionCorrection(this.state.direction,
getStates().get<boolean>('windowReducer', 'isLockRotation'));
this.currentRotate = DirectionToAngleUtil.geViewRotate(direction);
}
HiLog.i(TAG, 'aboutToAppear X.');
this.bigTextAppearAnim();
}
private isSubscriptionNeeded(): boolean { // 锁定状态
return !(((this.isShowLandscape && !this.isShowSemiCollapsed) || DeviceInfo.isTablet() || this.isShowTricollaps) &&
this.isLockRotation);
}
public aboutToDisappear(): void {
HiLog.i(TAG, 'aboutToDisappear invoke.');
this.mSubscriber?.destroy();
}
public getText(): Resource | string {
if (this.state.videoState < RecordingState.RECORDING || this.state.videoState === RecordingState.STOPPING) {
return ModeListManager.getInstance().getModeName(this.state.mode);
} else {
if (this.state.isRecordingPaused) {
return $r('app.string.video_pause');
} else {
return $r('app.string.video_resume');
}
}
}
private bigTextAppearAnim() {
this.bigTextAppearAnimator?.pause();
this.bigTextDisAppearAnimator?.pause();
let options: AnimatorOptions = {
duration: 150,
easing: 'cubic-bezier(0.33, 0.0, 0.67, 1.0)',
delay: 0,
fill: 'forwards',
direction: 'normal',
iterations: 1,
begin: 0,
end: 1
};
if (this.bigTextAppearAnimator) {
try {
this.bigTextAppearAnimator.reset(options);
} catch (error) {
HiLog.e(TAG, `bigTextAppearAnim error: ${error?.code}.`);
}
} else {
this.bigTextAppearAnimator = animator.create(options);
}
this.bigTextAppearAnimator.onframe = (value) => {
this.bigTextOpacity = value;
};
this.bigTextAppearAnimator.onfinish = () => {
this.bigTextAppearAnimator = null;
this.bigTextDisAppearAnim();
}
this.bigTextAppearAnimator.oncancel = () => {
this.bigTextOpacity = 0;
this.bigTextAppearAnimator = null;
}
this.bigTextAppearAnimator?.play();
}
private bigTextDisAppearAnim() {
let options: AnimatorOptions = {
duration: 150,
easing: 'cubic-bezier(0.33, 0.0, 0.67, 1.0)',
delay: this.state.videoState === RecordingState.READY && DeviceInfo.isPc() ? 350 : 100,
fill: 'forwards',
direction: 'normal',
iterations: 1,
begin: 1,
end: 0
};
if (this.bigTextDisAppearAnimator) {
try {
this.bigTextDisAppearAnimator.reset(options);
} catch (error) {
HiLog.e(TAG, `bigTextDisAppearAnimator reset error: ${error?.code}`);
}
} else {
try {
this.bigTextDisAppearAnimator = animator.create(options);
} catch (err) {
this.bigTextDisAppearAnimator = null;
this.bigTextOpacity = 0;
this.mAction.updateShowBigTextFlag(false);
HiLog.e(TAG, `bigTextDisAppearAnimator create error: ${err?.code}`);
return;
}
}
this.bigTextDisAppearAnimator.onframe = (value) => {
this.bigTextOpacity = value;
};
this.bigTextDisAppearAnimator.onfinish = () => {
this.bigTextDisAppearAnimator = null;
this.mAction.updateShowBigTextFlag(false);
}
this.bigTextDisAppearAnimator.oncancel = () => {
this.bigTextOpacity = 1;
this.bigTextDisAppearAnimator = null;
}
this.bigTextDisAppearAnimator?.play();
// 仅在无障碍服务下生效
if (this.isOpenTouchGuide) {
this.handleSendAccessibilityText();
} else if (this.enableScreenReader && !this.isOpenTouchGuide) {
if (this.state.videoState !== RecordingState.READY && this.state.videoState !== RecordingState.STOPPING) {
return;
} else {
this.handleSendAccessibilityText();
}
}
}
private handleSendAccessibilityText() {
AccessibilityUtils.sendAnnounceAccessibilityEvent(ResGetter.getStringSafe(this.getText()));
}
private getTextPosition(): PositionType {
if (DeviceInfo.isPc()) {
return {
x: this.isShowSemiCollapsed && !(this.state.direction === 1 || this.state.direction === 0) ?
getStates().get<number>('windowReducer', 'windowWidth') / 2 - 180 : 0,
y: getStates().get<number>('windowReducer', 'windowHeight') / 2 - BIG_TEXT_POSITION_Y
};
}
if (this.isVdeSemiCollapsed) {
return {
x: 0,
y: -100
};
}
if (DeviceInfo.isTablet() || this.isShowTricollaps) {
return {
x: 0,
y: 0
};
}
return {
x: 0,
y: 0
};
}
build() {
if ((this.isShowLandscape && !this.isShowSemiCollapsed && !DeviceInfo.isPc()) || this.isShowTricollaps) {
Text(this.getText())
.fontSize($r('sys.float.ohos_id_text_size_headline4'))
.lineHeight(BIG_TEXT_LINE_HEIGHT)
.maxFontScale(1)
.fontColor(Color.White)
.fontWeight(300)
.textAlign(TextAlign.Center)
.opacity(this.bigTextOpacity)
.textShadow({ radius: 3, color: '#33000000', offsetX: 0, offsetY: 0 })
.rotate({
angle: DeviceInfo.isPc() ? 0 :
(this.isShowSemiCollapsed ? 90 : this.currentRotate)
})
.fontFeature('\"ss01\" on')
} else {
Flex({ alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
Text(this.getText())
.fontSize((this.getIconLength(this.getText()) > 10) ?
$r('sys.float.ohos_id_text_size_headline5')
: $r('sys.float.ohos_id_text_size_headline4'))
.lineHeight(BIG_TEXT_LINE_HEIGHT)
.maxFontScale(1)
.fontColor(Color.White)
.fontWeight(300)
.textAlign(TextAlign.Center)
.width('100%')
.opacity(this.bigTextOpacity)
.textShadow({ radius: 3, color: '#33000000', offsetX: 0, offsetY: 0 })
.rotate({
angle: this.getTextRotate(),
})
.fontFeature('\"ss01\" on')
}
.width(this.isShowSemiCollapsed ? 360 : '100%')
.position(this.getTextPosition())
}
}
private getIconLength(val: Resource | string): number {
return ResGetter.getStringSafe(val).length;
}
private getTextRotate(): number {
if (DeviceInfo.isPc()) {
return 0;
} else if (this.isShowSemiCollapsed) {
return 90;
} else {
return this.currentRotate;
}
}
}