diff --git a/src/app/app.module.ts b/src/app/app.module.ts index ae7fe3573..ce8413a28 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -63,7 +63,9 @@ export function createTranslateLoader(http: HttpClient) { imports: [ BrowserModule, HttpClientModule, - IonicModule.forRoot(MyApp), + IonicModule.forRoot(MyApp, { + pageTransition: 'ios-transition' + }), TranslateModule.forRoot({ loader: { provide: TranslateLoader, diff --git a/src/app/app.scss b/src/app/app.scss index fe1b0ae15..e4bb994a4 100644 --- a/src/app/app.scss +++ b/src/app/app.scss @@ -16,6 +16,13 @@ // automatically applied to the element in the app. +// Alignment +// ------------------------- + +.text-left { text-align: left; } +.text-right { text-align: right; } +.text-center { text-align: center; } + /** * App Branding */ @@ -53,3 +60,18 @@ } } + +@media only screen and (min-width: 430px) { + .mm-center-view .scroll-content { + margin: 0 auto; + max-width: 600px; + display: table !important; + width: 100% !important; + height: 100% !important; + .mm-view-content { + display: table-cell; + vertical-align: middle; + } + } +} + diff --git a/src/assets/img/logo.png b/src/assets/img/logo.png new file mode 100644 index 000000000..8a2e142b2 Binary files /dev/null and b/src/assets/img/logo.png differ diff --git a/src/classes/site.ts b/src/classes/site.ts index a20b335a9..45911a296 100644 --- a/src/classes/site.ts +++ b/src/classes/site.ts @@ -1051,27 +1051,30 @@ export class CoreSite { modal.dismiss(); } - let promise; if (alertMessage) { - promise = this.domUtils.showAlert('mm.core.notice', alertMessage, null, 3000); + let alert = this.domUtils.showAlert('mm.core.notice', alertMessage, null, 3000); + alert.onDidDismiss(() => { + if (inApp) { + this.utils.openInApp(url, options); + } else { + this.utils.openInBrowser(url); + } + }); } else { - promise = Promise.resolve(); - } - - return promise.finally(() => { if (inApp) { this.utils.openInApp(url, options); } else { this.utils.openInBrowser(url); } - }); + } }; if (!this.privateToken || !this.wsAvailable('tool_mobile_get_autologin_key') || (this.lastAutoLogin && this.timeUtils.timestamp() - this.lastAutoLogin < 6 * CoreConstants.secondsMinute)) { // No private token, WS not available or last auto-login was less than 6 minutes ago. // Open the final URL without auto-login. - return open(url); + open(url); + return Promise.resolve(); } const userId = this.getUserId(), @@ -1084,15 +1087,16 @@ export class CoreSite { return this.write('tool_mobile_get_autologin_key', params).then((data) => { if (!data.autologinurl || !data.key) { // Not valid data, open the final URL without auto-login. - return open(url); + open(url); + return; } this.lastAutoLogin = this.timeUtils.timestamp(); - return open(data.autologinurl + '?userid=' + userId + '&key=' + data.key + '&urltogo=' + url); + open(data.autologinurl + '?userid=' + userId + '&key=' + data.key + '&urltogo=' + url); }).catch(() => { // Couldn't get autologin key, open the final URL without auto-login. - return open(url); + open(url); }); } diff --git a/src/config.json b/src/config.json index 077a0f1af..163458ae7 100644 --- a/src/config.json +++ b/src/config.json @@ -2,7 +2,7 @@ "app_id" : "com.moodle.moodlemobile", "appname": "Moodle Mobile", "desktopappname": "Moodle Desktop", - "versioncode" : "3000", + "versioncode" : 3000, "versionname" : "3.3.5", "cache_expiration_time" : 300000, "default_lang" : "en", @@ -13,6 +13,7 @@ "gcmpn": "694767596569", "customurlscheme": "moodlemobile", "siteurl": "", + "multisitesdisplay": "", "skipssoconfirmation": false, "forcedefaultlanguage": false, "privacypolicy": "https://moodle.org/mod/page/view.php?id=8148" diff --git a/src/core/login/pages/site-error/site-error.html b/src/core/login/pages/site-error/site-error.html new file mode 100644 index 000000000..960233c44 --- /dev/null +++ b/src/core/login/pages/site-error/site-error.html @@ -0,0 +1,27 @@ + + + {{ 'mm.core.error' | translate }} + + + + + + + +

{{ 'mm.core.whoops' | translate }}

+

{{ 'mm.login.problemconnectingerror' | translate }}

+

{{siteUrl}}

+

{{ 'mm.login.problemconnectingerrorcontinue' | translate }}

+ +

{{ 'mm.login.stillcantconnect' | translate }}

+

{{ 'mm.login.contactyouradministrator' | translate }}

+

+ {{ 'mm.login.contactyouradministratorissue' | translate:{$a: ''} }} +

+

+ {{issue}} +

+
+ diff --git a/src/core/login/pages/site-error/site-error.module.ts b/src/core/login/pages/site-error/site-error.module.ts new file mode 100644 index 000000000..30b2036d5 --- /dev/null +++ b/src/core/login/pages/site-error/site-error.module.ts @@ -0,0 +1,31 @@ +// (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. + +import { NgModule } from '@angular/core'; +import { IonicPageModule } from 'ionic-angular'; +import { CoreLoginSiteErrorPage } from './site-error'; +import { TranslateModule } from '@ngx-translate/core'; +import { CoreDirectivesModule } from '../../../../directives/directives.module'; + +@NgModule({ + declarations: [ + CoreLoginSiteErrorPage + ], + imports: [ + CoreDirectivesModule, + IonicPageModule.forChild(CoreLoginSiteErrorPage), + TranslateModule.forChild() + ] +}) +export class CoreLoginSiteErrorPageModule {} diff --git a/src/core/login/pages/site-error/site-error.ts b/src/core/login/pages/site-error/site-error.ts new file mode 100644 index 000000000..6f248056e --- /dev/null +++ b/src/core/login/pages/site-error/site-error.ts @@ -0,0 +1,41 @@ +// (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. + +import { Component } from '@angular/core'; +import { IonicPage, ViewController, NavParams } from 'ionic-angular'; + +/** + * Component that displays an error when trying to connect to a site. + */ +@IonicPage() +@Component({ + selector: 'page-core-login-site-error', + templateUrl: 'site-error.html', +}) +export class CoreLoginSiteErrorPage { + siteUrl: string; + issue: string; + + constructor(private viewCtrl: ViewController, params: NavParams) { + this.siteUrl = params.get('siteUrl'); + this.issue = params.get('issue'); + } + + /** + * Close modal. + */ + closeModal() : void { + this.viewCtrl.dismiss(); + } +} \ No newline at end of file diff --git a/src/core/login/pages/site-help/site-help.html b/src/core/login/pages/site-help/site-help.html new file mode 100644 index 000000000..639ad7440 --- /dev/null +++ b/src/core/login/pages/site-help/site-help.html @@ -0,0 +1,14 @@ + + + {{ 'mm.login.help' | translate }} + + + + + + + + + diff --git a/src/core/login/pages/site-help/site-help.module.ts b/src/core/login/pages/site-help/site-help.module.ts new file mode 100644 index 000000000..942199b3d --- /dev/null +++ b/src/core/login/pages/site-help/site-help.module.ts @@ -0,0 +1,31 @@ +// (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. + +import { NgModule } from '@angular/core'; +import { IonicPageModule } from 'ionic-angular'; +import { CoreLoginSiteHelpPage } from './site-help'; +import { TranslateModule } from '@ngx-translate/core'; +import { CoreDirectivesModule } from '../../../../directives/directives.module'; + +@NgModule({ + declarations: [ + CoreLoginSiteHelpPage + ], + imports: [ + CoreDirectivesModule, + IonicPageModule.forChild(CoreLoginSiteHelpPage), + TranslateModule.forChild(), + ] +}) +export class CoreLoginSiteHelpPageModule {} diff --git a/src/core/login/pages/site-help/site-help.ts b/src/core/login/pages/site-help/site-help.ts new file mode 100644 index 000000000..a003b28e7 --- /dev/null +++ b/src/core/login/pages/site-help/site-help.ts @@ -0,0 +1,35 @@ +// (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. + +import { Component } from '@angular/core'; +import { IonicPage, ViewController } from 'ionic-angular'; + +/** + * Component that displays some help regarding the CoreLoginSitePage. + */ +@IonicPage() +@Component({ + selector: 'page-core-login-site-help', + templateUrl: 'site-help.html', +}) +export class CoreLoginSiteHelpPage { + constructor(private viewCtrl: ViewController) {} + + /** + * Close help modal. + */ + closeHelp() : void { + this.viewCtrl.dismiss(); + } +} \ No newline at end of file diff --git a/src/core/login/pages/site/site.html b/src/core/login/pages/site/site.html new file mode 100644 index 000000000..88afa3c4c --- /dev/null +++ b/src/core/login/pages/site/site.html @@ -0,0 +1,50 @@ + + + {{ 'mm.login.connecttomoodle' | translate }} + + + + + + + +
+ +
+ +
+ + +
+

{{ 'mm.login.newsitedescription' | translate }}

+ + + +
+ + +
+ + + + {{ 'mm.login.selectsite' | translate }} + + {{site.name}} + + + + +
+

{{ 'mm.login.selectsite' | translate }}

+ {{site.name}} +
+
+ +
+ +
+
+
+
diff --git a/src/core/login/pages/site/site.module.ts b/src/core/login/pages/site/site.module.ts new file mode 100644 index 000000000..a22ceb243 --- /dev/null +++ b/src/core/login/pages/site/site.module.ts @@ -0,0 +1,33 @@ +// (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. + +import { NgModule } from '@angular/core'; +import { IonicPageModule } from 'ionic-angular'; +import { CoreLoginSitePage } from './site'; +import { CoreLoginModule } from '../../login.module'; +import { TranslateModule } from '@ngx-translate/core'; +import { CoreDirectivesModule } from '../../../../directives/directives.module'; + +@NgModule({ + declarations: [ + CoreLoginSitePage + ], + imports: [ + CoreDirectivesModule, + CoreLoginModule, + IonicPageModule.forChild(CoreLoginSitePage), + TranslateModule.forChild() + ] +}) +export class CoreLoginSitePageModule {} diff --git a/src/core/login/pages/site/site.scss b/src/core/login/pages/site/site.scss new file mode 100644 index 000000000..e2f5f80af --- /dev/null +++ b/src/core/login/pages/site/site.scss @@ -0,0 +1,25 @@ +page-core-login-site { + .content { + background: -webkit-radial-gradient(white, $gray-light); + background: radial-gradient(white, $gray-light); + + img { + max-width: 100%; + } + + img.moodle-logo { + width: 90%; + max-width: 300px; + } + + .box { + padding: 16px; + background: $white; + border: 1px solid $gray; + } + + .item-input { + border: 1px solid $list-border-color; + } + } +} diff --git a/src/core/login/pages/site/site.ts b/src/core/login/pages/site/site.ts new file mode 100644 index 000000000..ec1d9034f --- /dev/null +++ b/src/core/login/pages/site/site.ts @@ -0,0 +1,128 @@ +// (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. + +import { Component } from '@angular/core'; +import { IonicPage, NavController, ModalController } from 'ionic-angular'; +import { CoreAppProvider } from '../../../../providers/app'; +import { CoreSitesProvider } from '../../../../providers/sites'; +import { CoreDomUtilsProvider } from '../../../../providers/utils/dom'; +import { CoreConfigConstants } from '../../../../configconstants'; +import { CoreLoginHelperProvider } from '../../providers/helper'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; + +/** + * Page to enter or select the site URL to connect to. + */ +@IonicPage() +@Component({ + selector: 'page-core-login-site', + templateUrl: 'site.html', +}) +export class CoreLoginSitePage { + siteForm: FormGroup; + fixedSites: any[]; + displayAsButtons = false; + + constructor(private navCtrl: NavController, fb: FormBuilder, private appProvider: CoreAppProvider, + private sitesProvider: CoreSitesProvider, private loginHelper: CoreLoginHelperProvider, + private modalCtrl: ModalController, private domUtils: CoreDomUtilsProvider) { + let url = ''; + + // Load fixed sites if they're set. + if (this.loginHelper.hasSeveralFixedSites()) { + this.fixedSites = this.loginHelper.getFixedSites(); + this.displayAsButtons = CoreConfigConstants.multisitesdisplay == 'buttons'; + url = this.fixedSites[0].url; + } + + this.siteForm = fb.group({ + 'siteUrl': [url, Validators.required] + }); + } + + /** + * Try to connect to a site. + */ + connect(url: string) : void { + this.appProvider.closeKeyboard(); + + if (!url) { + this.domUtils.showErrorModal('mm.login.siteurlrequired', true); + return; + } + + if (!this.appProvider.isOnline()) { + this.domUtils.showErrorModal('mm.core.networkerrormsg', true); + return; + } + + let modal = this.domUtils.showModalLoading(), + siteData = this.sitesProvider.getDemoSiteData(url); + + if (siteData) { + // It's a demo site. + this.sitesProvider.getUserToken(siteData.url, siteData.username, siteData.password).then((data) => { + return this.sitesProvider.newSite(data.siteUrl, data.token, data.privateToken).then(() => { + return this.loginHelper.goToSiteInitialPage(this.navCtrl, true); + }, (error) => { + this.domUtils.showErrorModal(error); + }); + }, (error) => { + this.loginHelper.treatUserTokenError(siteData.url, error); + }).finally(() => { + modal.dismiss(); + }); + + } else { + // Not a demo site. + this.sitesProvider.checkSite(url).then((result) => { + + if (result.warning) { + this.domUtils.showErrorModal(result.warning, true, 4000); + } + + if (this.loginHelper.isSSOLoginNeeded(result.code)) { + // SSO. User needs to authenticate in a browser. + this.loginHelper.confirmAndOpenBrowserForSSOLogin( + result.siteUrl, result.code, result.service, result.config && result.config.launchurl); + } else { + this.navCtrl.push('CoreLoginCredentialsPage', {siteUrl: result.siteUrl, siteConfig: result.config}); + } + }, (error) => { + this.showLoginIssue(url, error); + }).finally(() => { + modal.dismiss(); + }); + } + } + + /** + * Show a help modal. + */ + showHelp() : void { + let modal = this.modalCtrl.create('CoreLoginSiteHelpPage'); + modal.present(); + } + + /** + * Show an error that aims people to solve the issue. + * + * @param {string} url The URL the user was trying to connect to. + * @param {string} error Error to display. + */ + protected showLoginIssue(url: string, error: string) : void { + let modal = this.modalCtrl.create('CoreLoginSiteErrorPage', {siteUrl: url, issue: error}); + modal.present(); + } +} diff --git a/src/providers/sites.ts b/src/providers/sites.ts index 99f138e90..bdbb7007b 100644 --- a/src/providers/sites.ts +++ b/src/providers/sites.ts @@ -301,7 +301,9 @@ export class CoreSitesProvider { } const observable = this.http.post(siteUrl + '/login/token.php', data).timeout(CoreConstants.wsTimeout); - return this.utils.observableToPromise(observable).then((data: any) => { + return this.utils.observableToPromise(observable).catch((error) => { + return Promise.reject(error.message); + }).then((data: any) => { if (data.errorcode && (data.errorcode == 'enablewsdescription' || data.errorcode == 'requirecorrectaccess')) { return Promise.reject({errorcode: data.errorcode, error: data.error}); } else if (data.error && data.error == 'Web services must be enabled in Advanced features.') { diff --git a/src/theme/variables.scss b/src/theme/variables.scss index 21fdcf593..23fdf718a 100644 --- a/src/theme/variables.scss +++ b/src/theme/variables.scss @@ -15,6 +15,39 @@ $app-direction: ltr; @import "ionic.globals"; +// Color palette +$black: #3a3a3a; // Headings, standard text. +$gray-darker: #626262; // Text (emphasis-detail), placeholder, background. +$gray-dark: #9e9e9e; // Borders (never text). +$gray: #dddddd; +$gray-light: #eeeeee; // Background. +$gray-lighter: #f5f5f5; +$white: #ffffff; // Background, reversed text. + +$blue: #0064d2; // Link, background. +$blue-light: mix($blue, white, 20%); // Background. +$blue-dark: darken($blue, 10%); + +$turquoise: #007982; // Accent. +$turquoise-light: mix($turquoise, white, 20%); // Background. +$turquoise-dark: darken($turquoise, 10%); + +$green: #5e8100; // Accent. +$green-light: mix($green, white, 20%); +$green-dark: darken($green, 10%); + +$red: #cb3d4d; +$red-light: mix($red, white, 20%); +$red-dark: darken($red, 10%); + +$orange: #f98012; // Accent (never text). +$orange-light: lighten($orange, 10%); + +$yellow: #fbad1a; // Accent (never text). +$yellow-light: mix($yellow, white, 20%); +$yellow-dark: mix($yellow, black, 40%); + + // Shared Variables // -------------------------------------------------- // To customize the look and feel of this app, you can override @@ -22,6 +55,8 @@ $app-direction: ltr; // To view all the possible Ionic variables, see: // http://ionicframework.com/docs/theming/overriding-ionic-variables/ +$toolbar-background: $orange; +$content-padding: 10px; @@ -91,41 +126,11 @@ $colors: ( // Moodle Mobile variables // -------------------------------------------------- -// Color palette -$black: #3a3a3a; // Headings, standard text. -$gray-darker: #626262; // Text (emphasis-detail), placeholder, background. -$gray-dark: #9e9e9e; // Borders (never text). -$gray: #dddddd; -$gray-light: #eeeeee; // Background. -$gray-lighter: #f5f5f5; -$white: #ffffff; // Background, reversed text. - -$blue: #0064d2; // Link, background. -$blue-light: mix($blue, white, 20%); // Background. -$blue-dark: darken($blue, 10%); - -$turquoise: #007982; // Accent. -$turquoise-light: mix($turquoise, white, 20%); // Background. -$turquoise-dark: darken($turquoise, 10%); - -$green: #5e8100; // Accent. -$green-light: mix($green, white, 20%); -$green-dark: darken($green, 10%); - -$red: #cb3d4d; -$red-light: mix($red, white, 20%); -$red-dark: darken($red, 10%); - -$orange: #f98012; // Accent (never text). -$orange-light: lighten($orange, 10%); - -$yellow: #fbad1a; // Accent (never text). -$yellow-light: mix($yellow, white, 20%); -$yellow-dark: mix($yellow, black, 40%); - // Init screen. $mm-color-init-screen: #ff5c00; $mm-color-init-screen-alt: #ff7600; $mm-init-screen-spinner-color: $white; $mm-init-screen-logo-width: 60%; $mm-init-screen-logo-max-width: 300px; + +$mm-fixed-url: false;