Merge pull request #2744 from dpalou/MOBILE-3320

Mobile 3320
main
Pau Ferrer Ocaña 2021-05-05 12:52:07 +02:00 committed by GitHub
commit 514bc2bcdd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 101 additions and 75 deletions

View File

@ -87,7 +87,7 @@ export class CoreTabsBaseComponent<T extends CoreTabBase> implements OnInit, Aft
protected isInTransition = false; // Weather Slides is in transition. protected isInTransition = false; // Weather Slides is in transition.
protected slidesSwiper: any; // eslint-disable-line @typescript-eslint/no-explicit-any protected slidesSwiper: any; // eslint-disable-line @typescript-eslint/no-explicit-any
protected slidesSwiperLoaded = false; protected slidesSwiperLoaded = false;
protected scrollListenersSet: Record<string | number, boolean> = {}; // Prevent setting listeners twice. protected scrollElements: Record<string | number, HTMLElement> = {}; // Scroll elements for each loaded tab.
constructor( constructor(
protected element: ElementRef, protected element: ElementRef,
@ -444,11 +444,11 @@ export class CoreTabsBaseComponent<T extends CoreTabBase> implements OnInit, Aft
/** /**
* Show or hide the tabs. This is used when the user is scrolling inside a tab. * Show or hide the tabs. This is used when the user is scrolling inside a tab.
* *
* @param scrollEvent Scroll event to check scroll position. * @param scrollTop Scroll top.
* @param content Content element to check measures. * @param scrollElement Content scroll element to check measures.
*/ */
showHideTabs(scrollEvent: CustomEvent, content: HTMLElement): void { showHideTabs(scrollTop: number, scrollElement: HTMLElement): void {
if (!this.tabBarElement || !this.tabsElement || !content) { if (!this.tabBarElement || !this.tabsElement || !scrollElement) {
return; return;
} }
@ -467,8 +467,7 @@ export class CoreTabsBaseComponent<T extends CoreTabBase> implements OnInit, Aft
return; return;
} }
const scroll = parseInt(scrollEvent.detail.scrollTop, 10); if (scrollTop <= 0) {
if (scroll <= 0) {
// Ensure tabbar is shown. // Ensure tabbar is shown.
this.tabsElement.style.top = '0'; this.tabsElement.style.top = '0';
this.tabsElement.style.height = ''; this.tabsElement.style.height = '';
@ -479,30 +478,30 @@ export class CoreTabsBaseComponent<T extends CoreTabBase> implements OnInit, Aft
return; return;
} }
if (scroll == this.lastScroll) { if (scrollTop == this.lastScroll) {
// Ensure scroll has been modified to avoid flicks. // Ensure scroll has been modified to avoid flicks.
return; return;
} }
if (this.tabsShown && scroll > this.tabBarHeight) { if (this.tabsShown && scrollTop > this.tabBarHeight) {
this.tabsShown = false; this.tabsShown = false;
// Hide tabs. // Hide tabs.
this.tabBarElement.classList.add('tabs-hidden'); this.tabBarElement.classList.add('tabs-hidden');
this.tabsElement.style.top = '0'; this.tabsElement.style.top = '0';
this.tabsElement.style.height = ''; this.tabsElement.style.height = '';
} else if (!this.tabsShown && scroll <= this.tabBarHeight) { } else if (!this.tabsShown && scrollTop <= this.tabBarHeight) {
this.tabsShown = true; this.tabsShown = true;
this.tabBarElement.classList.remove('tabs-hidden'); this.tabBarElement.classList.remove('tabs-hidden');
} }
if (this.tabsShown && content.scrollHeight > content.clientHeight + (this.tabBarHeight - scroll)) { if (this.tabsShown && scrollElement.scrollHeight > scrollElement.clientHeight + (this.tabBarHeight - scrollTop)) {
// Smooth translation. // Smooth translation.
this.tabsElement.style.top = - scroll + 'px'; this.tabsElement.style.top = - scrollTop + 'px';
this.tabsElement.style.height = 'calc(100% + ' + scroll + 'px'; this.tabsElement.style.height = 'calc(100% + ' + scrollTop + 'px';
} }
// Use lastScroll after moving the tabs to avoid flickering. // Use lastScroll after moving the tabs to avoid flickering.
this.lastScroll = parseInt(scrollEvent.detail.scrollTop, 10); this.lastScroll = scrollTop;
} }
/** /**
@ -546,7 +545,12 @@ export class CoreTabsBaseComponent<T extends CoreTabBase> implements OnInit, Aft
} }
if (this.selected) { if (this.selected) {
await this.slides!.slideTo(index); // Check if we need to slide to the tab because it's not visible.
const firstVisibleTab = await this.slides!.getActiveIndex();
const lastVisibleTab = firstVisibleTab + this.slidesOpts.slidesPerView - 1;
if (index < firstVisibleTab || index > lastVisibleTab) {
await this.slides!.slideTo(index, 0, true);
}
} }
const ok = await this.loadTab(tabToSelect); const ok = await this.loadTab(tabToSelect);
@ -580,17 +584,28 @@ export class CoreTabsBaseComponent<T extends CoreTabBase> implements OnInit, Aft
* @return Promise resolved when done. * @return Promise resolved when done.
*/ */
async listenContentScroll(element: HTMLElement, id: number | string): Promise<void> { async listenContentScroll(element: HTMLElement, id: number | string): Promise<void> {
const content = element.querySelector('ion-content'); if (this.scrollElements[id]) {
return; // Already set.
}
if (!content || this.scrollListenersSet[id]) { let content = element.querySelector('ion-content');
if (!content) {
return; return;
} }
// Search the inner ion-content if there's more than one.
let childContent = content.querySelector('ion-content') || null;
while (childContent != null) {
content = childContent;
childContent = content.querySelector('ion-content') || null;
}
const scroll = await content.getScrollElement(); const scroll = await content.getScrollElement();
content.scrollEvents = true; content.scrollEvents = true;
this.scrollListenersSet[id] = true; this.scrollElements[id] = scroll;
content.addEventListener('ionScroll', (e: CustomEvent): void => { content.addEventListener('ionScroll', (e: CustomEvent): void => {
this.showHideTabs(e, scroll); this.showHideTabs(parseInt(e.detail.scrollTop, 10), scroll);
}); });
} }

View File

@ -99,8 +99,15 @@ export class CoreTabsOutletComponent extends CoreTabsBaseComponent<CoreTabsOutle
return; return;
} }
this.listenContentScroll(stackEvent.enteringView.element, stackEvent.enteringView.id);
this.showHideNavBarButtons(stackEvent.enteringView.element.tagName); this.showHideNavBarButtons(stackEvent.enteringView.element.tagName);
await this.listenContentScroll(stackEvent.enteringView.element, stackEvent.enteringView.id);
const scrollElement = this.scrollElements[stackEvent.enteringView.id];
if (scrollElement) {
// Show or hide tabs based on the new page scroll.
this.showHideTabs(scrollElement.scrollTop, scrollElement);
}
}); });
} }

View File

@ -27,6 +27,9 @@ function buildRoutes(injector: Injector): Routes {
{ {
path: '', path: '',
component: CoreCourseIndexPage, component: CoreCourseIndexPage,
data: {
isCourseIndex: true,
},
children: routes.children, children: routes.children,
}, },
...routes.siblings, ...routes.siblings,

View File

@ -71,7 +71,7 @@ export class CoreCourseIndexPage implements OnInit, OnDestroy {
const index = this.tabs.findIndex((tab) => tab.name == data.name); const index = this.tabs.findIndex((tab) => tab.name == data.name);
if (index >= 0) { if (index >= 0) {
this.tabsComponent?.selectByIndex(index + 1); this.tabsComponent?.selectByIndex(index);
} }
} }
}); });
@ -129,11 +129,9 @@ export class CoreCourseIndexPage implements OnInit, OnDestroy {
// Load the course handlers. // Load the course handlers.
const handlers = await CoreCourseOptionsDelegate.getHandlersToDisplay(this.course!, false, false); const handlers = await CoreCourseOptionsDelegate.getHandlersToDisplay(this.course!, false, false);
this.tabs = [...this.tabs, ...handlers.map(handler => handler.data)];
let tabToLoad: number | undefined; let tabToLoad: number | undefined;
// Add the courseId to the handler component data. // Create the full path.
handlers.forEach((handler, index) => { handlers.forEach((handler, index) => {
handler.data.page = CoreTextUtils.concatenatePaths(this.currentPagePath, handler.data.page); handler.data.page = CoreTextUtils.concatenatePaths(this.currentPagePath, handler.data.page);
handler.data.pageParams = handler.data.pageParams || {}; handler.data.pageParams = handler.data.pageParams || {};
@ -144,6 +142,11 @@ export class CoreCourseIndexPage implements OnInit, OnDestroy {
} }
}); });
this.tabs = [...this.tabs, ...handlers.map(handler => ({
...handler.data,
name: handler.name,
}))];
// Select the tab if needed. // Select the tab if needed.
this.firstTabName = undefined; this.firstTabName = undefined;
if (tabToLoad) { if (tabToLoad) {

View File

@ -43,6 +43,7 @@ import { CoreCourseLogCronHandler } from './handlers/log-cron';
import { CoreSitePlugins } from '@features/siteplugins/services/siteplugins'; import { CoreSitePlugins } from '@features/siteplugins/services/siteplugins';
import { CoreCourseAutoSyncData, CoreCourseSyncProvider } from './sync'; import { CoreCourseAutoSyncData, CoreCourseSyncProvider } from './sync';
import { CoreTagItem } from '@features/tag/services/tag'; import { CoreTagItem } from '@features/tag/services/tag';
import { CoreNavigator } from '@services/navigator';
const ROOT_CACHE_KEY = 'mmCourse:'; const ROOT_CACHE_KEY = 'mmCourse:';
@ -176,10 +177,14 @@ export class CoreCourseProvider {
* @param courseId Course ID. * @param courseId Course ID.
* @return Whether the current view is a certain course. * @return Whether the current view is a certain course.
*/ */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
currentViewIsCourse(courseId: number): boolean { currentViewIsCourse(courseId: number): boolean {
// @todo implement const route = CoreNavigator.getCurrentRoute({ routeData: { isCourseIndex: true } });
return false;
if (!route) {
return false;
}
return Number(route.snapshot.params.courseId) == courseId;
} }
/** /**

View File

@ -34,6 +34,7 @@ import { CoreDomUtils } from '@services/utils/dom';
import { CoreNavigator } from '@services/navigator'; import { CoreNavigator } from '@services/navigator';
import { makeSingleton, Translate } from '@singletons'; import { makeSingleton, Translate } from '@singletons';
import { CoreError } from '@classes/errors/error'; import { CoreError } from '@classes/errors/error';
import { CoreCourseHelper } from '@features/course/services/course-helper';
/** /**
* Service that provides some features regarding grades information. * Service that provides some features regarding grades information.
@ -458,14 +459,13 @@ export class CoreGradesHelperProvider {
siteId?: string, siteId?: string,
): Promise<void> { ): Promise<void> {
const modal = await CoreDomUtils.showModalLoading(); const modal = await CoreDomUtils.showModalLoading();
let currentUserId: number;
const site = await CoreSites.getSite(siteId);
siteId = site.id;
const currentUserId = site.getUserId();
try { try {
const site = await CoreSites.getSite(siteId);
siteId = site.id;
currentUserId = site.getUserId();
if (!moduleId) { if (!moduleId) {
throw new CoreError('Invalid moduleId'); throw new CoreError('Invalid moduleId');
} }
@ -477,27 +477,27 @@ export class CoreGradesHelperProvider {
throw new CoreError('No grades found.'); throw new CoreError('No grades found.');
} }
// Can get grades. Do it.
const items = await CoreGrades.getGradeItems(courseId, userId, undefined, siteId);
// Find the item of the module.
const item = Array.isArray(items) && items.find((item) => moduleId == item.cmid);
if (!item) {
throw new CoreError('Grade item not found.');
}
// Open the item directly.
const gradeId = item.id;
await CoreUtils.ignoreErrors(
CoreNavigator.navigateToSitePath(`/grades/${courseId}/${gradeId}`, {
siteId,
params: { userId },
}),
);
} catch (error) {
try { try {
// Can get grades. Do it.
const items = await CoreGrades.getGradeItems(courseId, userId, undefined, siteId);
// Find the item of the module.
const item = Array.isArray(items) && items.find((item) => moduleId == item.cmid);
if (!item) {
throw new CoreError('Grade item not found.');
}
// Open the item directly.
const gradeId = item.id;
await CoreUtils.ignoreErrors(
CoreNavigator.navigateToSitePath(`/grades/${courseId}/${gradeId}`, {
siteId,
params: { userId },
}),
);
} catch (error) {
// Cannot get grade items or there's no need to. // Cannot get grade items or there's no need to.
if (userId && userId != currentUserId) { if (userId && userId != currentUserId) {
// View another user grades. Open the grades page directly. // View another user grades. Open the grades page directly.
@ -517,24 +517,12 @@ export class CoreGradesHelperProvider {
return; return;
} }
// @todo
// Open the course with the grades tab selected. // Open the course with the grades tab selected.
// await CoreCourseHelper.getCourse(courseId, siteId).then(async (result) => { await CoreCourseHelper.getAndOpenCourse(courseId, { selectedTab: 'CoreGrades' }, siteId);
// const pageParams = { } catch (error) {
// course: result.course, // Cannot get course for some reason, just open the grades page.
// selectedTab: 'CoreGrades', await CoreNavigator.navigateToSitePath(`/grades/${courseId}`, { siteId });
// };
// // CoreContentLinksHelper.goInSite(navCtrl, 'CoreCourseSectionPage', pageParams, siteId)
// return await CoreUtils.ignoreErrors(CoreNavigator.navigateToSitePath('/course', {
// siteId,
// params: pageParams,
// }));
// });
} }
} catch (error) {
// Cannot get course for some reason, just open the grades page.
await CoreNavigator.navigateToSitePath(`/grades/${courseId}`, { siteId });
} finally { } finally {
modal.dismiss(); modal.dismiss();
} }

View File

@ -64,6 +64,7 @@ export type CoreNavigatorCurrentRouteOptions = Partial<{
params: Params; // Params to get the value from. params: Params; // Params to get the value from.
route: ActivatedRoute; // Current Route. route: ActivatedRoute; // Current Route.
pageComponent: unknown; pageComponent: unknown;
routeData: Record<string, unknown>;
}>; }>;
/** /**
@ -401,18 +402,22 @@ export class CoreNavigatorService {
*/ */
getCurrentRoute(): ActivatedRoute; getCurrentRoute(): ActivatedRoute;
getCurrentRoute(options: CoreNavigatorCurrentRouteOptions): ActivatedRoute | null; getCurrentRoute(options: CoreNavigatorCurrentRouteOptions): ActivatedRoute | null;
getCurrentRoute({ route, pageComponent }: CoreNavigatorCurrentRouteOptions = {}): ActivatedRoute | null { getCurrentRoute({ route, pageComponent, routeData }: CoreNavigatorCurrentRouteOptions = {}): ActivatedRoute | null {
route = route ?? Router.routerState.root; route = route ?? Router.routerState.root;
if (pageComponent && route.component === pageComponent) { if (pageComponent && route.component === pageComponent) {
return route; return route;
} }
if (route.firstChild) { if (routeData && CoreUtils.basicLeftCompare(routeData, route.snapshot.data, 3)) {
return this.getCurrentRoute({ route: route.firstChild, pageComponent }); return route;
} }
return pageComponent ? null : route; if (route.firstChild) {
return this.getCurrentRoute({ route: route.firstChild, pageComponent, routeData });
}
return pageComponent || routeData ? null : route;
} }
/** /**

View File

@ -176,7 +176,7 @@ export class CoreUtilsProvider {
maxLevels: number = 0, maxLevels: number = 0,
level: number = 0, level: number = 0,
undefinedIsNull: boolean = true, undefinedIsNull: boolean = true,
): boolean | undefined { ): boolean {
if (typeof itemA == 'function' || typeof itemB == 'function') { if (typeof itemA == 'function' || typeof itemB == 'function') {
return true; // Don't compare functions. return true; // Don't compare functions.
} else if (typeof itemA == 'object' && typeof itemB == 'object') { } else if (typeof itemA == 'object' && typeof itemB == 'object') {
@ -189,7 +189,7 @@ export class CoreUtilsProvider {
const value = itemA[name]; const value = itemA[name];
if (name == '$$hashKey') { if (name == '$$hashKey') {
// Ignore $$hashKey property since it's a "calculated" property. // Ignore $$hashKey property since it's a "calculated" property.
return; continue;
} }
if (!this.basicLeftCompare(value, itemB[name], maxLevels, level + 1)) { if (!this.basicLeftCompare(value, itemB[name], maxLevels, level + 1)) {