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}