/*
* 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 { Dispatch, OhCombinedState, reduxSubscribe, Unsubscribe } from '../../../redux';
import lazy { Action } from '../../../redux/actions/Action';
import lazy { PersistType, PreferencesService } from '../../../service/preferences/PreferencesService';
import lazy { PublicTag } from '../../../service/preferences/PropTag';
import lazy { AdaptiveLayoutService } from '../../../service/UIAdaptive/AdaptiveLayoutService';
import lazy { DisplayService } from '../../../service/UIAdaptive/DisplayService';
import lazy { CommonConstants } from '../../../statistics/CommonConstants';
import lazy { DirectionToAngleUtil } from '../../../utils/DirectionToAngleUtil';
import lazy { HiLog } from '../../../utils/HiLog';
import lazy { PickerUtils } from '../../../utils/PickerUtils';
import lazy { XComponentSize } from '../../../utils/types';
import lazy { WindowDirection } from '../../../utils/WindowDirection';
import lazy { BaseComponent } from '../../../worker/BaseComponent';
import lazy { EventBus } from '../../../worker/eventbus/EventBus';
import lazy { EventBusManager } from '../../../worker/eventbus/EventBusManager';
import lazy { StoreManager } from '../../../worker/StoreManager';
import lazy { DeviceInfo } from '../../deviceinfo/DeviceInfo';
import display from '@ohos.display';
import lazy { ComponentIdKeys } from '../../../utils/ComponentIdKeys';
import lazy { WindowService } from '../../../service/window/WindowService';
import window from '@ohos.window';
import lazy { ActionType } from '../../../redux/actions/ActionType';
import lazy { WindowActionType } from '../../../redux/actions/WindowActionType';
import lazy { SettingsValueUtil } from '../../../utils/SettingsValueUtil';
/* instrument ignore file */
const TAG: string = 'RequestLocationDialog';
const ASPECT_RATIO_4_3 = 4 / 3;
const DIALOG_OFFSET: number = 120;
const LOCATION_BACKGROUND_COLOR: string = '#80000000';
const LOCATION_HEIGHT: number = 84;
const LOCATION_WIDTH_MAX: number = 480;
const MARGIN_LOCATION_TOP: number = 8;
const MARGIN_LOCATION_TOP_HORIZONTAL: number = 12;
const MARGIN_LOCATION_TOP_LANDSCAPE: number = 60;
const MARGIN_LOCATION_TOP_LANDSCAPE_HORIZONTAL: number = 84;
const MARGIN_LOCATION_LEFT_LANDSCAPE_HORIZONTAL: number = 88; //24为百宝箱左边距,48为百宝箱宽度,16为与百宝箱之间的距离。
//tablet
const TABLET_LOCATION_TOP: number = 60;
const TABLET_LOCATION_TOP_HORIZONTAL = 164;
// pc
const PCTABBAR_HEIGHT: number = 24;
const MARGIN_PCTABBAR_TOP: number = 32;
const LOCATION_ICON_SIZE: number = 32;
const FONT_SIZE_16: Resource = $r('sys.float.Body_L');
const FONT_SIZE_14: number = 14;
const FONT_COLOR: string = '#e6ffffff';
const FONT_COLOR_LEARN_MORE: string = '#FF317AF7';
const TEXT_HEIGHT_TITLE: number = 22;
const TEXT_HEIGHT_CONTENT: number = 19;
const TEXT_HEIGHT_CONTENT_SCROLL: number = 38;
const CANCEL_BTN_W: number = 18;
const MARGIN_LEFT_RIGHT: number = 16;
const PADDING_AROUND: number = 12;
const MARGIN_TEXT_LEFT_RIGHT: number = 12;
const LOCATION_BORDER_RADIUS: number = 16;
const BANNER_BACKGROUND_COLOR: string = '#80000000';
const BANNER_HEIGHT: number = 84;
const BANNER_WIDTH_MAX_LANDSCAPE: number = 360;
const BANNER_WIDTH_MAX_TAB: number = 480;
const MARGIN_BANNER_TOP: number = 8;
const MARGIN_BANNER_TOP_HORIZONTAL: number = 12;
const MARGIN_BANNER_TOP_LANDSCAPE: number = 60;
const MARGIN_BANNER_TOP_LANDSCAPE_HORIZONTAL: number = 84;
const MARGIN_BANNER_LEFT_LANDSCAPE_HORIZONTAL: number = 88; //24为百宝箱左边距,48为百宝箱宽度,16为与百宝箱之间的距离。
//tablet
const TABLET_BANNER_TOP: number = 60;
const BANNER_ICON_SIZE: number = 32;
const OFFSET_Y_CUSTOM_FILTER: number = 18;
class RequestLocationDispatcher {
private mDispatch: Dispatch = (data) => data;
public setDispatch(dispatch: Dispatch) {
this.mDispatch = dispatch;
}
}
@CustomDialog
export struct RequestLocationDialog {
controller: CustomDialogController;
private mSubscriber: Unsubscribe | null = null;
private mAction: RequestLocationDispatcher = new RequestLocationDispatcher();
private layoutService: AdaptiveLayoutService = AdaptiveLayoutService.getInstance();
private mEventBus: EventBus = EventBusManager.getInstance().getEventBus();
private mBase: BaseComponent = new BaseComponent();
@State previewPosition: number = 0;
@State windowHeight: number = 0;
@State windowWidth: number = 0;
@State xComponentWidth: number = 0;
@State xComponentHeight: number = 0;
@State @Watch('onDirectionChange') mDirection: WindowDirection = WindowDirection.TOP;
@State isShowSemiCollapsed: boolean = false;
@State isShowLandscape: boolean = false;
@State isHorizontal: boolean = false;
@State rotateAngle: number = 0;
@State isRotateLocked: boolean = true;
@State offsetFullScreen: number = 0;
@State isVdeLandscape: boolean = false;
private checkTreasureBoxTip: () => void = () => {};
@State offsetX: number = 0;
@State offsetY: number = 0;
@LocalStorageLink('changeCustomFilterImmersive') isCustomFilterImmersive: boolean = false;
@State @Watch('updateRotationStatus') mIsLockRotation: boolean = true;
@StorageLink('isOpenHdrBrightness') isOpenHdrBrightness: boolean = false;
aboutToAppear(): void {
HiLog.i(TAG, 'Picker privacy Location appears.');
this.mSubscriber = reduxSubscribe((state: OhCombinedState) => {
if (this.isSubscriptionNeeded(state.get<WindowDirection>('contextReducer', 'direction'))) {
this.mDirection = state.get<WindowDirection>('contextReducer', 'direction');
}
this.isRotateLocked = state.get<boolean>('windowReducer', 'isLockRotation');
this.windowHeight = state.get<number>('windowReducer', 'windowHeight');
this.xComponentWidth = state.get<number>('previewReducer', 'xComponentWidth');
this.xComponentHeight = state.get<number>('previewReducer', 'xComponentHeight');
this.isShowSemiCollapsed = state.get<boolean>('collapsReducer', 'isShowSemiCollapsed');
this.isShowLandscape = state.get<boolean>('collapsReducer', 'isShowLandscape');
this.isVdeLandscape = state.get<boolean>('collapsReducer', 'isVdeLandscape');
this.previewPosition = AdaptiveLayoutService.getInstance().previewPosition;
this.isHorizontal = DisplayService.getInstance().isHorizontal();
this.windowWidth = state.get<number>('windowReducer', 'windowWidth')
this.rotateAngle = state.get<number>('contextReducer', 'rotateAngle');
this.mIsLockRotation = state.get<boolean>('windowReducer', 'isLockRotation');
}, (dispatch: Dispatch) => {
this.mAction.setDispatch(dispatch);
});
this.getOffsetFullScreen();
this.uxInit();
this.onDirectionChange();
this.mEventBus.on(ActionType.ACTION_CHANGE_X_COMPONENT_SIZE,
(data: XComponentSize) => this.onChangeXComponentSize(), this.mBase.hashCode());
this.mEventBus.on(WindowActionType.ON_SIZE_CHANGE, () => this.uxInit(), this.mBase.hashCode());
}
aboutToDisappear(): void {
HiLog.i(TAG, 'Picker privacy Location disappears.');
this.mSubscriber?.destroy();
this.checkTreasureBoxTip();
}
private closeLocationButton(): void {
PreferencesService.getInstance()
.putPublicValue(PersistType.FOREVER, PublicTag.IS_REQUEST_LOCATION_DIALOG_LOADED, true);
AppStorage.setOrCreate('isRequestLocationLoaded', true);
// 开启相机在相机未弹出过位置卡片时初始化位置开关状态,供图库获取使用
SettingsValueUtil.setValueSync('camera_location_switch', '0');
this.controller.close();
}
private isVertical(): boolean {
return this.mDirection === WindowDirection.TOP || this.mDirection === WindowDirection.BOTTOM;
}
@Builder
LocationTextPart() {
Column() {
Row() {
Flex({ justifyContent: FlexAlign.SpaceBetween }) {
Scroll() {
Text($r('app.string.request_location_msg'))
.fontSize(FONT_SIZE_14)
.lineHeight(TEXT_HEIGHT_TITLE)
.maxFontScale(CommonConstants.TEXT_SIZE_HUGE)
.fontColor(FONT_COLOR)
.textAlign(TextAlign.Start)
.fontWeight(FontWeight.Medium)
.layoutWeight(1)
.id('Location_title')
}
.width(this.getTextPartWidth())
.scrollBar(BarState.Off)
this.LocationCancelPart();
}
}
.alignItems(VerticalAlign.Top)
.width('100%')
.height(2 * TEXT_HEIGHT_TITLE)
}
}
private getTextPartWidth(): Length {
if (DeviceInfo.isPhone()) {
return this.getBannerWidth() - CANCEL_BTN_W - PADDING_AROUND * 2;
}
return '';
}
@Builder
LocationCancelPart() {
Button() {
Image($r('app.media.ic_camera_cancel_w'))
.width(CANCEL_BTN_W)
.fillColor(Color.White)
.draggable(false)
.syncLoad(true)
}
.type(ButtonType.Normal)
.backgroundColor(Color.Transparent)
.key(ComponentIdKeys.LOCATION_DIALOG_CANCEL)
.onClick(() => {
HiLog.i(TAG, 'cancel location onClick.');
this.closeLocationButton();
})
}
build() {
Row() {
Column() {
this.LocationTextPart();
Text($r('app.string.allow_access')).fontColor('#5291FF').fontSize($r('sys.float.Subtitle_S')).margin({ top: 8 })
.key(ComponentIdKeys.LOCATION_DIALOG_ACCESS)
.maxFontScale(CommonConstants.TEXT_SIZE_HUGE)
.onClick(() => {
HiLog.i(TAG, 'allow access location onClick.');
this.closeLocationButton();
StoreManager.getInstance().postMessage(Action.isRequestPermission(true));
})
}.width('100%').alignItems(HorizontalAlign.Start)
}
.offset(this.getOffSet())
.translate({ y: this.isCustomFilterImmersive && this.isVertical() ? OFFSET_Y_CUSTOM_FILTER : 0 })
.margin({
left: this.isCustomFilterImmersive && this.mDirection === WindowDirection.LEFT ? OFFSET_Y_CUSTOM_FILTER : 0,
right: this.isCustomFilterImmersive && this.mDirection === WindowDirection.RIGHT ? OFFSET_Y_CUSTOM_FILTER : 0,
})
.width(this.getBannerWidth())
.borderRadius(LOCATION_BORDER_RADIUS)
.alignItems(VerticalAlign.Top)
.backgroundColor(LOCATION_BACKGROUND_COLOR)
.padding(PADDING_AROUND)
.rotate({ angle: DeviceInfo.isTablet() ? 0 : this.isShowSemiCollapsed ? 90 : this.rotateAngle })
}
private getOffSet(): Position {
if (WindowService.getInstance().getWindowStatus() === window.WindowStatusType.FLOATING) {
return { x: this.offsetX, y: this.offsetY - DIALOG_OFFSET + 24 };
}
return { x: this.offsetX, y: this.offsetY - DIALOG_OFFSET };
}
private getBannerWidth(): number {
let bannerWidth: number = this.isShowSemiCollapsed ? this.xComponentHeight - 2 * MARGIN_LEFT_RIGHT :
this.xComponentWidth - 2 * MARGIN_LEFT_RIGHT;
let maxWidth = DeviceInfo.isTablet() ? BANNER_WIDTH_MAX_TAB : BANNER_WIDTH_MAX_LANDSCAPE;
return Math.min(maxWidth, bannerWidth);
}
private updateOffsetXY(): void {
this.offsetX = this.getBannerOffsetX();
this.offsetY = this.getBannerOffsetY();
}
private onChangeXComponentSize(): void {
this.getOffsetFullScreen();
this.bannerTranslateAnimation();
}
private bannerTranslateAnimation(): void {
animateTo({ curve: Curve.Sharp, duration: 250 }, () => {
this.updateOffsetXY();
});
}
private getBannerOffsetX(): number {
let coefficient: number = this.mDirection === WindowDirection.LEFT ? -1 : 1;
if (DeviceInfo.isTablet()) {
return 0;
} else if (this.isHorizontal) {
return this.isRotateLocked && this.isVertical() ? (-1 / 2 * this.xComponentHeight + 30) * coefficient : 0;
} else {
let offset =
this.isShowLandscape ? -1 / 2 * this.windowHeight + MARGIN_BANNER_LEFT_LANDSCAPE_HORIZONTAL + BANNER_HEIGHT :
(1 / 2 * this.xComponentWidth - MARGIN_BANNER_TOP_HORIZONTAL - 1 / 2 * BANNER_HEIGHT) * coefficient;
return this.isShowSemiCollapsed ? BANNER_HEIGHT : !this.isVertical() ? offset : 0;
}
}
private getBannerOffsetY(): number {
if (DeviceInfo.isTablet()) {
return this.getTabletBannerOffsetY();
} else if (this.isShowSemiCollapsed) {
return 1 / 2 * this.windowHeight - BANNER_ICON_SIZE - PADDING_AROUND + 2;
} else {
return this.getBannerPhoneOffsetY();
}
}
private getTabletBannerOffsetY(): number {
if (DisplayService.getInstance().isOrientationVertical()) {
const previewPosition4to3: number = (this.windowHeight - this.windowWidth * 4 / 3) / 2;
const previewPosition1to1: number = (this.windowHeight - this.windowWidth) / 2;
const offset: number = previewPosition4to3 + (previewPosition1to1 - previewPosition4to3 - BANNER_HEIGHT) / 2;
if (DeviceInfo.isTablet() && this.isRotateLocked) {
return offset;
}
return this.mDirection === WindowDirection.TOP ? offset : this.windowHeight - offset - BANNER_HEIGHT;
} else if (DeviceInfo.isTablet() && this.isRotateLocked) {
return TABLET_BANNER_TOP;
} else {
return this.mDirection === WindowDirection.TOP ? TABLET_BANNER_TOP :
this.windowHeight - TABLET_BANNER_TOP - BANNER_HEIGHT;
}
}
private getOffsetFullScreen(): void {
const previewPosition4to3: number = this.layoutService.previewPosition4to3;
this.previewPosition = this.layoutService.previewPosition;
const isFullRatio = this.previewPosition === 0;
this.offsetFullScreen = isFullRatio ? previewPosition4to3 : 0;
}
private getBannerPhoneOffsetY(): number {
if (this.isHorizontal) {
return this.isRotateLocked && this.isVertical() ?
1 / 2 * this.xComponentWidth - MARGIN_BANNER_LEFT_LANDSCAPE_HORIZONTAL : MARGIN_BANNER_TOP_LANDSCAPE_HORIZONTAL;
}
if (this.isVertical()) {
return this.isShowLandscape ? MARGIN_BANNER_TOP_LANDSCAPE :
this.previewPosition + MARGIN_BANNER_TOP + this.offsetFullScreen;
}
return this.isShowLandscape ? 1 / 2 * this.xComponentHeight - 30 :
this.layoutService.previewPosition1to1 + 1 / 3 * this.getBannerWidth() + 10 +
1 / 2 * (this.xComponentWidth - this.getBannerWidth());
}
private onDirectionChange(): void {
this.updateOffsetXY();
if (!this.isRotateLocked) {
this.rotateAngle = PickerUtils.getIsPicker() ? 0 : DirectionToAngleUtil.geViewRotate(this.mDirection);
} else {
if (DeviceInfo.isTablet() && !PickerUtils.getIsTabletFullScreenFromReducer()) {
this.rotateAngle = 0;
return;
}
this.rotateAngle = this.getStackRotateAngle();
if (PickerUtils.getIsExpandedTopBottomSplitScreenFromReducer()) {
this.rotateAngle -= 90;
}
}
}
private updateRotationStatus(): void {
this.getStackRotateAngle();
}
private getStackRotateAngle(): number {
if ((this.isShowLandscape || DeviceInfo.isTablet()) && !this.isShowSemiCollapsed && !this.mIsLockRotation) {
return 0;
}
return this.rotateAngle;
}
private uxInit(): void {
this.updateOffsetXY();
}
//TODO 这里闪退先屏蔽
private isSubscriptionNeeded(direction: WindowDirection): boolean {
// const orientation: display.Orientation = DisplayService.getInstance().getDisplay().orientation;
// if (DeviceInfo.isTablet() && this.isRotateLocked &&
// !this.isTabletDirectionSameAxisWithOrientation(direction, orientation)) {
// return false;
// }
return true;
}
private isTabletDirectionSameAxisWithOrientation(direction: WindowDirection,
orientation: display.Orientation): boolean {
const verticalAxis: display.Orientation[] = [display.Orientation.PORTRAIT, display.Orientation.PORTRAIT_INVERTED];
if (verticalAxis.includes(orientation)) {
return direction === WindowDirection.TOP || direction === WindowDirection.BOTTOM;
} else {
return direction === WindowDirection.LEFT || direction === WindowDirection.RIGHT;
}
}
}