1/*
2 * Copyright (c) 2022-2023 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 type { BrowserDataInterface, PendingCondition } from '@ohos/common';
17import {
18  BroadCast,
19  BrowserDataFactory,
20  CommonObserverCallback,
21  Constants,
22  DateUtil,
23  Log,
24  MediaDataSource,
25  MediaItem,
26  MediaObserver,
27  PendingTask,
28  TimelineData,
29  TraceControllerUtils,
30  ViewData,
31  ViewType
32} from '@ohos/common';
33import { GetTimelineDataCallback } from './GetTimelineDataCallback';
34
35const TITLE_DATA_INDEX = -1;
36const TAG: string = 'timeline_TimelineDataSource';
37
38// TimelineDataSource
39export class TimelineDataSource extends MediaDataSource {
40  initDataTraceName = 'TimeLinePageInitData';
41  dataObserver: CommonObserverCallback = new CommonObserverCallback(this);
42  groups: TimelineData[] = [];
43
44  // layoutIndex to groupIndex
45  groupIndexes: number[] = [];
46  groupViewIndexes: number[] = [];
47  isActive = false;
48  pendingEmitCallbacks: PendingTask;
49  groupBrowserDataImpl: BrowserDataInterface;
50
51  constructor(windowSize: number, broadCast: BroadCast) {
52    super(windowSize);
53    this.groupBrowserDataImpl = BrowserDataFactory.getFeature(BrowserDataFactory.TYPE_GROUP);
54    this.broadCast = broadCast;
55    this.pendingEmitCallbacks = new PendingTask(<PendingCondition> {
56      shouldPending: () => {
57        return !this.isActive;
58      }
59    });
60    this.initData();
61  }
62
63  initialize(): void {
64  }
65
66  getGroupData(): TimelineData[] {
67    return this.groups;
68  }
69
70  loadData() {
71    let callback: GetTimelineDataCallback = new GetTimelineDataCallback(this);
72    this.groupBrowserDataImpl.getData(callback, null);
73  }
74
75  public registerTimelineObserver(): void {
76    MediaObserver.getInstance().registerObserver(this.dataObserver);
77  }
78
79  public unregisterTimelineObserver(): void {
80    MediaObserver.getInstance().unregisterObserver(this.dataObserver);
81  }
82
83  onMediaLibDataChange(changeType: string): void {
84    Log.info(TAG, `onMediaLibDataChange type: ${changeType}`);
85    if (!this.isActive) {
86      this.pendingEmitCallbacks.clear();
87    }
88    this.switchRefreshOn();
89    this.onChange(changeType);
90  }
91
92  updateGroupData(requestTime: number, groups: TimelineData[]): void {
93    TraceControllerUtils.startTraceWithTaskId('updateGroupData', requestTime);
94    Log.info(TAG, 'updateGroupData begin');
95    this.lastUpdateTime = requestTime;
96
97    this.isPendingUpdateData = true;
98    this.pendingEmitCallbacks.execute(() => {
99      this.updateGroupSize(requestTime, groups);
100    })
101
102    TraceControllerUtils.finishTraceWithTaskId('updateGroupData', requestTime);
103    this.isPendingUpdateData = false;
104    this.pendingUpdateData.flush();
105
106  }
107
108  /**
109   * Update related variables of group count
110   *
111   * @param requestTime
112   * @param groups
113   */
114  updateGroupSize(requestTime: number, groups: TimelineData[]): void {
115    Log.info(TAG, 'updateGroupSize');
116    let previousSize: number = this.size;
117    let previousMediaCount = this.mediaCount;
118    this.groups = groups;
119    this.mCallbacks['updateGroupData'] && this.mCallbacks['updateGroupData'](this.groups)
120    this.size = 0;
121    this.mediaCount = 0;
122    this.dataIndexes = [];
123    this.layoutIndexes = [];
124    this.groupIndexes = [];
125    this.groupViewIndexes = [];
126    let dataIndex = 0;
127    for (let groupIndex = 0; groupIndex < groups.length; groupIndex++) {
128      let group = groups[groupIndex];
129      this.mediaCount += group.count;
130
131      // title
132      this.size++;
133      this.dataIndexes.push(TITLE_DATA_INDEX);
134      this.groupIndexes.push(groupIndex);
135      this.groupViewIndexes.push(TITLE_DATA_INDEX);
136
137      // items
138      for (let i = 0; i < group.count; i++) {
139        this.dataIndexes.push(dataIndex);
140        this.groupIndexes.push(groupIndex);
141        this.layoutIndexes.push(this.size);
142        this.groupViewIndexes.push(i);
143        this.size++;
144        dataIndex++;
145      }
146    }
147
148    Log.info(TAG, `updateGroupSize, old size: ${previousSize} , old mediaCount: ${previousMediaCount},\
149            new size: ${this.size}, new mediaCount: ${this.mediaCount}, real size: ${this.realSize}`);
150
151    this.isCountChanged = previousSize != this.size;
152    this.isCountReduced = previousSize > this.size;
153    if (requestTime !== Constants.NUMBER_0) {
154      this.addedCount = (this.realSize > Constants.NUMBER_0) ? (this.size - this.realSize) : Constants.NUMBER_0;
155      this.realSize = this.size;
156    }
157    this.updateCountPostProcess();
158  }
159
160  emitCountUpdateCallbacks(): void {
161    this.pendingEmitCallbacks.execute(() => {
162      super.emitCountUpdateCallbacks();
163    })
164  }
165
166  updateCountThroughMediaItems(requestTime: number, mediaItems: MediaItem[]): void {
167    Log.info(TAG, 'updateCountThroughMediaItems');
168    this.updateGroupSize(0, this.getGroupDataThroughMediaItems(mediaItems));
169  }
170
171  // Get grouping information through media item
172  getGroupDataThroughMediaItems(mediaItems: MediaItem[]): TimelineData[] {
173    Log.info(TAG, 'getGroupDataThroughMediaItems');
174    let groupDataList: TimelineData[] = [];
175    if (mediaItems == null || mediaItems.length == 0) {
176      Log.error(TAG, 'getGroupDataThroughMediaItems, mediaItems are empty!');
177      return groupDataList;
178    }
179    let groupCount = 1;
180    let startTime = mediaItems[0].getDataTaken();
181    let endTime = mediaItems[0].getDataTaken();
182    for (let i = 1; i < mediaItems.length; i++) {
183      let dateTaken = mediaItems[i].getDataTaken();
184      if (DateUtil.isTheSameDay(startTime, dateTaken)) {
185        groupCount++;
186        endTime = dateTaken;
187      } else {
188        let groupData = new TimelineData(startTime, endTime, groupCount);
189        groupDataList.push(groupData);
190        groupCount = 1;
191        startTime = dateTaken;
192        endTime = dateTaken;
193      }
194    }
195    let groupData = new TimelineData(startTime, endTime, groupCount);
196    groupDataList.push(groupData);
197    return groupDataList;
198  }
199
200  // Packaging data for the view layer
201  getWrappedData(index: number): ViewData {
202    if (index < 0 || index >= this.dataIndexes.length) {
203      Log.error(TAG, `getWrappedData, index out of the total size, index: ${index},
204                total size: ${this.dataIndexes.length}`);
205      return undefined;
206    }
207    // title
208    if (this.dataIndexes[index] == TITLE_DATA_INDEX) {
209      let result: ViewData = {
210        viewType: ViewType.GROUP_TITLE,
211        viewData: this.groups[this.groupIndexes[index]],
212        viewIndex: this.groupIndexes[index],
213      };
214      Log.debug(TAG, `index: ${index}, type: ${result.viewType},\
215        data: ${result.viewData.startDate}, viewIndex: ${result.viewIndex}`);
216      return result;
217    } else {
218      let dataIndexInWindow = this.dataIndexes[index] - this.activeStart;
219      let result: ViewData;
220      if (dataIndexInWindow > this.items.length || dataIndexInWindow < 0) {
221        Log.error(TAG, 'index out of active window');
222        return undefined;
223      } else {
224        result = {
225          viewType: ViewType.ITEM,
226          mediaItem: this.getMediaItemSafely(dataIndexInWindow),
227          viewIndex: index,
228          indexInGroup: this.groupViewIndexes[index]
229        };
230      }
231      Log.debug(TAG, `index: ${index}, type: ${result.viewType},\
232        data: ${result.mediaItem.uri} indexInGroup: ${result.indexInGroup}`);
233      return result;
234    }
235  }
236
237  getPositionByIndex(index: number): number {
238    let pos = (this.dataIndexes || []).findIndex((item) => item === index);
239    Log.info(TAG, `pos ${index}, ${this.dataIndexes[pos]} , ${this.groupIndexes[pos]}`);
240    return this.dataIndexes[pos] + this.groupIndexes[pos] + 1;
241  }
242
243  getMediaItemByPosition(position: number): MediaItem {
244    // title
245    let index = position
246    if (this.dataIndexes[position] == TITLE_DATA_INDEX) {
247      index = index + 1
248    }
249    let dataIndexInWindow = this.dataIndexes[index] - this.activeStart;
250    if (dataIndexInWindow > this.items.length || dataIndexInWindow < 0) {
251      Log.error(TAG, 'index out of active window');
252      return undefined;
253    } else {
254      return this.getMediaItemSafely(dataIndexInWindow)
255    }
256  }
257
258  onPhotoBrowserActive(isActive: boolean, transition: string): void {
259    Log.debug(TAG, `onPhotoBrowserActive ${isActive}, ${transition}`);
260    if (transition == Constants.PHOTO_TRANSITION_TIMELINE) {
261      if (isActive) {
262        this.onActive();
263      } else {
264        this.onInActive();
265      }
266    } else if (transition == Constants.PHOTO_TRANSITION_EDIT) {
267      if (isActive) {
268        this.isEditSaveReload = true;
269        this.onActive();
270      } else {
271        this.isEditSaveReload = false;
272      }
273    }
274  }
275
276  onActive(): void {
277    super.onActive();
278    this.pendingEmitCallbacks.flush();
279  }
280
281  getGroupCountBeforeItem(item: MediaItem): number {
282    let groupCount = 0;
283    if (!item) {
284      return groupCount;
285    }
286    let itemTime: number = item.getDataTaken();
287    for (let index = 0; index < this.groups.length; index++) {
288      const group: TimelineData = this.groups[index];
289      if (DateUtil.isTheSameDay(itemTime, group.startDate)) {
290        groupCount = index + 1;
291        break;
292      }
293    }
294    return groupCount;
295  }
296}
297