MOBILE-3998 imscp: Remember last item viewed
parent
223fd19f1d
commit
f695d673ac
|
@ -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) {
|
||||
|
|
|
@ -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<AsyncInstance<CoreDatabaseTable<AddonModBookLastChapterViewedDBRecord>>>;
|
||||
|
||||
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<number | undefined> {
|
||||
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<void> {
|
||||
async storeLastChapterViewed(id: number, chapterId: number, courseId: number, siteId?: string): Promise<void> {
|
||||
const site = await CoreSites.getSite(siteId);
|
||||
|
||||
await this.lastChapterViewedTables[site.getId()].insert({ id, chapterid: chapterId });
|
||||
await site.storeLastViewed(AddonModBookProvider.COMPONENT, id, chapterId, String(courseId));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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<void> {
|
||||
async loadItemHref(itemHref: string): Promise<void> {
|
||||
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<void> {
|
||||
// Create the toc modal.
|
||||
const modalData = await CoreDomUtils.openSideModal<string>({
|
||||
const itemHref = await CoreDomUtils.openSideModal<string>({
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<string> {
|
||||
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<string> {
|
||||
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<string | undefined> {
|
||||
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<void> {
|
||||
const site = await CoreSites.getSite(siteId);
|
||||
|
||||
await site.storeLastViewed(AddonModImscpProvider.COMPONENT, id, href, String(courseId));
|
||||
}
|
||||
|
||||
}
|
||||
export const AddonModImscp = makeSingleton(AddonModImscpProvider);
|
||||
|
||||
|
|
|
@ -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<CoreDatabaseTable<CoreSiteWSCacheRecord>>;
|
||||
protected configTable: AsyncInstance<CoreDatabaseTable<CoreSiteConfigDBRecord, 'name'>>;
|
||||
protected lastViewedTable: AsyncInstance<CoreDatabaseTable<CoreSiteLastViewedDBRecord, 'component' | 'id'>>;
|
||||
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<CoreSiteLastViewedDBRecord>): Promise<void> {
|
||||
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<CoreSiteLastViewedDBRecord | undefined> {
|
||||
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<void> {
|
||||
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;
|
||||
};
|
||||
|
|
|
@ -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<void> {
|
||||
if (oldVersion < 2) {
|
||||
|
|
Loading…
Reference in New Issue