c77fb700创建于 2025年1月16日历史提交
/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * 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 { CommonConstants as Const, Logger } from '@ohos/utils';
import MapController from '../controller/MapController';
import { AddressItem, Location } from '../viewmodel/AddressItem';
import { PositionItem } from '../viewmodel/PositionItem';
import MapModel, { PositionList } from '../viewmodel/MapModel';

@Component
export struct Map {
  private panOption: PanGestureOptions = new PanGestureOptions({ direction: PanDirection.All });
  @State screenMapWidth: number = 0;
  @State screenMapHeight: number = 0;
  @State mapWidth: number = 0;
  @State mapHeight: number = 0;
  @State mapX: number = 0;
  @State mapY: number = 0;
  @Provide data: AddressItem = new AddressItem();

  build() {
    Stack({ alignContent: Alignment.BottomEnd }) {
      Column() {
        Stack({ alignContent: Alignment.TopStart }) {
          ForEach(this.data.locations, (item: Location) => {
            Image(this.data.icon)
              .width(Const.MAP_LANDMARKS_SIZE)
              .height(Const.MAP_LANDMARKS_SIZE)
              .offset({
                x: item.positionX,
                y: item.positionY
              })
            Text(this.data.name)
              .fontSize($r('app.float.map_text_size'))
              .fontColor(this.data.textColor)
              .fontWeight(FontWeight.Bold)
              .offset({
                x: item.positionX + Const.MAP_TEXT_OFFSET_X,
                y: item.positionY + Const.MAP_TEXT_OFFSET_Y
              })
          }, (item: Location) => JSON.stringify(item))
        }
        .backgroundImage($r('app.media.ic_nav_map'))
        .backgroundImageSize(ImageSize.Cover)
        .width(this.mapWidth)
        .height(this.mapHeight)
        .offset({ x: this.mapX, y: this.mapY })
      }
      .height(Const.FULL_PERCENT)
      .width(Const.FULL_PERCENT)
      .justifyContent(FlexAlign.Start)
      .alignItems(HorizontalAlign.Start)
      .onAreaChange((oldVal: Area, newVal: Area) => {
        if (this.screenMapWidth === 0 || this.screenMapHeight === 0) {
          this.screenMapWidth = Number(newVal.width);
          this.screenMapHeight = Number(newVal.height);
          this.mapHeight = this.screenMapHeight;
          this.mapWidth = Const.MAP_WIDTH * this.mapHeight / Const.MAP_HEIGHT;
          this.mapX = (this.screenMapWidth - this.mapWidth) / Const.DOUBLE_OR_HALF;
          MapController.initLeftTop(this.screenMapWidth, this.mapWidth);
        }
      })
      .gesture(
        GestureGroup(GestureMode.Exclusive,
          PinchGesture({ fingers: Const.MAP_FINGER_COUNT })
            .onActionUpdate((event: GestureEvent) => {
              MapController.pinchUpdate(event, (ratio: number) => {
                Logger.info(`======pinchUpdate====mapx: ${this.mapX},===mapy: ${this.mapY}`);
                this.mapWidth *= ratio;
                this.mapHeight *= ratio;
                let offsetX = (1 - ratio) * (this.screenMapWidth /
                Const.DOUBLE_OR_HALF - this.mapX);
                let offsetY = (1 - ratio) * (this.mapHeight /
                Const.MAP_ZOOM_RATIO / Const.DOUBLE_OR_HALF - this.mapY);
                this.mapX += offsetX;
                this.mapY += offsetY;
                this.data.locations = this.data.locations.map((item: Location) => {
                  item.positionX = item.oriPositionX * MapController.mapMultiples() -
                    Const.MAP_LANDMARKS_SIZE * Const.MAP_ZOOM_RATIO / Const.DOUBLE_OR_HALF;
                  item.positionY = item.oriPositionY * MapController.mapMultiples() -
                    Const.MAP_LANDMARKS_SIZE * Const.MAP_ZOOM_RATIO;
                  return item;
                })
                this.zoomOutCheck();
              });
            })
            .onActionEnd(() => {
              MapController.pinchEnd(this.screenMapWidth, this.mapWidth, this.mapHeight);
            }),
          TapGesture({ count: Const.MAP_FINGER_COUNT })
            .onAction(() => {
              MapController.tapAction((isMaxTime: boolean): number[] => {
                Logger.info(`==========isMaxTime: ${isMaxTime}`);
                if (isMaxTime) {
                  return [];
                }
                Logger.info(`======tapAction====mapx: ${this.mapX},===mapy: ${this.mapY}`);
                this.mapWidth *= Const.MAP_ZOOM_RATIO;
                this.mapHeight *= Const.MAP_ZOOM_RATIO;
                let offsetX = (1 - Const.MAP_ZOOM_RATIO) *
                  (this.screenMapWidth / Const.DOUBLE_OR_HALF - this.mapX);
                let offsetY = (1 - Const.MAP_ZOOM_RATIO) * (this.mapHeight /
                Const.MAP_ZOOM_RATIO / Const.DOUBLE_OR_HALF - this.mapY);
                this.mapX += offsetX;
                this.mapY += offsetY;
                this.data.locations = this.data.locations.map((item: Location) => {
                  item.positionX = item.oriPositionX * MapController.mapMultiples() -
                    Const.MAP_LANDMARKS_SIZE * Const.MAP_ZOOM_RATIO / Const.DOUBLE_OR_HALF;
                  item.positionY = item.oriPositionY * MapController.mapMultiples() -
                    Const.MAP_LANDMARKS_SIZE * Const.MAP_ZOOM_RATIO;
                  return item;
                })
                // Calculate the farthest coordinate of the upper left corner.
                let minX = (this.screenMapWidth - this.mapWidth);
                let minY = this.mapHeight / MapController.mapMultiples() - this.mapHeight;
                return [minX, minY];
              });
            }),
          PanGesture(this.panOption)
            .onActionUpdate((event: GestureEvent) => {
              MapController.panUpdate(event, (mapPanX: number, mapPanY: number, leftTop: Array<number>) => {
                this.mapX += mapPanX;
                this.mapY += mapPanY;
                if (this.mapX < leftTop[0]) {
                  this.mapX = leftTop[0];
                }
                if (this.mapX > 0) {
                  this.mapX = 0;
                }
                if (this.mapY < leftTop[1]) {
                  this.mapY = leftTop[1];
                }
                if (this.mapY > 0) {
                  this.mapY = 0;
                }
              });
            })
            .onActionEnd(() => {
              MapController.panEnd();
            })
        )
      )

      CustomPanel({
        mapWidth: this.mapWidth,
        mapHeight: this.mapHeight,
        screenMapWidth: this.screenMapWidth,
        screenMapHeight: this.screenMapHeight,
        mapX: this.mapX,
        mapY: this.mapY,
      })
    }
  }

  zoomOutCheck(): void {
    // Top left corner.
    if (this.mapX > 0) {
      this.mapX = 0;
    }
    if (this.mapY > 0) {
      this.mapY = 0;
    }
    // Lower right corner.
    if ((this.mapX + this.mapWidth) < this.screenMapWidth) {
      this.mapX = this.screenMapWidth - this.mapWidth;
    }
    if ((this.mapY + this.mapHeight) < (this.mapHeight / MapController.mapMultiples())) {
      this.mapY = this.mapHeight / MapController.mapMultiples() - this.mapHeight;
    }
  }
}

@Component
struct PositionGridView {
  positionItem: PositionItem = new PositionItem();

  build() {
    Column() {
      Image(this.positionItem.icon)
        .width($r('app.float.navigation_panel_icon_size'))
        .height($r('app.float.navigation_panel_icon_size'))
        .margin($r('app.float.navigation_panel_icon_margin'))
      Text(this.positionItem.text)
        .fontSize($r('app.float.navigation_icon_text'))
    }
  }
}

@Component
struct CustomPanel {
  @State positionList: Array<PositionItem> = PositionList;
  @State panelOpacity: number = Const.PANEL_HIGH_OPACITY;
  @State panelHeight: number = Const.PANEL_FULL_HEIGHT;
  @State flag: boolean = true;
  @State isDownImage: boolean = true;
  @State imageEnable: boolean = true;
  @State iconOpacity: number = Const.PANEL_HIGH_OPACITY;
  @Consume data: AddressItem;
  @Link mapWidth: number;
  @Link mapHeight: number;
  @Link screenMapWidth: number;
  @Link screenMapHeight: number;
  @Link mapX: number;
  @Link mapY: number;

  build() {
    Column() {
      Column() {
        Image(this.isDownImage ? $r('app.media.ic_panel_down') : $r('app.media.ic_panel_up'))
          .enabled(this.imageEnable)
          .height($r('app.float.navigation_icon_down'))
          .onClick(() => {
            this.upAndDown();
          })
      }
      .opacity(this.iconOpacity)
      .backgroundColor($r('app.color.custom_panel_background'))
      .borderRadius({
        topLeft: $r('app.float.navigation_panel_radius'),
        topRight: $r('app.float.navigation_panel_radius')
      })
      .width(Const.FULL_PERCENT)

      Column() {
        Search({ placeholder: Const.PANEL_PLACEHOLDER })
          .width(Const.SEARCHBAR_WIDTH)
          .height($r('app.float.navigation_search_height'))
          .searchIcon({
            src: $r('app.media.seach')
          })
          .cancelButton({
            style: CancelButtonStyle.CONSTANT,
            icon: {
              src: $r('app.media.cancel')
            }
          })
          .onSubmit((value: string) => {
            this.positionList.forEach((item: PositionItem) => {
              if (MapController.getResourceString(item.text) === value) {
                this.data = MapModel.calCoordinateByLonAndLat(item.lngLat, item.type, this.mapWidth, this.mapHeight);
                MapController.calLandmarksPosition(this.data);
                this.setFirstLandmarksCenter();
                this.upAndDown();
              }
            }, (item: PositionItem) => JSON.stringify(item));
          })
        Grid() {
          ForEach(this.positionList, (item: PositionItem) => {
            GridItem() {
              PositionGridView({ positionItem: item })
                .enabled(this.imageEnable)
                .onClick(() => {
                  this.data = MapModel.calCoordinateByLonAndLat(item.lngLat, item.type, this.mapWidth, this.mapHeight);
                  MapController.calLandmarksPosition(this.data);
                  Logger.info(`=====before setFirstLandmarksCenter=====mapx: ${this.mapX},===mapy: ${this.mapY}`);
                  this.setFirstLandmarksCenter();
                  Logger.info(`=====after setFirstLandmarksCenter=====mapx: ${this.mapX},===mapy: ${this.mapY}`);
                  this.upAndDown();
                })
            }
          }, (item: PositionItem) => JSON.stringify(item))
        }
        .columnsTemplate(Const.GRID_COLUMNS)
        .columnsGap($r('app.float.navigation_column_gap'))
        .rowsGap($r('app.float.navigation_row_gap'))
        .padding($r('app.float.navigation_grid_padding'))
      }
      .opacity(this.panelOpacity)
      .height(this.panelHeight)
      .animation({
        duration: Const.ANIMATION_DURATION,
        curve: Curve.EaseOut,
        iterations: 1,
        playMode: PlayMode.Normal
      })
      .backgroundColor($r('app.color.custom_panel_background'))
    }
    .width(Const.FULL_PERCENT)
    .borderRadius($r('app.float.navigation_panel_radius'))
  }

  upAndDown() {
    // Prevents repeated clicks during fade-down.
    this.imageEnable = false;
    if (this.isDownImage) {
      this.panelOpacity = 0;
      this.panelHeight = 0;
      this.iconOpacity = Const.PANEL_LOW_OPACITY;
    } else {
      this.panelHeight = Const.PANEL_FULL_HEIGHT;
      this.panelOpacity = Const.PANEL_HIGH_OPACITY;
      this.iconOpacity = Const.PANEL_HIGH_OPACITY;
    }
    this.isDownImage = !this.isDownImage;
    this.imageEnable = true;
  }

  setFirstLandmarksCenter(): void {
    let locations: Location[] = this.data.locations;
    if (locations.length > 0) {
      // Horizontally centered.
      this.mapX = this.screenMapWidth / Const.DOUBLE_OR_HALF - locations[0].positionX;
      if (this.mapX > 0) {
        this.mapX = 0;
      }
      if (this.mapX < (this.screenMapWidth - this.mapWidth)) {
        this.mapX = this.screenMapWidth - this.mapWidth;
      }
      // Vertically centered.
      this.mapY = this.screenMapHeight / Const.DOUBLE_OR_HALF - locations[0].positionY;
      if (this.mapY > 0) {
        this.mapY = 0;
      }
      if (this.mapY < (this.screenMapHeight - this.mapHeight)) {
        this.mapY = this.screenMapHeight - this.mapHeight;
      }
    }
  }
}