MOBILE-2253 login: Implement site page

main
Dani Palou 2017-11-24 12:31:09 +01:00
parent 80961c23d9
commit b3d77dfa5c
17 changed files with 497 additions and 46 deletions

View File

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

View File

@ -16,6 +16,13 @@
// automatically applied to the <body> 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;
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

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

View File

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

View File

@ -0,0 +1,27 @@
<ion-header>
<ion-navbar>
<ion-title>{{ 'mm.core.error' | translate }}</ion-title>
<ion-buttons end>
<button ion-button icon-only (click)="closeModal()" [attr.aria-label]="'mm.core.close' | translate">
<ion-icon name="close"></ion-icon>
</button>
</ion-buttons>
</ion-navbar>
</ion-header>
<ion-content padding>
<h3>{{ 'mm.core.whoops' | translate }}</h3>
<p>{{ 'mm.login.problemconnectingerror' | translate }}</p>
<p padding>{{siteUrl}}</p>
<p>{{ 'mm.login.problemconnectingerrorcontinue' | translate }}</p>
<button ion-button block (click)="closeModal()">{{ 'mm.core.tryagain' | translate }}</button>
<h3>{{ 'mm.login.stillcantconnect' | translate }}</h3>
<p>{{ 'mm.login.contactyouradministrator' | translate }}</p>
<p *ngIf="issue">
{{ 'mm.login.contactyouradministratorissue' | translate:{$a: ''} }}
</p>
<p *ngIf="issue">
<core-format-text>{{issue}}</core-format-text>
</p>
</ion-content>

View File

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

View File

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

View File

@ -0,0 +1,14 @@
<ion-header>
<ion-navbar>
<ion-title>{{ 'mm.login.help' | translate }}</ion-title>
<ion-buttons end>
<button ion-button icon-only (click)="closeHelp()" [attr.aria-label]="'mm.core.close' | translate">
<ion-icon name="close"></ion-icon>
</button>
</ion-buttons>
</ion-navbar>
</ion-header>
<ion-content padding>
<core-format-text [text]="'mm.login.helpmelogin' | translate"></core-format-text>
</ion-content>

View File

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

View File

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

View File

@ -0,0 +1,50 @@
<ion-header>
<ion-navbar>
<ion-title>{{ 'mm.login.connecttomoodle' | translate }}</ion-title>
<ion-buttons end>
<button ion-button icon-only (click)="showHelp()" [attr.aria-label]="'mm.core.help' | translate">
<ion-icon name="help-circle"></ion-icon>
</button>
</ion-buttons>
</ion-navbar>
</ion-header>
<ion-content id="mm-login-site" class="mm-center-view">
<form class="mm-view-content" [formGroup]="siteForm" (ngSubmit)="connect(siteForm.value.siteUrl)">
<ion-list class="box">
<div class="text-center" padding>
<img src="assets/img/logo.png" class="avatar-full moodle-logo" role="presentation">
</div>
<!-- Form to input the site URL if there are no fixed sites. -->
<div *ngIf="!fixedSites">
<p padding>{{ 'mm.login.newsitedescription' | translate }}</p>
<ion-item>
<ion-input type="url" name="url" placeholder="{{ 'mm.login.siteaddress' | translate }}" formControlName="siteUrl" core-auto-focus></ion-input>
</ion-item>
</div>
<!-- Pick the site from a list of fixed sites. -->
<div *ngIf="fixedSites" text-wrap>
<!-- Display them using a select. -->
<ion-item *ngIf="!displayAsButtons">
<!-- @todo: Display label and select in different lines. -->
<ion-label for="siteSelect">{{ 'mm.login.selectsite' | translate }}</ion-label>
<ion-select formControlName="siteUrl" name="url" placeholder="{{ 'mm.login.siteaddress' | translate }}">
<ion-option *ngFor="let site of fixedSites" [value]="site.url">{{site.name}}</ion-option>
</ion-select>
</ion-item>
<!-- Display them using buttons. -->
<div *ngIf="displayAsButtons">
<p class="padding no-padding-bottom">{{ 'mm.login.selectsite' | translate }}</p>
<a *ngFor="let site of fixedSites" ion-button block (click)="connect(site.url)" title="{{site.name}}">{{site.name}}</a>
</div>
</div>
<div *ngIf="!fixedSites || !displayAsButtons">
<button ion-button block color="primary" [disabled]="!siteForm.valid">{{ 'mm.login.connect' | translate }}</button>
</div>
</ion-list>
</form>
</ion-content>

View File

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

View File

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

View File

@ -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 = <any[]> 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();
}
}

View File

@ -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.') {

View File

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