From f695d673acd0609c0052e6dcae9d47c20f977c02 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Mon, 28 Feb 2022 12:38:46 +0100 Subject: [PATCH] MOBILE-3998 imscp: Remember last item viewed --- .../mod/book/pages/contents/contents.ts | 2 +- src/addons/mod/book/services/book.ts | 36 +++--------- .../mod/imscp/components/index/index.ts | 3 +- src/addons/mod/imscp/pages/view/view.ts | 28 +++++++-- src/addons/mod/imscp/services/imscp.ts | 47 ++++++++++----- src/core/classes/site.ts | 58 +++++++++++++++++++ src/core/services/database/sites.ts | 25 +++++++- 7 files changed, 149 insertions(+), 50 deletions(-) diff --git a/src/addons/mod/book/pages/contents/contents.ts b/src/addons/mod/book/pages/contents/contents.ts index e32661914..7a2055bd3 100644 --- a/src/addons/mod/book/pages/contents/contents.ts +++ b/src/addons/mod/book/pages/contents/contents.ts @@ -281,7 +281,7 @@ export class AddonModBookContentsPage implements OnInit, OnDestroy { } if (this.book) { - AddonModBook.storeLastChapterViewed(this.book.id, chapterId); + AddonModBook.storeLastChapterViewed(this.book.id, chapterId, this.courseId); } if (!this.module) { diff --git a/src/addons/mod/book/services/book.ts b/src/addons/mod/book/services/book.ts index 64d025dd6..16cdd0360 100644 --- a/src/addons/mod/book/services/book.ts +++ b/src/addons/mod/book/services/book.ts @@ -26,11 +26,6 @@ import { CoreTextUtils } from '@services/utils/text'; import { CoreDomUtils } from '@services/utils/dom'; import { CoreFile } from '@services/file'; import { CoreError } from '@classes/errors/error'; -import { lazyMap, LazyMap } from '@/core/utils/lazy-map'; -import { asyncInstance, AsyncInstance } from '@/core/utils/async-instance'; -import { CoreDatabaseTable } from '@classes/database/database-table'; -import { AddonModBookLastChapterViewedDBRecord, LAST_CHAPTER_VIEWED_TABLE } from './database/book'; -import { CoreDatabaseCachingStrategy } from '@classes/database/database-table-proxy'; /** * Constants to define how the chapters and subchapters of a book should be displayed in that table of contents. @@ -61,20 +56,6 @@ export class AddonModBookProvider { static readonly COMPONENT = 'mmaModBook'; - protected lastChapterViewedTables: LazyMap>>; - - constructor() { - this.lastChapterViewedTables = lazyMap( - siteId => asyncInstance( - () => CoreSites.getSiteTable(LAST_CHAPTER_VIEWED_TABLE, { - siteId, - config: { cachingStrategy: CoreDatabaseCachingStrategy.None }, - onDestroy: () => delete this.lastChapterViewedTables[siteId], - }), - ), - ); - } - /** * Get a book by course module ID. * @@ -243,14 +224,12 @@ export class AddonModBookProvider { * @return Promise resolved with last chapter viewed, undefined if none. */ async getLastChapterViewed(id: number, siteId?: string): Promise { - try { - const site = await CoreSites.getSite(siteId); - const entry = await this.lastChapterViewedTables[site.getId()].getOneByPrimaryKey({ id }); + const site = await CoreSites.getSite(siteId); + const entry = await site.getLastViewed(AddonModBookProvider.COMPONENT, id); - return entry.chapterid; - } catch { - // No last chapter viewed. - } + const chapterId = Number(entry?.value); + + return isNaN(chapterId) ? undefined : chapterId; } /** @@ -405,13 +384,14 @@ export class AddonModBookProvider { * * @param id Book instance ID. * @param chapterId Chapter ID. + * @param courseId Course ID. * @param siteId Site ID. If not defined, current site. * @return Promise resolved with last chapter viewed, undefined if none. */ - async storeLastChapterViewed(id: number, chapterId: number, siteId?: string): Promise { + async storeLastChapterViewed(id: number, chapterId: number, courseId: number, siteId?: string): Promise { const site = await CoreSites.getSite(siteId); - await this.lastChapterViewedTables[site.getId()].insert({ id, chapterid: chapterId }); + await site.storeLastViewed(AddonModBookProvider.COMPONENT, id, chapterId, String(courseId)); } } diff --git a/src/addons/mod/imscp/components/index/index.ts b/src/addons/mod/imscp/components/index/index.ts index 8538b3e54..6d6e501b4 100644 --- a/src/addons/mod/imscp/components/index/index.ts +++ b/src/addons/mod/imscp/components/index/index.ts @@ -87,7 +87,8 @@ export class AddonModImscpIndexComponent extends CoreCourseModuleMainResourceCom this.description = imscp.intro; - // @todo: Check if user already started the IMSCP. + const lastViewed = await AddonModImscp.getLastItemViewed(imscp.id); + this.hasStarted = lastViewed !== undefined; } /** diff --git a/src/addons/mod/imscp/pages/view/view.ts b/src/addons/mod/imscp/pages/view/view.ts index 58123db94..18da77400 100644 --- a/src/addons/mod/imscp/pages/view/view.ts +++ b/src/addons/mod/imscp/pages/view/view.ts @@ -14,6 +14,7 @@ import { CoreConstants } from '@/core/constants'; import { Component, OnInit } from '@angular/core'; +import { CoreError } from '@classes/errors/error'; import { CoreNavigationBarItem } from '@components/navigation-bar/navigation-bar'; import { CoreCourseResourceDownloadResult } from '@features/course/classes/main-resource-component'; import { CoreCourse } from '@features/course/services/course'; @@ -99,11 +100,22 @@ export class AddonModImscpViewPage implements OnInit { } if (this.currentHref === undefined) { - // @todo: Use last item viewed. - this.currentHref = this.items[0].href; + // Get last viewed. + const lastViewedHref = await AddonModImscp.getLastItemViewed(imscp.id); + + if (lastViewedHref !== undefined) { + this.currentHref = lastViewedHref; + } else { + // Use first one. + this.currentHref = this.items[0].href; + } } } + if (this.currentHref === undefined) { + throw new CoreError('Empty TOC'); + } + try { await this.loadItemHref(this.currentHref); } catch (error) { @@ -218,7 +230,7 @@ export class AddonModImscpViewPage implements OnInit { * @param itemHref Item Href. * @return Promise resolved when done. */ - async loadItemHref(itemHref?: string): Promise { + async loadItemHref(itemHref: string): Promise { if (!this.module) { return; } @@ -241,6 +253,10 @@ export class AddonModImscpViewPage implements OnInit { } else { this.src = src; } + + if (this.imscp) { + AddonModImscp.storeLastItemViewed(this.imscp.id, itemHref, this.courseId); + } } /** @@ -257,7 +273,7 @@ export class AddonModImscpViewPage implements OnInit { */ async showToc(): Promise { // Create the toc modal. - const modalData = await CoreDomUtils.openSideModal({ + const itemHref = await CoreDomUtils.openSideModal({ component: AddonModImscpTocComponent, componentProps: { items: this.items, @@ -265,8 +281,8 @@ export class AddonModImscpViewPage implements OnInit { }, }); - if (modalData) { - this.loadItemHref(modalData); + if (itemHref) { + this.loadItemHref(itemHref); } } diff --git a/src/addons/mod/imscp/services/imscp.ts b/src/addons/mod/imscp/services/imscp.ts index 121577ea3..abfcd0d87 100644 --- a/src/addons/mod/imscp/services/imscp.ts +++ b/src/addons/mod/imscp/services/imscp.ts @@ -168,29 +168,21 @@ export class AddonModImscpProvider { * Get src of a imscp item. * * @param module The module object. - * @param itemHref Href of item to get. If not defined, gets src of main item. + * @param itemHref Href of item to get. * @return Promise resolved with the item src. */ - async getIframeSrc(module: CoreCourseModuleData, itemHref?: string): Promise { - const contents = await CoreCourse.getModuleContents(module); - - if (!itemHref) { - const toc = this.getToc(contents); - if (!toc.length) { - throw new CoreError('Empty TOC'); - } - itemHref = toc[0].href; - } - + async getIframeSrc(module: CoreCourseModuleData, itemHref: string): Promise { const siteId = CoreSites.getCurrentSiteId(); try { - const dirPath = await CoreFilepool.getPackageDirUrlByUrl(siteId, module.url!); + const dirPath = await CoreFilepool.getPackageDirUrlByUrl(siteId, module.url || ''); return CoreTextUtils.concatenatePaths(dirPath, itemHref); } catch (error) { // Error getting directory, there was an error downloading or we're in browser. Return online URL if connected. if (CoreApp.isOnline()) { + const contents = await CoreCourse.getModuleContents(module); + const indexUrl = this.getFileUrlFromContents(contents, itemHref); if (indexUrl) { @@ -204,6 +196,20 @@ export class AddonModImscpProvider { } } + /** + * Get last item viewed's href in the app for a IMSCP. + * + * @param id IMSCP instance ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with last item viewed's href, undefined if none. + */ + async getLastItemViewed(id: number, siteId?: string): Promise { + const site = await CoreSites.getSite(siteId); + const entry = await site.getLastViewed(AddonModImscpProvider.COMPONENT, id); + + return entry?.value; + } + /** * Invalidate the prefetched content. * @@ -285,6 +291,21 @@ export class AddonModImscpProvider { ); } + /** + * Store last item viewed in the app for a IMSCP. + * + * @param id IMSCP instance ID. + * @param href Item href. + * @param courseId Course ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with last item viewed, undefined if none. + */ + async storeLastItemViewed(id: number, href: string, courseId: number, siteId?: string): Promise { + const site = await CoreSites.getSite(siteId); + + await site.storeLastViewed(AddonModImscpProvider.COMPONENT, id, href, String(courseId)); + } + } export const AddonModImscp = makeSingleton(AddonModImscpProvider); diff --git a/src/core/classes/site.ts b/src/core/classes/site.ts index 99f9bb496..0f5ace6b2 100644 --- a/src/core/classes/site.ts +++ b/src/core/classes/site.ts @@ -82,6 +82,7 @@ export class CoreSite { // Variables for the database. static readonly WS_CACHE_TABLE = 'wscache_2'; static readonly CONFIG_TABLE = 'core_site_config'; + static readonly LAST_VIEWED_TABLE = 'core_site_last_viewed'; static readonly MINIMUM_MOODLE_VERSION = '3.5'; @@ -110,6 +111,7 @@ export class CoreSite { protected db?: SQLiteDB; protected cacheTable: AsyncInstance>; protected configTable: AsyncInstance>; + protected lastViewedTable: AsyncInstance>; protected cleanUnicode = false; protected lastAutoLogin = 0; protected offlineDisabled = false; @@ -154,6 +156,12 @@ export class CoreSite { config: { cachingStrategy: CoreDatabaseCachingStrategy.Eager }, primaryKeyColumns: ['name'], })); + this.lastViewedTable = asyncInstance(() => CoreSites.getSiteTable(CoreSite.LAST_VIEWED_TABLE, { + siteId: this.getId(), + database: this.getDb(), + config: { cachingStrategy: CoreDatabaseCachingStrategy.None }, + primaryKeyColumns: ['component', 'id'], + })); this.setInfo(infos); this.calculateOfflineDisabled(); @@ -1955,6 +1963,49 @@ export class CoreSite { return this.containsUrl(url); } + /** + * Deletes last viewed records based on some conditions. + * + * @param conditions Conditions. + * @return Promise resolved when done. + */ + async deleteLastViewed(conditions?: Partial): Promise { + await this.lastViewedTable.delete(conditions); + } + + /** + * Get a last viewed record for a component+id. + * + * @param component The component. + * @param id ID. + * @return Resolves with last viewed record, undefined if not found. + */ + async getLastViewed(component: string, id: number): Promise { + try { + return await this.lastViewedTable.getOneByPrimaryKey({ component, id }); + } catch (error) { + // Not found. + } + } + + /** + * Store a last viewed record. + * + * @param component The component. + * @param id ID. + * @param value Last viewed item value. + * @param data Other data. + * @return Promise resolved when done. + */ + async storeLastViewed(component: string, id: number, value: string | number, data?: string): Promise { + await this.lastViewedTable.insert({ + component, + id, + value: String(value), + data, + }); + } + } /** @@ -2281,3 +2332,10 @@ export type CoreSiteWSCacheRecord = { component?: string; componentId?: number; }; + +export type CoreSiteLastViewedDBRecord = { + component: string; + id: number; + value: string; + data?: string; +}; diff --git a/src/core/services/database/sites.ts b/src/core/services/database/sites.ts index 930955e46..8d2083e1d 100644 --- a/src/core/services/database/sites.ts +++ b/src/core/services/database/sites.ts @@ -78,7 +78,7 @@ export const APP_SCHEMA: CoreAppSchema = { // Schema to register for Site DB. export const SITE_SCHEMA: CoreSiteSchema = { name: 'CoreSitesProvider', - version: 2, + version: 3, canBeCleared: [CoreSite.WS_CACHE_TABLE], tables: [ { @@ -125,6 +125,29 @@ export const SITE_SCHEMA: CoreSiteSchema = { }, ], }, + { + name: CoreSite.LAST_VIEWED_TABLE, + columns: [ + { + name: 'component', + type: 'TEXT', + }, + { + name: 'id', + type: 'INTEGER', + }, + { + name: 'value', + type: 'TEXT', + notNull: true, + }, + { + name: 'data', + type: 'TEXT', + }, + ], + primaryKeys: ['component', 'id'], + }, ], async migrate(db: SQLiteDB, oldVersion: number): Promise { if (oldVersion < 2) {