From 29e21191431a6c55f66d281f9751326a2d53b2ca Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Thu, 18 Nov 2021 14:42:12 +0100 Subject: [PATCH] MOBILE-3932 resource: Display file info in resource index page --- scripts/langindex.json | 22 ++++++ .../index/addon-mod-resource-index.html | 79 +++++++++++++++---- .../mod/resource/components/index/index.scss | 11 +++ .../mod/resource/components/index/index.ts | 49 +++++++++--- src/addons/mod/resource/lang.json | 2 + src/assets/mimetypes.json | 19 ++++- .../course/classes/main-resource-component.ts | 1 + .../features/course/services/course-helper.ts | 39 ++++++--- src/core/lang.json | 3 + 9 files changed, 189 insertions(+), 36 deletions(-) create mode 100644 src/addons/mod/resource/components/index/index.scss diff --git a/scripts/langindex.json b/scripts/langindex.json index 79ac9b310..6d97093db 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -909,6 +909,8 @@ "addon.mod_resource.modifieddate": "resource", "addon.mod_resource.modulenameplural": "resource", "addon.mod_resource.openthefile": "local_moodlemobileapp", + "addon.mod_resource.resourcestatusoutdated": "local_moodlemobileapp", + "addon.mod_resource.resourcestatusoutdatedconfirm": "local_moodlemobileapp", "addon.mod_resource.uploadeddate": "resource", "addon.mod_scorm.asset": "scorm", "addon.mod_scorm.assetlaunched": "scorm", @@ -1358,9 +1360,23 @@ "assets.countries.ZA": "countries", "assets.countries.ZM": "countries", "assets.countries.ZW": "countries", + "assets.mimetypes.application/dash_xml": "mimetypes", "assets.mimetypes.application/epub_zip": "mimetypes", + "assets.mimetypes.application/json": "mimetypes", "assets.mimetypes.application/msword": "mimetypes", "assets.mimetypes.application/pdf": "mimetypes", + "assets.mimetypes.application/vnd.google-apps.audio": "local_moodlemobileapp", + "assets.mimetypes.application/vnd.google-apps.document": "local_moodlemobileapp", + "assets.mimetypes.application/vnd.google-apps.drawing": "local_moodlemobileapp", + "assets.mimetypes.application/vnd.google-apps.file": "local_moodlemobileapp", + "assets.mimetypes.application/vnd.google-apps.folder": "local_moodlemobileapp", + "assets.mimetypes.application/vnd.google-apps.form": "local_moodlemobileapp", + "assets.mimetypes.application/vnd.google-apps.fusiontable": "local_moodlemobileapp", + "assets.mimetypes.application/vnd.google-apps.presentation": "local_moodlemobileapp", + "assets.mimetypes.application/vnd.google-apps.script": "local_moodlemobileapp", + "assets.mimetypes.application/vnd.google-apps.site": "local_moodlemobileapp", + "assets.mimetypes.application/vnd.google-apps.spreadsheet": "local_moodlemobileapp", + "assets.mimetypes.application/vnd.google-apps.video": "local_moodlemobileapp", "assets.mimetypes.application/vnd.moodle.backup": "mimetypes", "assets.mimetypes.application/vnd.ms-excel": "mimetypes", "assets.mimetypes.application/vnd.ms-excel.sheet.macroEnabled.12": "mimetypes", @@ -1379,6 +1395,7 @@ "assets.mimetypes.application/x-iwork-numbers-sffnumbers": "mimetypes", "assets.mimetypes.application/x-iwork-pages-sffpages": "mimetypes", "assets.mimetypes.application/x-javascript": "mimetypes", + "assets.mimetypes.application/x-mpegURL": "mimetypes", "assets.mimetypes.application/x-mspublisher": "mimetypes", "assets.mimetypes.application/x-shockwave-flash": "mimetypes", "assets.mimetypes.application/xhtml_xml": "mimetypes", @@ -1393,6 +1410,8 @@ "assets.mimetypes.group:html_track": "mimetypes", "assets.mimetypes.group:html_video": "mimetypes", "assets.mimetypes.group:image": "mimetypes", + "assets.mimetypes.group:media_source": "mimetypes", + "assets.mimetypes.group:optimised_image": "mimetypes", "assets.mimetypes.group:presentation": "mimetypes", "assets.mimetypes.group:sourcecode": "mimetypes", "assets.mimetypes.group:spreadsheet": "mimetypes", @@ -1593,6 +1612,7 @@ "core.custom": "form", "core.datastoredoffline": "local_moodlemobileapp", "core.date": "moodle", + "core.datecreated": "repository", "core.day": "moodle", "core.days": "moodle", "core.decsep": "langconfig", @@ -2208,6 +2228,7 @@ "core.sitehome.sitehome": "moodle", "core.sitehome.sitenews": "moodle", "core.sitemaintenance": "admin", + "core.size": "moodle", "core.sizeb": "moodle", "core.sizegb": "moodle", "core.sizekb": "moodle", @@ -2262,6 +2283,7 @@ "core.toggledelete": "local_moodlemobileapp", "core.tryagain": "local_moodlemobileapp", "core.twoparagraphs": "local_moodlemobileapp", + "core.type": "repository", "core.uhoh": "local_moodlemobileapp", "core.unexpectederror": "local_moodlemobileapp", "core.unicodenotsupported": "local_moodlemobileapp", diff --git a/src/addons/mod/resource/components/index/addon-mod-resource-index.html b/src/addons/mod/resource/components/index/addon-mod-resource-index.html index e0f1279be..bdeaa056d 100644 --- a/src/addons/mod/resource/components/index/addon-mod-resource-index.html +++ b/src/addons/mod/resource/components/index/addon-mod-resource-index.html @@ -42,22 +42,71 @@ - - - - {{ 'core.play' | translate }} - - - - {{ 'addon.mod_resource.openthefile' | translate }} - - + + + +

{{ 'core.type' | translate }}

+

{{ type }}

+
+
- - - {{ 'core.openwith' | translate }} - + + + +

{{ 'core.size' | translate }}

+

{{ readableSize }}

+
+
+ + + +

{{ 'core.datecreated' | translate }}

+

{{ timecreated | coreFormatDate }}

+
+
+ + + +

{{ 'core.lastmodified' | translate }}

+

{{ timemodified | coreFormatDate }}

+
+
+ + + +

{{ 'core.lastdownloaded' | translate }}

+

{{ downloadTimeReadable }}

+ + + + + + +

{{ 'addon.mod_resource.resourcestatusoutdated' | translate }}

+
+
+
+
+
+
+ + + + + {{ 'core.play' | translate }} + + + + {{ 'addon.mod_resource.openthefile' | translate }} + + + + + + {{ 'core.openwith' | translate }} + +
diff --git a/src/addons/mod/resource/components/index/index.scss b/src/addons/mod/resource/components/index/index.scss new file mode 100644 index 000000000..b14ad8392 --- /dev/null +++ b/src/addons/mod/resource/components/index/index.scss @@ -0,0 +1,11 @@ +@import "~theme/globals"; + +:host { + .addon-mod_resource-outdated { + @include padding(4px, 0px, 0px, 0px); + + ion-icon { + font-size: 24px; + } + } +} diff --git a/src/addons/mod/resource/components/index/index.ts b/src/addons/mod/resource/components/index/index.ts index 3987378fc..a5d38511a 100644 --- a/src/addons/mod/resource/components/index/index.ts +++ b/src/addons/mod/resource/components/index/index.ts @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +import { CoreConstants } from '@/core/constants'; import { Component, OnDestroy, OnInit, Optional } from '@angular/core'; import { CoreError } from '@classes/errors/error'; import { CoreCourseModuleMainResourceComponent } from '@features/course/classes/main-resource-component'; @@ -21,6 +22,7 @@ import { CoreCourseModulePrefetchDelegate } from '@features/course/services/modu import { CoreApp } from '@services/app'; import { CoreFileHelper } from '@services/file-helper'; import { CoreSites } from '@services/sites'; +import { CoreDomUtils } from '@services/utils/dom'; import { CoreMimetypeUtils } from '@services/utils/mimetype'; import { CoreTextUtils } from '@services/utils/text'; import { CoreUtils, OpenFileAction } from '@services/utils/utils'; @@ -39,6 +41,7 @@ import { AddonModResourceHelper } from '../../services/resource-helper'; @Component({ selector: 'addon-mod-resource-index', templateUrl: 'addon-mod-resource-index.html', + styleUrls: ['index.scss'], }) export class AddonModResourceIndexComponent extends CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy { @@ -55,6 +58,14 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource isStreamedFile = false; shouldOpenInBrowser = false; + // Variables for 'external' mode. + type = ''; + readableSize = ''; + timecreated = -1; + timemodified = -1; + isExternalFile = false; + outdatedStatus = CoreConstants.OUTDATED; + protected onlineObserver?: Subscription; constructor(@Optional() courseContentsPage?: CoreCourseContentsPage) { @@ -70,15 +81,13 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource this.isIOS = CoreApp.isIOS(); this.isOnline = CoreApp.isOnline(); - if (this.isIOS) { - // Refresh online status when changes. - this.onlineObserver = Network.onChange().subscribe(() => { - // Execute the callback in the Angular zone, so change detection doesn't stop working. - NgZone.run(() => { - this.isOnline = CoreApp.isOnline(); - }); + // Refresh online status when changes. + this.onlineObserver = Network.onChange().subscribe(() => { + // Execute the callback in the Angular zone, so change detection doesn't stop working. + NgZone.run(() => { + this.isOnline = CoreApp.isOnline(); }); - } + }); await this.loadContent(); try { @@ -153,13 +162,25 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource } else { this.mode = 'external'; this.warning = ''; + let mimetype: string; if (this.isIOS) { this.shouldOpenInBrowser = CoreFileHelper.shouldOpenInBrowser(contents[0]); } - const mimetype = await CoreUtils.getMimeTypeFromUrl(CoreFileHelper.getFileUrl(contents[0])); + if ('contentsinfo' in this.module && this.module.contentsinfo) { + mimetype = this.module.contentsinfo.mimetypes[0]; + this.readableSize = CoreTextUtils.bytesToSize(this.module.contentsinfo.filessize, 1); + this.timemodified = this.module.contentsinfo.lastmodified * 1000; + } else { + mimetype = await CoreUtils.getMimeTypeFromUrl(CoreFileHelper.getFileUrl(contents[0])); + this.readableSize = CoreTextUtils.bytesToSize(contents[0].filesize, 1); + this.timemodified = contents[0].timemodified * 1000; + } + this.timecreated = contents[0].timecreated * 1000; + this.isExternalFile = !!contents[0].isexternalfile; + this.type = CoreMimetypeUtils.getMimetypeDescription(mimetype); this.isStreamedFile = CoreMimetypeUtils.isStreamedMimetype(mimetype); } } finally { @@ -183,6 +204,16 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource downloadable = await AddonModResourceHelper.isMainFileDownloadable(this.module); if (downloadable) { + if (this.prefetchStatus === CoreConstants.OUTDATED && !this.isOnline) { + // Warn the user that the file isn't updated. + const alert = await CoreDomUtils.showAlert( + undefined, + Translate.instant('addon.mod_resource.resourcestatusoutdated'), + ); + + await alert.onWillDismiss(); + } + return AddonModResourceHelper.openModuleFile(this.module, this.courseId, { iOSOpenFileAction }); } } diff --git a/src/addons/mod/resource/lang.json b/src/addons/mod/resource/lang.json index 2449d53e3..22626646e 100644 --- a/src/addons/mod/resource/lang.json +++ b/src/addons/mod/resource/lang.json @@ -3,5 +3,7 @@ "modifieddate": "Modified {{$a}}", "modulenameplural": "Files", "openthefile": "Open", + "resourcestatusoutdated": "This file has changed since you last opened it.", + "resourcestatusoutdatedconfirm": "There is a newer version of this file. To see it, please connect your device to the internet.", "uploadeddate": "Uploaded {{$a}}" } diff --git a/src/assets/mimetypes.json b/src/assets/mimetypes.json index f94347be4..a1b422dc5 100644 --- a/src/assets/mimetypes.json +++ b/src/assets/mimetypes.json @@ -1,7 +1,21 @@ { + "application/dash_xml": "Dynamic Adaptive Streaming over HTTP (MPEG-DASH)", "application/epub_zip": "EPUB ebook", + "application/json": "{{$a.MIMETYPE2}} text", "application/msword": "Word document", "application/pdf": "PDF document", + "application/vnd.google-apps.audio": "Google Drive audio", + "application/vnd.google-apps.document": "Google Docs", + "application/vnd.google-apps.drawing": "Google Drawing", + "application/vnd.google-apps.file": "Google Drive file", + "application/vnd.google-apps.folder": "Google Drive folder", + "application/vnd.google-apps.form": "Google Forms", + "application/vnd.google-apps.fusiontable": "Google Fusion Tables", + "application/vnd.google-apps.presentation": "Google Slides", + "application/vnd.google-apps.script": "Google Apps Scripts", + "application/vnd.google-apps.site": "Google Sites", + "application/vnd.google-apps.spreadsheet": "Google Sheets", + "application/vnd.google-apps.video": "Google Drive video", "application/vnd.moodle.backup": "Moodle backup", "application/vnd.ms-excel": "Excel spreadsheet", "application/vnd.ms-excel.sheet.macroEnabled.12": "Excel 2007 macro-enabled workbook", @@ -20,6 +34,7 @@ "application/x-iwork-numbers-sffnumbers": "iWork Numbers spreadsheet", "application/x-iwork-pages-sffpages": "iWork Pages document", "application/x-javascript": "JavaScript source", + "application/x-mpegURL": "HTTP Live Streaming (HLS)", "application/x-mspublisher": "Publisher document", "application/x-shockwave-flash": "Flash animation", "application/xhtml_xml": "XHTML document", @@ -34,6 +49,8 @@ "group:html_track": "HTML track files", "group:html_video": "Video files natively supported by browsers", "group:image": "Image files", + "group:media_source": "Streaming media", + "group:optimised_image": "Image files to be optimised, such as badges", "group:presentation": "Presentation files", "group:sourcecode": "Source code", "group:spreadsheet": "Spreadsheet files", @@ -51,4 +68,4 @@ "text/rtf": "RTF document", "text/vtt": "Web Video Text Track", "video": "Video file ({{$a.EXT}})" -} \ No newline at end of file +} diff --git a/src/core/features/course/classes/main-resource-component.ts b/src/core/features/course/classes/main-resource-component.ts index d8ae2c48d..24964fc75 100644 --- a/src/core/features/course/classes/main-resource-component.ts +++ b/src/core/features/course/classes/main-resource-component.ts @@ -67,6 +67,7 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy, prefetchStatus?: string; // Used when calling fillContextMenu. prefetchText?: string; // Used when calling fillContextMenu. size?: string; // Used when calling fillContextMenu. + downloadTimeReadable?: string; // Last download time in a readable format. Used when calling fillContextMenu. isDestroyed = false; // Whether the component is destroyed, used when calling fillContextMenu. contextMenuStatusObserver?: CoreEventObserver; // Observer of package status, used when calling fillContextMenu. contextFileStatusObserver?: CoreEventObserver; // Observer of file status, used when calling fillContextMenu. diff --git a/src/core/features/course/services/course-helper.ts b/src/core/features/course/services/course-helper.ts index cfde46f6f..737a405ae 100644 --- a/src/core/features/course/services/course-helper.ts +++ b/src/core/features/course/services/course-helper.ts @@ -905,17 +905,30 @@ export class CoreCourseHelperProvider { } if (!path) { - path = await this.downloadModuleWithMainFile( - module, - courseId, - fixedUrl, - files, - status, - component, - componentId, - siteId, - options, - ); + try { + path = await this.downloadModuleWithMainFile( + module, + courseId, + fixedUrl, + files, + status, + component, + componentId, + siteId, + options, + ); + } catch (error) { + if (status !== CoreConstants.OUTDATED) { + throw error; + } + + // Use the local file even if it's outdated. + try { + path = await CoreFilepool.getInternalUrlByUrl(siteId, mainFile.fileurl); + } catch { + throw error; + } + } } return { @@ -1056,6 +1069,7 @@ export class CoreCourseHelperProvider { instance.size = moduleInfo.sizeReadable; instance.prefetchStatusIcon = moduleInfo.statusIcon; instance.prefetchStatus = moduleInfo.status; + instance.downloadTimeReadable = CoreTextUtils.ucFirst(moduleInfo.downloadTimeReadable); if (moduleInfo.status != CoreConstants.NOT_DOWNLOADABLE) { // Module is downloadable, get the text to display to prefetch. @@ -1478,6 +1492,8 @@ export class CoreCourseHelperProvider { // Currently, some modules pass invalidateCache=false because they already invalidate data in downloadResourceIfNeeded. // If this function is changed to do more actions if invalidateCache=true, please review those modules. CoreCourseModulePrefetchDelegate.invalidateModuleStatusCache(module); + + await CoreUtils.ignoreErrors(CoreCourseModulePrefetchDelegate.invalidateCourseUpdates(courseId)); } const results = await Promise.all([ @@ -2191,6 +2207,7 @@ type ComponentWithContextMenu = { size?: string; prefetchStatus?: string; prefetchText?: string; + downloadTimeReadable?: string; contextMenuStatusObserver?: CoreEventObserver; contextFileStatusObserver?: CoreEventObserver; }; diff --git a/src/core/lang.json b/src/core/lang.json index 43fab245f..6690dc4bb 100644 --- a/src/core/lang.json +++ b/src/core/lang.json @@ -64,6 +64,7 @@ "custom": "Custom", "datastoredoffline": "Data stored in the device because it couldn't be sent. It will be sent automatically later.", "date": "Date", + "datecreated": "Created", "day": "day", "days": "days", "decsep": ".", @@ -271,6 +272,7 @@ "showmore": "Show more...", "site": "Site", "sitemaintenance": "The site is undergoing maintenance and is currently not available", + "size": "Size", "sizeb": "bytes", "sizegb": "GB", "sizekb": "KB", @@ -309,6 +311,7 @@ "toggledelete": "Toggle delete buttons", "tryagain": "Try again", "twoparagraphs": "{{p1}}

{{p2}}", + "type": "Type", "uhoh": "Uh oh!", "unexpectederror": "Unexpected error. Please close and reopen the application then try again.", "unicodenotsupported": "Some emojis are not supported on this site. Such characters will be removed when the message is sent.",