diff --git a/scripts/langindex.json b/scripts/langindex.json index 5a733c08e..18d1bdd5c 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -450,7 +450,6 @@ "addon.mod_bigbluebuttonbn.view_message_session_started_at": "bigbluebuttonbn", "addon.mod_bigbluebuttonbn.view_message_viewer": "bigbluebuttonbn", "addon.mod_bigbluebuttonbn.view_message_viewers": "bigbluebuttonbn", - "addon.mod_book.book:read": "book", "addon.mod_book.errorchapter": "book", "addon.mod_book.modulenameplural": "book", "addon.mod_book.navnexttitle": "book", @@ -2127,6 +2126,7 @@ "core.resources": "moodle", "core.restore": "moodle", "core.restricted": "moodle", + "core.resume": "local_moodlemobileapp", "core.retry": "local_moodlemobileapp", "core.save": "moodle", "core.savechanges": "assign", @@ -2253,7 +2253,7 @@ "core.sorry": "local_moodlemobileapp", "core.sort": "moodle", "core.sortby": "moodle", - "core.start": "grouptool", + "core.start": "local_moodlemobileapp", "core.storingfiles": "local_moodlemobileapp", "core.strftimedate": "langconfig", "core.strftimedatefullshort": "langconfig", diff --git a/src/addons/calendar/services/database/calendar.ts b/src/addons/calendar/services/database/calendar.ts index 359f9ea4b..b21928140 100644 --- a/src/addons/calendar/services/database/calendar.ts +++ b/src/addons/calendar/services/database/calendar.ts @@ -19,7 +19,7 @@ import { CoreUtils } from '@services/utils/utils'; import { AddonCalendar, AddonCalendarEventType, AddonCalendarProvider } from '../calendar'; /** - * Database variables for AddonDatabase service. + * Database variables for AddonCalendarProvider service. */ export const EVENTS_TABLE = 'addon_calendar_events_3'; export const REMINDERS_TABLE = 'addon_calendar_reminders'; diff --git a/src/addons/mod/book/book.module.ts b/src/addons/mod/book/book.module.ts index 3ac80f207..351e3ee18 100644 --- a/src/addons/mod/book/book.module.ts +++ b/src/addons/mod/book/book.module.ts @@ -26,6 +26,8 @@ import { AddonModBookListLinkHandler } from './services/handlers/list-link'; import { AddonModBookPrefetchHandler } from './services/handlers/prefetch'; import { AddonModBookTagAreaHandler } from './services/handlers/tag-area'; import { AddonModBookProvider } from './services/book'; +import { CORE_SITE_SCHEMAS } from '@services/sites'; +import { BOOK_SITE_SCHEMA } from './services/database/book'; export const ADDON_MOD_BOOK_SERVICES: Type[] = [ AddonModBookProvider, @@ -44,6 +46,11 @@ const routes: Routes = [ AddonModBookComponentsModule, ], providers: [ + { + provide: CORE_SITE_SCHEMAS, + useValue: [BOOK_SITE_SCHEMA], + multi: true, + }, { provide: APP_INITIALIZER, multi: true, diff --git a/src/addons/mod/book/components/index/addon-mod-book-index.html b/src/addons/mod/book/components/index/addon-mod-book-index.html index a87c06a88..0ebdcb21f 100644 --- a/src/addons/mod/book/components/index/addon-mod-book-index.html +++ b/src/addons/mod/book/components/index/addon-mod-book-index.html @@ -45,7 +45,8 @@ - {{ 'addon.mod_book.book:read' | translate }} + {{ 'core.start' | translate }} + {{ 'core.resume' | translate }} diff --git a/src/addons/mod/book/components/index/index.ts b/src/addons/mod/book/components/index/index.ts index 35f24bbd2..4ef609091 100644 --- a/src/addons/mod/book/components/index/index.ts +++ b/src/addons/mod/book/components/index/index.ts @@ -32,6 +32,7 @@ export class AddonModBookIndexComponent extends CoreCourseModuleMainResourceComp addPadding = true; showBullets = false; chapters: AddonModBookTocChapter[] = []; + hasStartedBook = false; protected book?: AddonModBookBookWSData; @@ -53,25 +54,45 @@ export class AddonModBookIndexComponent extends CoreCourseModuleMainResourceComp */ protected async fetchContent(refresh?: boolean): Promise { try { - this.book = await AddonModBook.getBook(this.courseId, this.module.id); - - if (this.book) { - this.dataRetrieved.emit(this.book); - - this.description = this.book.intro; - this.showNumbers = this.book.numbering == AddonModBookNumbering.NUMBERS; - this.showBullets = this.book.numbering == AddonModBookNumbering.BULLETS; - this.addPadding = this.book.numbering != AddonModBookNumbering.NONE; - } - - const contents = await CoreCourse.getModuleContents(this.module, this.courseId); - - this.chapters = AddonModBook.getTocList(contents); + await Promise.all([ + this.loadBook(), + this.loadTOC(), + ]); } finally { this.fillContextMenu(refresh); } } + /** + * Load book data. + * + * @return Promise resolved when done. + */ + protected async loadBook(): Promise { + this.book = await AddonModBook.getBook(this.courseId, this.module.id); + + this.dataRetrieved.emit(this.book); + + this.description = this.book.intro; + this.showNumbers = this.book.numbering == AddonModBookNumbering.NUMBERS; + this.showBullets = this.book.numbering == AddonModBookNumbering.BULLETS; + this.addPadding = this.book.numbering != AddonModBookNumbering.NONE; + + const lastChapterViewed = await AddonModBook.getLastChapterViewed(this.book.id); + this.hasStartedBook = lastChapterViewed !== undefined; + } + + /** + * Load book TOC. + * + * @return Promise resolved when done. + */ + protected async loadTOC(): Promise { + const contents = await CoreCourse.getModuleContents(this.module, this.courseId); + + this.chapters = AddonModBook.getTocList(contents); + } + /** * Open the book in a certain chapter. * @@ -85,6 +106,8 @@ export class AddonModBookIndexComponent extends CoreCourseModuleMainResourceComp chapterId, }, }); + + this.hasStartedBook = true; } /** diff --git a/src/addons/mod/book/lang.json b/src/addons/mod/book/lang.json index 8b6812eb9..64afa1cd7 100644 --- a/src/addons/mod/book/lang.json +++ b/src/addons/mod/book/lang.json @@ -1,5 +1,4 @@ { - "book:read": "View book", "errorchapter": "Error reading chapter of book.", "modulenameplural": "Books", "navnexttitle": "Next: {{$a}}", diff --git a/src/addons/mod/book/pages/contents/contents.ts b/src/addons/mod/book/pages/contents/contents.ts index de3de0c6f..0f2444198 100644 --- a/src/addons/mod/book/pages/contents/contents.ts +++ b/src/addons/mod/book/pages/contents/contents.ts @@ -133,13 +133,9 @@ export class AddonModBookContentsPage implements OnInit, OnDestroy { const downloadResult = await this.downloadResourceIfNeeded(module, refresh); - if (book) { - this.displayNavBar = book.navstyle != AddonModBookNavStyle.TOC_ONLY; - this.displayTitlesInNavBar = book.navstyle == AddonModBookNavStyle.TEXT; - this.title = book.name; - } else { - this.title = this.title || Translate.instant('addon.mod_book.book:read'); - } + this.displayNavBar = book.navstyle != AddonModBookNavStyle.TOC_ONLY; + this.displayTitlesInNavBar = book.navstyle == AddonModBookNavStyle.TEXT; + this.title = book.name; // Get contents. No need to refresh, it has been done in downloadResourceIfNeeded. await source.loadContents(); @@ -283,6 +279,10 @@ export class AddonModBookContentsPage implements OnInit, OnDestroy { this.navigationItems = this.getNavigationItems(chapterId); } + if (this.book) { + AddonModBook.storeLastChapterViewed(this.book.id, chapterId); + } + if (!this.module) { return; } @@ -371,6 +371,15 @@ class AddonModBookSlidesItemsManagerSource extends CoreSwipeSlidesItemsManagerSo this.module = await CoreCourse.getModule(this.CM_ID, this.COURSE_ID); this.book = await AddonModBook.getBook(this.COURSE_ID, this.CM_ID); + if (!this.initialItem) { + // No chapter ID specified. Calculate last viewed. + const lastViewed = await AddonModBook.getLastChapterViewed(this.book.id); + + if (lastViewed) { + this.initialItem = { id: lastViewed }; + } + } + return { module: this.module, book: this.book, diff --git a/src/addons/mod/book/services/book.ts b/src/addons/mod/book/services/book.ts index 1efa28d21..6b6c18a8f 100644 --- a/src/addons/mod/book/services/book.ts +++ b/src/addons/mod/book/services/book.ts @@ -26,6 +26,11 @@ 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. @@ -56,6 +61,19 @@ 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 }, + }), + ), + ); + } + /** * Get a book by course module ID. * @@ -216,6 +234,24 @@ export class AddonModBookProvider { return chapters[0].id; } + /** + * Get last chapter viewed in the app for a book. + * + * @param id Book instance ID. + * @param siteId Site ID. If not defined, current site. + * @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 }); + + return entry.chapterid; + } catch { + // No last chapter viewed. + } + } + /** * Get the book toc as an array. * @@ -363,6 +399,20 @@ export class AddonModBookProvider { ); } + /** + * Store last chapter viewed in the app for a book. + * + * @param id Book instance ID. + * @param chapterId Chapter 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 { + const site = await CoreSites.getSite(siteId); + + await this.lastChapterViewedTables[site.getId()].insert({ id, chapterid: chapterId }); + } + } export const AddonModBook = makeSingleton(AddonModBookProvider); diff --git a/src/addons/mod/book/services/database/book.ts b/src/addons/mod/book/services/database/book.ts new file mode 100644 index 000000000..38f526348 --- /dev/null +++ b/src/addons/mod/book/services/database/book.ts @@ -0,0 +1,46 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { CoreSiteSchema } from '@services/sites'; + +/** + * Database variables for AddonModBookProvider service. + */ +export const LAST_CHAPTER_VIEWED_TABLE = 'addon_mod_book_last_chapter_viewed'; +export const BOOK_SITE_SCHEMA: CoreSiteSchema = { + name: 'AddonModBookProvider', + version: 1, + tables: [ + { + name: LAST_CHAPTER_VIEWED_TABLE, + columns: [ + { + name: 'id', + type: 'INTEGER', + primaryKey: true, + }, + { + name: 'chapterid', + type: 'INTEGER', + notNull: true, + }, + ], + }, + ], +}; + +export type AddonModBookLastChapterViewedDBRecord = { + id: number; + chapterid: number; +}; diff --git a/src/core/lang.json b/src/core/lang.json index 651d5fd54..5b58a7e07 100644 --- a/src/core/lang.json +++ b/src/core/lang.json @@ -248,6 +248,7 @@ "restore": "Restore", "restricted": "Restricted", "retry": "Retry", + "resume": "Resume", "save": "Save", "savechanges": "Save changes", "scanqr": "Scan QR code",