1/**
2 * Copyright (c) 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 agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16import { Log } from '../utils/Log';
17import { TabItem, TabItemWithText } from '../model/common/TabItem';
18import { BroadCastManager } from '../model/common/BroadCastManager';
19import { BroadCast } from '../utils/BroadCast';
20import { BroadCastConstants } from '../model/common/BroadCastConstants';
21import { Constants } from '../model/common/Constants';
22import { BigDataConstants, ReportToBigDataUtil } from '../utils/ReportToBigDataUtil';
23
24const TAG: string = 'common_TabBar';
25
26export enum DeviceType {
27  PHONE_LIKE,
28  PC_LIKE
29}
30
31const tabMap = [
32BigDataConstants.PHOTO_TAB,
33BigDataConstants.ALBUM_TAB
34]
35
36@Component
37export struct TabBar {
38  @Consume isSelectedMode: boolean;
39  @Consume isAlbumSetSelectedMode: boolean;
40  @Consume isShowSideBar: boolean;
41  @Link @Watch('updateCurrentIndex') currentIndex: 0 | 1;
42  @Link isSidebar: boolean;
43  @StorageLink('sideBarBoundaryLineOpacity') sideBarBoundaryLineOpacity: number = 1;
44  @StorageLink('sideBarOpacity') sideBarOpacity: number = 1;
45  private tabs: TabItem[] = [];
46  private controller: TabsController | null = null;
47  private appBroadCast: BroadCast = BroadCastManager.getInstance().getBroadCast();
48  private deviceType: DeviceType = DeviceType.PC_LIKE;
49  private funcOnTabSelected?: Function;
50
51  aboutToAppear(): void {
52    this.funcOnTabSelected = (index: number): void => this.onTabSelected(index);
53    this.tabs[this.currentIndex].isSelected = true;
54    this.tabs.forEach((tab: TabItem) => {
55      Log.info(TAG, `${JSON.stringify(tab.name)} , ${tab.iconSelected}`);
56    });
57  }
58
59  updateCurrentIndex(): void {
60    this.onTabSelected(this.currentIndex);
61  }
62
63  build() {
64    if (this.isSidebar) {
65      if (this.deviceType == DeviceType.PC_LIKE) {
66        Row() {
67          Flex({
68            direction: FlexDirection.Column,
69            alignItems: ItemAlign.Start,
70            justifyContent: FlexAlign.Start
71          }) {
72            ForEach(this.tabs, (tab: TabItem) => {
73              Tab({
74                tabItem: tab,
75                index: this.tabs.indexOf(tab),
76                onTabSelected: this.funcOnTabSelected,
77                isSidebar: $isSidebar,
78                deviceType: this.deviceType
79              })
80            }, (tab: TabItem): string => {
81              return (tab.name.id).toString();
82            })
83          }
84          .padding({ left: 16, top: 96, right: 16 })
85          .flexGrow(1)
86          .backgroundColor($r('app.color.default_background_color'))
87
88          // Sidebar boundary line
89          if (this.isShowSideBar) {
90            Row() {
91            }
92            .width(0)
93            .height(Constants.PERCENT_100)
94            .border({ width:  0.5, color: $r('app.color.album_cover_gradient_start_color') })
95            .opacity(this.sideBarBoundaryLineOpacity)
96          }
97        }
98      } else {
99        Flex({
100          direction: FlexDirection.Column,
101          alignItems: ItemAlign.Center,
102          justifyContent: FlexAlign.Center
103        }) {
104          Column() {
105            ForEach(this.tabs, (tab: TabItem) => {
106              Stack() {
107                Tab({
108                  tabItem: tab,
109                  index: this.tabs.indexOf(tab),
110                  onTabSelected: this.funcOnTabSelected,
111                  isSidebar: $isSidebar,
112                  deviceType: this.deviceType
113                })
114              }
115              .layoutWeight(1)
116            }, (tab: TabItem): string => {
117              return (tab.name.id).toString();
118            })
119          }
120          .height($r('app.float.horizontal_width'))
121        }
122        .width($r('app.float.tab_bar_width'))
123        .backgroundColor($r('app.color.default_background_color'))
124      }
125
126    } else {
127      Flex({
128        direction: FlexDirection.Row,
129        alignItems: ItemAlign.Center,
130        justifyContent: FlexAlign.Center
131      }) {
132        ForEach(this.tabs, (tab: TabItem) => {
133          Stack() {
134            TabPhone({ tabItem: tab, index: this.tabs.indexOf(tab), onTabSelected: this.funcOnTabSelected })
135          }
136          .layoutWeight(1)
137          .onClick(() => {
138            if (this.funcOnTabSelected) {
139              if (this.currentIndex == this.tabs.indexOf(tab)) {
140                Log.debug(TAG, `it is same: ${this.currentIndex}`);
141                this.appBroadCast.emit(BroadCastConstants.RESET_ZERO, [this.currentIndex]);
142              }
143              this.funcOnTabSelected(this.tabs.indexOf(tab));
144            }
145            tab.isSelected = true;
146          })
147        }, (tab: TabItem): string => {
148          return (tab.name.id).toString();
149        })
150      }
151      .visibility((this.isSelectedMode || this.isAlbumSetSelectedMode) ? Visibility.None : Visibility.Visible)
152      .height($r('app.float.tab_bar_vertical_height'))
153      .backgroundColor($r('app.color.default_background_color'))
154      .padding({ left: $r('app.float.max_padding_start'), right: $r('app.float.max_padding_end') })
155    }
156  }
157
158  private onTabSelected(index: number): void {
159    Log.debug(TAG, `TabBar this.currentIndex: ${this.currentIndex} index: ${index}`);
160    this.currentIndex = index as 0 | 1;
161    if (this.controller != null) {
162        this.controller.changeIndex(this.currentIndex);
163    };
164    this.tabs.forEach((tab: TabItem) => {
165      if (this.tabs.indexOf(tab) == index) {
166        tab.isSelected = true;
167      } else {
168        tab.isSelected = false;
169      }
170    })
171    let currentTab: string = tabMap[this.currentIndex] ? tabMap[this.currentIndex] : BigDataConstants.PHOTO_TAB;
172    interface Msg {
173      switchTab: string;
174      current: string;
175    }
176    let msg: Msg = {
177      switchTab: BigDataConstants.CLICK_SWITCH,
178      current: currentTab,
179    }
180    ReportToBigDataUtil.report(BigDataConstants.TAB_SWITCH_ID, msg);
181    Log.info(TAG, `select ${this.currentIndex}`);
182  }
183}
184
185// single tab
186@Component
187struct Tab {
188  @ObjectLink tabItem: TabItem;
189  @Link isSidebar: boolean;
190  index: number = 0;
191  onTabSelected?: Function;
192  private deviceType: number = 0;
193
194  build() {
195    if (this.deviceType == DeviceType.PC_LIKE) {
196      Flex({
197        direction: FlexDirection.Row,
198        alignItems: ItemAlign.Center,
199        justifyContent: FlexAlign.Start
200      }) {
201        Stack() {
202          Image(this.tabItem.getIcon(this.tabItem.isSelected))
203            .draggable(false)
204            .height($r('app.float.icon_size'))
205            .width($r('app.float.icon_size'))
206            .objectFit(ImageFit.Fill)
207        }
208        .padding({
209          left: $r('app.float.tab_bar_text_padding_left'),
210        })
211
212        Text(this.tabItem.name)
213          .fontSize($r('sys.float.ohos_id_text_size_sub_title2'))
214          .fontWeight(FontWeight.Medium)
215          .fontColor(this.tabItem.getTextColor())
216          .padding({
217            left: $r('app.float.tab_bar_text_padding_left'),
218            top: $r('app.float.tab_bar_text_padding_horizontal'),
219            bottom: $r('app.float.tab_bar_text_padding_horizontal')
220          })
221          .height($r('app.float.menu_height'))
222      }
223      .backgroundColor(this.tabItem.isSelected ? '#DAE2F5' : $r('app.color.transparent'))
224      .borderRadius($r('app.float.single_tab_margin'))
225      .onClick((): void => {
226        this.onTabSelected && this.onTabSelected(this.index);
227        this.tabItem.isSelected = true;
228      })
229    } else {
230      Flex({
231        direction: FlexDirection.Column,
232        alignItems: ItemAlign.Center,
233        justifyContent: FlexAlign.Center,
234      }) {
235        Stack() {
236          Image(this.tabItem.getIcon(this.tabItem.isSelected))
237            .draggable(false)
238            .height($r('app.float.icon_size'))
239            .width($r('app.float.icon_size'))
240            .objectFit(ImageFit.Fill)
241        }
242        .padding({
243          left: this.isSidebar ? 0 : $r('app.float.tab_bar_text_padding_left'),
244        })
245
246        Text(this.tabItem.name)
247          .fontSize($r('sys.float.ohos_id_text_size_caption1'))
248          .fontFamily($r('app.string.id_text_font_family_medium'))
249          .fontColor(this.tabItem.getTextColor())
250          .padding({
251            left: $r('app.float.tab_bar_text_padding_horizontal'),
252            top: $r('app.float.tab_bar_text_padding_top'),
253            right: $r('app.float.tab_bar_text_padding_horizontal')
254          })
255      }
256      .onClick((): void => {
257        this.onTabSelected && this.onTabSelected(this.index);
258        this.tabItem.isSelected = true;
259      })
260    }
261  }
262}
263
264// phone bottom tab
265@Component
266struct TabPhone {
267  @ObjectLink tabItem: TabItem;
268  index: number = 0;
269  onTabSelected?: Function;
270
271  build() {
272    Flex({
273      direction: FlexDirection.Column,
274      alignItems: ItemAlign.Center,
275      justifyContent: FlexAlign.Center
276    }) {
277      Image(this.tabItem.getIcon(this.tabItem.isSelected))
278        .draggable(false)
279        .height($r('app.float.icon_size'))
280        .width($r('app.float.icon_size'))
281        .objectFit(ImageFit.Fill)
282        .margin({
283          bottom: $r('app.float.tab_bar_image_bottom')
284        })
285      Text(this.tabItem.name)
286        .fontSize($r('app.float.tab_bar_text_size'))
287        .fontWeight(FontWeight.Medium)
288        .fontColor(this.tabItem.getTextColor())
289    }
290    .key('Tab' + this.tabItem.componentKey)
291    .padding({
292      top: $r('app.float.tab_bar_padding_top'),
293      left: $r('app.float.tab_bar_padding_left'),
294      right: $r('app.float.tab_bar_padding_right'),
295      bottom: $r('app.float.tab_bar_padding_bottom'),
296    })
297    .height($r('app.float.tab_bar_vertical_height'))
298    .borderRadius($r('app.float.single_tab_margin'))
299  }
300}
301
302// For Album Set
303@Component
304export struct TabBarForAlbumSet {
305  @Consume isTabBarShow: boolean;
306  private currentIndex: number = 0;
307  private tabs: TabItemWithText[] = [];
308  private controller: TabsController | null = null;
309  private funcOnTabSelected?: Function;
310
311  aboutToAppear(): void {
312    this.funcOnTabSelected = (index: number): void => this.onTabSelected(index);
313    this.tabs[this.currentIndex].isSelected = true;
314    this.tabs.forEach((tab: TabItemWithText) => {
315      Log.info(TAG, `${JSON.stringify(tab.name)}, ${tab.isSelected}`);
316    });
317  }
318
319  build() {
320    if (this.isTabBarShow) {
321      Flex({
322        direction: FlexDirection.Row,
323        justifyContent: FlexAlign.Center,
324        alignItems: ItemAlign.Start
325      }) {
326        ForEach(this.tabs, (tab: TabItemWithText) => {
327          TabWithText({ tabItemWithText: tab, index: this.tabs.indexOf(tab), onTabSelected: this.funcOnTabSelected })
328        }, (tab: TabItemWithText): string => {
329          return (tab.name.id).toString();
330        })
331      }
332      .width('100%')
333      .height($r('app.float.album_set_tab_bar_height'))
334      .padding({ left: $r('app.float.max_padding_start'), right: $r('app.float.max_padding_end') })
335      .backgroundColor($r('app.color.default_background_color'))
336    }
337  }
338
339  private onTabSelected(index: number): void {
340    Log.info(TAG, `TabBarForAlbumSet this.currentIndex: ${this.currentIndex} index: ${index}`);
341    this.currentIndex = index;
342    if (this.controller != null) {
343        this.controller.changeIndex(this.currentIndex);
344    };
345    this.tabs.forEach((tab: TabItemWithText) => {
346      tab.isSelected = false;
347    })
348    Log.info(TAG, `select ${this.currentIndex}`);
349  }
350}
351
352// single tab which only has text
353// For Album Set
354@Component
355struct TabWithText {
356  @Consume isAlbumSetSelectedMode: boolean;
357  @ObjectLink tabItemWithText: TabItemWithText;
358  @State tabWidth: number = 0;
359  index: number = 0;
360  onTabSelected?: Function;
361
362  aboutToAppear(): void {
363    // Determine the length of the underline based on the font length
364    if (this.index == 0) {
365      this.tabWidth = px2vp(fp2px(Constants.TEXT_SIZE_SUB_TITLE2)) * 2;
366    } else {
367      this.tabWidth = px2vp(fp2px(Constants.TEXT_SIZE_SUB_TITLE2)) * 4;
368    }
369    Log.info(TAG, `index is ${this.index} and tabWidth is ${this.tabWidth}`);
370  }
371
372  build() {
373    Flex({
374      direction: FlexDirection.Column,
375      justifyContent: FlexAlign.Center,
376      alignItems: ItemAlign.Center
377    }) {
378      Text(this.tabItemWithText.name)
379        .fontSize(this.tabItemWithText.getTextSize())
380        .fontWeight(this.tabItemWithText.getTextWeight())
381        .fontColor(this.tabItemWithText.getTextColor())
382        .maxLines(1)
383        .margin({ top: $r('app.float.tab_bar_line_margin_top'),
384          left: $r('app.float.single_tab_margin'),
385          right: $r('app.float.single_tab_margin'),
386          bottom: $r('app.float.tab_bar_line_margin_top') })
387      Column()
388        .width(this.tabWidth)
389        .height($r('app.float.tab_bar_line_height'))
390        .borderRadius($r('app.float.tab_bar_line_radius'))
391        .backgroundColor(this.tabItemWithText.getTextColor())
392        .visibility(this.tabItemWithText.isSelected ? Visibility.Visible : Visibility.Hidden)
393    }
394    .height('100%')
395    .onClick(() => {
396      if (!this.isAlbumSetSelectedMode) {
397        this.onTabSelected && this.onTabSelected(this.index);
398        this.tabItemWithText.isSelected = true
399      }
400    })
401  }
402}