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