diff --git a/src/addon/calendar/pages/list/list.html b/src/addon/calendar/pages/list/list.html index 8ecc9754b..7cdae7575 100644 --- a/src/addon/calendar/pages/list/list.html +++ b/src/addon/calendar/pages/list/list.html @@ -11,27 +11,27 @@ - - - - - - - - + + + + + + + + - - - - - -

-

{{ event.timestart | coreToLocaleString }}

-
-
+ + + + +

+

{{ event.timestart | coreToLocaleString }}

+
+
- - - -
-
+ + + +
+
+ \ No newline at end of file diff --git a/src/addon/calendar/pages/list/list.ts b/src/addon/calendar/pages/list/list.ts index 355cb5b89..82c8b748f 100644 --- a/src/addon/calendar/pages/list/list.ts +++ b/src/addon/calendar/pages/list/list.ts @@ -25,6 +25,7 @@ import { CoreLocalNotificationsProvider } from '../../../../providers/local-noti import { CoreCoursePickerMenuPopoverComponent } from '../../../../components/course-picker-menu/course-picker-menu-popover'; import { CoreEventsProvider } from '../../../../providers/events'; import { CoreAppProvider } from '../../../../providers/app'; +import { CoreSplitViewComponent } from '../../../../components/split-view/split-view'; /** * Page that displays the list of calendar events. @@ -36,6 +37,7 @@ import { CoreAppProvider } from '../../../../providers/app'; }) export class AddonCalendarListPage implements OnDestroy { @ViewChild(Content) content: Content; + @ViewChild(CoreSplitViewComponent) splitviewCtrl: CoreSplitViewComponent; protected daysLoaded = 0; protected emptyEventsTimes = 0; // Variable to identify consecutive calls returning 0 events. @@ -56,7 +58,6 @@ export class AddonCalendarListPage implements OnDestroy { events = []; notificationsEnabled = false; filteredEvents = []; - eventToLoad = 1; canLoadMore = false; filter = { course: this.allCourses @@ -84,26 +85,15 @@ export class AddonCalendarListPage implements OnDestroy { * View loaded. */ ionViewDidLoad() { - if (this.eventId && !this.appProvider.isWide()) { - // There is an event to load and it's a phone device, open the event in a new state. + if (this.eventId) { + // There is an event to load, open the event in a new state. this.gotoEvent(this.eventId); } this.fetchData().then(() => { - // @TODO: Split view once single event is done. - if (this.eventId && this.appProvider.isWide()) { - // There is an event to load and it's a tablet device. Search the position of the event in the list and load it. - let found = this.events.findIndex((e) => {return e.id == this.eventId}); - - if (found > 0) { - this.eventToLoad = found + 1; - } else { - // Event not found in the list, open it in a new state. Use a $timeout to open the state after the - // split view is loaded. - //$timeout(function() { - this.gotoEvent(this.eventId); - //}); - } + if (!this.eventId && this.splitviewCtrl.isOn() && this.events.length > 0) { + // Take first and load it. + this.gotoEvent(this.events[0].id); } }).finally(() => { this.eventsLoaded = true; @@ -324,7 +314,8 @@ export class AddonCalendarListPage implements OnDestroy { * Navigate to a particular event. */ gotoEvent(eventId) { - this.navCtrl.push('AddonCalendarEventPage', {id: eventId}); + this.eventId = eventId; + this.splitviewCtrl.push('AddonCalendarEventPage', {id: eventId}); } /** diff --git a/src/components/components.module.ts b/src/components/components.module.ts index 6a37a8dae..0ce4dbc4d 100644 --- a/src/components/components.module.ts +++ b/src/components/components.module.ts @@ -21,6 +21,7 @@ import { CoreLoadingComponent } from './loading/loading'; import { CoreMarkRequiredComponent } from './mark-required/mark-required'; import { CoreInputErrorsComponent } from './input-errors/input-errors'; import { CoreShowPasswordComponent } from './show-password/show-password'; +import { CoreSplitViewComponent } from './split-view/split-view'; import { CoreIframeComponent } from './iframe/iframe'; import { CoreProgressBarComponent } from './progress-bar/progress-bar'; import { CoreEmptyBoxComponent } from './empty-box/empty-box'; @@ -40,6 +41,7 @@ import { CoreSitePickerComponent } from './site-picker/site-picker'; CoreMarkRequiredComponent, CoreInputErrorsComponent, CoreShowPasswordComponent, + CoreSplitViewComponent, CoreIframeComponent, CoreProgressBarComponent, CoreEmptyBoxComponent, @@ -68,6 +70,7 @@ import { CoreSitePickerComponent } from './site-picker/site-picker'; CoreMarkRequiredComponent, CoreInputErrorsComponent, CoreShowPasswordComponent, + CoreSplitViewComponent, CoreIframeComponent, CoreProgressBarComponent, CoreEmptyBoxComponent, diff --git a/src/components/split-view/placeholder/placeholder.html b/src/components/split-view/placeholder/placeholder.html new file mode 100644 index 000000000..52e7e8bcc --- /dev/null +++ b/src/components/split-view/placeholder/placeholder.html @@ -0,0 +1,9 @@ + + +   + + + + + + diff --git a/src/components/split-view/placeholder/placeholder.module.ts b/src/components/split-view/placeholder/placeholder.module.ts new file mode 100644 index 000000000..d3133905f --- /dev/null +++ b/src/components/split-view/placeholder/placeholder.module.ts @@ -0,0 +1,20 @@ +import { NgModule } from '@angular/core'; +import { IonicPageModule } from 'ionic-angular'; +import { CoreSplitViewPlaceholderPage } from './placeholder'; +import { TranslateModule } from '@ngx-translate/core'; +import { CoreComponentsModule } from '../../components.module'; + +@NgModule({ + declarations: [ + CoreSplitViewPlaceholderPage, + ], + imports: [ + CoreComponentsModule, + IonicPageModule.forChild(CoreSplitViewPlaceholderPage), + TranslateModule.forChild() + ], + exports: [ + CoreSplitViewPlaceholderPage + ] +}) +export class CorePlaceholderPageModule { } diff --git a/src/components/split-view/placeholder/placeholder.scss b/src/components/split-view/placeholder/placeholder.scss new file mode 100644 index 000000000..b854d8617 --- /dev/null +++ b/src/components/split-view/placeholder/placeholder.scss @@ -0,0 +1,3 @@ +core-placeholder { + +} diff --git a/src/components/split-view/placeholder/placeholder.ts b/src/components/split-view/placeholder/placeholder.ts new file mode 100644 index 000000000..da0c7cf36 --- /dev/null +++ b/src/components/split-view/placeholder/placeholder.ts @@ -0,0 +1,16 @@ +import { Component } from '@angular/core'; +import { + IonicPage, + NavController, + NavParams } from 'ionic-angular'; + +@IonicPage() +@Component({ + selector: 'core-placeholder', + templateUrl: 'placeholder.html', +}) +export class CoreSplitViewPlaceholderPage { + + constructor(public navCtrl: NavController, public navParams: NavParams) { } + +} diff --git a/src/components/split-view/split-view.html b/src/components/split-view/split-view.html new file mode 100644 index 000000000..dbea8c69b --- /dev/null +++ b/src/components/split-view/split-view.html @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/components/split-view/split-view.scss b/src/components/split-view/split-view.scss new file mode 100644 index 000000000..4383232eb --- /dev/null +++ b/src/components/split-view/split-view.scss @@ -0,0 +1,39 @@ +core-split-view { + ion-menu.split-pane-side { + display: block; + + .menu-inner { + left: 0; + right: 0; + top: 0; + bottom: 0; + -webkit-transform: initial; + transform: initial; + width: 100%; + } + } + + .split-pane-main { + display: none; + } + + .split-pane-visible { + .split-pane-main { + display: block; + } + + .split-pane-side .core-split-item-selected { + background-color: $gray-lighter; + border-left: 5px solid $core-color-light; + &.item-md { + padding-left: $item-md-padding-start - 5px; + } + &.item-ios { + padding-left: $item-ios-padding-start - 5px; + } + &.item-wp { + padding-left: $item-wp-padding-start - 5px; + } + } + } +} \ No newline at end of file diff --git a/src/components/split-view/split-view.ts b/src/components/split-view/split-view.ts new file mode 100644 index 000000000..50295fbb1 --- /dev/null +++ b/src/components/split-view/split-view.ts @@ -0,0 +1,147 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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. + +// Code based on https://github.com/martinpritchardelevate/ionic-split-pane-demo + +import { Component, ViewChild, Injectable, Input, ElementRef, OnInit } from '@angular/core'; +import { NavController, Nav } from 'ionic-angular'; +import { CoreSplitViewPlaceholderPage } from './placeholder/placeholder'; + +/** + * Directive to create a split view layout. + * + * @description + * To init/change the right pane contents (content pane), inject this component in the master page. + * @ViewChild(CoreSplitViewComponent) splitviewCtrl: CoreSplitViewComponent; + * Then use the push function to load. + * + * Accepts the following params: + * + * @param {string|boolean} [when] When the split-pane should be shown. Can be a CSS media query expression, or a shortcut + * expression. Can also be a boolean expression. Check split-pane component documentation for more information. + * + * Example: + * + * + * + * + */ +@Component({ + selector: 'core-split-view', + templateUrl: 'split-view.html' +}) +export class CoreSplitViewComponent implements OnInit { + // @todo Mix both panels header buttons + + @ViewChild('detailNav') _detailNav: Nav; + @Input() when?: string | boolean = "md"; // + protected _isOn: boolean = false; + protected masterPageName: string = ""; + protected loadDetailPage: any = false; + protected element: HTMLElement; // Current element. + + // Empty placeholder for the 'detail' page. + detailPage: any = null; + + constructor(private _masterNav: NavController, element: ElementRef) { + this.element = element.nativeElement; + } + + /** + * Component being initialized. + */ + ngOnInit() { + // Get the master page name and set an empty page as a placeholder. + this.masterPageName = this._masterNav.getActive().component.name; + this.emptyDetails(); + } + + /** + * Check if both panels are shown. It depends on screen width. + * + * @return {boolean} If split view is enabled. + */ + isOn(): boolean { + return this._isOn; + } + + /** + * Push a page to the navigation stack. It will decide where to load it depending on the size of the screen. + * + * @param {any} page The component class or deeplink name you want to push onto the navigation stack. + * @param {any} params Any NavParams you want to pass along to the next view. + */ + push(page: any, params?: any, element?: HTMLElement) { + if (this._isOn) { + this._detailNav.setRoot(page, params); + } else { + this.loadDetailPage = { + component: page, + data: params + }; + this._masterNav.push(page, params); + } + } + + /** + * Set the details panel to default info. + */ + emptyDetails() { + this.loadDetailPage = false; + this._detailNav.setRoot('CoreSplitViewPlaceholderPage'); + } + + /** + * Splitpanel visibility has changed. + * + * @param {Boolean} isOn If it fits both panels at the same time. + */ + onSplitPaneChanged(isOn) { + this._isOn = isOn; + if (this._masterNav && this._detailNav) { + (isOn) ? this.activateSplitView() : this.deactivateSplitView(); + } + } + + /** + * Enable the split view, show both panels and do some magical navigation. + */ + activateSplitView() { + let currentView = this._masterNav.getActive(), + currentPageName = currentView.component.name; + if (currentPageName != this.masterPageName) { + // CurrentView is a 'Detail' page remove it from the 'master' nav stack. + this._masterNav.pop(); + + // and add it to the 'detail' nav stack. + this._detailNav.setRoot(currentView.component, currentView.data); + } else if (this.loadDetailPage) { + // MasterPage is shown, load the last detail page if found. + this._detailNav.setRoot(this.loadDetailPage.component, this.loadDetailPage.data); + } + this.loadDetailPage = false; + } + + /** + * Disabled the split view, show only one panel and do some magical navigation. + */ + deactivateSplitView() { + let detailView = this._detailNav.getActive(), + currentPageName = detailView.component.name; + if (currentPageName != 'CoreSplitViewPlaceholderPage') { + // Current detail view is a 'Detail' page so, not the placeholder page, push it on 'master' nav stack. + this._masterNav.push(detailView.component, detailView.data); + } + } +} \ No newline at end of file diff --git a/src/providers/app.ts b/src/providers/app.ts index d0cc6e579..7bc1f36be 100644 --- a/src/providers/app.ts +++ b/src/providers/app.ts @@ -200,16 +200,6 @@ export class CoreAppProvider { return limited.indexOf(type) > -1; } - /** - * Check if device is wide enough. It's used i.e. to show split view. - * - * @return {boolean} Whether the device uses a limited connection. - */ - isWide() : boolean { - //@todo Should use media querys like splitpane - return this.platform.is('tablet'); - } - /** * Check if the app is running in a Windows environment. *