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 { TimelineData } from './photo/TimelineData';
17import { Log } from '../../utils/Log';
18import { MediaItem } from './photo/MediaItem';
19import type { AsyncCallback } from '../common/AsyncCallback';
20import { BrowserDataFactory } from '../../interface/BrowserDataFactory';
21import type { BrowserDataInterface } from '../../interface/BrowserDataInterface';
22import { Constants } from '../common/Constants';
23
24const TAG: string = 'common_SelectManager';
25
26export class BucketSelectionEntry {
27  private groupId = -1;
28  private clickedSet: Set<string> = new Set();
29  private totalCount = 0;
30  private inverseSelection = false;
31  private groupSelect = false;
32  private selectedMap: Map<string, MediaItem> = new Map();
33
34  public setGroupId(groupId: number): void {
35    this.groupId = groupId;
36  }
37
38  public getGroupId(): number {
39    return this.groupId;
40  }
41
42  public setTotalCount(totalCount: number): void {
43    this.totalCount = totalCount;
44  }
45
46  public getTotalCount(): number {
47    return this.totalCount;
48  }
49
50  public setGroupSelect(selectMode: boolean): void {
51    this.groupSelect = selectMode;
52  }
53
54  public getGroupSelect(): boolean {
55    return this.groupSelect;
56  }
57
58  public getClickSet(): Set<string> {
59    return this.clickedSet;
60  }
61
62  public getSelectedMap(): Map<string, MediaItem> {
63    return this.selectedMap;
64  }
65
66  public getSelectedCount(): number {
67    if (this.inverseSelection) {
68      return this.totalCount - this.clickedSet.size;
69    }
70    return this.clickedSet.size;
71  }
72
73  public selectAll(): void {
74    this.inverseSelection = true;
75    this.groupSelect = true;
76    this.clickedSet.clear();
77    this.selectedMap.clear();
78  }
79
80  public deSelectAll(): void {
81    this.inverseSelection = false;
82    this.groupSelect = false;
83    this.clickedSet.clear();
84    this.selectedMap.clear();
85  }
86
87  public isItemSelected(targetId: string): boolean {
88    return (this.inverseSelection != this.clickedSet.has(targetId));
89  }
90
91  public inSelectAllMode(): boolean {
92    return this.inverseSelection && (this.clickedSet.size == 0);
93  }
94
95  /**
96   * Change the select all status of the entry, depending on the total deselection status of the timeline
97   *
98   * @param isInverseMode The total inverse selection status of the timeline. If it is true,
99   * it is global inverse selection and requires reverse operation
100   */
101  public changeSelectMode(isInverseMode: boolean): void {
102      isInverseMode
103      ? (this.getSelectedCount() == 0 ? this.selectAll() : this.deSelectAll())
104      : (this.inSelectAllMode() ? this.deSelectAll() : this.selectAll())
105  }
106
107  public getInverseSelection(): boolean {
108    return this.inverseSelection;
109  }
110}
111
112export class ItemCoordinate {
113  groupId = -1;
114  subIndex = -1;
115
116  constructor() {
117  }
118
119  public setGroupId(id: number): ItemCoordinate {
120    this.groupId = id;
121    return this;
122  }
123
124  public getGroupId(): number {
125    return this.groupId;
126  }
127
128  public setIndex(index: number): ItemCoordinate {
129    this.subIndex = index;
130    return this;
131  }
132
133  public getIndex(): number {
134    return this.subIndex;
135  }
136}
137
138export class SelectManager {
139  mIsSelectedMode = false;
140  clickedSet: Set<string> = new Set();
141  totalCount = 0;
142  inverseSelection = false;
143  inSingleMode = false;
144  isAllSelected = false;
145  mCallbacks = new Map<string, Function>();
146  photoDataImpl: BrowserDataInterface;
147  selectManagerCallback: SelectManagerCallback;
148  albumUri = undefined;
149  deviceId = undefined;
150  selectedMap: Map<string, MediaItem> = new Map();
151  getMediaItemFunc: Function;
152
153  constructor() {
154    this.selectManagerCallback = new SelectManagerCallback(this);
155  }
156
157  public setTotalCount(count: number): void {
158    this.totalCount = count;
159    this.mCallbacks.has('updateCount') && this.mCallbacks.get('updateCount')(this.getSelectedCount());
160
161    if (this.isAllSelected) {
162      this.isAllSelected = false;
163      this.mCallbacks.has('allSelect') && this.mCallbacks.get('allSelect')(false);
164    }
165    if (this.totalCount == this.getSelectedCount()) {
166      this.isAllSelected = true;
167      this.mCallbacks.has('allSelect') && this.mCallbacks.get('allSelect')(true);
168    }
169  }
170
171  public setPhotoDataImpl(): void {
172    this.photoDataImpl = BrowserDataFactory.getFeature(BrowserDataFactory.TYPE_PHOTO);
173  }
174
175  public setAlbumUri(albumUri): void {
176    this.albumUri = albumUri;
177  }
178
179  public setDeviceId(deviceId: string): void {
180    this.deviceId = deviceId;
181  }
182
183  public registerCallback(name: string, cb: Function): void {
184    this.mCallbacks.set(name, cb);
185  }
186
187  public unregisterCallback(name: string): void {
188    this.mCallbacks.delete(name);
189  }
190
191  public emitCallback(name: string, argument: unknown[]): void {
192    this.mCallbacks.has(name) && this.mCallbacks.get(name).apply(this, argument);
193  }
194
195  public toggle(targetId: string, isSelected: boolean, targetIndex?: number): boolean {
196    Log.info(TAG, `toggle ${targetId} ${isSelected} ${targetIndex}`);
197    if (targetId == undefined) {
198      return true;
199    }
200    if (isSelected == (!this.inverseSelection)) {
201      this.clickedSet.add(targetId);
202      if (this.getMediaItemFunc) {
203        this.selectedMap.set(targetId, this.getMediaItemFunc(targetId));
204      }
205      Log.info(TAG, `add targetID ${targetId}`);
206    } else {
207      this.clickedSet.delete(targetId);
208      if (this.getMediaItemFunc) {
209        this.selectedMap.delete(targetId);
210      }
211    }
212    if (this.totalCount == this.getSelectedCount()) {
213      this.isAllSelected = true;
214      this.mCallbacks.has('allSelect') && this.mCallbacks.get('allSelect')(true);
215    } else {
216      this.isAllSelected = false;
217      this.mCallbacks.has('allSelect') && this.mCallbacks.get('allSelect')(false);
218    }
219    this.mCallbacks.has('updateCount') && this.mCallbacks.get('updateCount')(this.getSelectedCount());
220    if (targetIndex !== undefined) {
221      this.mCallbacks.has('select') && this.mCallbacks.get('select')(targetIndex);
222    }
223    return true;
224  }
225
226  public selectAllWithoutNotify(reverseSelection: boolean, shouldCallSelectALl: boolean): void {
227    if (reverseSelection) {
228      this.inverseSelection = true;
229      this.clickedSet.clear();
230      this.selectedMap.clear();
231      this.isAllSelected = true;
232    } else {
233      this.isAllSelected = true;
234    }
235    AppStorage.setOrCreate('focusUpdate', true);
236    if (shouldCallSelectALl) {
237      this.mCallbacks.has('allSelect') && this.mCallbacks.get('allSelect')(true);
238    }
239    this.mCallbacks.has('updateCount') && this.mCallbacks.get('updateCount')(this.getSelectedCount());
240  }
241
242  public selectAll(reverseSelection: boolean): void {
243    this.selectAllWithoutNotify(reverseSelection, true);
244  }
245
246  public deSelectAll(): void {
247    this.inverseSelection = false;
248    this.isAllSelected = false;
249    this.clickedSet.clear();
250    this.selectedMap.clear();
251    AppStorage.setOrCreate('focusUpdate', true);
252    this.mCallbacks.has('allSelect') && this.mCallbacks.get('allSelect')(false);
253    this.mCallbacks.has('updateCount') && this.mCallbacks.get('updateCount')(this.getSelectedCount());
254  }
255
256  public isItemSelected(targetId: string, index?: number): boolean {
257    Log.info(TAG, `isItemSelected ${targetId}, ${index}`);
258    return (this.inverseSelection != this.clickedSet.has(targetId));
259  }
260
261  public getSelectedCount(): number {
262    return (this.inverseSelection) ? this.totalCount - this.clickedSet.size : this.clickedSet.size;
263  }
264
265  public onModeChange(newMode: boolean): void {
266    if (newMode) {
267      this.mIsSelectedMode = true;
268    } else {
269      this.mIsSelectedMode = false;
270      this.inverseSelection = false;
271      this.isAllSelected = false;
272      this.clickedSet.clear();
273      this.selectedMap.clear();
274      AppStorage.setOrCreate('focusUpdate', true);
275      this.mCallbacks.has('allSelect') && this.mCallbacks.get('allSelect')(false);
276      this.mCallbacks.has('updateCount') && this.mCallbacks.get('updateCount')(this.getSelectedCount());
277    }
278  }
279
280  public getSelection(callback: AsyncCallback<string[]>): void {
281    if (this.inverseSelection) {
282      this.selectManagerCallback.setSubCallback(callback);
283      this.photoDataImpl.getData(this.selectManagerCallback, {
284        albumUri: this.albumUri
285      });
286    } else {
287      let result = [];
288      result = Array.from(this.clickedSet);
289      callback.callback(result);
290    }
291  }
292
293  public getDeleteSelection(callback: AsyncCallback<string[]>): void {
294    if (this.inverseSelection) {
295      this.selectManagerCallback.setSubCallback(callback);
296      this.photoDataImpl.getData(this.selectManagerCallback, {
297        albumUri: this.albumUri
298      });
299    } else {
300      let result = [];
301      result = Array.from(this.clickedSet);
302      callback.callback(result);
303    }
304  }
305
306  public async getSelectedItems(callback: Function): Promise<void> {
307    let result = new Array<MediaItem>();
308    Log.info(TAG, 'getSelectedItems this.inverseSelection: ' + this.inverseSelection);
309    if (this.inverseSelection) {
310      await this.getItems(this.photoDataImpl, 0, this.totalCount, (temp: MediaItem[]) => {
311        temp.forEach((item) => {
312          if (item && !this.clickedSet.has(item.uri)) {
313            result.push(item);
314          }
315        });
316        Log.info(TAG, `enter callback result ${result.length}`);
317        callback(result);
318      });
319    } else {
320      let result = [];
321      result = Array.from(this.selectedMap.values());
322      callback(result);
323    }
324  }
325
326  public handleSelection(mediaItems: MediaItem[], callback: AsyncCallback<string[]>): void {
327    let result = [];
328    mediaItems.forEach((mediaItem) => {
329      if (mediaItem && !this.clickedSet.has(mediaItem.uri)) {
330        result.push(mediaItem.uri);
331      }
332    })
333    callback.callback(result);
334  }
335
336  public async getItems(photoDataImpl: BrowserDataInterface,
337                        start: number, count: number, callbackFunc: Function): Promise<void> {
338    Log.info(TAG, `getItems start: ${start} count: ${count}`);
339    let cb: AsyncCallback<MediaItem[]> = {
340      callback: (t: MediaItem[]) => {
341        //注意命名不要冲突
342        callbackFunc(t);
343      }
344    }
345    photoDataImpl.getData(cb, { albumUri: this.albumUri, start: start, count: count });
346  }
347
348  public getClassName(): string {
349    return 'SelectManager';
350  }
351
352  public setGetMediaItemFunc(func: Function): void {
353    this.getMediaItemFunc = func;
354  }
355
356  public releaseGetMediaItemFunc(): void {
357    this.getMediaItemFunc = undefined;
358  }
359}
360
361class SelectManagerCallback implements AsyncCallback<MediaItem[]> {
362  source: SelectManager;
363  subCallback: AsyncCallback<string[]>;
364
365  constructor(source: SelectManager) {
366    this.source = source;
367  }
368
369  public setSubCallback(cb: AsyncCallback<string[]>): void {
370    this.subCallback = cb;
371  }
372
373  public callback(mediaSetList: MediaItem[]): void {
374    this.source.handleSelection(mediaSetList, this.subCallback);
375  }
376}
377
378export class ThirdSelectManager extends SelectManager {
379  type: string;
380  isMultiPick: boolean;
381  selectedMap: Map<string, MediaItem> = new Map();
382  indexMap: Map<MediaItem, number> = new Map();
383  previewSet: Set<string> = new Set();
384  isPreview: boolean = false;
385
386  constructor() {
387    super();
388  }
389
390  public setType(type: string): void {
391    this.type = type;
392  }
393
394  public getType(): string {
395    return this.type;
396  }
397
398  public setIsMultiPick(isMultiPick: boolean): void {
399    this.isMultiPick = isMultiPick;
400  }
401
402  public getIsMultiPick(): boolean {
403    return this.isMultiPick;
404  }
405
406  public getSelectedSet(): Set<string> {
407    return super.clickedSet;
408  }
409
410  /**
411   * 初始化图库图片预选择
412   *
413   * @param targetIdList 预选择图库资源Id
414   */
415  public initPreSelectPhotos(targetIdList: string[]): void {
416    this.deSelectAll();
417    if (this.getMediaItemFunc) {
418      targetIdList.forEach((targetId: string): void => {
419        let containUri = this.selectedMap.has(targetId);
420        if (!containUri) {
421          this.getMediaItemFunc(targetId, (item) => {
422            this.toggleWithItem(targetId, true, item);
423          });
424        }
425      });
426    }
427  }
428
429  /**
430   * 针对指定mediaItem预加载缓存
431   *
432   * @param targetId 图库资源Id
433   * @param isSelected 图库资源是否被选中
434   * @param mediaItem mediaItem
435   * @returns 是否成功
436   */
437  public toggleWithItem(targetId: string, isSelected: boolean, mediaItem: MediaItem): boolean {
438    if (mediaItem) {
439      let containUri = this.selectedMap.has(targetId);
440      if (isSelected && !containUri) {
441        this.selectedMap.set(targetId, mediaItem);
442        this.indexMap.set(mediaItem, undefined);
443      }
444      if (!isSelected && containUri) {
445        this.selectedMap.delete(targetId);
446        this.indexMap.delete(mediaItem);
447      }
448      return super.toggle(targetId, isSelected, undefined);
449    }
450    return false;
451  }
452
453  public toggle(targetId: string, isSelected: boolean, targetIndex?: number): boolean {
454    if (this.getMediaItemFunc) {
455      let containsUri = this.selectedMap.has(targetId);
456      if (isSelected && !containsUri) {
457        this.selectedMap.set(targetId, this.getMediaItemFunc(targetId));
458        this.indexMap.set(this.getMediaItemFunc(targetId), targetIndex);
459      }
460      if (!isSelected && containsUri) {
461        this.selectedMap.delete(targetId);
462        this.indexMap.delete(this.getMediaItemFunc(targetId));
463      }
464    }
465    if (this.isPreview) {
466      return this.togglePreview(targetId, isSelected, targetIndex);
467    }
468    return super.toggle(targetId, isSelected, targetIndex);
469  }
470
471  public toggleEdit(targetId: string, isSelected: boolean, targetIndex?: number): boolean {
472    if (this.getMediaItemFunc) {
473      let containsUri = this.selectedMap.has(targetId);
474      if (isSelected && !containsUri) {
475        this.selectedMap.set(targetId, this.getMediaItemFunc(targetId));
476        this.indexMap.set(this.getMediaItemFunc(targetId), targetIndex as number);
477      }
478      if (!isSelected && containsUri) {
479        this.selectedMap.delete(targetId);
480        this.indexMap.delete(this.getMediaItemFunc(targetId));
481      }
482    }
483
484    if (this.isPreview) {
485      let result: boolean = this.togglePreview(targetId, isSelected, targetIndex);
486      // 优先将数据同步
487      this.clickedSet.clear();
488      for (let item of this.previewSet) {
489        this.clickedSet.add(item as string);
490      }
491      return result;
492    }
493
494    return super.toggle(targetId, isSelected, targetIndex);
495  }
496
497  public toggleEditThree(targetId: string, isSelected: boolean, mediaItem: MediaItem, targetIndex?: number): boolean {
498    let containsUri = this.selectedMap.has(targetId);
499    if (isSelected && !containsUri) {
500      this.selectedMap.set(targetId, mediaItem);
501      this.indexMap.set(mediaItem, targetIndex as number);
502    }
503    if (!isSelected && containsUri) {
504      this.selectedMap.delete(targetId);
505      this.indexMap.delete(mediaItem);
506    }
507
508    if (this.isPreview) {
509      let result: boolean = this.togglePreview(targetId, isSelected, targetIndex);
510      // 优先将数据同步
511      this.clickedSet.clear();
512      for (let item of this.previewSet) {
513        this.clickedSet.add(item as string);
514      }
515      return result;
516    }
517
518    return super.toggle(targetId, isSelected, targetIndex);
519  }
520
521  public togglePreview(targetId: string, isSelected: boolean, targetIndex?: number): boolean {
522    Log.info(TAG, `togglePreview ${targetId} ${isSelected} ${targetIndex}`);
523    if (targetId == null) {
524      return true;
525    }
526
527    if (isSelected) {
528      this.previewSet.add(targetId);
529      Log.info(TAG, `add targetID ${targetId}`);
530    } else {
531      this.previewSet.delete(targetId);
532    }
533    if (this.totalCount === this.getSelectedCount()) {
534      this.isAllSelected = true;
535      this.mCallbacks.has('allSelect') && this.mCallbacks.get('allSelect')?.(true);
536    } else {
537      this.isAllSelected = false;
538      this.mCallbacks.has('allSelect') && this.mCallbacks.get('allSelect')?.(false);
539    }
540    this.mCallbacks.has('updateCount') && this.mCallbacks.get('updateCount')?.(this.getSelectedCount());
541    if (targetIndex !== undefined) {
542      this.mCallbacks.has('select') && this.mCallbacks.get('select')?.(targetIndex);
543    }
544    return true;
545  }
546
547  public deSelectAll(): void {
548    this.selectedMap.clear();
549    this.indexMap.clear();
550    super.deSelectAll();
551  }
552
553  public getSelectItemIndex(item: MediaItem): number {
554    let index = 0;
555    for (let selectItem of this.selectedMap.values()) {
556      if (item === selectItem) {
557        return index;
558      }
559      index++;
560    }
561    return Constants.INVALID;
562  }
563
564  public getSelectItemDataSourceIndex(item: MediaItem): number {
565    return this.indexMap.get(item) ? this.indexMap.get(item) : Constants.INVALID;
566  }
567
568  public checkItemInSelectMap(item: MediaItem): number {
569    let index = -1;
570    for (let selectItem of this.selectedMap.values()) {
571      index++;
572      if (item.uri === selectItem.uri) {
573        return index;
574      }
575    }
576    return -1;
577  }
578
579  public getSelectItems(): Array<MediaItem> {
580    let itemArray = new Array<MediaItem>();
581    if (this.selectedMap.size === 0) {
582      return itemArray;
583    }
584    for (let item of this.selectedMap.values()) {
585      itemArray.push(item);
586    }
587    return itemArray;
588  }
589
590  public getClassName(): string {
591    return 'ThirdSelectManager';
592  }
593
594  public refreshData(): void {
595    this.mCallbacks.has('refreshData') && this.mCallbacks.get('refreshData')(true);
596  }
597}
598
599export class TimelineSelectManager extends SelectManager {
600  mGroupData: TimelineData[] = [];
601  mSelectionEntryArray: BucketSelectionEntry[] = [];
602
603  constructor() {
604    super();
605  }
606
607  public selectAll(reverseSelection: boolean): void {
608    Log.info(TAG, `selectAll ${reverseSelection}`);
609    if (reverseSelection) {
610      this.inverseSelection = true;
611      this.clearEntryArray();
612      this.isAllSelected = true;
613    } else {
614      this.isAllSelected = true;
615    }
616    AppStorage.setOrCreate('focusUpdate', true);
617    this.mCallbacks.has('updateGroupCount') && this.mCallbacks.get('updateGroupCount')();
618    this.mCallbacks.has('allSelect') && this.mCallbacks.get('allSelect')(true);
619    this.mCallbacks.has('updateCount') && this.mCallbacks.get('updateCount')(this.getSelectedCount());
620  }
621
622  public deSelectAll(): void {
623    this.inverseSelection = false;
624    this.isAllSelected = false;
625    this.clearEntryArray();
626    AppStorage.setOrCreate('focusUpdate', true);
627    this.mCallbacks.has('updateGroupCount') && this.mCallbacks.get('updateGroupCount')();
628    this.mCallbacks.has('allSelect') && this.mCallbacks.get('allSelect')(false);
629    this.mCallbacks.has('updateCount') && this.mCallbacks.get('updateCount')(this.getSelectedCount());
630  }
631
632  public toggle(targetId: string, isSelected: boolean, targetIndex: number): boolean {
633    Log.info(TAG, `toggleTimeline ${targetIndex} id: ${targetId} ${isSelected}`);
634    let itemCoordinate = this.getCoordinateFromPosition(targetIndex);
635    let entry = this.getGroupEntry(itemCoordinate);
636    this.toggleClickSet(entry, targetId, isSelected);
637    let entrySelectedCount = entry.getSelectedCount();
638    Log.info(TAG, `check all selected ${entrySelectedCount} ${entry.getTotalCount()}`);
639
640    if (entrySelectedCount == entry.getTotalCount()) {
641      Log.info(TAG, 'group selectAll');
642      entry.selectAll();
643    }
644
645    this.mCallbacks.has('updateGroupCount') && this.mCallbacks.get('updateGroupCount')();
646
647    if (this.isAllSelected && (entrySelectedCount < entry.getTotalCount())) {
648      this.isAllSelected = false;
649      this.mCallbacks.has('updateCount') && this.mCallbacks.get('updateCount')(this.getSelectedCount());
650    }
651
652    if (this.getSelectedCount() == this.totalCount) {
653      this.isAllSelected = true;
654      this.mCallbacks.has('allSelect') && this.mCallbacks.get('allSelect')(true);
655      this.mCallbacks.has('updateCount') && this.mCallbacks.get('updateCount')(this.getSelectedCount());
656    } else {
657      this.isAllSelected = false;
658      this.mCallbacks.has('allSelect') && this.mCallbacks.get('allSelect')(false);
659      this.mCallbacks.has('updateCount') && this.mCallbacks.get('updateCount')(this.getSelectedCount());
660    }
661    return true;
662  }
663
664  public toggleGroup(itemCoordinate: ItemCoordinate): boolean {
665    Log.info(TAG, `check  toggleGroup: ${itemCoordinate.getGroupId()}`);
666    if (this.inverseSelection) {
667      let entry = this.mSelectionEntryArray[itemCoordinate.getGroupId()];
668      if (entry == undefined) {
669        entry = this.getGroupEntry(itemCoordinate);
670        entry.selectAll();
671      } else {
672        entry.changeSelectMode(true);
673      }
674    } else {
675      let entry = this.getGroupEntry(itemCoordinate);
676      entry.changeSelectMode(false);
677    }
678
679    let count = this.getSelectedCount();
680    if (count == this.totalCount) {
681      this.selectAll(false);
682    }
683    this.mCallbacks.has('updateGroupCount') && this.mCallbacks.get('updateGroupCount')();
684    this.mCallbacks.has('updateCount') && this.mCallbacks.get('updateCount')(this.getSelectedCount());
685    if (this.getSelectedCount() == this.totalCount) {
686      this.isAllSelected = true;
687      this.mCallbacks.has('allSelect') && this.mCallbacks.get('allSelect')(true);
688    } else {
689      this.isAllSelected = false;
690      this.mCallbacks.has('allSelect') && this.mCallbacks.get('allSelect')(false);
691    }
692    return true;
693  }
694
695  public getTitleCoordinate(position: number): ItemCoordinate {
696    return new ItemCoordinate().setGroupId(position).setIndex(-1);
697  }
698
699  public getSelectedCount(): number {
700    let count = 0;
701    this.mSelectionEntryArray.forEach((item) => {
702      count += item ? item.getSelectedCount() : 0;
703    })
704    if (this.inverseSelection) {
705      Log.info(TAG, `inverseSelection totalCount: ${this.totalCount - count}`);
706      return this.totalCount - count;
707    }
708    return count;
709  }
710
711  public onModeChange(newMode: boolean): void {
712    if (newMode) {
713      this.mIsSelectedMode = true;
714    } else {
715      this.mIsSelectedMode = false;
716      this.inverseSelection = false;
717      this.isAllSelected = false;
718      this.clearEntryArray();
719      AppStorage.setOrCreate('focusUpdate', true);
720      this.mCallbacks.has('updateGroupCount') && this.mCallbacks.get('updateGroupCount')();
721      this.mCallbacks.has('allSelect') && this.mCallbacks.get('allSelect')(false);
722      this.mCallbacks.has('updateCount') && this.mCallbacks.get('updateCount')(this.getSelectedCount());
723    }
724  }
725
726  public isItemSelected(targetId: string, index: number): boolean {
727    let itemCoordinate = this.getCoordinateFromPosition(index);
728    let entry = this.mSelectionEntryArray[itemCoordinate.getGroupId()];
729    if (this.inverseSelection) {
730      return (entry == undefined) || (!entry.isItemSelected(targetId));
731    } else {
732      return (entry != undefined) && (entry.isItemSelected(targetId));
733    }
734  }
735
736  public isGroupSelected(index: number): boolean {
737    let entry = this.mSelectionEntryArray[index];
738    if (this.inverseSelection) {
739      return entry == null || entry.getSelectedCount() == 0;
740    } else {
741      return (entry != null) && (entry.inSelectAllMode());
742    }
743  }
744
745  public setGroupData(groupData: TimelineData[]): void {
746    if (groupData == undefined) {
747      return;
748    }
749    this.mGroupData = groupData;
750  }
751
752  public updateGroupData(groupData: TimelineData[]): void {
753    if (groupData == undefined) {
754      return;
755    }
756    this.mGroupData = groupData;
757    this.mSelectionEntryArray.forEach((entry: BucketSelectionEntry) => {
758      if (entry != undefined && (entry.getGroupId() < this.mGroupData.length)) {
759        entry.setTotalCount(this.mGroupData[entry.getGroupId()].count);
760      }
761    })
762    this.mCallbacks.has('updateCount') && this.mCallbacks.get('updateCount')(this.getSelectedCount());
763  }
764
765  public async getSelection(callback: AsyncCallback<string[]>): Promise<void> {
766    let result = new Array<string>();
767    let start = 0;
768    let doneCount = 0;
769    if (this.inverseSelection) {
770      for (let i = 0; i < this.mGroupData.length; i++) {
771        if (this.mSelectionEntryArray[i]) {
772          //全选模式下用户操作过的日期下的items根据用户选择反选
773          await this.getInverseSelectedFromEntry(this.mSelectionEntryArray[i],
774            start, this.mGroupData[i].count, (temp: string[]) => {
775              result = result.concat(temp);
776              Log.info(TAG, `getInverseSelectedFromEntry result ${result.length}`);
777              doneCount++;
778              this.checkIsGetSelectionFinish(doneCount, result, callback);
779            });
780        } else {
781          //全选模式下用户未操作过的日期下的items全量选中
782          await this.getItems(this.photoDataImpl, start, this.mGroupData[i].count, (temp: MediaItem[]) => {
783            temp.forEach((item) => {
784              result.push(item.uri);
785            });
786            Log.info(TAG, `getInverseGroupItems result ${result.length}`);
787            doneCount++;
788            this.checkIsGetSelectionFinish(doneCount, result, callback);
789          });
790        }
791        start += this.mGroupData[i].count;
792      }
793    } else {
794      for (let i = 0; i < this.mGroupData.length; i++) {
795        if (this.mSelectionEntryArray[i]) {
796          //正选模式下根据遍历日期分组用户选择正常取item
797          await this.getSelectedFromEntry(this.mSelectionEntryArray[i],
798            start, this.mGroupData[i].count, (temp: string[]) => {
799              Log.info(TAG, `getSelectedFromEntry result ${result.length}`);
800              result = result.concat(temp);
801              doneCount++;
802              this.checkIsGetSelectionFinish(doneCount, result, callback);
803            });
804        } else {
805          //正选模式下未操作过的分组直接跳过
806          doneCount++;
807          this.checkIsGetSelectionFinish(doneCount, result, callback);
808        }
809        start += this.mGroupData[i].count;
810      }
811    }
812  }
813
814  public async getSelectedItems(callback: Function): Promise<void> {
815    Log.info(TAG, 'getSelectedItems');
816    let result = new Array<MediaItem>();
817    let start = 0;
818    let doneCount = 0;
819    if (this.inverseSelection) {
820      Log.info(TAG, 'getSelectedItems: mode is inverseSelection');
821      for (let i = 0; i < this.mGroupData.length; i++) {
822        if (this.mSelectionEntryArray[i]) {
823          if (this.mSelectionEntryArray[i].getInverseSelection()) {
824            await this.getItems(this.photoDataImpl, start, this.mGroupData[i].count, (temp: MediaItem[]) => {
825              temp.forEach((item) => {
826                if (this.mSelectionEntryArray[i].getClickSet().has(item.uri)) {
827                  Log.debug(TAG, 'push one item');
828                  result.push(item);
829                }
830              });
831              doneCount++;
832              this.checkIsGetSelectionItemFinish(doneCount, result, callback);
833            });
834          } else {
835            await this.getItems(this.photoDataImpl, start, this.mGroupData[i].count, (temp: MediaItem[]) => {
836              temp.forEach((item) => {
837                if (!this.mSelectionEntryArray[i].getClickSet().has(item.uri)) {
838                  Log.debug(TAG, 'push one inverse item');
839                  result.push(item);
840                }
841              });
842              doneCount++;
843              this.checkIsGetSelectionItemFinish(doneCount, result, callback);
844            });
845          }
846        } else {
847          await this.getItems(this.photoDataImpl, start, this.mGroupData[i].count, (temp: MediaItem[]) => {
848            temp.forEach((item) => {
849              result.push(item);
850            });
851            doneCount++;
852            this.checkIsGetSelectionItemFinish(doneCount, result, callback);
853          });
854        }
855        start += this.mGroupData[i].count;
856      }
857    } else {
858      Log.info(TAG, 'getSelectedItems: mode is not inverseSelection');
859      for (let i = 0; i < this.mGroupData.length; i++) {
860        if (this.mSelectionEntryArray[i]) {
861          await this.getItems(this.photoDataImpl, start, this.mGroupData[i].count, (temp: MediaItem[]) => {
862            const entry = this.mSelectionEntryArray[i];
863            temp.forEach((item) => {
864              if (!entry.getInverseSelection()) {
865                if (entry.getClickSet().has(item.uri)) {
866                  Log.debug(TAG, 'push one item');
867                  result.push(item);
868                }
869              } else if (!entry.getClickSet().has(item.uri)) {
870                Log.debug(TAG, 'push one inverse item');
871                result.push(item);
872              }
873            });
874            doneCount++;
875            this.checkIsGetSelectionItemFinish(doneCount, result, callback);
876          });
877        } else {
878          doneCount++;
879          this.checkIsGetSelectionItemFinish(doneCount, result, callback);
880        }
881        start += this.mGroupData[i].count;
882      }
883    }
884  }
885
886  private toggleClickSet(entry: BucketSelectionEntry, targetId: string, isSelected: boolean): void {
887    Log.info(TAG, `toggleClickSet: ${targetId} + ${isSelected}`);
888    if (isSelected == (!this.inverseSelection)) {
889      this.toggleEntryItem(entry, targetId, true);
890    } else {
891      this.toggleEntryItem(entry, targetId, false);
892    }
893  }
894
895  private toggleEntryItem(entry: BucketSelectionEntry, targetId: string, isSelected: boolean): void {
896    Log.info(TAG, `toggleEntryItem ${targetId} ${isSelected}`);
897    let clickSet = entry.getClickSet();
898    if (isSelected != entry.getInverseSelection()) {
899      clickSet.add(targetId);
900    } else {
901      clickSet.delete(targetId);
902    }
903  }
904
905  private getCoordinateFromPosition(position: number): ItemCoordinate {
906    let index = 0;
907    let group = 0;
908    let totalSize = this.mGroupData.length;
909    for (; group < totalSize; group++) {
910      let count = this.mGroupData[group].count;
911      index += (count + 1);
912      if (index > position) {
913        index -= count;
914        group = Math.max(0, group);
915        break;
916      }
917    }
918    return new ItemCoordinate().setGroupId(group).setIndex(position - index);
919  }
920
921  private getGroupEntry(itemCoordinate: ItemCoordinate): BucketSelectionEntry {
922    let entry = this.mSelectionEntryArray[itemCoordinate.groupId];
923    if (entry == undefined) {
924      entry = new BucketSelectionEntry();
925      entry.setGroupId(itemCoordinate.groupId);
926      if (itemCoordinate.groupId >= 0 && itemCoordinate.groupId < this.mGroupData.length) {
927        Log.info(TAG, `entry.setTotalCount ${this.mGroupData[itemCoordinate.groupId].count}`);
928        entry.setTotalCount(this.mGroupData[itemCoordinate.groupId].count);
929      }
930      Log.info(TAG, `getGroupEntry mSelectionEntryArray ${itemCoordinate.groupId} entry: ${entry}`);
931      this.mSelectionEntryArray[itemCoordinate.groupId] = entry;
932    }
933    return entry;
934  }
935
936  private clearEntryArray(): void {
937    Log.info(TAG, 'clearEntryArray');
938    this.mSelectionEntryArray.length = 0;
939  }
940
941  private checkIsGetSelectionFinish(doneCount: number, result: string[], callback: AsyncCallback<string[]>): void {
942    if (this.mGroupData.length == doneCount) {
943      Log.info(TAG, `getSelection result ${result.length}`);
944      callback.callback(result);
945    }
946  }
947
948  private checkIsGetSelectionItemFinish(doneCount: number, result: MediaItem[], callback: Function): void {
949    if (this.mGroupData.length == doneCount) {
950      Log.info(TAG, `getSelection result ${result.length}`);
951      callback(result);
952    }
953  }
954
955  private async getSelectedFromEntry(entry: BucketSelectionEntry,
956                                     start: number, count: number, callback: Function): Promise<void> {
957    Log.info(TAG, `getSelectedFromEntry start: ${start}, count: ${count}`);
958    let result = new Array<string>();
959    if (entry.getInverseSelection()) {
960      await this.getItems(this.photoDataImpl, start, count, (temp: MediaItem[]) => {
961        temp.forEach((item) => {
962          if (!entry.getClickSet().has(item.uri)) {
963            result.push(item.uri);
964          }
965        });
966        callback(result);
967      });
968    } else {
969      Log.info(TAG, 'getSelectedFromEntry not inverse');
970      result = Array.from(entry.getClickSet());
971      callback(result);
972    }
973  }
974
975  private async getInverseSelectedFromEntry(entry: BucketSelectionEntry,
976                                            start: number, count: number, callback: Function): Promise<void> {
977    Log.info(TAG, `getInverseSelectedFromEntry start: ${start}, count: ${count}`);
978    let result = new Array<string>();
979    if (entry.getInverseSelection()) {
980      result = Array.from(entry.getClickSet());
981      callback(result);
982    } else {
983      Log.info(TAG, 'getInverseSelectedFromEntry not inverse');
984      await this.getItems(this.photoDataImpl, start, count, (temp: MediaItem[]) => {
985        Log.info(TAG, `enter callback temp: ${entry.getClickSet().size}`);
986        temp.forEach((item) => {
987          if (!entry.getClickSet().has(item.uri)) {
988            result.push(item.uri);
989          }
990        });
991        Log.info(TAG, `enter callback result ${result.length}`);
992        callback(result);
993      });
994    }
995  }
996}
997
998export class AlbumSetSelectManager extends SelectManager {
999  isDisableRenameClickedSet: Set<string> = new Set();
1000  isDisableDeleteClickedSet: Set<string> = new Set();
1001
1002  constructor() {
1003    super();
1004  }
1005
1006  public toolBarStateToggle(targetId: string, isSelected: boolean,
1007                            isDisableRename: boolean, isDisableDelete: boolean): void {
1008    Log.info(TAG, `toolBarStateToggle${targetId}/${isSelected}/${isDisableRename}/${isDisableDelete}`);
1009    if (isSelected == (!this.inverseSelection)) {
1010      if (isDisableRename) {
1011        Log.info(TAG, `add isDisableRename targetID ${targetId}`);
1012        this.isDisableRenameClickedSet.add(targetId);
1013      }
1014      if (isDisableDelete) {
1015        Log.info(TAG, `add isDisableDelete targetID ${targetId}`);
1016        this.isDisableDeleteClickedSet.add(targetId);
1017      }
1018    } else {
1019      if (isDisableRename) {
1020        Log.info(TAG, `delete isDisableRename targetID ${targetId}`);
1021        this.isDisableRenameClickedSet.delete(targetId);
1022      }
1023      if (isDisableDelete) {
1024        Log.info(TAG, `delete isDisableDelete targetID ${targetId}`);
1025        this.isDisableDeleteClickedSet.delete(targetId);
1026      }
1027    }
1028
1029    let isDisableRenameFlag = !(this.isDisableRenameClickedSet.size == 0);
1030    let isDisableDeleteFlag = !(this.isDisableDeleteClickedSet.size == 0);
1031    this.mCallbacks.has('updateToolBarState') &&
1032    this.mCallbacks.get('updateToolBarState')(isDisableRenameFlag, isDisableDeleteFlag);
1033  }
1034
1035  public onModeChange(newMode: boolean): void {
1036    super.onModeChange(newMode);
1037    if (!newMode) {
1038      this.isDisableRenameClickedSet.clear();
1039      this.isDisableDeleteClickedSet.clear();
1040    }
1041  }
1042}