1/**
2 * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or
10 {
11 "name": "bluetoothTab", agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18import Router from '@system.router';
19import deviceInfo from '@ohos.deviceInfo';
20import BluetoothDevice from '../model/bluetoothImpl/BluetoothDevice';
21import BluetoothDeviceController from '../controller/bluetooth/BluetoothDeviceController';
22import LogUtil from '../../../../../../common/utils/src/main/ets/default/baseUtil/LogUtil';
23import ConfigData from '../../../../../../common/utils/src/main/ets/default/baseUtil/ConfigData';
24import HeadComponent from '../../../../../../common/component/src/main/ets/default/headComponent';
25import EntryComponent from '../../../../../../common/component/src/main/ets/default/entryComponent';
26import { BondState, DeviceType, ProfileConnectionState } from '../model/bluetoothImpl/BluetoothModel';
27import BasicDataSource from '../../../../../../common/utils/src/main/ets/default/model/BasicDataSource';
28import DeviceNameEntryComponent from '../../../../../../common/component/src/main/ets/default/deviceNameEntryComponent';
29import ImageAnimatorComponent from '../../../../../../common/component/src/main/ets/default/imageAnimatorComponent';
30import { Callback } from '@ohos.base';
31
32const PAIRED_ITEM_NUMBER = 3;
33const PAGE_URI_DEVICE_NAME = 'pages/deviceName';
34const PAGE_URI_BLUETOOTH_PAIRED_DEVICE_INFO = 'pages/bluetoothPairedDeviceInfo';
35const deviceTypeInfo = deviceInfo.deviceType;
36let pinRequiredTIimer: number | undefined = undefined;
37
38@Entry
39@Component
40struct Bluetooth {
41  @StorageLink('bluetoothIsOn') isOn: boolean = false;
42  @StorageLink('bluetoothToggleEnabled') isEnabled: boolean = true;
43  @StorageLink('bluetoothLocalName') localName: string = '';
44  private PAGE_TAG = ConfigData.TAG + 'Bluetooth page ';
45  private deviceController: BluetoothDeviceController = new BluetoothDeviceController();
46
47  aboutToAppear(): void {
48    LogUtil.log(this.PAGE_TAG + 'aboutToAppear in : isOn = ' + this.isOn)
49    this.deviceController
50      .initData()
51      .subscribe();
52    LogUtil.log(this.PAGE_TAG + 'aboutToAppear out : isOn = ' + this.isOn)
53  }
54
55  onPageShow(): void {
56    LogUtil.log(this.PAGE_TAG + 'onPageShow in : localName = ' + this.localName)
57    this.deviceController.getLocalName();
58    LogUtil.log(this.PAGE_TAG + 'onPageShow out : localName = ' + this.localName)
59  }
60
61  aboutToDisappear(): void {
62    this.deviceController.unsubscribe();
63  }
64
65  build() {
66    Column() {
67      GridContainer({ gutter: ConfigData.GRID_CONTAINER_GUTTER_24, margin: ConfigData.GRID_CONTAINER_MARGIN_24 }) {
68        Column() {
69          HeadComponent({ headName: $r('app.string.bluetoothTab'), isActive: true });
70
71          Row() {
72            Text($r("app.string.bluetoothTab"))
73              .fontColor($r('sys.color.ohos_fa_text_primary'))
74              .fontSize($r("app.float.font_16"))
75              .fontWeight(FontWeight.Medium)
76
77            Blank()
78
79            Toggle({ type: ToggleType.Switch, isOn: this.isOn })
80              .width('36vp')
81              .height('20vp')
82              .selectedColor('#007DFF')
83              .margin({ left: $r('app.float.wh_value_6') })
84              .onChange((isOn: boolean) => {
85                LogUtil.log(this.PAGE_TAG + 'Toggle onClick: isOn = ' + isOn + ', enabled = ' + this.isEnabled)
86                if (!this.isEnabled) return;
87                this.deviceController.toggleValue(isOn);
88              });
89          }
90          .margin({ top: $r("app.float.distance_8") })
91          .width(ConfigData.WH_100_100)
92          .height($r('app.float.wh_value_56'))
93          .backgroundColor($r("app.color.white_bg_color"))
94          .borderRadius($r('app.float.wh_value_28'))
95          .padding({ left: $r('app.float.wh_value_12'), right: $r('app.float.wh_value_6') })
96          .alignItems(VerticalAlign.Center)
97          .borderRadius($r('app.float.distance_24'))
98
99          Text($r('app.string.bluetooth_visible_to_nearby'))
100            .width(ConfigData.WH_100_100)
101            .fontSize($r('app.float.font_14'))
102            .fontColor($r('sys.color.ohos_id_color_text_secondary'))
103            .visibility(this.isOn ? Visibility.Visible : Visibility.None)
104            .height($r("app.float.wh_value_52"))
105            .lineHeight($r("app.float.wh_value_20"))
106            .padding({
107              left: $r('app.float.wh_value_12'),
108              top: $r('app.float.distance_8'),
109              bottom: $r('app.float.distance_24')
110            })
111
112          Scroll() {
113            Column() {
114              DeviceNameComponent({
115                isEnabled: $isOn,
116                localName: $localName
117              })
118
119              if (this.isOn) {
120                PairedDeviceComponent({
121                  controller: this.deviceController
122                })
123
124                AvailableDeviceComponent({
125                  controller: this.deviceController,
126                })
127              }
128            }
129            .width(ConfigData.WH_100_100)
130          }
131          .scrollBarWidth(0)
132          .width(ConfigData.WH_100_100)
133          .align(Alignment.TopStart)
134          .layoutWeight(ConfigData.LAYOUT_WEIGHT_1)
135        }
136        .useSizeType({
137          sm: { span: 4, offset: 0 },
138          md: { span: 6, offset: 1 },
139          lg: { span: 8, offset: 2 }
140        });
141      }
142      .width(ConfigData.WH_100_100)
143      .height(ConfigData.WH_100_100);
144    }
145    .backgroundColor($r("sys.color.ohos_id_color_sub_background"))
146    .width(ConfigData.WH_100_100)
147    .height(ConfigData.WH_100_100);
148  }
149}
150
151/**
152 * Device name component
153 */
154@Component
155struct DeviceNameComponent {
156  @Link isEnabled: boolean;
157  @Link localName: string;
158
159  build() {
160    Row() {
161      DeviceNameEntryComponent({
162        settingIcon: '',
163        settingTitle: $r('app.string.bluetooth_device_name'),
164        settingSummary: '',
165        settingValue: $localName,
166        settingArrow: "",
167        settingArrowStyle: '',
168        settingUri: '',
169        isEnabled: $isEnabled,
170        heights: ($r('app.float.wh_value_48')),
171        fontSize: ($r('app.float.font_16'))
172      })
173    }
174    .padding($r("app.float.distance_4"))
175    .margin({ top: this.isEnabled ? $r('app.float.wh_value_0') : $r('app.float.wh_value_12') })
176    .width(ConfigData.WH_100_100)
177    .height($r('app.float.wh_value_56'))
178    .borderRadius($r("app.float.radius_24"))
179    .backgroundColor($r("app.color.white_bg_color"))
180    .onClick(() => {
181      if (this.isEnabled) {
182        Router.push({ uri: PAGE_URI_DEVICE_NAME });
183      }
184    });
185  }
186}
187
188export interface bluetoothParam {
189  bluetoothDevice: string;
190}
191
192/**
193 * Paired device component
194 */
195@Component
196struct PairedDeviceComponent {
197  @StorageLink('bluetoothPairedDevices') pairedDevices: BluetoothDevice[] = [];
198  @State isTouched: boolean = false;
199  @State hide: boolean = true;
200  private TAG_PAGE = ConfigData.TAG + 'PairedDeviceComponent ';
201  private controller?: BluetoothDeviceController;
202
203  aboutToAppear(): void {
204    if (this.controller) {
205      // bind component and initialize
206      this.controller.bindComponent(this)
207        .bindProperties(["pairedDevices"])
208        .initData();
209    }
210  }
211
212  build() {
213    Column() {
214      if (this.pairedDevices && this.pairedDevices.length > 0) {
215        // paired devices title
216        Row() {
217          Text($r('app.string.bluetooth_paired_devices'))
218            .width(ConfigData.WH_100_100)
219            .fontSize($r('app.float.font_14'))
220            .fontWeight(FontWeight.Medium)
221            .fontColor($r('sys.color.ohos_id_color_text_secondary'))
222        }
223        .width(ConfigData.WH_100_100)
224        .padding({
225          left: $r('app.float.wh_value_12'),
226          top: $r('app.float.distance_19_5'),
227          bottom: $r('app.float.distance_9_5')
228        })
229
230        List() {
231          // paired devices list
232          ForEach(this.pairedDevices, (item: BluetoothDevice, index?: number) => {
233            if (index !== undefined) {
234              if ((index < PAIRED_ITEM_NUMBER) || !this.hide) {
235                ListItem() {
236                  Row() {
237                    EntryComponent({
238                      settingIcon: getDeviceIconPath(item.deviceType),
239                      settingTitle: item.deviceName,
240                      settingSummary: this.getConnectionStateText(item),
241                      settingValue: '',
242                      settingArrow: JSON.parse(JSON.stringify($r("app.media.ic_public_settings"))),
243                      settingArrowStyle: 'bluetooth',
244                      settingUri: '',
245                      titleFontColor: this.isHeadPhoneConnected(item) ? $r("app.color.bluetooth_text_color_highlight") : $r("sys.color.ohos_id_color_text_primary"),
246                      image_wh: $r('app.float.wh_value_24'),
247                      heights: this.getConnectionStateText(item) == '' ? $r('app.float.wh_value_48') : ($r('app.float.wh_value_56')),
248                      fontSize: ($r('app.float.font_16')),
249                      onArrowClick: () => {
250                        LogUtil.info(this.TAG_PAGE + 'item go detail');
251                        this.gotoPairedDeviceInfo(item);
252                      }
253                    });
254                  }
255                  .width(ConfigData.WH_100_100)
256                  .borderRadius($r("app.float.radius_24"))
257                  .backgroundColor($r("app.color.white_bg_color"))
258                  .onClick(() => {
259                    this.itemClicked(item);
260                  })
261                }
262              }
263              if ((this.hide && index === PAIRED_ITEM_NUMBER) || //more
264                (!this.hide && index >= PAIRED_ITEM_NUMBER && index == this.pairedDevices.length - 1)) { //put_away
265                ListItem() {
266                  Stack({ alignContent: Alignment.Center }) {
267                    Stack({ alignContent: Alignment.Center }) {
268                      Text(this.hide ? $r('app.string.more') : $r('app.string.put_away'))
269                        .fontColor($r('app.color.color_333333_grey'))
270                        .fontSize($r('app.float.font_14'))
271                    }
272                    .width(ConfigData.WH_100_100)
273                    .height($r("app.float.wh_value_48"))
274                    .borderRadius($r("app.float.radius_20"))
275                    .backgroundColor(this.isTouched ? $r("app.color.color_D8D8D8_grey") : $r("sys.color.ohos_id_color_foreground_contrary"))
276                    .onTouch((event?: TouchEvent | undefined) => {
277                      if (event?.type === TouchType.Down) {
278                        this.isTouched = true;
279                      }
280                      if (event?.type === TouchType.Up) {
281                        this.isTouched = false;
282                      }
283                    })
284                    .onClick(() => {
285                      this.hide = !this.hide;
286                    })
287                  }
288                  .height($r("app.float.wh_value_48"))
289                  .backgroundColor($r("sys.color.ohos_id_color_foreground_contrary"))
290                }
291              }
292            }
293          }, (item: BluetoothDevice) => {
294            return JSON.stringify(item)
295          });
296        }
297        .padding($r("app.float.distance_4"))
298        .divider({
299          strokeWidth: $r('app.float.divider_wh'),
300          color: $r('app.color.color_E3E3E3_grey'),
301          startMargin: $r('app.float.wh_48'),
302          endMargin: $r('app.float.wh_value_12')
303        })
304        .backgroundColor($r("app.color.white_bg_color"))
305        .borderRadius($r("app.float.radius_24"))
306      }
307    }
308  }
309
310  /**
311   * Get connection state text
312   * @param device
313   */
314  getConnectionStateText(device: BluetoothDevice): string {
315    let stateText: string = '';
316    switch (device.connectionState) {
317      case ProfileConnectionState.STATE_DISCONNECTED:
318        stateText = '';
319        break;
320
321      case ProfileConnectionState.STATE_CONNECTING:
322        stateText = JSON.parse(JSON.stringify($r('app.string.bluetooth_state_connecting')));
323        break;
324
325      case ProfileConnectionState.STATE_CONNECTED:
326        if (device.deviceType === DeviceType.HEADPHONE) {
327          stateText = JSON.parse(JSON.stringify($r('app.string.bluetooth_state_connected')));
328        } else {
329          stateText = '';
330        }
331        break;
332
333      case ProfileConnectionState.STATE_DISCONNECTING:
334        stateText = JSON.parse(JSON.stringify($r('app.string.bluetooth_state_disconnecting')));
335        break;
336    }
337    return stateText;
338  }
339
340  /**
341   * Disconnect Dialog
342   */
343  showDisconnectDialog(deviceName: string, callback: Callback<void>) {
344    AlertDialog.show({
345      title: $r('app.string.bluetooth_disconnect'),
346      message: $r("app.string.bluetooth_disconnect_device", deviceName),
347      primaryButton: {
348        value: $r('app.string.cancel'),
349        action: () => {
350          LogUtil.info(ConfigData.TAG + 'Closed callbacks');
351        }
352      },
353      secondaryButton: {
354        value: $r('app.string.confirm'),
355        action: () => {
356          LogUtil.info(ConfigData.TAG + `AlertDialog success:`);
357          callback();
358        }
359      },
360      alignment: deviceTypeInfo === 'phone' || deviceTypeInfo === 'default' ? DialogAlignment.Bottom : DialogAlignment.Center,
361      offset: ({ dx: 0, dy: deviceTypeInfo === 'phone' || deviceTypeInfo === 'default' ? '-24dp' : 0 })
362    })
363  }
364
365  /**
366   * Whether headphone connected.
367   * @param item device
368   * @return headphone connected or not
369   */
370  private isHeadPhoneConnected(item: BluetoothDevice): boolean {
371    return item.deviceType === DeviceType.HEADPHONE && item.connectionState === ProfileConnectionState.STATE_CONNECTED
372  }
373
374  /**
375   * Goto paired device Info
376   * @param item device
377   */
378  private gotoPairedDeviceInfo(item: BluetoothDevice) {
379    if (item.connectionState != ProfileConnectionState.STATE_CONNECTING
380      && item.connectionState != ProfileConnectionState.STATE_DISCONNECTING) {
381      LogUtil.info(this.TAG_PAGE + 'item right icon on click.');
382      let param: bluetoothParam = {
383        bluetoothDevice: JSON.stringify(item)
384      }
385      Router.push({
386        uri: PAGE_URI_BLUETOOTH_PAIRED_DEVICE_INFO,
387        params: param
388      });
389    }
390  }
391
392  /**
393   * Item clicked
394   * @param item device
395   */
396  private itemClicked(item: BluetoothDevice) {
397    switch (item.connectionState) {
398      case ProfileConnectionState.STATE_CONNECTED:
399        this.showDisconnectDialog(item.deviceName, () => {
400          if (this.controller) {
401            this.controller.disconnect(item.deviceId)
402          }
403        });
404        break;
405
406      case ProfileConnectionState.STATE_DISCONNECTED:
407        if (this.controller && !this.controller.connect(item.deviceId)) {
408          this.showConnectFailedDialog(item.deviceName);
409        }
410        break;
411    }
412  }
413
414  /**
415   * Connect Failed Dialog
416   */
417  private showConnectFailedDialog(deviceName: string) {
418    showDialog(
419      $r("app.string.bluetooth_connect_failed"),
420      $r("app.string.bluetooth_connect_failed_msg", deviceName),
421      $r("app.string.bluetooth_know_button")
422    );
423  }
424}
425
426/**
427 * Discovering animator component
428 */
429@Component
430struct DiscoveringAnimatorComponent {
431  build() {
432    Column() {
433      ImageAnimatorComponent({
434        imageWidth: $r('app.float.wh_value_24'),
435        imageHeight: $r('app.float.wh_value_24') })
436    }
437  }
438}
439
440export interface PinRequiredParam {
441  deviceId: string;
442  pinCode: string;
443}
444
445/**
446 * Available device component
447 */
448@Component
449struct AvailableDeviceComponent {
450  @State isDeviceDiscovering: boolean = false;
451  @StorageLink('bluetoothAvailableDevices') @Watch("availableDevicesChange") availableDevices: BluetoothDevice[] = [];
452  @State availableDevicesList: AvailableDevicesDataSource = new AvailableDevicesDataSource(this.availableDevices);
453  @State pairPinCode: string = '';
454  @StorageLink("controlPairing") controlPairing: boolean = true;
455  @StorageLink("pairData") pairData: BluetoothDevice = new BluetoothDevice();
456  @StorageLink("pinRequiredParam") @Watch("pinRequiredParamChange") pinRequiredParam: PinRequiredParam = {
457    deviceId: '',
458    pinCode: ''
459  };
460  private TAG_PAGE = ConfigData.TAG + 'AvailableDeviceComponent ';
461  private controller: BluetoothDeviceController | null = null;
462  private pairingDevice: BluetoothDevice | null = null;
463  pairDialog: CustomDialogController | null = new CustomDialogController({
464    builder: PairDialog({
465      deviceName: (this.pairingDevice && this.pairingDevice.deviceName) ? this.pairingDevice.deviceName : undefined,
466      pinCode: this.pairPinCode,
467      action: (accept: boolean) => {
468        this.confirmPairing(accept);
469      }
470    }),
471    alignment: deviceTypeInfo === 'phone' || deviceTypeInfo === 'default' ? DialogAlignment.Bottom : DialogAlignment.Center,
472    offset: ({ dx: 0, dy: deviceTypeInfo === 'phone' || deviceTypeInfo === 'default' ? '-24dp' : 0 }),
473    autoCancel: true,
474  });
475
476  availableDevicesChange() {
477    this.availableDevicesList.setData(this.availableDevices)
478    this.availableDevicesList.notifyDataReload()
479  }
480
481  aboutToAppear(): void {
482    if (this.controller) {
483      // bind component and initialize
484      this.controller.bindComponent(this)
485        .bindProperties(["isDeviceDiscovering", "availableDevices", "pairPinCode"])
486        .initData();
487
488      this.controller.startBluetoothDiscovery();
489    }
490  }
491
492  aboutToDisappear(): void {
493    this.pairDialog = null;
494  }
495
496  build() {
497    Column() {
498      Row() {
499        Row() {
500          // available devices title
501          Text($r('app.string.bluetooth_available_devices'))
502            .fontSize($r('app.float.font_14'))
503            .fontColor($r('sys.color.ohos_id_color_text_secondary'))
504            .height($r('app.float.distance_19'))
505            .fontWeight(FontWeight.Medium)
506        }
507        .padding({
508          left: $r('sys.float.ohos_id_card_margin_start'),
509          right: $r('sys.float.ohos_id_card_margin_end'),
510          top: $r('app.float.distance_19_5'),
511          bottom: $r('app.float.distance_9_5')
512        })
513
514        Blank()
515
516        // bluetooth discovering
517        if (this.isDeviceDiscovering) {
518          DiscoveringAnimatorComponent()
519        }
520      }
521      .height($r("app.float.wh_value_48"))
522      .width(ConfigData.WH_100_100)
523
524      if (this.availableDevices && this.availableDevices.length >= 1) {
525        List() {
526          // paired devices list
527          ForEach(this.availableDevices, (item: BluetoothDevice) => {
528            ListItem() {
529              Row() {
530                EntryComponent({
531                  settingIcon: getDeviceIconPath(item.deviceType),
532                  settingTitle: item.deviceName ? item.deviceName : item.deviceId,
533                  settingSummary: this.getPairStateText(item),
534                  settingValue: '',
535                  settingArrow: "",
536                  settingArrowStyle: '',
537                  settingUri: '',
538                  image_wh: $r('app.float.wh_value_24'),
539                  heights: this.getPairStateText(item) == '' ? $r('app.float.wh_value_56') : ($r('app.float.wh_value_64')),
540                  fontSize: ($r('app.float.font_16')),
541                });
542              }
543              .width(ConfigData.WH_100_100)
544              .borderRadius($r("app.float.radius_24"))
545              .backgroundColor($r("app.color.white_bg_color"))
546              .onClick(() => {
547                LogUtil.info(this.TAG_PAGE + 'item on click');
548                if (this.controlPairing) {
549                  this.pairDevice(item);
550                } else {
551                  return;
552                }
553              })
554            }
555          }, (item: BluetoothDevice) => {
556            return JSON.stringify(item);
557          });
558        }
559        .padding($r("app.float.distance_4"))
560        .backgroundColor($r("app.color.white_bg_color"))
561        .borderRadius($r("app.float.radius_24"))
562        .divider({
563          strokeWidth: $r('app.float.divider_wh'),
564          color: $r('app.color.color_E3E3E3_grey'),
565          startMargin: $r('app.float.wh_48'),
566          endMargin: $r('app.float.wh_value_12')
567        })
568      } else {
569        // Scanning...
570        Text($r('app.string.scanning'))
571          .fontSize($r('sys.float.ohos_id_text_size_body2'))
572          .textCase(TextCase.UpperCase)
573          .fontWeight(FontWeight.Medium)
574          .fontColor($r("sys.color.ohos_id_color_primary"))
575          .height($r('app.float.wh_value_48'))
576      }
577    }
578  }
579
580  /**
581   * Get pair state text
582   * @param device
583   */
584  getPairStateText(device: BluetoothDevice): string {
585    return device.connectionState == BondState.BOND_STATE_BONDING ? JSON.parse(JSON.stringify($r('app.string.bluetooth_state_pairing'))) : '';
586  }
587
588  pinRequiredParamChange() {
589    clearTimeout(pinRequiredTIimer);
590    pinRequiredTIimer = setTimeout(() => {
591      this.pairPinCode = this.pinRequiredParam.pinCode;
592      this.pairingDevice = this.pairData;
593      if (this.pairDialog) {
594        this.pairDialog.open();
595      }
596      () => {
597        this.showPairFailedDialog();
598      }
599    }, 1000)
600  }
601
602  /**
603   * Pair device
604   * @param device
605   */
606  pairDevice(device: BluetoothDevice) {
607    if (this.controller) {
608      this.controller.pair(device.deviceId);
609    }
610  }
611
612  /**
613   * Confirm pairing
614   */
615
616  confirmPairing(accept: boolean) {
617    LogUtil.info(this.TAG_PAGE + 'confirmPairing pairingDevice');
618    try {
619      if (this.pairingDevice && this.pairingDevice.deviceId != null && this.controller) {
620        this.controller.confirmPairing(this.pairingDevice.deviceId, accept);
621      }
622    } catch (err) {
623      LogUtil.info(this.TAG_PAGE + `confirmPairing pairingDevice error ${err}`);
624    }
625  }
626
627  /**
628   * Show pair failed dialog
629   */
630  showPairFailedDialog() {
631    this.showPairingFailedDialog();
632  }
633
634  /**
635   * Show connect Failed Dialog
636   */
637  private showConnectFailedDialog(deviceName: string) {
638    showDialog(
639      $r("app.string.bluetooth_connect_failed"),
640      $r("app.string.bluetooth_connect_failed_msg", deviceName),
641      $r("app.string.bluetooth_know_button")
642    );
643  }
644
645  /**
646   * Show pairing failed title Dialog
647   */
648  private showPairingFailedDialog() {
649    showDialog(
650      $r("app.string.pairing_failed_title"),
651      $r("app.string.pairing_failed_message"),
652      $r("app.string.bluetooth_know_button")
653    );
654  }
655}
656
657/**
658 * AvailableDevicesDataSource For Lazy Loading
659 */
660class AvailableDevicesDataSource extends BasicDataSource {
661  private availableDevicesArray: BluetoothDevice[] | null = null;
662
663  constructor(availableDevicesArray: BluetoothDevice[]) {
664    super();
665    this.availableDevicesArray = availableDevicesArray;
666  }
667
668  public setData(data: BluetoothDevice[]) {
669    this.availableDevicesArray = data;
670  }
671
672  public totalCount(): number {
673    if (this.availableDevicesArray) {
674      return this.availableDevicesArray.length;
675    }
676    return 0;
677  }
678
679  public getData(index: number): BluetoothDevice | null {
680    if (!this.availableDevicesArray) {
681      LogUtil.log(ConfigData.TAG + 'array is null.');
682      return null;
683    }
684    if (index < 0 || index >= this.totalCount()) {
685      LogUtil.log(ConfigData.TAG + 'index out of range.');
686      return null;
687    }
688    return this.availableDevicesArray[index];
689  }
690
691  public delData(device: BluetoothDevice): void {
692    if (this.availableDevicesArray) {
693      let index = this.availableDevicesArray.indexOf(device);
694      this.availableDevicesArray.splice(index, 1);
695      this.notifyDataDelete(index);
696    }
697  }
698}
699
700/**
701 * Pair dialog
702 */
703@CustomDialog
704struct PairDialog {
705  dialogController?: CustomDialogController;
706  private deviceName: string | undefined = undefined;
707  private pinCode: string = '';
708
709  action: (accept: boolean) => void = (accept: boolean) => {
710  };
711
712  aboutToAppear(): void {
713    LogUtil.log(ConfigData.TAG + `bluetooth PairDialog aboutToAppear.`)
714  }
715
716  build() {
717    Column() {
718      Text($r('app.string.bluetooth_pairing_request'))
719        .fontSize($r('app.float.font_20'))
720        .height($r('app.float.wh_value_56'))
721        .fontColor($r("sys.color.ohos_id_color_primary"))
722        .width(ConfigData.WH_100_100)
723        .fontWeight(500)
724        .padding({
725          left: $r('app.float.distance_24'),
726          top: $r('app.float.distance_14'),
727          bottom: $r('app.float.distance_14')
728        })
729
730      Column() {
731        if (this.pinCode) {
732          Text($r('app.string.bluetooth_pairing_intelligent_device_hit', this.deviceName, this.deviceName))
733            .fontSize($r('sys.float.ohos_id_text_size_body1'))
734            .fontColor($r("sys.color.ohos_id_color_primary"))
735            .width(ConfigData.WH_100_100)
736            .fontWeight(FontWeight.Regular)
737            .margin({ bottom: $r('app.float.distance_16') })
738
739          Text(`${this.pinCode}`)
740            .fontSize($r('app.float.pinCode_font_size'))
741            .fontWeight(500)
742            .fontColor($r("sys.color.ohos_id_color_primary"))
743            .width(ConfigData.WH_100_100)
744            .textAlign(TextAlign.Center)
745            .margin({
746              top: $r('app.float.distance_6'),
747              bottom: $r('app.float.distance_10')
748            })
749        } else {
750          Text($r('app.string.bluetooth_pairing_media_device_hit'))
751            .fontSize($r('app.float.font_16'))
752            .fontColor($r("sys.color.ohos_id_color_primary"))
753            .width(ConfigData.WH_100_100)
754            .margin({ bottom: $r('app.float.switch_summary_margin') })
755
756          Text(this.deviceName)
757            .fontSize($r('app.float.font_16'))
758            .width(ConfigData.WH_100_100)
759            .fontWeight(FontWeight.Bold)
760        }
761
762        // button
763        Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
764          Button() {
765            Text($r('app.string.cancel'))
766              .fontSize($r('app.float.font_16'))
767              .fontColor('#007DFF')
768              .fontWeight(500)
769          }
770          .backgroundColor($r("app.color.white_bg_color"))
771          .width($r("app.float.wh_value_160"))
772          .height($r("app.float.wh_value_40"))
773          .flexGrow(1)
774          .onClick(() => {
775            if (this.dialogController) {
776              this.dialogController.close();
777            }
778            this.action(false);
779          })
780
781          Divider()
782            .height($r("app.float.wh_value_24"))
783            .strokeWidth(0.5)
784            .vertical(true)
785            .color($r("sys.color.ohos_id_color_list_separator"))
786
787          Button() {
788            Text($r('app.string.bluetooth_pair_button'))
789              .fontSize($r('app.float.font_16'))
790              .fontColor('#007DFF')
791              .fontWeight(500)
792          }
793          .backgroundColor($r("app.color.white_bg_color"))
794          .width($r("app.float.wh_value_160"))
795          .height($r("app.float.wh_value_40"))
796          .flexGrow(1)
797          .onClick(() => {
798            if (this.dialogController) {
799              this.dialogController.close();
800            }
801            this.action(true);
802          })
803        }
804        .width(ConfigData.WH_100_100)
805        .height($r('app.float.wh_value_56'))
806        .margin({ top: $r('app.float.wh_value_10') })
807        .padding({ bottom: $r('app.float.wh_value_16') })
808      }
809      .width(ConfigData.WH_100_100)
810      .padding({
811        left: $r('app.float.distance_24'),
812        right: $r('app.float.distance_24')
813      })
814    }
815    .width(deviceTypeInfo === 'phone' || deviceTypeInfo === 'default' ? ConfigData.WH_100_100 : $r("app.float.wh_value_410"))
816    .padding(deviceTypeInfo === 'phone' || deviceTypeInfo === 'default' ? {
817      left: $r("app.float.wh_value_12"),
818      right: $r("app.float.wh_value_12")
819    } : {})
820  }
821}
822
823/**
824 * Get device icon resource
825 * @param type
826 * @return device icon path
827 */
828function getDeviceIconPath(deviceType: string): string {
829  let path: string = "/res/image/ic_bluetooth_device.svg";
830  switch (deviceType) {
831    case DeviceType.HEADPHONE:
832      path = "/res/image/ic_device_earphone_hero.svg";
833      break;
834
835    case DeviceType.PHONE:
836      path = "/res/image/ic_public_devices_phone.svg";
837      break;
838
839    case DeviceType.WATCH:
840      path = "/res/image/ic_device_watch.svg";
841      break;
842
843    case DeviceType.COMPUTER:
844      path = "/res/image/ic_device_matebook.svg";
845      break;
846  }
847  return path;
848}
849
850/**
851 * Pair mode prompt
852 * @param dialogTitle Dialog title
853 * @param dialogMessage Dialog message
854 * @param buttonValue Dialog buttonValue
855 */
856function showDialog(dialogTitle: string | Resource, dialogMessage: string | Resource, buttonValue: string | Resource) {
857  LogUtil.info(ConfigData.TAG + 'Bluetooth page showDialog in.');
858
859  AlertDialog.show({
860    title: dialogTitle,
861    message: dialogMessage,
862    confirm: {
863      value: buttonValue,
864      action: () => {
865        LogUtil.info(ConfigData.TAG + 'Bluetooth page showDialog : Button-clicking callback');
866      }
867    },
868    cancel: () => {
869      LogUtil.info(ConfigData.TAG + 'Bluetooth page showDialog : Closed callbacks');
870    }
871  })
872
873  LogUtil.info(ConfigData.TAG + 'Bluetooth page showDialog out.');
874}