• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
384  constructor() {
385    super();
386  }
387
388  public setType(type: string): void {
389    this.type = type;
390  }
391
392  public getType(): string {
393    return this.type;
394  }
395
396  public setIsMultiPick(isMultiPick: boolean): void {
397    this.isMultiPick = isMultiPick;
398  }
399
400  public getIsMultiPick(): boolean {
401    return this.isMultiPick;
402  }
403
404  public getSelectedSet(): Set<string> {
405    return super.clickedSet;
406  }
407
408  public toggle(targetId: string, isSelected: boolean, targetIndex?: number): boolean {
409    if (this.getMediaItemFunc) {
410      let containsUri = this.selectedMap.has(targetId);
411      if (isSelected && !containsUri) {
412        this.selectedMap.set(targetId, this.getMediaItemFunc(targetId));
413        this.indexMap.set(this.getMediaItemFunc(targetId), targetIndex);
414      }
415      if (!isSelected && containsUri) {
416        this.selectedMap.delete(targetId);
417        this.indexMap.delete(this.getMediaItemFunc(targetId));
418      }
419    }
420    return super.toggle(targetId, isSelected, targetIndex);
421  }
422
423  public deSelectAll(): void {
424    this.selectedMap.clear();
425    this.indexMap.clear();
426    super.deSelectAll();
427  }
428
429  public getSelectItemIndex(item: MediaItem): number {
430    let index = 0;
431    for (let selectItem of this.selectedMap.values()) {
432      if (item === selectItem) {
433        return index;
434      }
435      index++;
436    }
437    return Constants.INVALID;
438  }
439
440  public getSelectItemDataSourceIndex(item: MediaItem): number {
441    return this.indexMap.get(item) ? this.indexMap.get(item) : Constants.INVALID;
442  }
443
444  public getSelectItems(): Array<MediaItem> {
445    let itemArray = new Array<MediaItem>();
446    if (this.selectedMap.size === 0) {
447      return itemArray;
448    }
449    for (let item of this.selectedMap.values()) {
450      itemArray.push(item);
451    }
452    return itemArray;
453  }
454
455  public getClassName(): string {
456    return 'ThirdSelectManager';
457  }
458}
459
460export class TimelineSelectManager extends SelectManager {
461  mGroupData: TimelineData[] = [];
462  mSelectionEntryArray: BucketSelectionEntry[] = [];
463
464  constructor() {
465    super();
466  }
467
468  public selectAll(reverseSelection: boolean): void {
469    Log.info(TAG, `selectAll ${reverseSelection}`);
470    if (reverseSelection) {
471      this.inverseSelection = true;
472      this.clearEntryArray();
473      this.isAllSelected = true;
474    } else {
475      this.isAllSelected = true;
476    }
477    AppStorage.SetOrCreate('focusUpdate', true);
478    this.mCallbacks.has('updateGroupCount') && this.mCallbacks.get('updateGroupCount')();
479    this.mCallbacks.has('allSelect') && this.mCallbacks.get('allSelect')(true);
480    this.mCallbacks.has('updateCount') && this.mCallbacks.get('updateCount')(this.getSelectedCount());
481  }
482
483  public deSelectAll(): void {
484    this.inverseSelection = false;
485    this.isAllSelected = false;
486    this.clearEntryArray();
487    AppStorage.SetOrCreate('focusUpdate', true);
488    this.mCallbacks.has('updateGroupCount') && this.mCallbacks.get('updateGroupCount')();
489    this.mCallbacks.has('allSelect') && this.mCallbacks.get('allSelect')(false);
490    this.mCallbacks.has('updateCount') && this.mCallbacks.get('updateCount')(this.getSelectedCount());
491  }
492
493  public toggle(targetId: string, isSelected: boolean, targetIndex: number): boolean {
494    Log.info(TAG, `toggleTimeline ${targetIndex} id: ${targetId} ${isSelected}`);
495    let itemCoordinate = this.getCoordinateFromPosition(targetIndex);
496    let entry = this.getGroupEntry(itemCoordinate);
497    this.toggleClickSet(entry, targetId, isSelected);
498    let entrySelectedCount = entry.getSelectedCount();
499    Log.info(TAG, `check all selected ${entrySelectedCount} ${entry.getTotalCount()}`);
500
501    if (entrySelectedCount == entry.getTotalCount()) {
502      Log.info(TAG, 'group selectAll');
503      entry.selectAll();
504    }
505
506    this.mCallbacks.has('updateGroupCount') && this.mCallbacks.get('updateGroupCount')();
507
508    if (this.isAllSelected && (entrySelectedCount < entry.getTotalCount())) {
509      this.isAllSelected = false;
510      this.mCallbacks.has('updateCount') && this.mCallbacks.get('updateCount')(this.getSelectedCount());
511    }
512
513    if (this.getSelectedCount() == this.totalCount) {
514      this.isAllSelected = true;
515      this.mCallbacks.has('allSelect') && this.mCallbacks.get('allSelect')(true);
516      this.mCallbacks.has('updateCount') && this.mCallbacks.get('updateCount')(this.getSelectedCount());
517    } else {
518      this.isAllSelected = false;
519      this.mCallbacks.has('allSelect') && this.mCallbacks.get('allSelect')(false);
520      this.mCallbacks.has('updateCount') && this.mCallbacks.get('updateCount')(this.getSelectedCount());
521    }
522    return true;
523  }
524
525  public toggleGroup(itemCoordinate: ItemCoordinate): boolean {
526    Log.info(TAG, `check  toggleGroup: ${itemCoordinate.getGroupId()}`);
527    if (this.inverseSelection) {
528      let entry = this.mSelectionEntryArray[itemCoordinate.getGroupId()];
529      if (entry == undefined) {
530        entry = this.getGroupEntry(itemCoordinate);
531        entry.selectAll();
532      } else {
533        entry.changeSelectMode(true);
534      }
535    } else {
536      let entry = this.getGroupEntry(itemCoordinate);
537      entry.changeSelectMode(false);
538    }
539
540    let count = this.getSelectedCount();
541    if (count == this.totalCount) {
542      this.selectAll(false);
543    }
544    this.mCallbacks.has('updateGroupCount') && this.mCallbacks.get('updateGroupCount')();
545    this.mCallbacks.has('updateCount') && this.mCallbacks.get('updateCount')(this.getSelectedCount());
546    if (this.getSelectedCount() == this.totalCount) {
547      this.isAllSelected = true;
548      this.mCallbacks.has('allSelect') && this.mCallbacks.get('allSelect')(true);
549    } else {
550      this.isAllSelected = false;
551      this.mCallbacks.has('allSelect') && this.mCallbacks.get('allSelect')(false);
552    }
553    return true;
554  }
555
556  public getTitleCoordinate(position: number): ItemCoordinate {
557    return new ItemCoordinate().setGroupId(position).setIndex(-1);
558  }
559
560  public getSelectedCount(): number {
561    let count = 0;
562    this.mSelectionEntryArray.forEach((item) => {
563      count += item ? item.getSelectedCount() : 0;
564    })
565    if (this.inverseSelection) {
566      Log.info(TAG, `inverseSelection totalCount: ${this.totalCount - count}`);
567      return this.totalCount - count;
568    }
569    return count;
570  }
571
572  public onModeChange(newMode: boolean): void {
573    if (newMode) {
574      this.mIsSelectedMode = true;
575    } else {
576      this.mIsSelectedMode = false;
577      this.inverseSelection = false;
578      this.isAllSelected = false;
579      this.clearEntryArray();
580      AppStorage.SetOrCreate('focusUpdate', true);
581      this.mCallbacks.has('updateGroupCount') && this.mCallbacks.get('updateGroupCount')();
582      this.mCallbacks.has('allSelect') && this.mCallbacks.get('allSelect')(false);
583      this.mCallbacks.has('updateCount') && this.mCallbacks.get('updateCount')(this.getSelectedCount());
584    }
585  }
586
587  public isItemSelected(targetId: string, index: number): boolean {
588    let itemCoordinate = this.getCoordinateFromPosition(index);
589    let entry = this.mSelectionEntryArray[itemCoordinate.getGroupId()];
590    if (this.inverseSelection) {
591      return (entry == undefined) || (!entry.isItemSelected(targetId));
592    } else {
593      return (entry != undefined) && (entry.isItemSelected(targetId));
594    }
595  }
596
597  public isGroupSelected(index: number): boolean {
598    let entry = this.mSelectionEntryArray[index];
599    if (this.inverseSelection) {
600      return entry == null || entry.getSelectedCount() == 0;
601    } else {
602      return (entry != null) && (entry.inSelectAllMode());
603    }
604  }
605
606  public setGroupData(groupData: TimelineData[]): void {
607    if (groupData == undefined) {
608      return;
609    }
610    this.mGroupData = groupData;
611  }
612
613  public updateGroupData(groupData: TimelineData[]): void {
614    if (groupData == undefined) {
615      return;
616    }
617    this.mGroupData = groupData;
618    this.mSelectionEntryArray.forEach((entry: BucketSelectionEntry) => {
619      if (entry != undefined && (entry.getGroupId() < this.mGroupData.length)) {
620        entry.setTotalCount(this.mGroupData[entry.getGroupId()].count);
621      }
622    })
623    this.mCallbacks.has('updateCount') && this.mCallbacks.get('updateCount')(this.getSelectedCount());
624  }
625
626  public async getSelection(callback: AsyncCallback<string[]>): Promise<void> {
627    let result = new Array<string>();
628    let start = 0;
629    let doneCount = 0;
630    if (this.inverseSelection) {
631      for (let i = 0; i < this.mGroupData.length; i++) {
632        if (this.mSelectionEntryArray[i]) {
633          //全选模式下用户操作过的日期下的items根据用户选择反选
634          await this.getInverseSelectedFromEntry(this.mSelectionEntryArray[i],
635            start, this.mGroupData[i].count, (temp: string[]) => {
636              result = result.concat(temp);
637              Log.info(TAG, `getInverseSelectedFromEntry result ${result.length}`);
638              doneCount++;
639              this.checkIsGetSelectionFinish(doneCount, result, callback);
640            });
641        } else {
642          //全选模式下用户未操作过的日期下的items全量选中
643          await this.getItems(this.photoDataImpl, start, this.mGroupData[i].count, (temp: MediaItem[]) => {
644            temp.forEach((item) => {
645              result.push(item.uri);
646            });
647            Log.info(TAG, `getInverseGroupItems result ${result.length}`);
648            doneCount++;
649            this.checkIsGetSelectionFinish(doneCount, result, callback);
650          });
651        }
652        start += this.mGroupData[i].count;
653      }
654    } else {
655      for (let i = 0; i < this.mGroupData.length; i++) {
656        if (this.mSelectionEntryArray[i]) {
657          //正选模式下根据遍历日期分组用户选择正常取item
658          await this.getSelectedFromEntry(this.mSelectionEntryArray[i],
659            start, this.mGroupData[i].count, (temp: string[]) => {
660              Log.info(TAG, `getSelectedFromEntry result ${result.length}`);
661              result = result.concat(temp);
662              doneCount++;
663              this.checkIsGetSelectionFinish(doneCount, result, callback);
664            });
665        } else {
666          //正选模式下未操作过的分组直接跳过
667          doneCount++;
668          this.checkIsGetSelectionFinish(doneCount, result, callback);
669        }
670        start += this.mGroupData[i].count;
671      }
672    }
673  }
674
675  public async getSelectedItems(callback: Function): Promise<void> {
676    Log.info(TAG, 'getSelectedItems');
677    let result = new Array<MediaItem>();
678    let start = 0;
679    let doneCount = 0;
680    if (this.inverseSelection) {
681      Log.info(TAG, 'getSelectedItems: mode is inverseSelection');
682      for (let i = 0; i < this.mGroupData.length; i++) {
683        if (this.mSelectionEntryArray[i]) {
684          if (this.mSelectionEntryArray[i].getInverseSelection()) {
685            await this.getItems(this.photoDataImpl, start, this.mGroupData[i].count, (temp: MediaItem[]) => {
686              temp.forEach((item) => {
687                if (this.mSelectionEntryArray[i].getClickSet().has(item.uri)) {
688                  Log.debug(TAG, 'push one item');
689                  result.push(item);
690                }
691              });
692              doneCount++;
693              this.checkIsGetSelectionItemFinish(doneCount, result, callback);
694            });
695          } else {
696            await this.getItems(this.photoDataImpl, start, this.mGroupData[i].count, (temp: MediaItem[]) => {
697              temp.forEach((item) => {
698                if (!this.mSelectionEntryArray[i].getClickSet().has(item.uri)) {
699                  Log.debug(TAG, 'push one inverse item');
700                  result.push(item);
701                }
702              });
703              doneCount++;
704              this.checkIsGetSelectionItemFinish(doneCount, result, callback);
705            });
706          }
707        } else {
708          await this.getItems(this.photoDataImpl, start, this.mGroupData[i].count, (temp: MediaItem[]) => {
709            temp.forEach((item) => {
710              result.push(item);
711            });
712            doneCount++;
713            this.checkIsGetSelectionItemFinish(doneCount, result, callback);
714          });
715        }
716        start += this.mGroupData[i].count;
717      }
718    } else {
719      Log.info(TAG, 'getSelectedItems: mode is not inverseSelection');
720      for (let i = 0; i < this.mGroupData.length; i++) {
721        if (this.mSelectionEntryArray[i]) {
722          await this.getItems(this.photoDataImpl, start, this.mGroupData[i].count, (temp: MediaItem[]) => {
723            const entry = this.mSelectionEntryArray[i];
724            temp.forEach((item) => {
725              if (!entry.getInverseSelection()) {
726                if (entry.getClickSet().has(item.uri)) {
727                  Log.debug(TAG, 'push one item');
728                  result.push(item);
729                }
730              } else if (!entry.getClickSet().has(item.uri)) {
731                Log.debug(TAG, 'push one inverse item');
732                result.push(item);
733              }
734            });
735            doneCount++;
736            this.checkIsGetSelectionItemFinish(doneCount, result, callback);
737          });
738        } else {
739          doneCount++;
740          this.checkIsGetSelectionItemFinish(doneCount, result, callback);
741        }
742        start += this.mGroupData[i].count;
743      }
744    }
745  }
746
747  private toggleClickSet(entry: BucketSelectionEntry, targetId: string, isSelected: boolean): void {
748    Log.info(TAG, `toggleClickSet: ${targetId} + ${isSelected}`);
749    if (isSelected == (!this.inverseSelection)) {
750      this.toggleEntryItem(entry, targetId, true);
751    } else {
752      this.toggleEntryItem(entry, targetId, false);
753    }
754  }
755
756  private toggleEntryItem(entry: BucketSelectionEntry, targetId: string, isSelected: boolean): void {
757    Log.info(TAG, `toggleEntryItem ${targetId} ${isSelected}`);
758    let clickSet = entry.getClickSet();
759    if (isSelected != entry.getInverseSelection()) {
760      clickSet.add(targetId);
761    } else {
762      clickSet.delete(targetId);
763    }
764  }
765
766  private getCoordinateFromPosition(position: number): ItemCoordinate {
767    let index = 0;
768    let group = 0;
769    let totalSize = this.mGroupData.length;
770    for (; group < totalSize; group++) {
771      let count = this.mGroupData[group].count;
772      index += (count + 1);
773      if (index > position) {
774        index -= count;
775        group = Math.max(0, group);
776        break;
777      }
778    }
779    return new ItemCoordinate().setGroupId(group).setIndex(position - index);
780  }
781
782  private getGroupEntry(itemCoordinate: ItemCoordinate): BucketSelectionEntry {
783    let entry = this.mSelectionEntryArray[itemCoordinate.groupId];
784    if (entry == undefined) {
785      entry = new BucketSelectionEntry();
786      entry.setGroupId(itemCoordinate.groupId);
787      if (itemCoordinate.groupId >= 0 && itemCoordinate.groupId < this.mGroupData.length) {
788        Log.info(TAG, `entry.setTotalCount ${this.mGroupData[itemCoordinate.groupId].count}`);
789        entry.setTotalCount(this.mGroupData[itemCoordinate.groupId].count);
790      }
791      Log.info(TAG, `getGroupEntry mSelectionEntryArray ${itemCoordinate.groupId} entry: ${entry}`);
792      this.mSelectionEntryArray[itemCoordinate.groupId] = entry;
793    }
794    return entry;
795  }
796
797  private clearEntryArray(): void {
798    Log.info(TAG, 'clearEntryArray');
799    this.mSelectionEntryArray.length = 0;
800  }
801
802  private checkIsGetSelectionFinish(doneCount: number, result: string[], callback: AsyncCallback<string[]>): void {
803    if (this.mGroupData.length == doneCount) {
804      Log.info(TAG, `getSelection result ${result.length}`);
805      callback.callback(result);
806    }
807  }
808
809  private checkIsGetSelectionItemFinish(doneCount: number, result: MediaItem[], callback: Function): void {
810    if (this.mGroupData.length == doneCount) {
811      Log.info(TAG, `getSelection result ${result.length}`);
812      callback(result);
813    }
814  }
815
816  private async getSelectedFromEntry(entry: BucketSelectionEntry,
817                                     start: number, count: number, callback: Function): Promise<void> {
818    Log.info(TAG, `getSelectedFromEntry start: ${start}, count: ${count}`);
819    let result = new Array<string>();
820    if (entry.getInverseSelection()) {
821      await this.getItems(this.photoDataImpl, start, count, (temp: MediaItem[]) => {
822        temp.forEach((item) => {
823          if (!entry.getClickSet().has(item.uri)) {
824            result.push(item.uri);
825          }
826        });
827        callback(result);
828      });
829    } else {
830      Log.info(TAG, 'getSelectedFromEntry not inverse');
831      result = Array.from(entry.getClickSet());
832      callback(result);
833    }
834  }
835
836  private async getInverseSelectedFromEntry(entry: BucketSelectionEntry,
837                                            start: number, count: number, callback: Function): Promise<void> {
838    Log.info(TAG, `getInverseSelectedFromEntry start: ${start}, count: ${count}`);
839    let result = new Array<string>();
840    if (entry.getInverseSelection()) {
841      result = Array.from(entry.getClickSet());
842      callback(result);
843    } else {
844      Log.info(TAG, 'getInverseSelectedFromEntry not inverse');
845      await this.getItems(this.photoDataImpl, start, count, (temp: MediaItem[]) => {
846        Log.info(TAG, `enter callback temp: ${entry.getClickSet().size}`);
847        temp.forEach((item) => {
848          if (!entry.getClickSet().has(item.uri)) {
849            result.push(item.uri);
850          }
851        });
852        Log.info(TAG, `enter callback result ${result.length}`);
853        callback(result);
854      });
855    }
856  }
857}
858
859export class AlbumSetSelectManager extends SelectManager {
860  isDisableRenameClickedSet: Set<string> = new Set();
861  isDisableDeleteClickedSet: Set<string> = new Set();
862
863  constructor() {
864    super();
865  }
866
867  public toolBarStateToggle(targetId: string, isSelected: boolean,
868                            isDisableRename: boolean, isDisableDelete: boolean): void {
869    Log.info(TAG, `toolBarStateToggle${targetId}/${isSelected}/${isDisableRename}/${isDisableDelete}`);
870    if (isSelected == (!this.inverseSelection)) {
871      if (isDisableRename) {
872        Log.info(TAG, `add isDisableRename targetID ${targetId}`);
873        this.isDisableRenameClickedSet.add(targetId);
874      }
875      if (isDisableDelete) {
876        Log.info(TAG, `add isDisableDelete targetID ${targetId}`);
877        this.isDisableDeleteClickedSet.add(targetId);
878      }
879    } else {
880      if (isDisableRename) {
881        Log.info(TAG, `delete isDisableRename targetID ${targetId}`);
882        this.isDisableRenameClickedSet.delete(targetId);
883      }
884      if (isDisableDelete) {
885        Log.info(TAG, `delete isDisableDelete targetID ${targetId}`);
886        this.isDisableDeleteClickedSet.delete(targetId);
887      }
888    }
889
890    let isDisableRenameFlag = !(this.isDisableRenameClickedSet.size == 0);
891    let isDisableDeleteFlag = !(this.isDisableDeleteClickedSet.size == 0);
892    this.mCallbacks.has('updateToolBarState') &&
893    this.mCallbacks.get('updateToolBarState')(isDisableRenameFlag, isDisableDeleteFlag);
894  }
895
896  public onModeChange(newMode: boolean): void {
897    super.onModeChange(newMode);
898    if (!newMode) {
899      this.isDisableRenameClickedSet.clear();
900      this.isDisableDeleteClickedSet.clear();
901    }
902  }
903}