MOBILE-3932 resource: Display file info in resource index page

main
Dani Palou 2021-11-18 14:42:12 +01:00
parent 5a2016cb67
commit 29e2119143
9 changed files with 189 additions and 36 deletions

View File

@ -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",

View File

@ -42,22 +42,71 @@
</div>
<ng-container *ngIf="mode == 'external'">
<ion-button expand="block" class="ion-margin" (click)="open(openFileAction.OPEN)">
<ng-container *ngIf="isStreamedFile">
<ion-icon name="fas-play" slot="start" aria-hidden="true"></ion-icon>
{{ 'core.play' | translate }}
</ng-container>
<ng-container *ngIf="!isStreamedFile">
<ion-icon name="far-file" slot="start" aria-hidden="true"></ion-icon>
{{ 'addon.mod_resource.openthefile' | translate }}
</ng-container>
</ion-button>
<ion-list>
<ion-item class="ion-text-wrap" *ngIf="type">
<ion-label>
<h3>{{ 'core.type' | translate }}</h3>
<p>{{ type }}</p>
</ion-label>
</ion-item>
<ion-button *ngIf="isIOS && (!shouldOpenInBrowser || !isOnline)" expand="block" class="ion-margin"
(click)="open(openFileAction.OPEN_WITH)">
<ion-icon name="far-share-square" slot="start" aria-hidden="true"></ion-icon>
{{ 'core.openwith' | translate }}
</ion-button>
<ng-container *ngIf="!isExternalFile">
<ion-item class="ion-text-wrap" *ngIf="readableSize">
<ion-label>
<h3>{{ 'core.size' | translate }}</h3>
<p>{{ readableSize }}</p>
</ion-label>
</ion-item>
<ion-item class="ion-text-wrap" *ngIf="timecreated > 0">
<ion-label>
<h3>{{ 'core.datecreated' | translate }}</h3>
<p>{{ timecreated | coreFormatDate }}</p>
</ion-label>
</ion-item>
<ion-item class="ion-text-wrap" *ngIf="timemodified > 0">
<ion-label>
<h3>{{ 'core.lastmodified' | translate }}</h3>
<p>{{ timemodified | coreFormatDate }}</p>
</ion-label>
</ion-item>
<ion-item class="ion-text-wrap" *ngIf="downloadTimeReadable">
<ion-label>
<h3>{{ 'core.lastdownloaded' | translate }}</h3>
<p>{{ downloadTimeReadable }}</p>
<ion-grid *ngIf="prefetchStatus === outdatedStatus" class="addon-mod_resource-outdated">
<ion-row class="ion-align-items-center">
<ion-col size="auto">
<ion-icon color="warning" name="fas-exclamation-triangle" aria-hidden="true"></ion-icon>
</ion-col>
<ion-col>
<p><strong>{{ 'addon.mod_resource.resourcestatusoutdated' | translate }}</strong></p>
</ion-col>
</ion-row>
</ion-grid>
</ion-label>
</ion-item>
</ng-container>
<ion-button expand="block" class="ion-margin" (click)="open(openFileAction.OPEN)">
<ng-container *ngIf="isStreamedFile">
<ion-icon name="fas-play" slot="start" aria-hidden="true"></ion-icon>
{{ 'core.play' | translate }}
</ng-container>
<ng-container *ngIf="!isStreamedFile">
<ion-icon name="far-file" slot="start" aria-hidden="true"></ion-icon>
{{ 'addon.mod_resource.openthefile' | translate }}
</ng-container>
</ion-button>
<ion-button *ngIf="isIOS && (!shouldOpenInBrowser || !isOnline)" expand="block" class="ion-margin"
(click)="open(openFileAction.OPEN_WITH)">
<ion-icon name="far-share-square" slot="start" aria-hidden="true"></ion-icon>
{{ 'core.openwith' | translate }}
</ion-button>
</ion-list>
</ng-container>
</core-loading>

View File

@ -0,0 +1,11 @@
@import "~theme/globals";
:host {
.addon-mod_resource-outdated {
@include padding(4px, 0px, 0px, 0px);
ion-icon {
font-size: 24px;
}
}
}

View File

@ -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 });
}
}

View File

@ -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}}"
}

View File

@ -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}})"
}
}

View File

@ -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.

View File

@ -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;
};

View File

@ -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}}<br><br>{{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.",