From e49fa18261b192a361ced0c850e10b2acca09737 Mon Sep 17 00:00:00 2001
From: Dani Palou <dani@moodle.com>
Date: Tue, 2 May 2023 16:27:47 +0200
Subject: [PATCH] MOBILE-4322 bbb: Display all playbacks in recordings

---
 scripts/langindex.json                        |  2 -
 .../components/index/index.html               | 23 +++--
 .../components/index/index.scss               |  4 +
 .../bigbluebuttonbn/components/index/index.ts | 96 +++++++++++++------
 src/addons/mod/bigbluebuttonbn/lang.json      |  2 -
 .../services/bigbluebuttonbn.ts               | 12 +++
 .../tests/behat/basic_usage.feature           | 10 +-
 7 files changed, 104 insertions(+), 45 deletions(-)

diff --git a/scripts/langindex.json b/scripts/langindex.json
index 051f03dde..308ec401c 100644
--- a/scripts/langindex.json
+++ b/scripts/langindex.json
@@ -462,8 +462,6 @@
   "addon.mod_bigbluebuttonbn.view_message_viewer": "bigbluebuttonbn",
   "addon.mod_bigbluebuttonbn.view_message_viewers": "bigbluebuttonbn",
   "addon.mod_bigbluebuttonbn.view_nojoin": "bigbluebuttonbn",
-  "addon.mod_bigbluebuttonbn.view_recording_format_presentation": "bigbluebuttonbn",
-  "addon.mod_bigbluebuttonbn.view_recording_list_action_play": "bigbluebuttonbn",
   "addon.mod_bigbluebuttonbn.view_section_title_recordings": "bigbluebuttonbn",
   "addon.mod_book.errorchapter": "book",
   "addon.mod_book.modulenameplural": "book",
diff --git a/src/addons/mod/bigbluebuttonbn/components/index/index.html b/src/addons/mod/bigbluebuttonbn/components/index/index.html
index 79da43a01..5d3ce0a95 100644
--- a/src/addons/mod/bigbluebuttonbn/components/index/index.html
+++ b/src/addons/mod/bigbluebuttonbn/components/index/index.html
@@ -90,9 +90,8 @@
             </ion-label>
         </ion-item>
         <ng-container *ngFor="let recording of recordings">
-            <ion-item *ngIf="recording.url" button class="addon-mod_bbb-recording-title" [attr.aria-expanded]="recording.expanded"
-                (click)="toggle(recording)" [attr.aria-label]="(recording.expanded ? 'core.collapse' : 'core.expand') | translate"
-                detail="false">
+            <ion-item button class="addon-mod_bbb-recording-title" [attr.aria-expanded]="recording.expanded" (click)="toggle(recording)"
+                [attr.aria-label]="(recording.expanded ? 'core.collapse' : 'core.expand') | translate" detail="false">
                 <ion-icon name="fas-chevron-right" flip-rtl slot="start" aria-hidden="true" class="expandable-status-icon"
                     [class.expandable-status-icon-expanded]="recording.expanded">
                 </ion-icon>
@@ -100,12 +99,22 @@
                     <p>{{ recording.type }}</p>
                     <p>{{ recording.name }}</p>
                 </ion-label>
-                <ion-button slot="end" fill="clear" (click)="playRecording($event, recording)"
-                    [attr.aria-label]="'addon.mod_bigbluebuttonbn.view_recording_list_action_play' | translate">
-                    <ion-icon name="fas-circle-play" slot="icon-only" aria-hidden="true"></ion-icon>
-                </ion-button>
             </ion-item>
             <div [hidden]="!recording.expanded" class="addon-mod_bbb-recording-details">
+                <div *ngIf="recording.playbacks.length" class="addon-mod_bbb-recording-playbacks">
+                    <ion-item class="ion-text-wrap addon-mod_bbb-recording-playback-title">
+                        <ion-label>
+                            <p class="item-heading">{{ recording.playbackLabel }}</p>
+                        </ion-label>
+                    </ion-item>
+                    <ion-item *ngFor="let playback of recording.playbacks" button (click)="openPlayback($event, playback)"
+                        class="ion-text-wrap addon-mod_bbb-recording-playback-item">
+                        <ion-label>
+                            <p>{{ playback.name }}</p>
+                        </ion-label>
+                        <ion-icon slot="end" [name]="playback.icon" aria-hidden="true"></ion-icon>
+                    </ion-item>
+                </div>
                 <ion-item *ngFor="let data of recording.details" class="ion-text-wrap">
                     <ion-label>
                         <p class="item-heading">{{ data.label }}</p>
diff --git a/src/addons/mod/bigbluebuttonbn/components/index/index.scss b/src/addons/mod/bigbluebuttonbn/components/index/index.scss
index dd64226f6..1aac75966 100644
--- a/src/addons/mod/bigbluebuttonbn/components/index/index.scss
+++ b/src/addons/mod/bigbluebuttonbn/components/index/index.scss
@@ -10,6 +10,10 @@
         font-size: 100%;
     }
 
+    .addon-mod_bbb-recording-playback-title ion-label {
+        margin-bottom: 0;
+    }
+
     .addon-mod_bbb-recording-details {
         border-top: 2px solid var(--recording-details-border);
         border-bottom: 2px solid var(--recording-details-border);
diff --git a/src/addons/mod/bigbluebuttonbn/components/index/index.ts b/src/addons/mod/bigbluebuttonbn/components/index/index.ts
index 2129af495..72ad86990 100644
--- a/src/addons/mod/bigbluebuttonbn/components/index/index.ts
+++ b/src/addons/mod/bigbluebuttonbn/components/index/index.ts
@@ -25,7 +25,13 @@ import { CoreTextUtils } from '@services/utils/text';
 import { CoreTimeUtils } from '@services/utils/time';
 import { CoreUtils } from '@services/utils/utils';
 import { Translate } from '@singletons';
-import { AddonModBBB, AddonModBBBData, AddonModBBBMeetingInfo, AddonModBBBService } from '../../services/bigbluebuttonbn';
+import {
+    AddonModBBB,
+    AddonModBBBData,
+    AddonModBBBMeetingInfo,
+    AddonModBBBRecordingPlaybackTypes,
+    AddonModBBBService,
+} from '../../services/bigbluebuttonbn';
 
 /**
  * Component that displays a Big Blue Button activity.
@@ -43,7 +49,7 @@ export class AddonModBBBIndexComponent extends CoreCourseModuleMainActivityCompo
     groupInfo?: CoreGroupInfo;
     groupId = 0;
     meetingInfo?: AddonModBBBMeetingInfo;
-    recordings?: RecordingData[];
+    recordings?: Recording[];
 
     constructor(
         protected content?: IonContent,
@@ -139,13 +145,17 @@ export class AddonModBBBIndexComponent extends CoreCourseModuleMainActivityCompo
         const columns = CoreUtils.arrayToObject(recordingsTable.columns, 'key');
 
         this.recordings = recordingsTable.parsedData.map(recordingData => {
-            const playbackEl = CoreDomUtils.convertToElement(String(recordingData.playback));
-            const playbackAnchor = playbackEl.querySelector('a');
-            const details: RecordingDetailData[] = [];
+            const details: RecordingDetail[] = [];
+            const playbacksEl = CoreDomUtils.convertToElement(String(recordingData.playback));
+            const playbacks: RecordingPlayback[] = Array.from(playbacksEl.querySelectorAll('a')).map(playbackAnchor => ({
+                name: playbackAnchor.textContent ?? '',
+                url: playbackAnchor.href,
+                icon: this.getPlaybackIcon(playbackAnchor),
+            }));
 
             Object.entries(recordingData).forEach(([key, value]) => {
                 const columnData = columns[key];
-                if (!columnData || value === '' || key === 'actionbar') {
+                if (!columnData || value === '' || key === 'actionbar' || key === 'playback') {
                     return;
                 }
 
@@ -158,16 +168,11 @@ export class AddonModBBBIndexComponent extends CoreCourseModuleMainActivityCompo
                         return;
                     }
 
-                    if (key === 'playback') {
-                        // Remove HTML, we're only interested in the text.
-                        value = (valueElement.textContent ?? '').trim();
-                    } else {
-                        // Treat "quick edit" buttons, they aren't supported in the app.
-                        const quickEditLink = valueElement.querySelector('.quickeditlink');
-                        if (quickEditLink) {
-                            // The first span in quick edit link contains the actual HTML, use it.
-                            value = (quickEditLink.querySelector('span')?.innerHTML ?? '').trim();
-                        }
+                    // Treat "quick edit" buttons, they aren't supported in the app.
+                    const quickEditLink = valueElement.querySelector('.quickeditlink');
+                    if (quickEditLink) {
+                        // The first span in quick edit link contains the actual HTML, use it.
+                        value = (quickEditLink.querySelector('span')?.innerHTML ?? '').trim();
                     }
                 }
 
@@ -179,16 +184,40 @@ export class AddonModBBBIndexComponent extends CoreCourseModuleMainActivityCompo
             });
 
             return {
-                type: playbackAnchor?.innerText ??
-                    Translate.instant('addon.mod_bigbluebuttonbn.view_recording_format_presentation'),
                 name: CoreTextUtils.cleanTags(String(recordingData.recording), { singleLine: true }),
-                url: playbackAnchor?.href ?? '',
+                playbackLabel: columns.playback.label,
+                playbacks,
                 details,
                 expanded: false,
             };
         });
     }
 
+    /**
+     * Get the playback icon.
+     *
+     * @param playbackAnchor Anchor element.
+     * @returns Icon name.
+     */
+    protected getPlaybackIcon(playbackAnchor: HTMLAnchorElement): string {
+        const type = playbackAnchor.dataset.target;
+        switch (type) {
+            case AddonModBBBRecordingPlaybackTypes.NOTES:
+                return 'far-file-lines';
+            case AddonModBBBRecordingPlaybackTypes.PODCAST:
+                return 'fas-microphone-lines';
+            case AddonModBBBRecordingPlaybackTypes.SCREENSHARE:
+                return 'fas-display';
+            case AddonModBBBRecordingPlaybackTypes.STATISTICS:
+                return 'fas-chart-line';
+            case AddonModBBBRecordingPlaybackTypes.VIDEO:
+                return 'fas-video';
+            case AddonModBBBRecordingPlaybackTypes.PRESENTATION:
+            default:
+                return 'fas-circle-play';
+        }
+    }
+
     /**
      * @inheritdoc
      */
@@ -323,21 +352,21 @@ export class AddonModBBBIndexComponent extends CoreCourseModuleMainActivityCompo
      *
      * @param recording Recording.
      */
-    toggle(recording: RecordingData): void {
+    toggle(recording: Recording): void {
         recording.expanded = !recording.expanded;
     }
 
     /**
-     * Play a recording.
+     * Open a recording playback.
      *
      * @param event Click event.
-     * @param recording Recording.
+     * @param playback Playback.
      */
-    playRecording(event: MouseEvent, recording: RecordingData): void {
+    openPlayback(event: MouseEvent, playback: RecordingPlayback): void {
         event.preventDefault();
         event.stopPropagation();
 
-        CoreSites.getCurrentSite()?.openInBrowserWithAutoLogin(recording.url);
+        CoreSites.getCurrentSite()?.openInBrowserWithAutoLogin(playback.url);
     }
 
 }
@@ -345,19 +374,28 @@ export class AddonModBBBIndexComponent extends CoreCourseModuleMainActivityCompo
 /**
  * Recording data.
  */
-type RecordingData = {
-    type: string;
+type Recording = {
     name: string;
-    url: string;
     expanded: boolean;
-    details: RecordingDetailData[];
+    playbackLabel: string;
+    playbacks: RecordingPlayback[];
+    details: RecordingDetail[];
 };
 
 /**
  * Recording detail data.
  */
-type RecordingDetailData = {
+type RecordingDetail = {
     label: string;
     value: string;
     allowHTML: boolean;
 };
+
+/**
+ * Recording playback data.
+ */
+type RecordingPlayback = {
+    name: string;
+    url: string;
+    icon: string;
+};
diff --git a/src/addons/mod/bigbluebuttonbn/lang.json b/src/addons/mod/bigbluebuttonbn/lang.json
index 5a12c19d1..abead21d5 100644
--- a/src/addons/mod/bigbluebuttonbn/lang.json
+++ b/src/addons/mod/bigbluebuttonbn/lang.json
@@ -17,7 +17,5 @@
     "view_message_viewer": "viewer",
     "view_message_viewers": "viewers",
     "view_nojoin": "You do not have a role that is allowed to join this session.",
-    "view_recording_format_presentation": "Presentation",
-    "view_recording_list_action_play": "Play",
     "view_section_title_recordings": "Recordings"
 }
diff --git a/src/addons/mod/bigbluebuttonbn/services/bigbluebuttonbn.ts b/src/addons/mod/bigbluebuttonbn/services/bigbluebuttonbn.ts
index 29c398b13..a59218853 100644
--- a/src/addons/mod/bigbluebuttonbn/services/bigbluebuttonbn.ts
+++ b/src/addons/mod/bigbluebuttonbn/services/bigbluebuttonbn.ts
@@ -548,3 +548,15 @@ export type AddonModBBBRecordingsWSTableData = {
 export type AddonModBBBRecordingsTableData = AddonModBBBRecordingsWSTableData & {
     parsedData: Record<string, string|number|boolean>[];
 };
+
+/**
+ * Recording playback types.
+ */
+export enum AddonModBBBRecordingPlaybackTypes {
+    NOTES = 'notes',
+    PODCAST = 'podcast',
+    PRESENTATION = 'presentation',
+    SCREENSHARE = 'screenshare',
+    STATISTICS = 'statistics',
+    VIDEO = 'video',
+}
diff --git a/src/addons/mod/bigbluebuttonbn/tests/behat/basic_usage.feature b/src/addons/mod/bigbluebuttonbn/tests/behat/basic_usage.feature
index 502e4b8e1..1e2175676 100755
--- a/src/addons/mod/bigbluebuttonbn/tests/behat/basic_usage.feature
+++ b/src/addons/mod/bigbluebuttonbn/tests/behat/basic_usage.feature
@@ -143,15 +143,15 @@ Feature: Test basic usage of BBB activity in app
       | BBB             | Recording 1 | Description 1 | 3      |
       | BBB             | Recording 2 | Description 2 | 3      |
     And I entered the bigbluebuttonbn activity "BBB" on course "Course 1" as "student1" in the app
-    Then I should find "Presentation" in the app
-    And I should find "Recording 1" in the app
+    Then I should find "Recording 1" in the app
     And I should find "Recording 2" in the app
     But I should not find "Description 1" in the app
     And I should not find "Description 2" in the app
+    And I should not find "Presentation" in the app
 
     When I press "Recording 1" in the app
     Then I should find "Description 1" in the app
-    And I should find "Presentation" within "Playback" "ion-item" in the app
+    And I should find "Presentation" in the app
     And I should find "Recording 1" within "Name" "ion-item" in the app
     And I should find "Date" in the app
     And I should find "3600" within "Duration" "ion-item" in the app
@@ -162,12 +162,12 @@ Feature: Test basic usage of BBB activity in app
 
     When I press "Recording 2" in the app
     Then I should find "Description 2" in the app
-    And I should find "Presentation" within "Playback" "ion-item" in the app
+    And I should find "Presentation" in the app
     And I should find "Recording 2" within "Name" "ion-item" in the app
     But I should not find "Description 1" in the app
 
     # Test play button, but the mock server doesn't support viewing recordings.
-    When I press "Play" near "Recording 1" in the app
+    When I press "Presentation" in the app
     And I press "OK" in the app
     And I switch to the browser tab opened by the app
     And I log in as "student1"