refactor(): add pages to conf app

This commit is contained in:
Dan Bucholtz
2018-01-09 00:06:54 -06:00
parent 4a59c3f58d
commit 4c37cc5416
19 changed files with 749 additions and 9 deletions

View File

@@ -292,9 +292,9 @@
}
},
"@ionic/core": {
"version": "0.0.2-36",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-0.0.2-36.tgz",
"integrity": "sha512-pKzFN8elEPsOaugopar148GM4E88tzd3Q523XEyhOUsUP4NKD+B0bpro3OwDmvLlv6U7z0lrz9jBCs3ZIBeIUA=="
"version": "0.0.2-37",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-0.0.2-37.tgz",
"integrity": "sha512-Tx4yFHqJMzfb8KBtp/dk/1GD+0SUnjlycqk3Egtw43a+4t8XSyJRbcjr1tAeH622TRntj6mLTDRwJ86MPJQMGQ=="
},
"@ionic/storage": {
"version": "2.1.3",

View File

@@ -1,3 +1,29 @@
<ion-app>
<ion-nav [root]="rootPage" #content swipeBackEnabled="false" main name="app"></ion-nav>
<ion-split-pane>
<ion-menu>
<ion-header>
<ion-toolbar color="secondary">
<ion-title>Menu</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-list>
<ion-list-header>
Navigate
</ion-list-header>
<ion-item (click)="openPage(p)" *ngFor="let p of appPages">
<ion-icon slot="icon-only" [name]="p.icon" [color]="isActive(p)"></ion-icon>
<ion-label>
{{p.title}}
</ion-label>
</ion-item>
</ion-list>
</ion-content>
</ion-menu>
<ion-nav #nav [root]="rootPage" #content swipeBackEnabled="false" main name="app"></ion-nav>
</ion-split-pane>
</ion-app>

View File

@@ -1,12 +1,179 @@
import { Component } from '@angular/core';
import {
Component,
ElementRef,
OnInit,
ViewChild
} from '@angular/core';
import {
Events,
MenuController,
} from '@ionic/angular';
import { Storage } from '@ionic/storage';
import { AboutPage } from '../pages/about/about';
import { AccountPage } from '../pages/account/account';
import { LoginPage } from '../pages/login/login';
import { MapPage } from '../pages/map/map';
import { SignupPage } from '../pages/signup/signup';
import { TabsPage } from '../pages/tabs-page/tabs-page';
import { TutorialPage } from '../pages/tutorial/tutorial';
import { SchedulePage } from '../pages/schedule/schedule';
import { SpeakerListPage } from '../pages/speaker-list/speaker-list';
import { SupportPage } from '../pages/support/support';
import { ConferenceData } from '../providers/conference-data';
import { UserData } from '../providers/user-data';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
rootPage = TabsPage;
export class AppComponent implements OnInit {
@ViewChild('nav') navRef: ElementRef;
rootPage: any;
// List of pages that can be navigated to from the left menu
// the left menu only works after login
// the login page disables the left menu
appPages: PageInterface[] = [
{ title: 'Schedule', name: 'TabsPage', component: TabsPage, tabComponent: SchedulePage, index: 0, icon: 'calendar' },
{ title: 'Speakers', name: 'TabsPage', component: TabsPage, tabComponent: SpeakerListPage, index: 1, icon: 'contacts' },
{ title: 'Map', name: 'TabsPage', component: TabsPage, tabComponent: MapPage, index: 2, icon: 'map' },
{ title: 'About', name: 'TabsPage', component: TabsPage, tabComponent: AboutPage, index: 3, icon: 'information-circle' }
];
loggedInPages: PageInterface[] = [
{ title: 'Account', name: 'AccountPage', component: AccountPage, icon: 'person' },
{ title: 'Support', name: 'SupportPage', component: SupportPage, icon: 'help' },
{ title: 'Logout', name: 'TabsPage', component: TabsPage, icon: 'log-out', logsOut: true }
];
loggedOutPages: PageInterface[] = [
{ title: 'Login', name: 'LoginPage', component: LoginPage, icon: 'log-in' },
{ title: 'Support', name: 'SupportPage', component: SupportPage, icon: 'help' },
{ title: 'Signup', name: 'SignupPage', component: SignupPage, icon: 'person-add' }
];
constructor(
public confData: ConferenceData,
public events: Events,
public menu: MenuController,
public storage: Storage,
public userData: UserData) {
}
ngOnInit() {
// Check if the user has already seen the tutorial
this.storage.get('hasSeenTutorial')
.then((hasSeenTutorial) => {
if (hasSeenTutorial) {
this.rootPage = TabsPage;
} else {
this.rootPage = TutorialPage;
}
// this.platformReady()
});
// load the conference data
this.confData.load();
// decide which menu items should be hidden by current login status stored in local storage
this.userData.hasLoggedIn().then((hasLoggedIn) => {
this.enableMenu(hasLoggedIn === true);
});
this.enableMenu(true);
this.listenToLoginEvents();
}
openPage(page: PageInterface) {
let params = {};
// the nav component was found using @ViewChild(Nav)
// setRoot on the nav to remove previous pages and only have this page
// we wouldn't want the back button to show in this scenario
if (page.index) {
params = { tabIndex: page.index };
}
// If we are already on tabs just change the selected tab
// don't setRoot again, this maintains the history stack of the
// tabs even if changing them from the menu
/*if (getNav(this.navRef).getChildNavs().length && page.index !== undefined) {
getNav(this.navRef).getChildNavs()[0].select(page.index);
} else {
// Set the root of the nav with params if it's a tab index
getNav(this.navRef).setRoot(page.name, params).catch((err: any) => {
console.log(`Didn't set nav root: ${err}`);
});
}
*/
console.log('TODO DANNNNNNN');
if (page.logsOut === true) {
// Give the menu time to close before changing to logged out
this.userData.logout();
}
}
openTutorial() {
return getNav(this.navRef).setRoot(TutorialPage);
}
listenToLoginEvents() {
this.events.subscribe('user:login', () => {
this.enableMenu(true);
});
this.events.subscribe('user:signup', () => {
this.enableMenu(true);
});
this.events.subscribe('user:logout', () => {
this.enableMenu(false);
});
}
enableMenu(loggedIn: boolean) {
this.menu.enable(loggedIn, 'loggedInMenu');
this.menu.enable(!loggedIn, 'loggedOutMenu');
}
isActive(page: PageInterface) {
return 'primary';
//const childNav = getNav(this.navRef).getChildNavs()[0];
// Tabs are a special case because they have their own navigation
/*if (childNav) {
if (childNav.getSelected() && childNav.getSelected().root === page.tabComponent) {
return 'primary';
}
return;
}
if (this.nav.getActive() && this.nav.getActive().name === page.name) {
return 'primary';
}
return;
*/
}
}
function getNav(elementRef: ElementRef) {
return elementRef.nativeElement as HTMLIonNavElement;
}
export interface PageInterface {
title: string;
name: string;
component: any;
icon: string;
logsOut?: boolean;
index?: number;
tabName?: string;
tabComponent?: any;
}

View File

@@ -1,5 +1,6 @@
import { BrowserModule, } from '@angular/platform-browser';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { IonicAngularModule } from '@ionic/angular';
@@ -11,13 +12,18 @@ import { AppComponent } from './app.component';
import { AboutPage } from '../pages/about/about';
import { PopoverPage } from '../pages/about-popover/about-popover';
import { AccountPage } from '../pages/account/account';
import { LoginPage } from '../pages/login/login';
import { MapPage } from '../pages/map/map';
import { SchedulePage } from '../pages/schedule/schedule';
import { ScheduleFilterPage } from '../pages/schedule-filter/schedule-filter';
import { SessionDetailPage } from '../pages/session-detail/session-detail';
import { SignupPage } from '../pages/signup/signup';
import { SpeakerDetailPage } from '../pages/speaker-detail/speaker-detail';
import { SpeakerListPage } from '../pages/speaker-list/speaker-list';
import { SupportPage } from '../pages/support/support';
import { TabsPage } from '../pages/tabs-page/tabs-page';
import { TutorialPage } from '../pages/tutorial/tutorial';
import { ConferenceData } from '../providers/conference-data';
import { UserData } from '../providers/user-data';
@@ -25,29 +31,40 @@ import { UserData } from '../providers/user-data';
@NgModule({
declarations: [
AboutPage,
AccountPage,
AppComponent,
LoginPage,
MapPage,
PopoverPage,
SchedulePage,
ScheduleFilterPage,
SessionDetailPage,
SignupPage,
SpeakerDetailPage,
SpeakerListPage,
TabsPage
SupportPage,
TabsPage,
TutorialPage
],
entryComponents: [
AboutPage,
AccountPage,
LoginPage,
MapPage,
PopoverPage,
SchedulePage,
ScheduleFilterPage,
SessionDetailPage,
SignupPage,
SpeakerDetailPage,
SpeakerListPage,
TabsPage
SupportPage,
TabsPage,
TutorialPage
],
imports: [
BrowserModule,
FormsModule,
HttpModule,
IonicAngularModule.forRoot(),
IonicStorageModule.forRoot()

View File

@@ -0,0 +1,25 @@
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-button> <!-- TODO menuToggle -->
<ion-icon slot="icon-only" name="menu"></ion-icon>
</ion-button>
</ion-buttons>
<ion-title>Account</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="outer-content">
<div padding-top text-center *ngIf="username">
<img src="http://www.gravatar.com/avatar?d=mm&s=140" alt="avatar">
<h2>{{username}}</h2>
<ion-list inset>
<ion-item (click)="updatePicture()">Update Picture</ion-item>
<ion-item (click)="changeUsername()">Change Username</ion-item>
<ion-item (click)="changePassword()">Change Password</ion-item>
<ion-item (click)="support()">Support</ion-item>
<ion-item (click)="logout()">Logout</ion-item>
</ion-list>
</div>
</ion-content>

View File

@@ -0,0 +1,5 @@
page-account img {
max-width: 140px;
border-radius: 50%;
}

View File

@@ -0,0 +1,73 @@
import { Component } from '@angular/core';
import { AlertController, NavController } from '@ionic/angular';
import { UserData } from '../../providers/user-data';
@Component({
selector: 'page-account',
templateUrl: 'account.html',
styleUrls: ['./account.scss']
})
export class AccountPage {
username: string;
constructor(public alertCtrl: AlertController, public nav: NavController, public userData: UserData) {
}
ngAfterViewInit() {
this.getUsername();
}
updatePicture() {
console.log('Clicked to update picture');
}
// Present an alert with the current username populated
// clicking OK will update the username and display it
// clicking Cancel will close the alert and do nothing
changeUsername() {
const alert = this.alertCtrl.create({
title: 'Change Username',
buttons: [
'Cancel',
{
text: 'Ok',
handler: (data: any) => {
this.userData.setUsername(data.username);
this.getUsername();
}
}
],
inputs: [
{
name: 'username',
value: this.username,
placeholder: 'username'
}
]
});
alert.present();
}
getUsername() {
this.userData.getUsername().then((username) => {
this.username = username;
});
}
changePassword() {
console.log('Clicked to change password');
}
logout() {
this.userData.logout();
this.nav.setRoot('LoginPage');
}
support() {
this.nav.push('SupportPage');
}
}

View File

@@ -0,0 +1,50 @@
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-button> <!-- TODO menu toggle -->
<ion-icon slot="icon-only" name="menu"></ion-icon>
</ion-button>
</ion-buttons>
<ion-title>Login</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<div class="logo">
<img src="assets/img/appicon.svg" alt="Ionic logo">
</div>
<form #loginForm="ngForm" novalidate>
<ion-list no-lines>
<ion-item>
<ion-label stacked color="primary">Username</ion-label>
<ion-input [(ngModel)]="login.username" name="username" type="text" #username="ngModel" spellcheck="false" autocapitalize="off"
required>
</ion-input>
</ion-item>
<p ion-text [hidden]="username.valid || submitted == false" color="danger" padding-left>
Username is required
</p>
<ion-item>
<ion-label stacked color="primary">Password</ion-label>
<ion-input [(ngModel)]="login.password" name="password" type="password" #password="ngModel" required>
</ion-input>
</ion-item>
<p ion-text [hidden]="password.valid || submitted == false" color="danger" padding-left>
Password is required
</p>
</ion-list>
<ion-row responsive-sm>
<ion-col>
<button ion-button (click)="onLogin(loginForm)" type="submit" block>Login</button>
</ion-col>
<ion-col>
<button ion-button (click)="onSignup()" color="light" block>Signup</button>
</ion-col>
</ion-row>
</form>
</ion-content>

View File

@@ -0,0 +1,18 @@
// Styles for all of the user pages:
// Login, Signup, Support
page-user {
.logo {
padding: 20px 0;
min-height: 200px;
text-align: center;
img {
max-width: 150px;
}
}
.list {
margin-bottom: 0;
}
}

View File

@@ -0,0 +1,37 @@
import { Component } from '@angular/core';
import { NgForm } from '@angular/forms';
import { NavController } from '@ionic/angular';
import { UserData } from '../../providers/user-data';
import { UserOptions } from '../../interfaces/user-options';
import { TabsPage } from '../tabs-page/tabs-page';
import { SignupPage } from '../signup/signup';
@Component({
selector: 'page-user',
templateUrl: 'login.html',
styleUrls: ['./login.scss']
})
export class LoginPage {
login: UserOptions = { username: '', password: '' };
submitted = false;
constructor(public navCtrl: NavController, public userData: UserData) { }
onLogin(form: NgForm) {
this.submitted = true;
if (form.valid) {
this.userData.login(this.login.username);
this.navCtrl.push(TabsPage);
}
}
onSignup() {
this.navCtrl.push(SignupPage);
}
}

View File

@@ -0,0 +1,44 @@
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-button> <!-- TODO menuToggle -->
<ion-icon slot="icon-only" name="menu"></ion-icon>
</ion-button>
</ion-buttons>
<ion-title>Signup</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="login-page">
<div class="logo">
<img src="assets/img/appicon.svg" alt="Ionic Logo">
</div>
<form #signupForm="ngForm" novalidate>
<ion-list no-lines>
<ion-item>
<ion-label stacked color="primary">Username</ion-label>
<ion-input [(ngModel)]="signup.username" name="username" type="text" #username="ngModel" required>
</ion-input>
</ion-item>
<p ion-text [hidden]="username.valid || submitted == false" color="danger" padding-left>
Username is required
</p>
<ion-item>
<ion-label stacked color="primary">Password</ion-label>
<ion-input [(ngModel)]="signup.password" name="password" type="password" #password="ngModel" required>
</ion-input>
</ion-item>
<p ion-text [hidden]="password.valid || submitted == false" color="danger" padding-left>
Password is required
</p>
</ion-list>
<div padding>
<ion-button (click)="onSignup(signupForm)" type="submit" block>Create</ion-button>
</div>
</form>
</ion-content>

View File

@@ -0,0 +1,32 @@
import { Component } from '@angular/core';
import { NgForm } from '@angular/forms';
import { NavController } from '@ionic/angular';
import { UserData } from '../../providers/user-data';
import { UserOptions } from '../../interfaces/user-options';
import { TabsPage } from '../tabs-page/tabs-page';
@Component({
selector: 'page-user',
templateUrl: 'signup.html',
styleUrls: ['./signup.scss']
})
export class SignupPage {
signup: UserOptions = { username: '', password: '' };
submitted = false;
constructor(public navCtrl: NavController, public userData: UserData) {}
onSignup(form: NgForm) {
this.submitted = true;
if (form.valid) {
this.userData.signup(this.signup.username);
this.navCtrl.push(TabsPage);
}
}
}

View File

@@ -0,0 +1,35 @@
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-button> <!-- TODO menuToggle -->
<ion-icon slot="start" name="menu"></ion-icon>
</ion-button>
</ion-buttons>
<ion-title>Support</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<div class="logo">
<img src="assets/img/appicon.svg" alt="Ionic Logo">
</div>
<form #submitForm="ngForm" novalidate (ngSubmit)="submit(submitForm)">
<ion-list no-lines>
<ion-item>
<ion-label stacked color="primary">Enter your support message below</ion-label>
<ion-textarea [(ngModel)]="supportMessage" name="supportQuestion" #supportQuestion="ngModel" rows="6" required></ion-textarea>
</ion-item>
</ion-list>
<p ion-text [hidden]="supportQuestion.valid || submitted === false" color="danger" padding-left>
Support message is required
</p>
<div padding>
<ion-button block type="submit">Submit</ion-button>
</div>
</form>
</ion-content>

View File

@@ -0,0 +1,69 @@
import { Component } from '@angular/core';
import { NgForm } from '@angular/forms';
import { AlertController, NavController, ToastController } from '@ionic/angular';
@Component({
selector: 'page-user',
templateUrl: 'support.html',
styleUrls: ['./support.scss']
})
export class SupportPage {
submitted = false;
supportMessage: string;
constructor(
public navCtrl: NavController,
public alertCtrl: AlertController,
public toastCtrl: ToastController
) {
}
ionViewDidEnter() {
const toast = this.toastCtrl.create({
message: 'This does not actually send a support request.',
duration: 3000
});
toast.present();
}
submit(form: NgForm) {
this.submitted = true;
if (form.valid) {
this.supportMessage = '';
this.submitted = false;
const toast = this.toastCtrl.create({
message: 'Your support request has been sent.',
duration: 3000
});
toast.present();
}
}
// If the user enters text in the support question and then navigates
// without submitting first, ask if they meant to leave the page
ionViewCanLeave(): boolean | Promise<boolean> {
// If the support message is empty we should just navigate
if (!this.supportMessage || this.supportMessage.trim().length === 0) {
return true;
}
return new Promise((resolve: any, reject: any) => {
const alert = this.alertCtrl.create({
title: 'Leave this page?',
message: 'Are you sure you want to leave this page? Your support message will not be submitted.',
buttons: [
{ text: 'Stay', handler: reject },
{ text: 'Leave', role: 'cancel', handler: resolve }
]
});
alert.present();
});
}
}

View File

@@ -0,0 +1,44 @@
<ion-header no-border>
<ion-toolbar>
<ion-buttons slot="end" end *ngIf="showSkip">
<ion-button (click)="startApp()" color="primary">Skip</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content no-bounce>
<ion-slides #slides (ionSlideWillChange)="onSlideChangeStart($event)" pager>
<ion-slide>
<img src="assets/img/ica-slidebox-img-1.png" class="slide-image"/>
<h2 class="slide-title">
Welcome to <b>ICA</b>
</h2>
<p>
The <b>ionic conference app</b> is a practical preview of the ionic framework in action, and a demonstration of proper code use.
</p>
</ion-slide>
<ion-slide>
<img src="assets/img/ica-slidebox-img-2.png" class="slide-image"/>
<h2 class="slide-title" >What is Ionic?</h2>
<p><b>Ionic Framework</b> is an open source SDK that enables developers to build high quality mobile apps with web technologies like HTML, CSS, and JavaScript.</p>
</ion-slide>
<ion-slide>
<img src="assets/img/ica-slidebox-img-3.png" class="slide-image"/>
<h2 class="slide-title">What is Ionic Pro?</h2>
<p><b>Ionic Pro</b> is a powerful set of services and features built on top of Ionic Framework that brings a totally new level of app development agility to mobile dev teams.</p>
</ion-slide>
<ion-slide>
<img src="assets/img/ica-slidebox-img-4.png" class="slide-image"/>
<h2 class="slide-title">Ready to Play?</h2>
<ion-button clear="true" (click)="startApp()">
Continue
<ion-icon slot="icon-end" name="arrow-forward"></ion-icon>
</ion-button>
</ion-slide>
</ion-slides>
</ion-content>

View File

@@ -0,0 +1,37 @@
page-tutorial {
.toolbar-background {
background: transparent;
border-color: transparent;
}
.slide-zoom {
height: 100%;
}
.slide-title {
margin-top: 2.8rem;
}
.slide-image {
max-height: 50%;
max-width: 60%;
margin: 36px 0;
}
b {
font-weight: 500;
}
p {
padding: 0 40px;
font-size: 14px;
line-height: 1.5;
color: #60646B;
b {
color: #000000;
}
}
}

View File

@@ -0,0 +1,61 @@
import {
Component,
ElementRef,
ViewChild
} from '@angular/core';
import { MenuController, NavController } from '@ionic/angular';
import { Storage } from '@ionic/storage';
import { TabsPage } from '../tabs-page/tabs-page';
@Component({
selector: 'page-tutorial',
templateUrl: 'tutorial.html'
})
export class TutorialPage {
showSkip = true;
@ViewChild('slides') slidesRef: ElementRef;
constructor(
public navCtrl: NavController,
public menu: MenuController,
public storage: Storage
) { }
startApp() {
this.navCtrl.push(TabsPage).then(() => {
this.storage.set('hasSeenTutorial', 'true');
});
}
onSlideChangeStart(slider: any) {
this.showSkip = !slider.isEnd();
}
ionViewWillEnter() {
return getHydratedSlides(this.slidesRef).then((slides) => {
slides.update();
});
}
ionViewDidEnter() {
// the root left menu should be disabled on the tutorial page
this.menu.enable(false);
}
ionViewDidLeave() {
// enable the root left menu when leaving the tutorial page
this.menu.enable(true);
}
}
function getHydratedSlides(slidesRef: ElementRef): Promise<HTMLIonSlidesElement> {
return slidesRef.nativeElement.componentOnReady().then(() => {
return slidesRef.nativeElement as HTMLIonSlidesElement;
});
}