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 { common, Permissions, Want } from '@kit.AbilityKit';
import { promptAction } from '@kit.ArkUI';
import { AsyncCallback, BusinessError } from '@kit.BasicServicesKit';
import { geoLocationManager } from '@kit.LocationKit';
import { MapComponent, mapCommon, map, navi } from '@kit.MapKit';
import { AudioPlayerService, AudioPlayerStatus } from '@ohos/audioplayer';
import {
  BreakpointTypeEnum,
  CommonConstants,
  CommonUtil,
  Logger,
  PermissionUtil,
  ResourceUtil
} from '@ohos/utils';
import ZonesItem from '../model/ZonesItem';
import ZonesModel, { TravelModeType } from '../model/ZonesModel';
import { RouteActionType, RoutesSheet } from '../components/RoutesSheet';
import { ModalActionType, ZoneDetailModal } from '../components/ZoneDetailModal';
import { intl } from '@kit.LocalizationKit';

const TAG: string = '[MapView]';

@Component
export struct MapView {
  private mapOptions?: mapCommon.MapOptions;
  private callback?: AsyncCallback<map.MapComponentController>;
  private mapController?: map.MapComponentController;
  @Provide('positionX') positionX: number = 24;
  private pointIds: string[] = [];
  permissions: Permissions[] = ['ohos.permission.LOCATION', 'ohos.permission.APPROXIMATELY_LOCATION'];
  zonesList: ZonesItem[] = ZonesModel.getZonesList();
  currentSheetHeight: number = 0;
  requestInfo: geoLocationManager.LocationRequest = {
    'priority': geoLocationManager.LocationRequestPriority.FIRST_FIX,
    'timeInterval': 0,
    'distanceInterval': 0,
    'maxAccuracy': 0
  };
  @StorageProp('naviIndicatorHeight') naviIndicatorHeight: number = 0;
  @StorageProp('currentBreakpoint') @Watch('handleBreakpointChange') currentBreakpoint: string = BreakpointTypeEnum.MD;
  @Provide('introductionData') selectedZone: ZonesItem = this.zonesList[0];
  currentLocation: mapCommon.LatLng = this.selectedZone.location;
  @State detailSheetHeight: number = 0;
  @State distance: string = '';
  @State showRoutes: boolean = false;
  @State @Watch('sheetVisiblyChange') showSheet: boolean = false;
  @State showDetail: boolean = false
  @State routePaths: Array<navi.Route> = [];
  @State selectedMode: number = 0;
  @State selectedRoute: number = 0;

  async aboutToAppear() {
    this.initMapOptions();
    PermissionUtil.getPermissionUtil().requestPermissions(this.permissions);
  }

  aboutToDisappear(): void {
    if (canIUse('SystemCapability.Map.Core')) {
      this.mapController?.off('mapLoad', () => {
        Logger.info(TAG, `off-mapLoad`);
      });
      this.mapController?.off('mapClick', () => {
        Logger.info(TAG, `off-mapClick`);
      });
      this.mapController?.off('pointAnnotationClick', () => {
        Logger.info(TAG, `off-pointAnnotationClick`);
      });
    }
  }

  handleBreakpointChange() {
    if (this.currentBreakpoint !== BreakpointTypeEnum.SM && this.showRoutes) {
      this.showSheet = true;
      this.showRoutes = false;
    } else if (this.currentBreakpoint === BreakpointTypeEnum.SM && this.showSheet) {
      this.showRoutes = true;
      this.showSheet = false;
    }
  }

  sheetVisiblyChange() {
    Logger.info(TAG, 'sheetVisiblyChange showSheet:' + this.showSheet + '   showRoutes:' + this.showRoutes);
    if ((this.showSheet || this.showRoutes) && !(this.showRoutes && this.showSheet)) {
      this.currentSheetHeight = this.detailSheetHeight;
      this.detailSheetHeight = 320 - this.naviIndicatorHeight - 56;
    }
    if (!this.showSheet && !this.showRoutes) {
      this.detailSheetHeight = this.currentSheetHeight;
      this.redrawScenes();
    }
  }

  initMapOptions(): void {
    let target: mapCommon.LatLng = {
      latitude: 22.880878379924344,
      longitude: 113.88852986984118
    };
    let cameraPosition: mapCommon.CameraPosition = {
      target: target,
      zoom: 16,
    };
    this.mapOptions = {
      position: cameraPosition,
      scaleControlsEnabled: true
    };
    this.callback = async (err, mapController) => {
      if (!err) {
        this.mapController = mapController;
        if (canIUse('SystemCapability.Map.Core')) {
          this.mapController.setBuildingEnabled(true);
          this.mapController.setDayNightMode(mapCommon.DayNightMode.AUTO);
          this.mapController.setMyLocationStyle({ icon: $r('app.media.ic_me') });
          this.drawBuilding();
          this.mapController.on('mapLoad', async () => {
            Logger.info(TAG, `on-mapLoad`);
            const hasPermissions = await PermissionUtil.getPermissionUtil().checkPermissions(this.permissions);
            if (!hasPermissions) {
              PermissionUtil.getPermissionUtil().requestPermissions(this.permissions, () => {
                this.setMyLocationControl()
              });
            } else {
              this.setMyLocationControl();
            }
          })
          this.mapController.setMinZoom(2);
          this.mapController.setMaxZoom(18);

          this.mapController.on('mapClick', (position) => {
            Logger.info(TAG, `on-mapClick position = ${JSON.stringify(position)}`);
          });

          this.mapController.on('pointAnnotationClick', (pointAnnotation: map.PointAnnotation) => {
            let index: number = this.pointIds.indexOf(pointAnnotation.getId());
            this.showDetail = true;
            if (AppStorage.get('audioPlayerStatus') !== AudioPlayerStatus.IDLE) {
              AudioPlayerService.getInstance().stop();
            }
            if (index >= 0) {
              let zone: ZonesItem = this.zonesList[index % this.zonesList.length];
              let cameraPosition: mapCommon.CameraPosition = {
                target: zone.location,
                zoom: 15
              };
              map.newCameraPosition(cameraPosition);
              if (zone.id !== this.selectedZone.id || zone.id === this.zonesList[0].id) {
                this.selectedZone = zone;
                if (this.showRoutes) {
                  this.showRoutes = false;
                  this.showSheet = false;
                  this.routePaths = [];
                  this.redrawScenes();
                }
                this.getDistances();
              }
            }
            Logger.info(TAG, `on-PointAnnotationClick position = ${JSON.stringify(pointAnnotation)}`);
          });
        }
      }
    };
  }

  setMyLocationControl() {
    if (canIUse('SystemCapability.Map.Core')) {
      this.mapController?.setMyLocationEnabled(true);
      this.mapController?.setMyLocationControlsEnabled(true);
    }
  }

  startNavigation() {
    if (canIUse('SystemCapability.Map.Core')) {
      let context = getContext(this) as common.UIAbilityContext;
      let name = ResourceUtil.getResourceString(context, $r('app.string.my_location'));
      let title = ResourceUtil.getResourceString(context, this.selectedZone.title);
      let petalMapWant: Want = {
        bundleName: 'com.huawei.hmos.maps.app',
        uri: 'maps://navigation',
        parameters: {
          linkSource: 'com.huawei.hmos.world',
          destinationLatitude: this.selectedZone.location.latitude,
          destinationLongitude: this.selectedZone.location.longitude,
          destinationName: title,
          originName: name,
          originLatitude: this.currentLocation.latitude,
          originLongitude: this.currentLocation.longitude,
          vehicleType: this.selectedMode,
        }
      }
      context.startAbility(petalMapWant);
    }
  }

  async drawBuilding() {
    if (this.mapController) {
      let length: number = this.zonesList.length;
      for (let i = 0; i < length; i++) {
        let zoneItem: ZonesItem = this.zonesList[i];
        if (canIUse('SystemCapability.Map.Core')) {
          let context = getContext(this) as common.UIAbilityContext;
          let title = ResourceUtil.getResourceString(context, zoneItem.title);
          let pointAnnotationOptions: mapCommon.PointAnnotationParams = {
            position: zoneItem.location,
            repeatable: true,
            collisionRule: mapCommon.CollisionRule.NAME,
            titles: [{
              content: title,
              color: zoneItem.titleColor,
              fontSize: 12,
              strokeColor: 0xFFFFFFFF,
              strokeWidth: 2,
              fontStyle: mapCommon.FontStyle.BOLD
            }
            ],
            icon: zoneItem.icon,
            showIcon: true,
            forceVisible: false,
            minZoom: (i === 0 ? 2 : 14),
            maxZoom: 18,
            visible: true,
            zIndex: 5
          };
          let pointAnnotation = await this.mapController.addPointAnnotation(pointAnnotationOptions);
          this.pointIds.push(pointAnnotation.getId());
        }
      }
    }
  }

  async drawPaths() {
    if (canIUse('SystemCapability.Map.Core')) {
      try {
        let train2List: Array<mapCommon.LatLng> = (this.showRoutes || this.showSheet) ?
          this.routePaths[this.selectedRoute].overviewPolyline as Array<mapCommon.LatLng> : [];
        let points: Array<mapCommon.LatLng> = [train2List[0], train2List[train2List.length-1]];
        let icons: Resource[] = [$r('app.media.ic_travel_end'), $r('app.media.ic_end_point')];
        for (let i = 0; i < points.length; i++) {
          let markOption: mapCommon.MarkerOptions = {
            position: points[i],
            icon: icons[i],
            visible: true,
            zIndex: 12 + i
          };
          await this.mapController?.addMarker(markOption);
        }
        let polylineOption: mapCommon.MapPolylineOptions = {
          points: train2List,
          clickable: true,
          color: 0xff0C801F,
          startCap: mapCommon.CapStyle.ROUND,
          endCap: mapCommon.CapStyle.ROUND,
          geodesic: true,
          jointType: mapCommon.JointType.ROUND,
          visible: true,
          zIndex: 9,
          width: 32,
          gradient: false
        };
        await this.mapController?.addPolyline(polylineOption);
        polylineOption.color = 0xff65BB5C;
        polylineOption.zIndex = 10;
        polylineOption.width = 26;
        polylineOption.customTexture = $r('app.media.ic_arrow_right');
        await this.mapController?.addPolyline(polylineOption);
        Logger.info(TAG, 'draw success');
      } catch (error) {
        Logger.error(TAG, 'catch the error: ' + JSON.stringify(error));
      }
    }
  }

  getDistances() {
    this.distance = '';
    if (canIUse('SystemCapability.Map.Core')) {
      try {
        geoLocationManager.getCurrentLocation(this.requestInfo)
          .then(async (location: geoLocationManager.Location) => {
            Logger.info(TAG, 'current location: ' + JSON.stringify(location));
            let mapLocation: mapCommon.LatLng =
              await map.convertCoordinate(mapCommon.CoordinateType.WGS84, mapCommon.CoordinateType.GCJ02, {
                latitude: location.latitude,
                longitude: location.longitude
              });
            this.currentLocation = mapLocation;
            let distance: number = map.calculateDistance(mapLocation, this.selectedZone.location);
            this.distance = CommonUtil.transDistanceWithUnit(distance);
          })
          .catch((error: BusinessError) => {
            if (error.code === 201) {
              promptAction.showToast({
                message: $r('app.string.manually_setting_permissions')
              });
            }
            Logger.error(TAG, 'promise, getCurrentLocation: error=' + JSON.stringify(error));
          });
      } catch (err) {
        Logger.error(TAG, 'errCode:' + (err as BusinessError).code + ',errMessage:' + (err as BusinessError).message);
      }
    }
  }

  async getMapRoutes() {
    let local = new intl.Locale();
    let language = 'en';
    if (local.language === 'zh') {
      language = 'zh_CN';
    }
    let params: navi.RouteParams = {
      origins: [this.distance.length > 7 ? {
        latitude: 22.874712,
        longitude: 113.880028
      } : this.currentLocation],
      destination: this.selectedZone.location,
      language: language
    };
    if (canIUse('SystemCapability.Map.Core')) {
      try {
        let routeResult: navi.RouteResult | null = null;
        if (this.selectedMode === TravelModeType.DRIVING) {
          let drivingParam: navi.DrivingRouteParams = params as navi.DrivingRouteParams;
          drivingParam.alternatives = true;
          routeResult = await navi.getDrivingRoutes(drivingParam);
          Logger.info(TAG, 'getDrivingRoutes success result =' + JSON.stringify(routeResult));
        } else if (this.selectedMode === TravelModeType.CYCLING) {
          routeResult = await navi.getCyclingRoutes(params);
          Logger.info(TAG, 'getCyclingRoutes success result =' + JSON.stringify(routeResult));
        } else {
          routeResult = await navi.getWalkingRoutes(params);
          Logger.info(TAG, 'getWalkingRoutes success result =' + JSON.stringify(routeResult));
        }
        if (routeResult) {
          this.routePaths = routeResult.routes;
          this.selectedRoute = 0;
          this.redrawScenes();
        }
      } catch (err) {
        Logger.error(TAG, 'getRoutes fail err =' + JSON.stringify(err));
      }
    }
  }

  redrawScenes() {
    if (canIUse('SystemCapability.Map.Core')) {
      this.mapController?.clear();
      this.pointIds = [];
      this.drawBuilding();
      this.drawPaths();
    }
  }

  @Builder
  routesSheetBuilder() {
    Column() {
      RoutesSheet({
        showSheet: $showSheet,
        selectedZones: [this.selectedZone],
        routePaths: this.routePaths,
        selectedMode: $selectedMode,
        selectedRoute: $selectedRoute,
        clickAction: (type: RouteActionType) => {
          if (type === RouteActionType.MODE) {
            this.getMapRoutes()
          } else if (type === RouteActionType.ROUTE) {
            this.redrawScenes();
          } else {
            this.startNavigation();
          }
        }
      })
    }
  }

  build() {
    Stack({ alignContent: Alignment.BottomStart }) {
      if (canIUse('SystemCapability.Map.Core')) {
        MapComponent({ mapOptions: this.mapOptions, mapCallback: this.callback })
          .width(CommonConstants.FULL_PERCENT)
          .height(CommonConstants.FULL_PERCENT)
      }
      ZoneDetailModal({
        detailSheetHeight: $detailSheetHeight,
        showDetail: $showDetail,
        distance: this.distance,
        clickAction: (type: ModalActionType) => {
          if (type === ModalActionType.ROUTE) {
            if (this.currentBreakpoint === BreakpointTypeEnum.SM) {
              this.showRoutes = true;
            } else {
              this.showSheet = true;
            }
            this.selectedMode = 0;
            this.getMapRoutes();
          } else if (type === ModalActionType.NAVIGATION) {
            this.startNavigation();
          }
        }
      })
      RoutesSheet({
        showSheet: $showSheet,
        selectedZones: [this.selectedZone],
        routePaths: this.routePaths,
        selectedMode: $selectedMode,
        selectedRoute: $selectedRoute,
        clickAction: (type: RouteActionType) => {
          if (type === RouteActionType.MODE) {
            this.getMapRoutes()
          } else if (type === RouteActionType.ROUTE) {
            this.redrawScenes();
          } else {
            this.startNavigation();
          }
        }
      })
        .visibility(this.currentBreakpoint === BreakpointTypeEnum.SM ? Visibility.None :
          (this.showSheet ? Visibility.Visible : Visibility.Hidden))
      Button({ type: ButtonType.Circle }) {
        Image($r('app.media.ic_back'))
      }
      .onClick(() => {
        this.showRoutes = false;
        this.showSheet = false;
        this.sheetVisiblyChange();
      })
      .position({
        x: 24,
        y: (AppStorage.get('statusBarHeight') as number) + 8
      })
      .visibility((this.showSheet || this.showRoutes) ? Visibility.Visible : Visibility.Hidden)
      .height($r('app.float.md_btn_height'))
      .aspectRatio(1)
      .shadow({
        radius: $r('app.float.md_border_radius'),
        color: $r('app.color.shadow_color'),
        offsetY: $r('app.float.sm_padding_margin')
      })
      .backgroundColor($r('sys.color.ohos_id_color_titlebar_bg'))
    }
    .bindSheet($$this.showRoutes, this.routesSheetBuilder(), {
      enableOutsideInteractive: true,
      height: 320,
      showClose: false,
      dragBar: false,
      maskColor: '#00000000',
      backgroundColor: $r('sys.color.ohos_id_color_panel_bg'),
      onAppear: () => {
        this.sheetVisiblyChange();
        Logger.info(TAG, 'BindSheet onAppear.');
      },
      onDisappear: () => {
        this.showRoutes = false;
        this.sheetVisiblyChange();
        Logger.info(TAG, 'BindSheet onDisappear.');
      }
    })
  }
}