MOBILE-3980 book: Support resuming a book

main
Dani Palou 2022-02-16 08:18:36 +01:00
parent 5f3e7bb0b5
commit 1b67036aca
10 changed files with 162 additions and 26 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,4 @@
{
"book:read": "View book",
"errorchapter": "Error reading chapter of book.",
"modulenameplural": "Books",
"navnexttitle": "Next: {{$a}}",

View File

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

View File

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

View File

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

View File

@ -248,6 +248,7 @@
"restore": "Restore",
"restricted": "Restricted",
"retry": "Retry",
"resume": "Resume",
"save": "Save",
"savechanges": "Save changes",
"scanqr": "Scan QR code",