MOBILE-3980 book: Support resuming a book
parent
5f3e7bb0b5
commit
1b67036aca
|
@ -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",
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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<unknown>[] = [
|
||||
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,
|
||||
|
|
|
@ -45,7 +45,8 @@
|
|||
</ion-item>
|
||||
|
||||
<ion-button class="ion-margin" expand="block" (click)="openBook()">
|
||||
{{ 'addon.mod_book.book:read' | translate }}
|
||||
<span *ngIf="!hasStartedBook">{{ 'core.start' | translate }}</span>
|
||||
<span *ngIf="hasStartedBook">{{ 'core.resume' | translate }}</span>
|
||||
</ion-button>
|
||||
|
||||
</ion-list>
|
||||
|
|
|
@ -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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"book:read": "View book",
|
||||
"errorchapter": "Error reading chapter of book.",
|
||||
"modulenameplural": "Books",
|
||||
"navnexttitle": "Next: {{$a}}",
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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<AsyncInstance<CoreDatabaseTable<AddonModBookLastChapterViewedDBRecord>>>;
|
||||
|
||||
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<number | undefined> {
|
||||
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<void> {
|
||||
const site = await CoreSites.getSite(siteId);
|
||||
|
||||
await this.lastChapterViewedTables[site.getId()].insert({ id, chapterid: chapterId });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const AddonModBook = makeSingleton(AddonModBookProvider);
|
||||
|
|
|
@ -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;
|
||||
};
|
|
@ -248,6 +248,7 @@
|
|||
"restore": "Restore",
|
||||
"restricted": "Restricted",
|
||||
"retry": "Retry",
|
||||
"resume": "Resume",
|
||||
"save": "Save",
|
||||
"savechanges": "Save changes",
|
||||
"scanqr": "Scan QR code",
|
||||
|
|
Loading…
Reference in New Issue