forked from CIT/Vmeda.Online
		
	MOBILE-3980 book: Support resuming a book
This commit is contained in:
		
							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,23 +54,43 @@ export class AddonModBookIndexComponent extends CoreCourseModuleMainResourceComp
 | 
			
		||||
     */
 | 
			
		||||
    protected async fetchContent(refresh?: boolean): Promise<void> {
 | 
			
		||||
        try {
 | 
			
		||||
            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);
 | 
			
		||||
 | 
			
		||||
            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 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);
 | 
			
		||||
        } finally {
 | 
			
		||||
            this.fillContextMenu(refresh);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@ -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');
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // 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);
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										46
									
								
								src/addons/mod/book/services/database/book.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								src/addons/mod/book/services/database/book.ts
									
									
									
									
									
										Normal 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;
 | 
			
		||||
};
 | 
			
		||||
@ -248,6 +248,7 @@
 | 
			
		||||
    "restore": "Restore",
 | 
			
		||||
    "restricted": "Restricted",
 | 
			
		||||
    "retry": "Retry",
 | 
			
		||||
    "resume": "Resume",
 | 
			
		||||
    "save": "Save",
 | 
			
		||||
    "savechanges": "Save changes",
 | 
			
		||||
    "scanqr": "Scan QR code",
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user