DownloadCenter.ets 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748
  1. import { SegmentButton, SegmentButtonItemTuple, SegmentButtonOptions } from '@kit.ArkUI';
  2. import { DownloadCenterTask } from '../common/util/DownloadCenterManager';
  3. import { CommonConstants } from '../common/constants/CommonConstants';
  4. import { StrUtil } from '@pura/harmony-utils';
  5. import { PointLightDefaultButton } from './PointLight/PointLightDeFaultButton';
  6. import { ButtonFancyModifier, ShadowModifier, SymbolGlyphFancyModifier } from '../common/util/AttributeModifierUtil';
  7. @Observed
  8. class DownloadCenterTaskState implements DownloadCenterTask {
  9. taskId: string = '';
  10. title: string = '';
  11. fileName: string = '';
  12. coverPath: string = '';
  13. sizeText: string = '';
  14. sourceUrl: string = '';
  15. targetPath: string = '';
  16. downloadDir: string = '';
  17. totalBytes: number = 0;
  18. downloadedBytes: number = 0;
  19. progress: number = 0;
  20. speedBytesPerSec: number = 0;
  21. status: 'pending' | 'downloading' | 'paused' | 'completed' | 'failed' = 'pending';
  22. errorMessage: string = '';
  23. createdAt: number = 0;
  24. finishedAt: number = 0;
  25. constructor(task: DownloadCenterTask) {
  26. this.apply(task);
  27. }
  28. apply(task: DownloadCenterTask): void {
  29. this.taskId = task.taskId;
  30. this.title = task.title;
  31. this.fileName = task.fileName;
  32. this.coverPath = task.coverPath;
  33. this.sizeText = task.sizeText;
  34. this.sourceUrl = task.sourceUrl;
  35. this.targetPath = task.targetPath;
  36. this.downloadDir = task.downloadDir;
  37. this.totalBytes = task.totalBytes;
  38. this.downloadedBytes = task.downloadedBytes;
  39. this.progress = task.progress;
  40. this.speedBytesPerSec = task.speedBytesPerSec;
  41. this.status = task.status;
  42. this.errorMessage = task.errorMessage;
  43. this.createdAt = task.createdAt;
  44. this.finishedAt = task.finishedAt;
  45. }
  46. }
  47. @Component
  48. struct DownloadCenterTaskRow {
  49. @Prop themeColor: string = CommonConstants.DEFAULT_THEME_COLOR;
  50. @ObjectLink task: DownloadCenterTaskState;
  51. @Prop isDownloading: boolean = true;
  52. @Prop refreshVersion: number = 0;
  53. @Prop isSelectionMode: boolean = false;
  54. @Prop isSelected: boolean = false;
  55. onSelectionChange: (taskId: string, selected: boolean) => void = (_taskId: string, _selected: boolean): void => {};
  56. onPauseTask: (taskId: string) => void = (_taskId: string): void => {};
  57. onResumeTask: (taskId: string) => void = (_taskId: string): void => {};
  58. onDeleteTask: (taskId: string) => void = (_taskId: string): void => {};
  59. onRowClick: (taskId: string) => void = (_taskId: string): void => {};
  60. build() {
  61. ListItem() {
  62. Column({ space: 8 }) {
  63. Row({ space: 10 }) {
  64. if (this.isSelectionMode && this.isDownloading) {
  65. Checkbox({ name: this.task.taskId })
  66. .select(this.isSelected)
  67. .onChange((value: boolean) => {
  68. this.onSelectionChange(this.task.taskId, value)
  69. })
  70. }
  71. Image(StrUtil.isNotEmpty(this.task.coverPath) ? this.task.coverPath : $r('app.media.alt'))
  72. .width(54)
  73. .height(54)
  74. .borderRadius(9)
  75. .sourceSize({ width: 38, height: 38 })
  76. .alt($r('app.media.alt'))
  77. .fillColor(this.themeColor)
  78. .objectFit(ImageFit.Cover)
  79. Column({ space: 4 }) {
  80. Text(this.task.title)
  81. .fontSize(14)
  82. .fontWeight(FontWeight.Medium)
  83. .fontColor($r('app.color.text_color'))
  84. .maxLines(1)
  85. .padding({bottom:4})
  86. .textOverflow({ overflow: TextOverflow.Ellipsis })
  87. if (this.isDownloading) {
  88. Progress({ value: this.task.progress, total: 100, type: ProgressType.Linear })
  89. .width('100%')
  90. .color(this.getTaskStatusColor())
  91. .padding({bottom:4})
  92. .backgroundColor($r('app.color.track_color'))
  93. .style({ strokeWidth: 5 })
  94. Row() {
  95. Text(this.getTaskProgressLabelText())
  96. .fontSize(12)
  97. .fontColor(this.getTaskStatusColor())
  98. .maxLines(1)
  99. .textOverflow({ overflow: TextOverflow.Ellipsis })
  100. .layoutWeight(1)
  101. Text(this.getTaskSpeedText())
  102. .fontSize(12)
  103. .fontColor($r('app.color.text_color'))
  104. .opacity(0.72)
  105. .margin({ right: 8 })
  106. Text(this.getTaskProgressInfo())
  107. .fontSize(12)
  108. .fontColor($r('app.color.text_color'))
  109. .opacity(0.6)
  110. }
  111. .width('100%')
  112. } else {
  113. Row() {
  114. Text(this.getTaskTotalSizeText())
  115. .fontSize(12)
  116. .fontColor($r('app.color.text_color'))
  117. .opacity(0.6)
  118. Text(' · ')
  119. .fontSize(12)
  120. .fontColor($r('app.color.text_color'))
  121. .opacity(0.35)
  122. Text('已完成')
  123. .fontSize(12)
  124. .fontColor($r('app.color.text_color'))
  125. .opacity(0.85)
  126. }
  127. .width('100%')
  128. }
  129. }
  130. .alignItems(HorizontalAlign.Start)
  131. .layoutWeight(1)
  132. if (this.isDownloading) {
  133. this.taskActionButtonBuilder()
  134. }
  135. }
  136. .width('100%')
  137. Text(`${this.refreshVersion}`)
  138. .fontSize(0.1)
  139. .fontColor(Color.Transparent)
  140. .opacity(0)
  141. .width(0)
  142. .height(0)
  143. }
  144. .width('100%')
  145. .padding(10)
  146. .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.8 })
  147. .border({
  148. color: $r('app.color.index_background'),
  149. width: 1.8
  150. })
  151. .borderRadius(15)
  152. }
  153. .swipeAction({ end: this.deleteActionBuilder(), edgeEffect: SwipeEdgeEffect.None })
  154. .onClick(() => {
  155. if (this.isSelectionMode && this.isDownloading) {
  156. this.onSelectionChange(this.task.taskId, !this.isSelected)
  157. return
  158. }
  159. this.onRowClick(this.task.taskId)
  160. })
  161. }
  162. @Builder
  163. private taskActionButtonBuilder() {
  164. Button() {
  165. PointLightDefaultButton({
  166. isPx: false,
  167. isSysBol: true,
  168. pointColor: this.isTaskRunning() ? $r('app.color.text_color') : this.themeColor,
  169. imageResource: this.isTaskRunning() ? $r('sys.symbol.pause') : $r('sys.symbol.play_fill'),
  170. builderHeight: 36,
  171. builderWidth: 36,
  172. buttonScale: 1,
  173. canShadow: true,
  174. })
  175. }
  176. .width(38)
  177. .height(38)
  178. .backgroundColor(Color.Transparent)
  179. .borderRadius(16)
  180. .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.8 })
  181. .stateEffect(false)
  182. .onClick(() => {
  183. if (this.isTaskRunning()) {
  184. this.onPauseTask(this.task.taskId);
  185. return;
  186. }
  187. this.onResumeTask(this.task.taskId);
  188. })
  189. }
  190. @Builder
  191. private deleteActionBuilder() {
  192. Row() {
  193. Button('删除')
  194. .width(72)
  195. .height(52)
  196. .fontSize(14)
  197. .fontColor(Color.White)
  198. .backgroundColor($r('app.color.btn_red'))
  199. .borderRadius(14)
  200. .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.8 })
  201. .onClick(() => {
  202. this.onDeleteTask(this.task.taskId)
  203. })
  204. }
  205. .padding({ left: 8, right: 4 })
  206. .justifyContent(FlexAlign.Center)
  207. }
  208. private isTaskRunning(): boolean {
  209. return this.task.status === 'downloading';
  210. }
  211. private getTaskTotalSizeText(): string {
  212. if (this.task.totalBytes > 0) {
  213. return this.formatBytes(this.task.totalBytes);
  214. }
  215. return StrUtil.isNotEmpty(this.task.sizeText) ? this.task.sizeText : '--';
  216. }
  217. private getTaskProgressInfo(): string {
  218. const downloadedText: string = this.task.downloadedBytes > 0 ? this.formatBytes(this.task.downloadedBytes) : '0 B';
  219. const totalText: string = this.getTaskTotalSizeText();
  220. return `${downloadedText} / ${totalText}`;
  221. }
  222. private getTaskProgressLabel(): string {
  223. const value = this.task.progress;
  224. const rounded = Math.round(value * 10) / 10;
  225. const isInt = Math.abs(rounded - Math.round(rounded)) < 0.001;
  226. return isInt ? `${Math.round(rounded)}%` : `${rounded.toFixed(1)}%`;
  227. }
  228. private getEffectiveTaskStatus(): 'pending' | 'downloading' | 'paused' | 'completed' | 'failed' {
  229. if (this.shouldUsePauseStyleForFailure()) {
  230. return 'paused';
  231. }
  232. return this.task.status;
  233. }
  234. private shouldUsePauseStyleForFailure(): boolean {
  235. if (this.task.status !== 'failed') {
  236. return false;
  237. }
  238. const message = StrUtil.isNotEmpty(this.task.errorMessage) ? this.task.errorMessage.toLowerCase() : '';
  239. return message.includes('failed writing received') ||
  240. message.includes('failed wwiting received') ||
  241. message.includes('writing received');
  242. }
  243. private getTaskProgressLabelText(): string {
  244. const status = this.getEffectiveTaskStatus();
  245. if (status === 'failed') {
  246. return StrUtil.isNotEmpty(this.task.errorMessage) ? this.task.errorMessage : '下载失败';
  247. }
  248. if (status === 'paused') {
  249. return `已暂停 ${this.getTaskProgressLabel()}`;
  250. }
  251. if (status === 'pending') {
  252. return `等待中 ${this.getTaskProgressLabel()}`;
  253. }
  254. return this.getTaskProgressLabel();
  255. }
  256. private getTaskSpeedText(): string {
  257. const status = this.getEffectiveTaskStatus();
  258. if (status === 'failed') {
  259. return '--';
  260. }
  261. if (status === 'paused' || status === 'pending') {
  262. return '0 B/s';
  263. }
  264. if (this.task.speedBytesPerSec > 0) {
  265. return `${this.formatBytes(this.task.speedBytesPerSec)}/s`;
  266. }
  267. return '0 B/s';
  268. }
  269. private getTaskStatusText(): string {
  270. switch (this.getEffectiveTaskStatus()) {
  271. case 'downloading':
  272. return '下载中';
  273. case 'paused':
  274. return '已暂停';
  275. case 'failed':
  276. return '下载失败';
  277. case 'pending':
  278. return '等待中';
  279. case 'completed':
  280. default:
  281. return '已完成';
  282. }
  283. }
  284. private getTaskStatusColor() {
  285. const status = this.getEffectiveTaskStatus();
  286. if (status === 'failed') {
  287. return $r('app.color.btn_red');
  288. }
  289. if (status === 'paused' || status === 'pending') {
  290. return $r('app.color.text_color');
  291. }
  292. return this.themeColor;
  293. }
  294. private formatBytes(bytes: number): string {
  295. if (!Number.isFinite(bytes) || bytes <= 0) {
  296. return '';
  297. }
  298. const units: string[] = ['B', 'KB', 'MB', 'GB', 'TB'];
  299. let size: number = bytes;
  300. let index: number = 0;
  301. while (size >= 1024 && index < units.length - 1) {
  302. size /= 1024;
  303. index += 1;
  304. }
  305. const precision: number = index === 0 ? 0 : 2;
  306. return `${size.toFixed(precision)} ${units[index]}`;
  307. }
  308. }
  309. @Component
  310. export struct DownloadCenter {
  311. @Prop themeColor: string = CommonConstants.DEFAULT_THEME_COLOR;
  312. @Prop isDarkMode: boolean = false;
  313. @Prop appName: string = '';
  314. @Prop topSafeHeight: number = 0;
  315. @Prop bottomSafeHeight: number = 0;
  316. @Prop @Watch('onTasksVersionChanged') tasksVersion: number = 0;
  317. @Prop activeTasksProp: DownloadCenterTask[] = [];
  318. @Prop completedTasksProp: DownloadCenterTask[] = [];
  319. @Link selectedIndexes: number[];
  320. @State activeTasks: DownloadCenterTaskState[] = [];
  321. @State completedTasks: DownloadCenterTaskState[] = [];
  322. @State renderVersion: number = 0;
  323. @State isSelectionMode: boolean = false;
  324. @State selectedTaskIds: string[] = [];
  325. onClose: () => void = () => {};
  326. onPauseTask: (taskId: string) => void = (_taskId: string): void => {};
  327. onResumeTask: (taskId: string) => void = (_taskId: string): void => {};
  328. onDeleteTask: (taskId: string) => void = (_taskId: string): void => {};
  329. onPlayCompletedTask: (taskId: string) => void = (_taskId: string): void => {};
  330. aboutToAppear(): void {
  331. this.syncTasksFromProps();
  332. }
  333. aboutToDisappear(): void {
  334. }
  335. aboutToReuse(): void {
  336. this.syncTasksFromProps();
  337. }
  338. private onTasksVersionChanged(): void {
  339. this.syncTasksFromProps();
  340. }
  341. build() {
  342. Column({ space: 12 }) {
  343. Row({ space: 10 }) {
  344. Button({ type: ButtonType.Circle, stateEffect: true }) {
  345. SymbolGlyph($r('sys.symbol.chevron_left'))
  346. .attributeModifier(new SymbolGlyphFancyModifier(25, '', ''))
  347. }
  348. .attributeModifier(new ButtonFancyModifier(40, 40))
  349. .attributeModifier(new ShadowModifier())
  350. .onClick(() => this.onClose())
  351. Text('下载中心')
  352. .fontSize(20)
  353. .fontWeight(FontWeight.Medium)
  354. .fontColor($r('app.color.text_color'))
  355. .layoutWeight(1)
  356. if (this.getCurrentTabIndex() === 0 && this.activeTasks.length >= 2) {
  357. Button({ type: ButtonType.Circle, stateEffect: true }) {
  358. SymbolGlyph(this.isSelectionMode ? $r('sys.symbol.checkmark_circle') : $r('sys.symbol.checkmark_square_on_square'))
  359. .attributeModifier(new SymbolGlyphFancyModifier(25, '', ''))
  360. }
  361. .attributeModifier(new ButtonFancyModifier(40, 40))
  362. .attributeModifier(new ShadowModifier())
  363. .onClick(() => {
  364. this.toggleSelectionMode()
  365. })
  366. }
  367. Button({ type: ButtonType.Circle, stateEffect: true }) {
  368. SymbolGlyph($r('sys.symbol.exclamationmark'))
  369. .attributeModifier(new SymbolGlyphFancyModifier(25, '', ''))
  370. }
  371. .attributeModifier(new ButtonFancyModifier(40, 40))
  372. .attributeModifier(new ShadowModifier())
  373. .zIndex(0)
  374. .onClick(() => {
  375. this.showDownloadDirectoryDialog();
  376. })
  377. }
  378. .width('100%')
  379. SegmentButton({
  380. options: SegmentButtonOptions.capsule({
  381. buttons: [{ text: '下载中' }, { text: '已完成' }] as SegmentButtonItemTuple,
  382. backgroundColor: $r('app.color.index_background'),
  383. selectedBackgroundColor: $r('app.color.start_window_background'),
  384. selectedFontColor: $r('app.color.text_color'),
  385. buttonPadding: { top: 10, bottom: 10 },
  386. multiply: false
  387. }),
  388. selectedIndexes: $selectedIndexes
  389. })
  390. .width('100%')
  391. Column() {
  392. this.taskListBuilder()
  393. }
  394. .width('100%')
  395. .layoutWeight(1)
  396. if (this.isSelectionMode && this.getCurrentTabIndex() === 0) {
  397. this.selectionActionBarBuilder()
  398. }
  399. }
  400. .width('100%')
  401. .height('100%')
  402. .padding({ top: this.topSafeHeight + 10, left: 12, right: 12, bottom: this.bottomSafeHeight + 12 })
  403. .backgroundColor($r('app.color.start_window_background'))
  404. }
  405. private syncTasksFromProps(): void {
  406. this.activeTasks = this.mergeTaskStates(this.activeTasks, this.activeTasksProp);
  407. this.completedTasks = this.mergeTaskStates(this.completedTasks, this.completedTasksProp);
  408. this.selectedTaskIds = this.filterExistingSelectedTaskIds();
  409. this.renderVersion = this.tasksVersion;
  410. }
  411. @Builder
  412. private taskListBuilder() {
  413. if (this.getCurrentTabIndex() === 0) {
  414. if (this.activeTasks.length <= 0) {
  415. Column() {
  416. Text('暂无下载任务')
  417. .fontSize(14)
  418. .fontColor($r('app.color.text_color'))
  419. .opacity(0.55)
  420. }
  421. .width('100%')
  422. .height('100%')
  423. .justifyContent(FlexAlign.Center)
  424. } else {
  425. List({ space: 10 }) {
  426. ForEach(this.activeTasks, (task: DownloadCenterTaskState) => {
  427. DownloadCenterTaskRow({
  428. themeColor: this.themeColor,
  429. task: task,
  430. isDownloading: true,
  431. refreshVersion: this.renderVersion,
  432. isSelectionMode: this.isSelectionMode,
  433. isSelected: this.isTaskSelected(task.taskId),
  434. onSelectionChange: (taskId: string, selected: boolean) => {
  435. this.setTaskSelection(taskId, selected)
  436. },
  437. onPauseTask: this.onPauseTask,
  438. onResumeTask: this.onResumeTask,
  439. onDeleteTask: this.handleDeleteTask.bind(this),
  440. onRowClick: (_taskId: string): void => {}
  441. })
  442. }, (task: DownloadCenterTaskState): string => {
  443. return this.getTaskRenderKey(task);
  444. })
  445. }
  446. .scrollBar(BarState.Off)
  447. .height('100%')
  448. .width('100%')
  449. }
  450. } else {
  451. if (this.completedTasks.length <= 0) {
  452. Column() {
  453. Text('暂无历史下载记录')
  454. .fontSize(14)
  455. .fontColor($r('app.color.text_color'))
  456. .opacity(0.55)
  457. }
  458. .width('100%')
  459. .height('100%')
  460. .justifyContent(FlexAlign.Center)
  461. } else {
  462. List({ space: 10 }) {
  463. ForEach(this.completedTasks, (task: DownloadCenterTaskState) => {
  464. DownloadCenterTaskRow({
  465. themeColor: this.themeColor,
  466. task: task,
  467. isDownloading: false,
  468. refreshVersion: this.renderVersion,
  469. onDeleteTask: this.handleDeleteTask.bind(this),
  470. onRowClick: this.handleCompletedTaskClick.bind(this)
  471. })
  472. }, (task: DownloadCenterTaskState): string => {
  473. return this.getTaskRenderKey(task);
  474. })
  475. }
  476. .scrollBar(BarState.Off)
  477. .height('100%')
  478. .width('100%')
  479. }
  480. }
  481. }
  482. @Builder
  483. private selectionActionBarBuilder() {
  484. Row({ space: 10 }) {
  485. Button(this.isAllActiveTasksSelected() ? '反选' : '全选')
  486. .layoutWeight(1)
  487. .height(38)
  488. .fontSize(13)
  489. .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.8 })
  490. .backgroundColor($r('app.color.bg_card'))
  491. .fontColor($r('app.color.text_color'))
  492. .onClick(() => {
  493. this.toggleSelectAllActiveTasks()
  494. })
  495. Button('开始')
  496. .layoutWeight(1)
  497. .height(38)
  498. .fontSize(13)
  499. .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.8 })
  500. .backgroundColor(this.themeColor)
  501. .fontColor(Color.White)
  502. .onClick(() => {
  503. this.resumeSelectedTasks()
  504. })
  505. Button('暂停')
  506. .layoutWeight(1)
  507. .height(38)
  508. .fontSize(13)
  509. .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.8 })
  510. .backgroundColor($r('app.color.bg_card'))
  511. .fontColor($r('app.color.text_color'))
  512. .onClick(() => {
  513. this.pauseSelectedTasks()
  514. })
  515. Button('删除')
  516. .layoutWeight(1)
  517. .height(38)
  518. .fontSize(13)
  519. .clickEffect({ level: ClickEffectLevel.MIDDLE, scale: 0.8 })
  520. .backgroundColor($r('app.color.btn_red'))
  521. .fontColor(Color.White)
  522. .onClick(() => {
  523. this.deleteSelectedTasks()
  524. })
  525. }
  526. .width('100%')
  527. }
  528. private showDownloadDirectoryDialog(): void {
  529. this.getUIContext().showAlertDialog({
  530. title: '下载目录说明',
  531. message: this.buildDownloadDirectoryMessage(),
  532. primaryButton: {
  533. value: '知道了',
  534. action: () => {}
  535. }
  536. });
  537. }
  538. private buildDownloadDirectoryMessage(): string {
  539. const appNameText: string = StrUtil.isNotEmpty(this.appName) ? this.appName : '本应用';
  540. const expectedPath: string = `DownLoad/${appNameText}/下载`;
  541. const currentDir: string = this.resolveCurrentDownloadDirectory();
  542. if (StrUtil.isNotEmpty(currentDir)) {
  543. return `下载完成后会保存到系统DownLoad目录下的应用文件夹,再进入下载文件夹。\n默认结构:${expectedPath}\n\n当前下载文件夹路径:\n${currentDir}`;
  544. }
  545. return `下载完成后会保存到系统DownLoad目录下的应用文件夹,再进入下载文件夹。\n默认结构:${expectedPath}\n\n当前暂无下载任务,开始下载后会显示实际下载路径。`;
  546. }
  547. private resolveCurrentDownloadDirectory(): string {
  548. for (let i = 0; i < this.activeTasks.length; i += 1) {
  549. const task: DownloadCenterTaskState = this.activeTasks[i];
  550. if (StrUtil.isNotEmpty(task.downloadDir)) {
  551. return task.downloadDir;
  552. }
  553. }
  554. for (let i = 0; i < this.completedTasks.length; i += 1) {
  555. const task: DownloadCenterTaskState = this.completedTasks[i];
  556. if (StrUtil.isNotEmpty(task.downloadDir)) {
  557. return task.downloadDir;
  558. }
  559. }
  560. return '';
  561. }
  562. private mergeTaskStates(currentTasks: DownloadCenterTaskState[], nextTasks: DownloadCenterTask[]): DownloadCenterTaskState[] {
  563. const nextStateMap: Map<string, DownloadCenterTaskState> = new Map<string, DownloadCenterTaskState>();
  564. for (let i = 0; i < currentTasks.length; i += 1) {
  565. const state: DownloadCenterTaskState = currentTasks[i];
  566. nextStateMap.set(state.taskId, state);
  567. }
  568. const mergedTasks: DownloadCenterTaskState[] = [];
  569. for (let i = 0; i < nextTasks.length; i += 1) {
  570. const nextTask: DownloadCenterTask = nextTasks[i];
  571. const existingState: DownloadCenterTaskState | undefined = nextStateMap.get(nextTask.taskId);
  572. if (existingState) {
  573. existingState.apply(nextTask);
  574. mergedTasks.push(existingState);
  575. continue;
  576. }
  577. mergedTasks.push(new DownloadCenterTaskState(nextTask));
  578. }
  579. return mergedTasks;
  580. }
  581. private toggleSelectionMode(): void {
  582. this.isSelectionMode = !this.isSelectionMode;
  583. if (!this.isSelectionMode) {
  584. this.selectedTaskIds = [];
  585. }
  586. }
  587. private setTaskSelection(taskId: string, selected: boolean): void {
  588. const index: number = this.selectedTaskIds.indexOf(taskId);
  589. if (selected) {
  590. if (index < 0) {
  591. this.selectedTaskIds = [...this.selectedTaskIds, taskId];
  592. }
  593. return;
  594. }
  595. if (index >= 0) {
  596. const nextSelected: string[] = this.selectedTaskIds.slice();
  597. nextSelected.splice(index, 1);
  598. this.selectedTaskIds = nextSelected;
  599. }
  600. }
  601. private isTaskSelected(taskId: string): boolean {
  602. return this.selectedTaskIds.indexOf(taskId) >= 0;
  603. }
  604. private isAllActiveTasksSelected(): boolean {
  605. return this.activeTasks.length > 0 && this.selectedTaskIds.length === this.activeTasks.length;
  606. }
  607. private toggleSelectAllActiveTasks(): void {
  608. if (this.isAllActiveTasksSelected()) {
  609. this.selectedTaskIds = [];
  610. return;
  611. }
  612. this.selectedTaskIds = this.activeTasks.map((task: DownloadCenterTaskState): string => task.taskId);
  613. }
  614. private pauseSelectedTasks(): void {
  615. for (let i = 0; i < this.selectedTaskIds.length; i += 1) {
  616. this.onPauseTask(this.selectedTaskIds[i]);
  617. }
  618. }
  619. private resumeSelectedTasks(): void {
  620. for (let i = 0; i < this.selectedTaskIds.length; i += 1) {
  621. this.onResumeTask(this.selectedTaskIds[i]);
  622. }
  623. }
  624. private deleteSelectedTasks(): void {
  625. const taskIds: string[] = [...this.selectedTaskIds];
  626. this.removeTasksLocally(taskIds);
  627. for (let i = 0; i < taskIds.length; i += 1) {
  628. this.onDeleteTask(taskIds[i]);
  629. }
  630. this.selectedTaskIds = [];
  631. this.isSelectionMode = false;
  632. }
  633. private handleDeleteTask(taskId: string): void {
  634. if (StrUtil.isEmpty(taskId)) {
  635. return
  636. }
  637. this.removeTasksLocally([taskId])
  638. this.onDeleteTask(taskId)
  639. }
  640. private handleCompletedTaskClick(taskId: string): void {
  641. if (StrUtil.isEmpty(taskId)) {
  642. return
  643. }
  644. this.onPlayCompletedTask(taskId)
  645. }
  646. private getTaskRenderKey(task: DownloadCenterTaskState): string {
  647. return task.taskId;
  648. }
  649. private filterExistingSelectedTaskIds(): string[] {
  650. if (this.selectedTaskIds.length <= 0) {
  651. return [];
  652. }
  653. const activeTaskIds: Set<string> = new Set<string>();
  654. for (let i = 0; i < this.activeTasks.length; i += 1) {
  655. activeTaskIds.add(this.activeTasks[i].taskId);
  656. }
  657. const nextSelected: string[] = [];
  658. for (let i = 0; i < this.selectedTaskIds.length; i += 1) {
  659. const taskId: string = this.selectedTaskIds[i];
  660. if (activeTaskIds.has(taskId)) {
  661. nextSelected.push(taskId);
  662. }
  663. }
  664. return nextSelected;
  665. }
  666. private removeTasksLocally(taskIds: string[]): void {
  667. if (taskIds.length <= 0) {
  668. return;
  669. }
  670. this.activeTasks = this.activeTasks.filter((task: DownloadCenterTaskState): boolean => {
  671. return taskIds.indexOf(task.taskId) < 0;
  672. });
  673. this.completedTasks = this.completedTasks.filter((task: DownloadCenterTaskState): boolean => {
  674. return taskIds.indexOf(task.taskId) < 0;
  675. });
  676. }
  677. private getCurrentTabIndex(): number {
  678. if (!this.selectedIndexes || this.selectedIndexes.length <= 0) {
  679. return 0;
  680. }
  681. return this.selectedIndexes[0];
  682. }
  683. }