9afce6f6创建于 2025年5月7日历史提交
/*
 * Copyright (c) 2024 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 { curves, promptAction} from '@kit.ArkUI';
import { BusinessError } from '@kit.BasicServicesKit';
import { vibrator } from '@kit.SensorServiceKit';
import { GlobalContext } from '../common/util/GlobalUtil';
import { XComponentSize } from '../model/ScanSize'
import { ScanTitle } from './ScanTitle';
import { scanBarcode } from '@kit.ScanKit';
import { logger } from 'utils';
import CommonConstants from '../common/constants/CommonConstants';
import CustomScanViewModel, { ScanResults } from '../viewmodel/CustomScanViewModel';
import { PromptTone } from '../model/PromptTone';
import { funcDelayer } from '../common/util/FunctionUtil';

/**
 * 二维码位置的样式
 */
@Extend(Image)
function selected(scanState: boolean, x: number, y: number) {
  .width(40)
  .height(40)
  .position({ x: x, y: y })
  .markAnchor({ x: 20, y: 20 })
  .visibility(scanState ? Visibility.Visible : Visibility.Hidden)
  .draggable(false)
}

/**
 * 二维码位置组件
 */
@Component
export struct CodeLayout {
  @Consume('subPageStack') subPageStack: NavPathStack;
  @ObjectLink xComponentSize: XComponentSize;
  @Prop navHeight: number;
  @State scanResults: ScanResults = {
    code: 0,
    data: [],
    size: 0,
    uri: ''
  }
  @Prop foldStatus: number = -1; // 折叠状态
  @State multiCodeScanLocation: Array<Array<number>> = [];
  @State multiCodeScanResult: Array<object> = [];// 多个二维码结果集
  @State isMultiSelected: boolean = false;// 多二维码下是否已选择
  @State multiSelectedIndex: number = 0;// 多二维码下是否选择的index
  @State singleCodeX: number = 0;// 单个结果的位置
  @State singleCodeY: number = 0;
  @State multiCodeScale: number = 0.3; // 多二维码图案比例参数
  @State multiCodeOpacity: number = 0; // 透明度设置
  @State singleCodeScale: number = 0.3; // 单二维码图案比例参数
  @State singleCodeOpacity: number = 0; // 透明度设置
  @State fadeOutScale: number = 1;
  @State fadeOutOpacity: number = 1;
  @State isPickerDialogShow: boolean = false;
  @State isShowCode: boolean = true;
  @Consume('customScanVM') customScanVM: CustomScanViewModel;
  @Link avPlayer: PromptTone;// 成功扫描二维码的提示音

  aboutToAppear() {
    // 触发传感器震动
    this.vibratorPlay();
    // 播放二维码扫描成功提示音
    this.avplayerPlay();
    // 处理扫码结果信息
    for (let i = 0; i < this.scanResults.size; i++) {
      let scanResult: scanBarcode.ScanResult = this.scanResults.data[i];
      this.multiCodeScanResult.push(scanResult);
      let scanCodeRect: scanBarcode.ScanCodeRect | undefined = scanResult.scanCodeRect;
      if (scanCodeRect) {
        this.multiCodeScanLocation.push(
          [scanCodeRect.left,
            scanCodeRect.top,
            scanCodeRect.right,
            scanCodeRect.bottom]
        );
      }
    }

    // 只扫描到一个二维码时,单独处理
    if (this.scanResults.size === 1) {
      this.multiSelectedIndex = 0;
      let location = this.multiCodeScanLocation[0];
      this.singleCodeX = this.getOffset('x', location);
      this.singleCodeY = this.getOffset('y', location);
    }

  }

  aboutToDisappear() {
    GlobalContext.getContext().setProperty((CommonConstants.GLOBAL_SCAN_SELECT_A_PICTURE), false);
    this.isPickerDialogShow = false;
  }

  /**
   * 单个二维码的位置图片
   */
  @Builder
  SingleCodeLayout() {
    Column() {
      Image($rawfile('scan_selected.svg'))
        // TODO: 知识点: 在扫描结果返回的水平坐标和纵坐标位置上展示图片
        .selected(true, this.singleCodeX, this.singleCodeY)
        .scale({ x: this.singleCodeScale, y: this.singleCodeScale })
        .opacity(this.singleCodeOpacity)
        .onAppear(() => {
          this.singleCodeBreathe();
        })
    }
    .position({ x: 0, y: 0 })
    .width('100%')
    .height('100%')
  }

  /**
   * 多个二维码的位置图片渲染
   */
  @Builder
  MultiCodeLayout(arr: Array<number>, index: number) {
    Row() {
      Image($rawfile('scan_selected2.svg'))
        .width(40)
        .height(40)
        .visibility((this.isMultiSelected && this.multiSelectedIndex !== index) ? Visibility.None : Visibility.Visible)
        .scale({ x: this.multiCodeScale, y: this.multiCodeScale })
        .opacity(this.multiCodeOpacity)
        .onAppear(() => {
          // 展示动画,因为共用状态变量,只需要第一次执行
          if (index === 0) {
            this.multiAppear();
          }
        })
        .onClick(() => {
          // 点击打开二维码信息弹窗
          this.openMultiCode(arr, index);
        })
    }
    // TODO: 知识点: 预览流有固定比例,XComponent只能展示部分,返回的扫码结果和当前展示存在一定偏移量
    .position({
      x: this.getOffset('x', arr),
      y: this.getOffset('y', arr)
    })
    .width(40)
    .height(40)
    .markAnchor({ x: 20, y: 20 })
    .scale({ x: this.fadeOutScale, y: this.fadeOutScale })
    .opacity(this.fadeOutOpacity)
    .animation({
      duration: 200,
      curve: curves.cubicBezierCurve(0.33, 0, 0.67, 1),
      delay: 0,
      iterations: 1,
      playMode: PlayMode.Alternate,
    })
  }

  @Builder
  scanResultDialogBuilder(params: ESObject) {
    Column() {
      Text($r('app.string.custom_scan_tips_scan_result_title'))
        .fontSize($r('app.integer.custom_scan_result_dialog_title_font_size'))
        .fontWeight(FontWeight.Bold)
      Text(params.msg)
        .margin({ top: $r('app.integer.custom_scan_result_dialog_msg_margin_top') })
      Button($r('app.string.custom_scan_text_btn_restart_scan'))
        .onClick(() => {
          params.restartCb();
        })
    }
    .alignItems(HorizontalAlign.Center)
    .padding($r('app.integer.custom_scan_result_dialog_padding'))
  }

  build() {
    Stack() {
      // 如果只有一个结果,渲染单个位置
      if (this.scanResults.size === 1 && this.isShowCode) {
        this.SingleCodeLayout();
      } else {
        // 多结果提示文案
        ScanTitle({
          navHeight: this.navHeight,
        }).width('100%').height('100%')

        // 渲染多二维码位置结果
        ForEach(this.multiCodeScanLocation, (item: Array<number>, index: number) => {
          this.MultiCodeLayout(item, index)
        }, (item: number) => item.toString())
        // 点击某一个二维码后,展示选中图案
        Image($rawfile('scan_selected.svg'))
          .selected(true, this.singleCodeX, this.singleCodeY)
          .scale({ x: this.singleCodeScale, y: this.singleCodeScale })
          .opacity(this.singleCodeOpacity)
          .visibility(this.isMultiSelected ? Visibility.Visible : Visibility.None)
      }
    }
    .width('100%')
    .height('100%')
  }

  // 计算水平或者竖直的偏移量
  getOffset(coordinateAxis: string, location: Array<number>): number {
    if (coordinateAxis === 'x') {
      return this.setOffsetXByOrientation(location);
    }
    return this.setOffsetYByOrientation(location);
  }

  setOffsetXByOrientation(location: Array<number>): number {
    let offset: number = (location[0] + location[2]) / 2 + this.xComponentSize.offsetX;
    return offset;
  }

  setOffsetYByOrientation(location: Array<number>): number {
    let offset: number = (location[3] + location[1]) / 2 + this.xComponentSize.offsetY;
    return offset;
  }

  // 传感器震动
  vibratorPlay() {
    try {
      vibrator.startVibration({
        type: 'time',
        duration: 100
      }, {
        id: 0,
        usage: 'alarm'
      }).then((): void => {
      }, (error: BusinessError) => {
        logger.error(`Failed to start vibration. Code: ${error.code}, message: ${error.message}`);
      });
    } catch (err) {
      let error: BusinessError = err as BusinessError;
      logger.error(`Failed to play vibration, An unexpected error occurred. Code: ${error.code}, message: ${error.message}`);
    }
  }

  // 播放二维码扫描成功提示音
  avplayerPlay() {
    if (this.avPlayer) {
      this.avPlayer.playDrip();
    }
  }

  /**
   * 多个二维码,点击查看某个二维码信息
   * @param arr
   * @param index
   */
  openMultiCode(arr: Array<number>, index: number): void {
    this.singleCodeX = this.getOffset('x', arr);
    this.singleCodeY = this.getOffset('y', arr);
    this.isMultiSelected = true;
    this.singleCodeScale = 0.3;
    this.singleCodeOpacity = 0;
    this.multiSelectedIndex = index || 0;
    this.fadeOutScale = 0.3;
    this.fadeOutOpacity = 0;
    this.showScanResult(this.scanResults.data[index])
  }

  /**
   * 二维码图标呼吸动效
   */
  singleCodeBreathe(): void {
    this.singleCodeOpacity = 0.3;
    this.singleCodeScale = 0.3;
    animateTo({
      duration: 300,
      curve: curves.cubicBezierCurve(0.33, 0, 0.67, 1),
      delay: 0,
      iterations: 1,
      playMode: PlayMode.Alternate,
      onFinish: () => {
        this.showScanResult(this.scanResults.data[0])
      }
    }, () => {
      this.singleCodeOpacity = 1;
      this.singleCodeScale = 1;
    });
  }

  multiAppear(): void {
    this.multiCodeScale = 0.3;
    animateTo({
      duration: 350,
      curve: curves.cubicBezierCurve(0.33, 0, 0.67, 1), // Animation curve.
      delay: 0,
      iterations: 1,
      playMode: PlayMode.Alternate,
      onFinish: () => {
        this.multiAppearEnd();
      }
    }, () => {
      this.multiCodeScale = 1.1;
      this.multiCodeOpacity = 1;
    });
  }

  multiAppearEnd(): void {
    animateTo({
      duration: 250,
      curve: curves.cubicBezierCurve(0.33, 0, 0.67, 1), // Animation curve.
      delay: 0,
      iterations: 1,
      playMode: PlayMode.Alternate,
      onFinish: () => {
        funcDelayer(() => {
          this.multiCodeBreathe();
        }, 500);
      }
    }, () => {
      this.multiCodeScale = 1;
    });
  }

  /**
   * 多二维码结果图案的呼吸动效
   */
  multiCodeBreathe(): void {
    this.multiCodeScale = 1;
    animateTo({
      duration: 300,
      curve: curves.cubicBezierCurve(0.33, 0, 0.67, 1), // Animation curve.
      delay: 0,
      iterations: 4,
      playMode: PlayMode.Alternate,
      onFinish: () => {
        funcDelayer(() => {
          this.multiCodeBreathe();
        }, 400);
      }
    }, () => {
      this.multiCodeScale = 0.8;
    });
  }

  /**
   * 显示扫码结果
   * @param {scanBarcode.ScanResult} result 扫码结果数据
   * @returns {Promise<void>}
   */
  async showScanResult(scanResult: scanBarcode.ScanResult): Promise<void> {
    // 码源信息
    const originalValue: string = scanResult.originalValue;
    // 二维码识别结果展示
    this.subPageStack.pushPathByName(CommonConstants.SUB_PAGE_DETECT_BARCODE, {
      detectResult: originalValue
    } as ESObject, true);
  }
}