├── .browserslistrc ├── .editorconfig ├── .eslintrc.json ├── .firebaserc ├── .github ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md ├── ionic-issue-bot.yml └── workflows │ └── CI.yml ├── .gitignore ├── LICENSE ├── README.md ├── angular.json ├── config.xml ├── docs └── tutorial-page.png ├── firebase.json ├── generate-h2-push.js ├── ionic.config.json ├── ngsw-config.json ├── package-lock.json ├── package.json ├── resources ├── android │ ├── icon │ │ ├── drawable-hdpi-icon.png │ │ ├── drawable-ldpi-icon.png │ │ ├── drawable-mdpi-icon.png │ │ ├── drawable-xhdpi-icon.png │ │ ├── drawable-xxhdpi-icon.png │ │ └── drawable-xxxhdpi-icon.png │ ├── splash │ │ ├── drawable-land-hdpi-screen.png │ │ ├── drawable-land-ldpi-screen.png │ │ ├── drawable-land-mdpi-screen.png │ │ ├── drawable-land-xhdpi-screen.png │ │ ├── drawable-land-xxhdpi-screen.png │ │ ├── drawable-land-xxxhdpi-screen.png │ │ ├── drawable-port-hdpi-screen.png │ │ ├── drawable-port-ldpi-screen.png │ │ ├── drawable-port-mdpi-screen.png │ │ ├── drawable-port-xhdpi-screen.png │ │ ├── drawable-port-xxhdpi-screen.png │ │ └── drawable-port-xxxhdpi-screen.png │ └── xml │ │ └── network_security_config.xml ├── icon.png ├── icon.psd ├── icon.psd.md5 ├── ios │ ├── icon │ │ ├── icon-1024.png │ │ ├── icon-108@2x.png │ │ ├── icon-20.png │ │ ├── icon-20@2x.png │ │ ├── icon-20@3x.png │ │ ├── icon-24@2x.png │ │ ├── icon-27.5@2x.png │ │ ├── icon-29.png │ │ ├── icon-29@2x.png │ │ ├── icon-29@3x.png │ │ ├── icon-40.png │ │ ├── icon-40@2x.png │ │ ├── icon-40@3x.png │ │ ├── icon-44@2x.png │ │ ├── icon-50.png │ │ ├── icon-50@2x.png │ │ ├── icon-60.png │ │ ├── icon-60@2x.png │ │ ├── icon-60@3x.png │ │ ├── icon-72.png │ │ ├── icon-72@2x.png │ │ ├── icon-76.png │ │ ├── icon-76@2x.png │ │ ├── icon-83.5@2x.png │ │ ├── icon-86@2x.png │ │ ├── icon-98@2x.png │ │ ├── icon-small.png │ │ ├── icon-small@2x.png │ │ ├── icon-small@3x.png │ │ ├── icon.png │ │ └── icon@2x.png │ └── splash │ │ ├── Default-1792h~iphone.png │ │ ├── Default-2436h.png │ │ ├── Default-2688h~iphone.png │ │ ├── Default-568h@2x~iphone.png │ │ ├── Default-667h.png │ │ ├── Default-736h.png │ │ ├── Default-Landscape-1792h~iphone.png │ │ ├── Default-Landscape-2436h.png │ │ ├── Default-Landscape-2688h~iphone.png │ │ ├── Default-Landscape-736h.png │ │ ├── Default-Landscape@2x~ipad.png │ │ ├── Default-Landscape@~ipadpro.png │ │ ├── Default-Landscape~ipad.png │ │ ├── Default-Portrait@2x~ipad.png │ │ ├── Default-Portrait@~ipadpro.png │ │ ├── Default-Portrait~ipad.png │ │ ├── Default@2x~iphone.png │ │ ├── Default@2x~universal~anyany.png │ │ └── Default~iphone.png ├── screenshots │ ├── android-about.png │ ├── android-menu.png │ ├── android-schedule.png │ ├── android-speaker-detail.png │ ├── android-speakers.png │ ├── ios-about.png │ ├── ios-menu.png │ ├── ios-schedule.png │ ├── ios-speaker-detail.png │ └── ios-speakers.png ├── splash.png └── splash.png.md5 ├── scripts ├── build-android.sh ├── build-ios.sh └── build.sh ├── src ├── app │ ├── app-routing.module.ts │ ├── app.component.html │ ├── app.component.scss │ ├── app.component.spec.ts │ ├── app.component.ts │ ├── app.module.ts │ ├── app.scss │ ├── interfaces │ │ └── user-options.ts │ ├── pages │ │ ├── about-popover │ │ │ └── about-popover.ts │ │ ├── about │ │ │ ├── about-routing.module.ts │ │ │ ├── about.html │ │ │ ├── about.module.ts │ │ │ ├── about.scss │ │ │ └── about.ts │ │ ├── account │ │ │ ├── account-routing.module.ts │ │ │ ├── account.html │ │ │ ├── account.module.ts │ │ │ ├── account.scss │ │ │ └── account.ts │ │ ├── login │ │ │ ├── login-routing.module.ts │ │ │ ├── login.html │ │ │ ├── login.module.ts │ │ │ ├── login.scss │ │ │ └── login.ts │ │ ├── map │ │ │ ├── map-dark-style.js │ │ │ ├── map-routing.module.ts │ │ │ ├── map.html │ │ │ ├── map.module.ts │ │ │ ├── map.scss │ │ │ └── map.ts │ │ ├── schedule-filter │ │ │ ├── schedule-filter.html │ │ │ ├── schedule-filter.scss │ │ │ └── schedule-filter.ts │ │ ├── schedule │ │ │ ├── schedule-routing.module.ts │ │ │ ├── schedule.html │ │ │ ├── schedule.module.ts │ │ │ ├── schedule.scss │ │ │ └── schedule.ts │ │ ├── session-detail │ │ │ ├── session-detail-routing.module.ts │ │ │ ├── session-detail.html │ │ │ ├── session-detail.module.ts │ │ │ ├── session-detail.scss │ │ │ └── session-detail.ts │ │ ├── signup │ │ │ ├── signup-routing.module.ts │ │ │ ├── signup.html │ │ │ ├── signup.module.ts │ │ │ ├── signup.scss │ │ │ └── signup.ts │ │ ├── speaker-detail │ │ │ ├── speaker-detail-routing.module.ts │ │ │ ├── speaker-detail.html │ │ │ ├── speaker-detail.module.ts │ │ │ ├── speaker-detail.scss │ │ │ └── speaker-detail.ts │ │ ├── speaker-list │ │ │ ├── speaker-list-routing.module.ts │ │ │ ├── speaker-list.html │ │ │ ├── speaker-list.module.ts │ │ │ ├── speaker-list.scss │ │ │ ├── speaker-list.spec.ts │ │ │ └── speaker-list.ts │ │ ├── support │ │ │ ├── support-routing.module.ts │ │ │ ├── support.html │ │ │ ├── support.module.ts │ │ │ ├── support.scss │ │ │ └── support.ts │ │ ├── tabs-page │ │ │ ├── tabs-page-routing.module.ts │ │ │ ├── tabs-page.html │ │ │ ├── tabs-page.module.ts │ │ │ ├── tabs-page.scss │ │ │ ├── tabs-page.spec.ts │ │ │ └── tabs-page.ts │ │ └── tutorial │ │ │ ├── tutorial-routing.module.ts │ │ │ ├── tutorial.html │ │ │ ├── tutorial.module.ts │ │ │ ├── tutorial.scss │ │ │ ├── tutorial.spec.ts │ │ │ └── tutorial.ts │ └── providers │ │ ├── check-tutorial.service.ts │ │ ├── conference-data.ts │ │ └── user-data.ts ├── assets │ ├── data │ │ └── data.json │ └── img │ │ ├── about │ │ ├── Archive.zip │ │ ├── austin.jpg │ │ ├── chicago.jpg │ │ ├── madison.jpg │ │ └── seattle.jpg │ │ ├── appicon.png │ │ ├── appicon.svg │ │ ├── ica-slidebox-img-1.png │ │ ├── ica-slidebox-img-2.png │ │ ├── ica-slidebox-img-3.png │ │ ├── ica-slidebox-img-4.png │ │ ├── ionic-logo-white.svg │ │ ├── speaker-background.png │ │ └── speakers │ │ ├── bear.jpg │ │ ├── cheetah.jpg │ │ ├── duck.jpg │ │ ├── eagle.jpg │ │ ├── elephant.jpg │ │ ├── giraffe.jpg │ │ ├── iguana.jpg │ │ ├── kitten.jpg │ │ ├── lion.jpg │ │ ├── mouse.jpg │ │ ├── puppy.jpg │ │ ├── rabbit.jpg │ │ └── turtle.jpg ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── global.scss ├── index.html ├── karma.conf.js ├── main.ts ├── manifest.json ├── polyfills.ts ├── test.ts ├── theme │ └── variables.scss └── zone-flags.ts ├── tests ├── config │ ├── wdio.android.config.ts │ ├── wdio.ios.config.ts │ ├── wdio.shared.appium.config.ts │ ├── wdio.shared.config.ts │ └── wdio.web.config.ts ├── helpers │ ├── browser.ts │ ├── definitions.ts │ ├── element.ts │ ├── gestures.ts │ ├── index.ts │ ├── ionic │ │ ├── components │ │ │ ├── alert.ts │ │ │ ├── button.ts │ │ │ ├── component.ts │ │ │ ├── content.ts │ │ │ ├── index.ts │ │ │ ├── input.ts │ │ │ ├── item.ts │ │ │ ├── menu.ts │ │ │ ├── page.ts │ │ │ ├── segment.ts │ │ │ ├── select.ts │ │ │ ├── slides.ts │ │ │ ├── textarea.ts │ │ │ └── toast.ts │ │ └── index.ts │ ├── platform │ │ ├── android.ts │ │ ├── index.ts │ │ └── ios.ts │ ├── storage.ts │ └── webview.ts ├── pageobjects │ ├── about.page.ts │ ├── account.page.ts │ ├── login.page.ts │ ├── map.page.ts │ ├── page.ts │ ├── schedule-filter.page.ts │ ├── schedule.page.ts │ ├── session-detail.page.ts │ ├── signup.page.ts │ ├── speaker-detail.page.ts │ ├── speaker-list.page.ts │ ├── support.page.ts │ └── tutorial.page.ts ├── specs │ ├── app.about.spec.ts │ ├── app.account.spec.ts │ ├── app.login.spec.ts │ ├── app.map.spec.ts │ ├── app.schedule-filter.spec.ts │ ├── app.schedule.spec.ts │ ├── app.session-detail.spec.ts │ ├── app.signup.spec.ts │ ├── app.speaker-detail.spec.ts │ ├── app.speaker-list.spec.ts │ ├── app.support.spec.ts │ └── app.tutorial.spec.ts └── tsconfig.json ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.spec.json └── tslint.json /.browserslistrc: -------------------------------------------------------------------------------- 1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | 5 | # You can see what browsers were selected by your queries by running: 6 | # npx browserslist 7 | 8 | > 0.5% 9 | last 2 versions 10 | Firefox ESR 11 | not dead 12 | not IE 9-11 # For IE 9-11 support, remove 'not'. -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "ignorePatterns": [ 4 | "projects/**/*" 5 | ], 6 | "overrides": [ 7 | { 8 | "files": [ 9 | "*.ts" 10 | ], 11 | "parserOptions": { 12 | "project": [ 13 | "tsconfig.json", 14 | "e2e/tsconfig.json" 15 | ], 16 | "createDefaultProgram": true 17 | }, 18 | "extends": [ 19 | "plugin:@angular-eslint/recommended", 20 | "plugin:@angular-eslint/template/process-inline-templates" 21 | ], 22 | "rules": { 23 | "@angular-eslint/directive-selector": [ 24 | "error", 25 | { 26 | "type": "attribute", 27 | "prefix": "app", 28 | "style": "camelCase" 29 | } 30 | ], 31 | "@angular-eslint/component-class-suffix": "off" 32 | } 33 | }, 34 | { 35 | "files": [ 36 | "*.html" 37 | ], 38 | "extends": [ 39 | "plugin:@angular-eslint/template/recommended" 40 | ], 41 | "rules": {} 42 | } 43 | ] 44 | } 45 | -------------------------------------------------------------------------------- /.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "ionic-ng-conf-app" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to the Ionic Conference Application 2 | 3 | Thank you for taking the time to contribute! :tada::+1: 4 | 5 | The following is a set of guidelines for contributing to the conference app. These are just guidelines, not rules, use your best judgment and feel free to propose changes to this document in a pull request. 6 | 7 | ## Table of Contents 8 | - [How To Contribute](#how-to-contribute) 9 | - [Reporting Issues](#reporting-issues) 10 | - [Before Submitting an Issue](#before-submitting-an-issue) 11 | - [Determining the Repository](#determining-the-repository) 12 | - [Submitting the Issue](#submitting-the-issue) 13 | - [Submitting a Pull Request](#submitting-a-pull-request) 14 | - [Guidelines for Submitting](#guidelines-for-submitting) 15 | - [Code Style](#code-style) 16 | 17 | ## How To Contribute 18 | 19 | ### Reporting Issues 20 | 21 | Before submitting an issue, please go through [the list below](#before-submitting-an-issue) as you might find a solution to your issue. 22 | 23 | #### Before Submitting an Issue 24 | 25 | * Make sure you get the latest version of the code and run through the [Getting Started](https://github.com/ionic-team/ionic-conference-app#getting-started) steps to see if this resolves your issue. 26 | * Check the [forum](https://forum.ionicframework.com) for similar questions and answers. 27 | * Go through [all issues](https://github.com/ionic-team/ionic-conference-app/issues?utf8=%E2%9C%93&q=is%3Aissue) on this repository to see if the issue has already been created. It could have been closed with a resolution, so check closed issues, too. 28 | * Chat with us in the [#ionic-v2](https://ionic-worldwide.slack.com/messages/ionic-v2/) channel on [Slack](http://ionicworldwide.herokuapp.com/) to see if we can find a solution to the problem! 29 | * [Determine which repository](#determining-the-repository) the problem should be reported in. 30 | 31 | 32 | #### Determining the Repository 33 | 34 | There are several repositories being used for Ionic, which makes it difficult to determine which one to report an issue to. Don't worry if you aren't sure, we can always move it! 35 | 36 | * The [Ionic repository](https://github.com/ionic-team/ionic) is a repository for all things related to the Ionic Framework. If you are able to reproduce the issue in any of the Ionic starters (or an existing project), you'll want to submit the issue [here](http://ionicframework.com/submit-issue/). 37 | * The [Ionic CLI repository](https://github.com/ionic-team/ionic-cli) contains all of the code that allows you to run `ionic` commands from a terminal window. It is safe to put any issues [here](https://github.com/ionic-team/ionic-cli/issues) that relate to running an `ionic` command. 38 | * [This repository](https://github.com/ionic-team/ionic-conference-app) is a demo of the Ionic Framework. If you find an issue with this app that does not occur on [a new app](http://ionicframework.com/docs/v2/getting-started/installation/), please submit the issue [here](https://github.com/ionic-team/ionic-conference-app/issues). 39 | 40 | 41 | #### Submitting the Issue 42 | 43 | * **Use a clear and descriptive title** for the issue to identify the problem. This makes it easier for others to find. 44 | * **Describe the exact steps to reproduce the problem** with as many details as needed. 45 | * **Provide your configuration** by running `ionic info` in a terminal from *within* the project folder and pasting this information in the issue. 46 | 47 | ### Submitting a Pull Request 48 | 49 | #### Guidelines for Submitting 50 | 51 | When in doubt, keep your pull requests small. To give a PR the best chance of getting accepted, do not bundle more than one "feature" or bug fix per PR. Doing so makes it very hard to accept it if one of the fixes has issues. 52 | 53 | It's always best to create two smaller PRs than one big one. 54 | 55 | Talk to us before creating a PR that refactors the code or directory structure of the project. This project is constantly changing to reflect the latest version of Ionic Framework so sometimes it will be in the process of getting fixed. 56 | 57 | #### Code Style 58 | 59 | Make sure to follow the existing code style as much as possible. 60 | 61 | * No underscores prefixing JS functions. 62 | * Use flat Sass. 63 | * **Don't** use [BEM conventions](https://css-tricks.com/bem-101/). 64 | * Avoid nesting selectors. This is done to make it easier for users without Sass experience to understand and read. 65 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | **I'm submitting a ...** (check one with "x") 8 | [ ] bug report 9 | [ ] feature request 10 | [ ] support request => Please do not submit support requests here, use one of these channels: https://forum.ionicframework.com/ or http://ionicworldwide.herokuapp.com/ 11 | 12 | **Current behavior:** 13 | 14 | 15 | **Expected behavior:** 16 | 17 | 18 | **Steps to reproduce:** 19 | 23 | 24 | **Related code:** 25 | 26 | ``` 27 | insert any relevant code here 28 | ``` 29 | 30 | **Other information:** 31 | 32 | 33 | **Ionic info:** (run `ionic info` from a terminal/cmd prompt and paste output below): 34 | 35 | ``` 36 | insert the output from ionic info here 37 | ``` 38 | 39 | -------------------------------------------------------------------------------- /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | # GitHub Actions docs 2 | # https://help.github.com/en/articles/about-github-actions 3 | # https://help.github.com/en/articles/workflow-syntax-for-github-actions 4 | 5 | name: Install Dependencies, Lint src and tests 6 | 7 | on: [push, pull_request] 8 | 9 | jobs: 10 | test: 11 | name: Test on node ${{ matrix.node_version }} and ${{ matrix.os }} 12 | runs-on: ${{ matrix.os }} 13 | strategy: 14 | matrix: 15 | node_version: [12] 16 | os: [ubuntu-latest] 17 | 18 | steps: 19 | - uses: actions/checkout@v1 20 | - name: Use Node.js ${{ matrix.node_version }} 21 | uses: actions/setup-node@v1 22 | with: 23 | node-version: ${{ matrix.node_version }} 24 | - name: Install Dependencies 25 | run: npm ci 26 | - name: Lint 27 | run: npm run lint 28 | - name: Lint E2E 29 | run: npm run lint:e2e 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.angular/cache 2 | # Specifies intentionally untracked files to ignore when using Git 3 | # http://git-scm.com/docs/gitignore 4 | www/ 5 | 6 | .ionic/ 7 | 8 | *~ 9 | *.sw[mnpcod] 10 | *.log 11 | *.tmp 12 | *.tmp.* 13 | log.txt 14 | *.sublime-project 15 | *.sublime-workspace 16 | .vscode/ 17 | npm-debug.log* 18 | .firebase/ 19 | .idea/ 20 | .sourcemaps/ 21 | .sass-cache/ 22 | .tmp/ 23 | .versions/ 24 | coverage/ 25 | dist/ 26 | node_modules/ 27 | tmp/ 28 | temp/ 29 | hooks/ 30 | platforms/ 31 | plugins/ 32 | plugins/android.json 33 | plugins/ios.json 34 | $RECYCLE.BIN/ 35 | 36 | .DS_Store 37 | Thumbs.db 38 | UserInterfaceState.xcuserstate 39 | -------------------------------------------------------------------------------- /docs/tutorial-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/docs/tutorial-page.png -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "hosting": { 3 | "public": "www", 4 | "ignore": [ 5 | "firebase.json", 6 | "**/.*", 7 | "**/node_modules/**" 8 | ], 9 | "rewrites": [ 10 | { 11 | "source": "**", 12 | "destination": "/index.html" 13 | } 14 | ], 15 | 16 | "headers": [ 17 | { 18 | "source": "ngsw-worker.js", 19 | "headers": [ 20 | { 21 | "key": "Cache-Control", 22 | "value": "no-cache" 23 | } 24 | ] 25 | }, 26 | { 27 | "source": "/", 28 | "headers": [ 29 | { 30 | "key": "Link", 31 | "value": 32 | ";rel=preload;as=script,;rel=preload;as=script,;rel=preload;as=script,;rel=preload;as=script,;rel=preload;as=script,;rel=preload;as=script,;rel=preload;as=style,;rel=preload;as=script,;rel=preload;as=script,;rel=preload;as=script,;rel=preload;as=script,;rel=preload;as=script,;rel=preload;as=script,;rel=preload;as=script,;rel=preload;as=script,;rel=preload;as=script,;rel=preload;as=script,;rel=preload;as=script,;rel=preload;as=script,;rel=preload;as=script,;rel=preload;as=script,;rel=preload;as=script,;rel=preload;as=script," 33 | } 34 | ] 35 | } 36 | ] 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /generate-h2-push.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const puppeteer = require('puppeteer'); 3 | const fs = require('fs'); 4 | const args = process.argv.slice(2); 5 | const host = args[0] || 'http://127.0.0.1:8080'; 6 | const indexMatches = [ 7 | '.gz', 8 | '.map', 9 | '3rdpartylicenses.txt', 10 | 'ngsw-manifest.json', 11 | 'ngsw.json', 12 | 'worker-basic.min.js', 13 | 'ngsw-worker.js', 14 | 'favicon', 15 | 'index.html', 16 | 'manifest.webmanifest', 17 | '.svg' 18 | ]; 19 | let results = ''; 20 | async function main() { 21 | const browser = await puppeteer.launch(); 22 | const page = await browser.newPage(); 23 | await page._client.send('ServiceWorker.disable') 24 | 25 | page.on('console', msg => console.log(msg.text())); 26 | 27 | console.log('Page: loaded'); 28 | await page.goto(host); 29 | 30 | const allRequests = new Map(); 31 | page.on('request', req => { 32 | allRequests.set(req.url(), req); 33 | }); 34 | 35 | await page.reload({ waitUntil: 'networkidle2' }); 36 | 37 | Array.from(allRequests.values()).forEach(req => { 38 | const url = req.url(); 39 | 40 | // filter out urls that match these extensions 41 | for (const exlude of indexMatches) { 42 | if (url.indexOf(exlude) != -1) return false; 43 | } 44 | 45 | // if external, dont worry about it for now 46 | // 47 | const origin = new URL(host); 48 | 49 | if (url.indexOf(origin.origin) === -1) return false; 50 | 51 | // Format the url to remove the host 52 | const formatted = url.replace(`${origin.origin}/`, ''); 53 | 54 | if (origin.pathname.includes(formatted)) return false; 55 | 56 | // if it's an empty string, just ignore it 57 | if (!formatted) return false; 58 | 59 | let type = url.substr(-3) == 'css' ? 'style' : 'script'; 60 | results += `;rel=preload;as=${type},`; 61 | 62 | }); 63 | await browser.close(); 64 | updateWith(results); 65 | } 66 | function updateWith(result) { 67 | fs.readFile('firebase.json', 'utf8', function(err, data) { 68 | if (err) { 69 | return console.log(err); 70 | } 71 | let re = /("headers":\s*\[\s*{\s*"key":\s*"Link",\s*"value":\s*")(.*)("\s*}\s*\])/gm; 72 | if (re.exec(data)) { 73 | let newConfig = data.replace(re, `$1${result}$3`); 74 | fs.writeFile('firebase.json', newConfig, 'utf8', function(err) { 75 | if (err) return console.log(err); 76 | console.log('firebase.json updated successfully.'); 77 | }); 78 | } else { 79 | console.log("Couldn't find a valid firebase config to update."); 80 | return; 81 | } 82 | }); 83 | } 84 | 85 | main(); 86 | -------------------------------------------------------------------------------- /ionic.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ionic-conference-app", 3 | "type": "angular", 4 | "integrations": { 5 | "cordova": {} 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /ngsw-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "index": "/index.html", 3 | "assetGroups": [ 4 | { 5 | "name": "app", 6 | "installMode": "prefetch", 7 | "resources": { 8 | "files": [ 9 | "/favicon.ico", 10 | "/index.html", 11 | "/*.css", 12 | "/*.js" 13 | ] 14 | } 15 | }, { 16 | "name": "assets", 17 | "installMode": "lazy", 18 | "updateMode": "prefetch", 19 | "resources": { 20 | "files": [ 21 | "/assets/**" 22 | ] 23 | } 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /resources/android/icon/drawable-hdpi-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/android/icon/drawable-hdpi-icon.png -------------------------------------------------------------------------------- /resources/android/icon/drawable-ldpi-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/android/icon/drawable-ldpi-icon.png -------------------------------------------------------------------------------- /resources/android/icon/drawable-mdpi-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/android/icon/drawable-mdpi-icon.png -------------------------------------------------------------------------------- /resources/android/icon/drawable-xhdpi-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/android/icon/drawable-xhdpi-icon.png -------------------------------------------------------------------------------- /resources/android/icon/drawable-xxhdpi-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/android/icon/drawable-xxhdpi-icon.png -------------------------------------------------------------------------------- /resources/android/icon/drawable-xxxhdpi-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/android/icon/drawable-xxxhdpi-icon.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-land-hdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/android/splash/drawable-land-hdpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-land-ldpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/android/splash/drawable-land-ldpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-land-mdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/android/splash/drawable-land-mdpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-land-xhdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/android/splash/drawable-land-xhdpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-land-xxhdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/android/splash/drawable-land-xxhdpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-land-xxxhdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/android/splash/drawable-land-xxxhdpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-port-hdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/android/splash/drawable-port-hdpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-port-ldpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/android/splash/drawable-port-ldpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-port-mdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/android/splash/drawable-port-mdpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-port-xhdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/android/splash/drawable-port-xhdpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-port-xxhdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/android/splash/drawable-port-xxhdpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-port-xxxhdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/android/splash/drawable-port-xxxhdpi-screen.png -------------------------------------------------------------------------------- /resources/android/xml/network_security_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | localhost 10 | http://api.servicecity.ca 11 | 12 | -------------------------------------------------------------------------------- /resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/icon.png -------------------------------------------------------------------------------- /resources/icon.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/icon.psd -------------------------------------------------------------------------------- /resources/icon.psd.md5: -------------------------------------------------------------------------------- 1 | c1760af57a8089fda7537fc043804bc8 -------------------------------------------------------------------------------- /resources/ios/icon/icon-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/ios/icon/icon-1024.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-108@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/ios/icon/icon-108@2x.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/ios/icon/icon-20.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/ios/icon/icon-20@2x.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/ios/icon/icon-20@3x.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-24@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/ios/icon/icon-24@2x.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-27.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/ios/icon/icon-27.5@2x.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/ios/icon/icon-29.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/ios/icon/icon-29@2x.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/ios/icon/icon-29@3x.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/ios/icon/icon-40.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/ios/icon/icon-40@2x.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/ios/icon/icon-40@3x.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-44@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/ios/icon/icon-44@2x.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/ios/icon/icon-50.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-50@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/ios/icon/icon-50@2x.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/ios/icon/icon-60.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/ios/icon/icon-60@2x.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/ios/icon/icon-60@3x.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/ios/icon/icon-72.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-72@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/ios/icon/icon-72@2x.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/ios/icon/icon-76.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/ios/icon/icon-76@2x.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/ios/icon/icon-83.5@2x.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-86@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/ios/icon/icon-86@2x.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-98@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/ios/icon/icon-98@2x.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/ios/icon/icon-small.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-small@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/ios/icon/icon-small@2x.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-small@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/ios/icon/icon-small@3x.png -------------------------------------------------------------------------------- /resources/ios/icon/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/ios/icon/icon.png -------------------------------------------------------------------------------- /resources/ios/icon/icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/ios/icon/icon@2x.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-1792h~iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/ios/splash/Default-1792h~iphone.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-2436h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/ios/splash/Default-2436h.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-2688h~iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/ios/splash/Default-2688h~iphone.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-568h@2x~iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/ios/splash/Default-568h@2x~iphone.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-667h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/ios/splash/Default-667h.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-736h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/ios/splash/Default-736h.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-Landscape-1792h~iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/ios/splash/Default-Landscape-1792h~iphone.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-Landscape-2436h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/ios/splash/Default-Landscape-2436h.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-Landscape-2688h~iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/ios/splash/Default-Landscape-2688h~iphone.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-Landscape-736h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/ios/splash/Default-Landscape-736h.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-Landscape@2x~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/ios/splash/Default-Landscape@2x~ipad.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-Landscape@~ipadpro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/ios/splash/Default-Landscape@~ipadpro.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-Landscape~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/ios/splash/Default-Landscape~ipad.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-Portrait@2x~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/ios/splash/Default-Portrait@2x~ipad.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-Portrait@~ipadpro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/ios/splash/Default-Portrait@~ipadpro.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-Portrait~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/ios/splash/Default-Portrait~ipad.png -------------------------------------------------------------------------------- /resources/ios/splash/Default@2x~iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/ios/splash/Default@2x~iphone.png -------------------------------------------------------------------------------- /resources/ios/splash/Default@2x~universal~anyany.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/ios/splash/Default@2x~universal~anyany.png -------------------------------------------------------------------------------- /resources/ios/splash/Default~iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/ios/splash/Default~iphone.png -------------------------------------------------------------------------------- /resources/screenshots/android-about.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/screenshots/android-about.png -------------------------------------------------------------------------------- /resources/screenshots/android-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/screenshots/android-menu.png -------------------------------------------------------------------------------- /resources/screenshots/android-schedule.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/screenshots/android-schedule.png -------------------------------------------------------------------------------- /resources/screenshots/android-speaker-detail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/screenshots/android-speaker-detail.png -------------------------------------------------------------------------------- /resources/screenshots/android-speakers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/screenshots/android-speakers.png -------------------------------------------------------------------------------- /resources/screenshots/ios-about.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/screenshots/ios-about.png -------------------------------------------------------------------------------- /resources/screenshots/ios-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/screenshots/ios-menu.png -------------------------------------------------------------------------------- /resources/screenshots/ios-schedule.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/screenshots/ios-schedule.png -------------------------------------------------------------------------------- /resources/screenshots/ios-speaker-detail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/screenshots/ios-speaker-detail.png -------------------------------------------------------------------------------- /resources/screenshots/ios-speakers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/screenshots/ios-speakers.png -------------------------------------------------------------------------------- /resources/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/resources/splash.png -------------------------------------------------------------------------------- /resources/splash.png.md5: -------------------------------------------------------------------------------- 1 | 0dcf1df8c92c1ece4382d3357d9f8562 -------------------------------------------------------------------------------- /scripts/build-android.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ionic cordova build android 4 | # cp platforms/android/app/build/outputs/apk/debug/app-debug.apk 5 | 6 | 7 | # java -jar apks/bundletool.jar build-apks \ 8 | # --bundle apks/release/release/app.aab \ # A generated aab file 9 | # --output apks/AppBundleSample.apks \ # An apks file you'd like to out put to 10 | # --ks apks/sign \ # Signing keystore 11 | # --ks-key-alias key0 \ # Alias of the keytstore 12 | # --ks-pass pass:kazucocoa \ # Password of the keystore 13 | # --overwrite 14 | 15 | # Capacitor would be: 16 | # android/gradlew assembleRelease 17 | # cp android/app/build/outputs/app/debug/app-release.apk app/ -------------------------------------------------------------------------------- /scripts/build-ios.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ionic cordova build ios 4 | # mkdir -p platforms/ios/build/appium/Payload 5 | # cp -R platforms/ios/build/emulator/Ionic\ Conference\ App.app platforms/ios/build/appium/Payload 6 | 7 | # zip -r platforms/ios/build/app-debug.zip platforms/ios/build/appium 8 | 9 | # xcodebuild \ 10 | # clean \ 11 | # archive \ 12 | # -workspace platforms/ios/Ionic\ Conference\ App\.xcworkspace \ 13 | # -scheme "Ionic Conference App" \ 14 | # -sdk iphoneos \ 15 | # -archivePath app \ 16 | # -configuration Release 17 | # -quiet 18 | 19 | # xcodebuild \ 20 | # -exportArchive \ 21 | # -archivePath app \ 22 | # -exportOptionsPlist platforms/ios/Ionic\ Conference\ App/exportOptions.plist \ 23 | # -exportPath app/App-device.ipa 24 | -------------------------------------------------------------------------------- /scripts/build.sh: -------------------------------------------------------------------------------- 1 | if [[ $OSTYPE == 'darwin'* ]]; then 2 | bash "`dirname $0`"/build-ios.sh 3 | fi 4 | 5 | bash "`dirname $0`"/build-android.sh -------------------------------------------------------------------------------- /src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | import { CheckTutorial } from './providers/check-tutorial.service'; 4 | 5 | const routes: Routes = [ 6 | { 7 | path: '', 8 | redirectTo: '/tutorial', 9 | pathMatch: 'full' 10 | }, 11 | { 12 | path: 'account', 13 | loadChildren: () => import('./pages/account/account.module').then(m => m.AccountModule) 14 | }, 15 | { 16 | path: 'support', 17 | loadChildren: () => import('./pages/support/support.module').then(m => m.SupportModule) 18 | }, 19 | { 20 | path: 'login', 21 | loadChildren: () => import('./pages/login/login.module').then(m => m.LoginModule) 22 | }, 23 | { 24 | path: 'signup', 25 | loadChildren: () => import('./pages/signup/signup.module').then(m => m.SignUpModule) 26 | }, 27 | { 28 | path: 'app', 29 | loadChildren: () => import('./pages/tabs-page/tabs-page.module').then(m => m.TabsModule) 30 | }, 31 | { 32 | path: 'tutorial', 33 | loadChildren: () => import('./pages/tutorial/tutorial.module').then(m => m.TutorialModule), 34 | canLoad: [CheckTutorial] 35 | } 36 | ]; 37 | 38 | @NgModule({ 39 | imports: [RouterModule.forRoot(routes)], 40 | exports: [RouterModule] 41 | }) 42 | export class AppRoutingModule {} 43 | -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Conference 9 | 10 | 11 | 12 | 13 | 14 | {{p.title}} 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | Account 24 | 25 | 26 | 27 | 28 | 29 | 30 | Account 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | Support 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | Logout 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | Account 58 | 59 | 60 | 61 | 62 | 63 | 64 | Login 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | Support 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | Signup 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | Dark Mode 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | Tutorial 99 | 100 | 101 | 102 | 103 | Show Tutorial 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /src/app/app.component.scss: -------------------------------------------------------------------------------- 1 | ion-menu ion-content { 2 | --padding-top: 20px; 3 | --padding-bottom: 20px; 4 | 5 | --background: var(--ion-item-background, var(--ion-background-color, #fff)); 6 | } 7 | 8 | /* Remove background transitions for switching themes */ 9 | ion-menu ion-item { 10 | --transition: none; 11 | } 12 | 13 | ion-item.selected { 14 | --color: var(--ion-color-primary); 15 | } 16 | 17 | /* 18 | * Material Design Menu 19 | */ 20 | ion-menu.md ion-list { 21 | padding: 20px 0; 22 | } 23 | 24 | ion-menu.md ion-list-header { 25 | padding-left: 18px; 26 | padding-right: 18px; 27 | 28 | text-transform: uppercase; 29 | letter-spacing: .1em; 30 | font-weight: 450; 31 | } 32 | 33 | ion-menu.md ion-item { 34 | --padding-start: 18px; 35 | 36 | margin-right: 10px; 37 | 38 | border-radius: 0 50px 50px 0; 39 | 40 | font-weight: 500; 41 | } 42 | 43 | ion-menu.md ion-item.selected { 44 | --background: rgba(var(--ion-color-primary-rgb), 0.14); 45 | } 46 | 47 | ion-menu.md ion-item.selected ion-icon { 48 | color: var(--ion-color-primary); 49 | } 50 | 51 | ion-menu.md ion-list-header, 52 | ion-menu.md ion-item ion-icon { 53 | color: var(--ion-color-step-650, #5f6368); 54 | } 55 | 56 | ion-menu.md ion-list:not(:last-of-type) { 57 | border-bottom: 1px solid var(--ion-color-step-150, #d7d8da); 58 | } 59 | 60 | 61 | /* 62 | * iOS Menu 63 | */ 64 | ion-menu.ios ion-list-header { 65 | padding-left: 16px; 66 | padding-right: 16px; 67 | 68 | margin-bottom: 8px; 69 | } 70 | 71 | ion-menu.ios ion-list { 72 | padding: 20px 0 0; 73 | } 74 | 75 | ion-menu.ios ion-item { 76 | --padding-start: 16px; 77 | --min-height: 50px; 78 | } 79 | 80 | ion-menu.ios ion-item ion-icon { 81 | font-size: 24px; 82 | color: #73849a; 83 | } 84 | 85 | ion-menu.ios ion-item.selected ion-icon { 86 | color: var(--ion-color-primary); 87 | } 88 | -------------------------------------------------------------------------------- /src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | import { SwUpdate } from '@angular/service-worker'; 4 | import { TestBed, waitForAsync } from '@angular/core/testing'; 5 | 6 | import { MenuController, Platform } from '@ionic/angular'; 7 | import { SplashScreen } from '@ionic-native/splash-screen/ngx'; 8 | import { StatusBar } from '@ionic-native/status-bar/ngx'; 9 | import { IonicStorageModule } from '@ionic/storage'; 10 | import { AppComponent } from './app.component'; 11 | import { UserData } from './providers/user-data'; 12 | 13 | describe('AppComponent', () => { 14 | let menuSpy, 15 | routerSpy, 16 | userDataSpy, 17 | statusBarSpy, 18 | splashScreenSpy, 19 | swUpdateSpy, 20 | platformReadySpy, 21 | platformSpy, 22 | app, 23 | fixture; 24 | 25 | beforeEach(waitForAsync(() => { 26 | menuSpy = jasmine.createSpyObj('MenuController', ['toggle', 'enable']); 27 | routerSpy = jasmine.createSpyObj('Router', ['navigateByUrl']); 28 | userDataSpy = jasmine.createSpyObj('UserData', ['isLoggedIn', 'logout']); 29 | statusBarSpy = jasmine.createSpyObj('StatusBar', ['styleDefault']); 30 | splashScreenSpy = jasmine.createSpyObj('SplashScreen', ['hide']); 31 | swUpdateSpy = jasmine.createSpyObj('SwUpdate', ['available', 'activateUpdate']); 32 | platformReadySpy = Promise.resolve(); 33 | platformSpy = jasmine.createSpyObj('Platform', { ready: platformReadySpy }); 34 | 35 | TestBed.configureTestingModule({ 36 | declarations: [AppComponent], 37 | imports: [IonicStorageModule.forRoot()], 38 | schemas: [CUSTOM_ELEMENTS_SCHEMA], 39 | providers: [ 40 | { provide: MenuController, useValue: menuSpy }, 41 | { provide: Router, useValue: routerSpy }, 42 | { provide: UserData, useValue: userDataSpy }, 43 | { provide: StatusBar, useValue: statusBarSpy }, 44 | { provide: SplashScreen, useValue: splashScreenSpy }, 45 | { provide: SwUpdate, useValue: swUpdateSpy }, 46 | { provide: Platform, useValue: platformSpy } 47 | ] 48 | }).compileComponents(); 49 | })); 50 | beforeEach(() => { 51 | fixture = TestBed.createComponent(AppComponent); 52 | app = fixture.debugElement.componentInstance; 53 | }); 54 | 55 | it('should create the app', () => { 56 | expect(app).toBeTruthy(); 57 | }); 58 | 59 | it('should initialize the app', async () => { 60 | expect(platformSpy.ready).toHaveBeenCalled(); 61 | await platformReadySpy; 62 | expect(statusBarSpy.styleDefault).toHaveBeenCalled(); 63 | expect(splashScreenSpy.hide).toHaveBeenCalled(); 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ViewEncapsulation } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | import { SwUpdate } from '@angular/service-worker'; 4 | 5 | import { MenuController, Platform, ToastController } from '@ionic/angular'; 6 | 7 | import { SplashScreen } from '@ionic-native/splash-screen/ngx'; 8 | import { StatusBar } from '@ionic-native/status-bar/ngx'; 9 | 10 | import { Storage } from '@ionic/storage'; 11 | 12 | import { UserData } from './providers/user-data'; 13 | 14 | @Component({ 15 | selector: 'app-root', 16 | templateUrl: './app.component.html', 17 | styleUrls: ['./app.component.scss'], 18 | encapsulation: ViewEncapsulation.None 19 | }) 20 | export class AppComponent implements OnInit { 21 | appPages = [ 22 | { 23 | title: 'Schedule', 24 | url: '/app/tabs/schedule', 25 | icon: 'calendar' 26 | }, 27 | { 28 | title: 'Speakers', 29 | url: '/app/tabs/speakers', 30 | icon: 'people' 31 | }, 32 | { 33 | title: 'Map', 34 | url: '/app/tabs/map', 35 | icon: 'map' 36 | }, 37 | { 38 | title: 'About', 39 | url: '/app/tabs/about', 40 | icon: 'information-circle' 41 | } 42 | ]; 43 | loggedIn = false; 44 | dark = false; 45 | 46 | constructor( 47 | private menu: MenuController, 48 | private platform: Platform, 49 | private router: Router, 50 | private splashScreen: SplashScreen, 51 | private statusBar: StatusBar, 52 | private storage: Storage, 53 | private userData: UserData, 54 | private swUpdate: SwUpdate, 55 | private toastCtrl: ToastController, 56 | ) { 57 | this.initializeApp(); 58 | } 59 | 60 | async ngOnInit() { 61 | this.checkLoginStatus(); 62 | this.listenForLoginEvents(); 63 | 64 | this.swUpdate.available.subscribe(async res => { 65 | const toast = await this.toastCtrl.create({ 66 | message: 'Update available!', 67 | position: 'bottom', 68 | buttons: [ 69 | { 70 | role: 'cancel', 71 | text: 'Reload' 72 | } 73 | ] 74 | }); 75 | 76 | await toast.present(); 77 | 78 | toast 79 | .onDidDismiss() 80 | .then(() => this.swUpdate.activateUpdate()) 81 | .then(() => window.location.reload()); 82 | }); 83 | } 84 | 85 | initializeApp() { 86 | this.platform.ready().then(() => { 87 | this.statusBar.styleDefault(); 88 | this.splashScreen.hide(); 89 | }); 90 | } 91 | 92 | checkLoginStatus() { 93 | return this.userData.isLoggedIn().then(loggedIn => { 94 | return this.updateLoggedInStatus(loggedIn); 95 | }); 96 | } 97 | 98 | updateLoggedInStatus(loggedIn: boolean) { 99 | setTimeout(() => { 100 | this.loggedIn = loggedIn; 101 | }, 300); 102 | } 103 | 104 | listenForLoginEvents() { 105 | window.addEventListener('user:login', () => { 106 | this.updateLoggedInStatus(true); 107 | }); 108 | 109 | window.addEventListener('user:signup', () => { 110 | this.updateLoggedInStatus(true); 111 | }); 112 | 113 | window.addEventListener('user:logout', () => { 114 | this.updateLoggedInStatus(false); 115 | }); 116 | } 117 | 118 | logout() { 119 | this.userData.logout().then(() => { 120 | return this.router.navigateByUrl('/app/tabs/schedule'); 121 | }); 122 | } 123 | 124 | openTutorial() { 125 | this.menu.enable(false); 126 | this.storage.set('ion_did_tutorial', false); 127 | this.router.navigateByUrl('/tutorial'); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { HttpClientModule } from '@angular/common/http'; 2 | import { NgModule } from '@angular/core'; 3 | import { BrowserModule } from '@angular/platform-browser'; 4 | import { InAppBrowser } from '@ionic-native/in-app-browser/ngx'; 5 | import { SplashScreen } from '@ionic-native/splash-screen/ngx'; 6 | import { StatusBar } from '@ionic-native/status-bar/ngx'; 7 | import { IonicModule } from '@ionic/angular'; 8 | import { IonicStorageModule } from '@ionic/storage'; 9 | 10 | import { AppRoutingModule } from './app-routing.module'; 11 | import { AppComponent } from './app.component'; 12 | import { ServiceWorkerModule } from '@angular/service-worker'; 13 | import { environment } from '../environments/environment'; 14 | import { FormsModule } from '@angular/forms'; 15 | 16 | @NgModule({ 17 | imports: [ 18 | BrowserModule, 19 | AppRoutingModule, 20 | HttpClientModule, 21 | FormsModule, 22 | IonicModule.forRoot(), 23 | IonicStorageModule.forRoot(), 24 | ServiceWorkerModule.register('ngsw-worker.js', { 25 | enabled: environment.production 26 | }) 27 | ], 28 | declarations: [AppComponent], 29 | providers: [InAppBrowser, SplashScreen, StatusBar], 30 | bootstrap: [AppComponent] 31 | }) 32 | export class AppModule {} 33 | -------------------------------------------------------------------------------- /src/app/app.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/src/app/app.scss -------------------------------------------------------------------------------- /src/app/interfaces/user-options.ts: -------------------------------------------------------------------------------- 1 | 2 | export interface UserOptions { 3 | username: string; 4 | password: string; 5 | } 6 | -------------------------------------------------------------------------------- /src/app/pages/about-popover/about-popover.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | import { PopoverController } from '@ionic/angular'; 4 | 5 | @Component({ 6 | template: ` 7 | 8 | 9 | Learn Ionic 10 | 11 | 12 | Documentation 13 | 14 | 15 | Showcase 16 | 17 | 18 | GitHub Repo 19 | 20 | 21 | Support 22 | 23 | 24 | ` 25 | }) 26 | export class PopoverPage { 27 | constructor(public popoverCtrl: PopoverController) {} 28 | 29 | support() { 30 | // this.app.getRootNavs()[0].push('/support'); 31 | this.popoverCtrl.dismiss(); 32 | } 33 | 34 | close(url: string) { 35 | window.open(url, '_blank'); 36 | this.popoverCtrl.dismiss(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/app/pages/about/about-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | 4 | import { AboutPage } from './about'; 5 | 6 | const routes: Routes = [ 7 | { 8 | path: '', 9 | component: AboutPage 10 | } 11 | ]; 12 | 13 | @NgModule({ 14 | imports: [RouterModule.forChild(routes)], 15 | exports: [RouterModule] 16 | }) 17 | export class AboutPageRoutingModule { } 18 | -------------------------------------------------------------------------------- /src/app/pages/about/about.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 |
18 |
19 |
20 |
21 |
22 | 23 |
24 |

About

25 | 26 |

27 | The Ionic Conference is a one-day conference on {{ conferenceDate | date: 'mediumDate' }} featuring talks from the Ionic team. It is focused on Ionic applications being built with Ionic Framework. This includes migrating apps to the latest version of the framework, Angular concepts, Webpack, Sass, and many other technologies used in Ionic 2. Tickets are completely sold out, and we’re expecting more than 1000 developers – making this the largest Ionic conference ever! 28 |

29 | 30 |

Details

31 | 32 | 33 | 34 | 35 | Location 36 | 37 | 38 | Madison, WI 39 | Austin, TX 40 | Chicago, IL 41 | Seattle, WA 42 | 43 | 44 | 45 | 46 | Date 47 | 48 | {{ conferenceDate | date: 'mediumDate' }} 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 |

Internet

58 | 59 | 60 | 61 | 62 | Wifi network 63 | 64 | 65 | ica{{conferenceDate | date: 'y'}} 66 | 67 | 68 | 69 | 70 | Password 71 | 72 | 73 | makegoodthings 74 | 75 | 76 | 77 |
78 |
79 | -------------------------------------------------------------------------------- /src/app/pages/about/about.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FormsModule } from '@angular/forms'; 4 | import { IonicModule } from '@ionic/angular'; 5 | 6 | import { AboutPage } from './about'; 7 | import { PopoverPage } from '../about-popover/about-popover'; 8 | import { AboutPageRoutingModule } from './about-routing.module'; 9 | 10 | @NgModule({ 11 | imports: [ 12 | CommonModule, 13 | FormsModule, 14 | IonicModule, 15 | AboutPageRoutingModule 16 | ], 17 | declarations: [AboutPage, PopoverPage], 18 | entryComponents: [PopoverPage], 19 | bootstrap: [AboutPage], 20 | }) 21 | export class AboutModule {} 22 | -------------------------------------------------------------------------------- /src/app/pages/about/about.scss: -------------------------------------------------------------------------------- 1 | ion-toolbar { 2 | position: absolute; 3 | 4 | top: 0; 5 | left: 0; 6 | right: 0; 7 | 8 | --background: transparent; 9 | --color: white; 10 | } 11 | 12 | ion-toolbar ion-button, 13 | ion-toolbar ion-back-button, 14 | ion-toolbar ion-menu-button { 15 | --color: white; 16 | } 17 | 18 | .about-header { 19 | position: relative; 20 | 21 | width: 100%; 22 | height: 30%; 23 | } 24 | 25 | .about-header .about-image { 26 | position: absolute; 27 | 28 | top: 0; 29 | left: 0; 30 | bottom: 0; 31 | right: 0; 32 | 33 | background-position: center; 34 | background-size: cover; 35 | background-repeat: no-repeat; 36 | 37 | opacity: 0; 38 | 39 | transition: opacity 500ms ease-in-out; 40 | } 41 | 42 | .about-header .madison { 43 | background-image: url(/assets/img/about/madison.jpg); 44 | } 45 | .about-header .austin { 46 | background-image: url(/assets/img/about/austin.jpg); 47 | } 48 | .about-header .chicago { 49 | background-image: url(/assets/img/about/chicago.jpg); 50 | } 51 | .about-header .seattle { 52 | background-image: url(/assets/img/about/seattle.jpg); 53 | } 54 | 55 | .about-info { 56 | position: absolute; 57 | margin-top: -10px; 58 | border-radius: 10px; 59 | background: var(--ion-background-color, #fff); 60 | } 61 | 62 | .about-info h3 { 63 | margin-top: 0; 64 | } 65 | 66 | .about-info ion-list { 67 | padding-top: 0; 68 | } 69 | 70 | .about-info p { 71 | line-height: 130%; 72 | 73 | color: var(--ion-color-dark); 74 | } 75 | 76 | .about-info ion-icon { 77 | margin-inline-end: 32px; 78 | } 79 | 80 | #date-input-popover { 81 | --offset-y: -var(--ion-safe-area-bottom); 82 | 83 | --max-width: 90%; 84 | --width: 336px; 85 | } 86 | 87 | /* 88 | * iOS Only 89 | */ 90 | 91 | .ios .about-info { 92 | --ion-padding: 19px; 93 | } 94 | 95 | .ios .about-info h3 { 96 | font-weight: 700; 97 | } 98 | -------------------------------------------------------------------------------- /src/app/pages/about/about.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | import { PopoverController } from '@ionic/angular'; 4 | 5 | import { PopoverPage } from '../about-popover/about-popover'; 6 | 7 | @Component({ 8 | selector: 'page-about', 9 | templateUrl: 'about.html', 10 | styleUrls: ['./about.scss'], 11 | }) 12 | export class AboutPage { 13 | location = 'madison'; 14 | conferenceDate = '2047-05-17'; 15 | 16 | selectOptions = { 17 | header: 'Select a Location' 18 | }; 19 | 20 | constructor(public popoverCtrl: PopoverController) { } 21 | 22 | async presentPopover(event: Event) { 23 | const popover = await this.popoverCtrl.create({ 24 | component: PopoverPage, 25 | event 26 | }); 27 | await popover.present(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/app/pages/account/account-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | 4 | import { AccountPage } from './account'; 5 | 6 | const routes: Routes = [ 7 | { 8 | path: '', 9 | component: AccountPage 10 | } 11 | ]; 12 | 13 | @NgModule({ 14 | imports: [RouterModule.forChild(routes)], 15 | exports: [RouterModule] 16 | }) 17 | export class AccountPageRoutingModule { } 18 | -------------------------------------------------------------------------------- /src/app/pages/account/account.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Account 7 | 8 | 9 | 10 | 11 |
12 | avatar 13 |

{{username}}

14 | 15 | 16 | Update Picture 17 | Change Username 18 | Change Password 19 | Support 20 | Logout 21 | 22 |
23 |
24 | -------------------------------------------------------------------------------- /src/app/pages/account/account.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { IonicModule } from '@ionic/angular'; 4 | 5 | import { AccountPage } from './account'; 6 | import { AccountPageRoutingModule } from './account-routing.module'; 7 | 8 | @NgModule({ 9 | imports: [ 10 | CommonModule, 11 | IonicModule, 12 | AccountPageRoutingModule 13 | ], 14 | declarations: [ 15 | AccountPage, 16 | ] 17 | }) 18 | export class AccountModule { } 19 | -------------------------------------------------------------------------------- /src/app/pages/account/account.scss: -------------------------------------------------------------------------------- 1 | img { 2 | max-width: 140px; 3 | border-radius: 50%; 4 | } 5 | -------------------------------------------------------------------------------- /src/app/pages/account/account.ts: -------------------------------------------------------------------------------- 1 | import { AfterViewInit, Component } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | 4 | import { AlertController } from '@ionic/angular'; 5 | 6 | import { UserData } from '../../providers/user-data'; 7 | 8 | 9 | @Component({ 10 | selector: 'page-account', 11 | templateUrl: 'account.html', 12 | styleUrls: ['./account.scss'], 13 | }) 14 | export class AccountPage implements AfterViewInit { 15 | username: string; 16 | 17 | constructor( 18 | public alertCtrl: AlertController, 19 | public router: Router, 20 | public userData: UserData 21 | ) { } 22 | 23 | ngAfterViewInit() { 24 | this.getUsername(); 25 | } 26 | 27 | updatePicture() { 28 | console.log('Clicked to update picture'); 29 | } 30 | 31 | // Present an alert with the current username populated 32 | // clicking OK will update the username and display it 33 | // clicking Cancel will close the alert and do nothing 34 | async changeUsername() { 35 | const alert = await this.alertCtrl.create({ 36 | header: 'Change Username', 37 | buttons: [ 38 | 'Cancel', 39 | { 40 | text: 'Ok', 41 | handler: (data: any) => { 42 | this.userData.setUsername(data.username); 43 | this.getUsername(); 44 | } 45 | } 46 | ], 47 | inputs: [ 48 | { 49 | type: 'text', 50 | name: 'username', 51 | value: this.username, 52 | placeholder: 'username' 53 | } 54 | ] 55 | }); 56 | await alert.present(); 57 | } 58 | 59 | getUsername() { 60 | this.userData.getUsername().then((username) => { 61 | this.username = username; 62 | }); 63 | } 64 | 65 | changePassword() { 66 | console.log('Clicked to change password'); 67 | } 68 | 69 | logout() { 70 | this.userData.logout(); 71 | this.router.navigateByUrl('/login'); 72 | } 73 | 74 | support() { 75 | this.router.navigateByUrl('/support'); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/app/pages/login/login-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | 4 | import { LoginPage } from './login'; 5 | 6 | const routes: Routes = [ 7 | { 8 | path: '', 9 | component: LoginPage 10 | } 11 | ]; 12 | 13 | @NgModule({ 14 | imports: [RouterModule.forChild(routes)], 15 | exports: [RouterModule] 16 | }) 17 | export class LoginPageRoutingModule { } 18 | -------------------------------------------------------------------------------- /src/app/pages/login/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Login 8 | 9 | 10 | 11 | 12 | 15 | 16 |
17 | 18 | 19 | Username 20 | 22 | 23 | 24 | 25 | 26 |

27 | Username is required 28 |

29 |
30 | 31 | 32 | Password 33 | 34 | 35 | 36 | 37 | 38 |

39 | Password is required 40 |

41 |
42 |
43 | 44 | 45 | 46 | Login 47 | 48 | 49 | Signup 50 | 51 | 52 |
53 | 54 |
55 | -------------------------------------------------------------------------------- /src/app/pages/login/login.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FormsModule } from '@angular/forms'; 4 | import { IonicModule } from '@ionic/angular'; 5 | 6 | import { LoginPage } from './login'; 7 | import { LoginPageRoutingModule } from './login-routing.module'; 8 | 9 | @NgModule({ 10 | imports: [ 11 | CommonModule, 12 | FormsModule, 13 | IonicModule, 14 | LoginPageRoutingModule 15 | ], 16 | declarations: [ 17 | LoginPage, 18 | ] 19 | }) 20 | export class LoginModule { } 21 | -------------------------------------------------------------------------------- /src/app/pages/login/login.scss: -------------------------------------------------------------------------------- 1 | .login-logo { 2 | padding: 20px 0; 3 | min-height: 200px; 4 | text-align: center; 5 | } 6 | 7 | .login-logo img { 8 | max-width: 150px; 9 | } 10 | 11 | .list { 12 | margin-bottom: 0; 13 | } 14 | -------------------------------------------------------------------------------- /src/app/pages/login/login.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { NgForm } from '@angular/forms'; 3 | import { Router } from '@angular/router'; 4 | 5 | import { UserData } from '../../providers/user-data'; 6 | 7 | import { UserOptions } from '../../interfaces/user-options'; 8 | 9 | 10 | 11 | @Component({ 12 | selector: 'page-login', 13 | templateUrl: 'login.html', 14 | styleUrls: ['./login.scss'], 15 | }) 16 | export class LoginPage { 17 | login: UserOptions = { username: '', password: '' }; 18 | submitted = false; 19 | 20 | constructor( 21 | public userData: UserData, 22 | public router: Router 23 | ) { } 24 | 25 | onLogin(form: NgForm) { 26 | this.submitted = true; 27 | 28 | if (form.valid) { 29 | this.userData.login(this.login.username); 30 | this.router.navigateByUrl('/app/tabs/schedule'); 31 | } 32 | } 33 | 34 | onSignup() { 35 | this.router.navigateByUrl('/signup'); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/app/pages/map/map-dark-style.js: -------------------------------------------------------------------------------- 1 | export const darkStyle = [ 2 | { 3 | "elementType": "geometry", 4 | "stylers": [ 5 | { 6 | "color": "#242f3e" 7 | } 8 | ] 9 | }, 10 | { 11 | "elementType": "labels.text.fill", 12 | "stylers": [ 13 | { 14 | "color": "#746855" 15 | } 16 | ] 17 | }, 18 | { 19 | "elementType": "labels.text.stroke", 20 | "stylers": [ 21 | { 22 | "color": "#242f3e" 23 | } 24 | ] 25 | }, 26 | { 27 | "featureType": "administrative.locality", 28 | "elementType": "labels.text.fill", 29 | "stylers": [ 30 | { 31 | "color": "#d59563" 32 | } 33 | ] 34 | }, 35 | { 36 | "featureType": "poi", 37 | "elementType": "labels.text.fill", 38 | "stylers": [ 39 | { 40 | "color": "#d59563" 41 | } 42 | ] 43 | }, 44 | { 45 | "featureType": "poi.park", 46 | "elementType": "geometry", 47 | "stylers": [ 48 | { 49 | "color": "#263c3f" 50 | } 51 | ] 52 | }, 53 | { 54 | "featureType": "poi.park", 55 | "elementType": "labels.text.fill", 56 | "stylers": [ 57 | { 58 | "color": "#6b9a76" 59 | } 60 | ] 61 | }, 62 | { 63 | "featureType": "road", 64 | "elementType": "geometry", 65 | "stylers": [ 66 | { 67 | "color": "#38414e" 68 | } 69 | ] 70 | }, 71 | { 72 | "featureType": "road", 73 | "elementType": "geometry.stroke", 74 | "stylers": [ 75 | { 76 | "color": "#212a37" 77 | } 78 | ] 79 | }, 80 | { 81 | "featureType": "road", 82 | "elementType": "labels.text.fill", 83 | "stylers": [ 84 | { 85 | "color": "#9ca5b3" 86 | } 87 | ] 88 | }, 89 | { 90 | "featureType": "road.highway", 91 | "elementType": "geometry", 92 | "stylers": [ 93 | { 94 | "color": "#746855" 95 | } 96 | ] 97 | }, 98 | { 99 | "featureType": "road.highway", 100 | "elementType": "geometry.stroke", 101 | "stylers": [ 102 | { 103 | "color": "#1f2835" 104 | } 105 | ] 106 | }, 107 | { 108 | "featureType": "road.highway", 109 | "elementType": "labels.text.fill", 110 | "stylers": [ 111 | { 112 | "color": "#f3d19c" 113 | } 114 | ] 115 | }, 116 | { 117 | "featureType": "transit", 118 | "elementType": "geometry", 119 | "stylers": [ 120 | { 121 | "color": "#2f3948" 122 | } 123 | ] 124 | }, 125 | { 126 | "featureType": "transit.station", 127 | "elementType": "labels.text.fill", 128 | "stylers": [ 129 | { 130 | "color": "#d59563" 131 | } 132 | ] 133 | }, 134 | { 135 | "featureType": "water", 136 | "elementType": "geometry", 137 | "stylers": [ 138 | { 139 | "color": "#17263c" 140 | } 141 | ] 142 | }, 143 | { 144 | "featureType": "water", 145 | "elementType": "labels.text.fill", 146 | "stylers": [ 147 | { 148 | "color": "#515c6d" 149 | } 150 | ] 151 | }, 152 | { 153 | "featureType": "water", 154 | "elementType": "labels.text.stroke", 155 | "stylers": [ 156 | { 157 | "color": "#17263c" 158 | } 159 | ] 160 | } 161 | ] -------------------------------------------------------------------------------- /src/app/pages/map/map-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | 4 | import { MapPage } from './map'; 5 | 6 | const routes: Routes = [ 7 | { 8 | path: '', 9 | component: MapPage 10 | } 11 | ]; 12 | 13 | @NgModule({ 14 | imports: [RouterModule.forChild(routes)], 15 | exports: [RouterModule] 16 | }) 17 | export class MapPageRoutingModule { } 18 | -------------------------------------------------------------------------------- /src/app/pages/map/map.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Map 7 | 8 | 9 | 10 | 11 |
12 |
13 | -------------------------------------------------------------------------------- /src/app/pages/map/map.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { IonicModule } from '@ionic/angular'; 4 | 5 | import { MapPage } from './map'; 6 | import { MapPageRoutingModule } from './map-routing.module'; 7 | 8 | @NgModule({ 9 | imports: [ 10 | CommonModule, 11 | IonicModule, 12 | MapPageRoutingModule 13 | ], 14 | declarations: [ 15 | MapPage, 16 | ] 17 | }) 18 | export class MapModule { } 19 | -------------------------------------------------------------------------------- /src/app/pages/map/map.scss: -------------------------------------------------------------------------------- 1 | .map-canvas { 2 | position: absolute; 3 | 4 | height: 100%; 5 | width: 100%; 6 | 7 | background-color: transparent; 8 | 9 | opacity: 0; 10 | transition: opacity 150ms ease-in; 11 | } 12 | 13 | .show-map { 14 | opacity: 1; 15 | } 16 | -------------------------------------------------------------------------------- /src/app/pages/map/map.ts: -------------------------------------------------------------------------------- 1 | import { Component, ElementRef, Inject, ViewChild, AfterViewInit } from '@angular/core'; 2 | import { ConferenceData } from '../../providers/conference-data'; 3 | import { Platform } from '@ionic/angular'; 4 | import { DOCUMENT} from '@angular/common'; 5 | 6 | import { darkStyle } from './map-dark-style'; 7 | 8 | @Component({ 9 | selector: 'page-map', 10 | templateUrl: 'map.html', 11 | styleUrls: ['./map.scss'] 12 | }) 13 | export class MapPage implements AfterViewInit { 14 | @ViewChild('mapCanvas', { static: true }) mapElement: ElementRef; 15 | 16 | constructor( 17 | @Inject(DOCUMENT) private doc: Document, 18 | public confData: ConferenceData, 19 | public platform: Platform) {} 20 | 21 | async ngAfterViewInit() { 22 | const appEl = this.doc.querySelector('ion-app'); 23 | let isDark = false; 24 | let style = []; 25 | if (appEl.classList.contains('dark-theme')) { 26 | style = darkStyle; 27 | } 28 | 29 | const googleMaps = await getGoogleMaps( 30 | 'YOUR_API_KEY_HERE' 31 | ); 32 | 33 | let map; 34 | 35 | this.confData.getMap().subscribe((mapData: any) => { 36 | const mapEle = this.mapElement.nativeElement; 37 | 38 | map = new googleMaps.Map(mapEle, { 39 | center: mapData.find((d: any) => d.center), 40 | zoom: 16, 41 | styles: style 42 | }); 43 | 44 | mapData.forEach((markerData: any) => { 45 | const infoWindow = new googleMaps.InfoWindow({ 46 | content: `
${markerData.name}
` 47 | }); 48 | 49 | const marker = new googleMaps.Marker({ 50 | position: markerData, 51 | map, 52 | title: markerData.name 53 | }); 54 | 55 | marker.addListener('click', () => { 56 | infoWindow.open(map, marker); 57 | }); 58 | }); 59 | 60 | googleMaps.event.addListenerOnce(map, 'idle', () => { 61 | mapEle.classList.add('show-map'); 62 | }); 63 | }); 64 | 65 | const observer = new MutationObserver((mutations) => { 66 | mutations.forEach((mutation) => { 67 | if (mutation.attributeName === 'class') { 68 | const el = mutation.target as HTMLElement; 69 | isDark = el.classList.contains('dark-theme'); 70 | if (map && isDark) { 71 | map.setOptions({styles: darkStyle}); 72 | } else if (map) { 73 | map.setOptions({styles: []}); 74 | } 75 | } 76 | }); 77 | }); 78 | observer.observe(appEl, { 79 | attributes: true 80 | }); 81 | } 82 | } 83 | 84 | function getGoogleMaps(apiKey: string): Promise { 85 | const win = window as any; 86 | const googleModule = win.google; 87 | if (googleModule && googleModule.maps) { 88 | return Promise.resolve(googleModule.maps); 89 | } 90 | 91 | return new Promise((resolve, reject) => { 92 | const script = document.createElement('script'); 93 | script.src = `https://maps.googleapis.com/maps/api/js?key=${apiKey}&v=3.31`; 94 | script.async = true; 95 | script.defer = true; 96 | document.body.appendChild(script); 97 | script.onload = () => { 98 | const googleModule2 = win.google; 99 | if (googleModule2 && googleModule2.maps) { 100 | resolve(googleModule2.maps); 101 | } else { 102 | reject('google maps not available'); 103 | } 104 | }; 105 | }); 106 | } 107 | 108 | -------------------------------------------------------------------------------- /src/app/pages/schedule-filter/schedule-filter.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Cancel 5 | Reset 6 | 7 | 8 | 9 | Filter Sessions 10 | 11 | 12 | 13 | Done 14 | 15 | 16 | 17 | 18 | 19 | 20 | Tracks 21 | 22 | 23 | 24 | {{track.name}} 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | Deselect All 36 | 37 | 38 | Select All 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/app/pages/schedule-filter/schedule-filter.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * Material Design 3 | */ 4 | 5 | .md ion-toolbar ion-button { 6 | text-transform: capitalize; 7 | letter-spacing: 0; 8 | } 9 | 10 | .md ion-checkbox { 11 | --background-checked: transparent; 12 | --border-color: transparent; 13 | --border-color-checked: transparent; 14 | --checkmark-color: var(--ion-color-primary); 15 | } 16 | 17 | .md ion-list { 18 | background: inherit; 19 | } 20 | 21 | 22 | /* 23 | * iOS 24 | */ 25 | 26 | .ios ion-list-header { 27 | margin-top: 10px; 28 | } 29 | 30 | .ios ion-label { 31 | color: var(--ion-color-primary); 32 | } 33 | -------------------------------------------------------------------------------- /src/app/pages/schedule-filter/schedule-filter.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { Config, ModalController, NavParams } from '@ionic/angular'; 3 | 4 | import { ConferenceData } from '../../providers/conference-data'; 5 | 6 | 7 | @Component({ 8 | selector: 'page-schedule-filter', 9 | templateUrl: 'schedule-filter.html', 10 | styleUrls: ['./schedule-filter.scss'], 11 | }) 12 | export class ScheduleFilterPage { 13 | ios: boolean; 14 | 15 | tracks: {name: string, icon: string, isChecked: boolean}[] = []; 16 | 17 | constructor( 18 | public confData: ConferenceData, 19 | private config: Config, 20 | public modalCtrl: ModalController, 21 | public navParams: NavParams 22 | ) { } 23 | 24 | ionViewWillEnter() { 25 | this.ios = this.config.get('mode') === `ios`; 26 | 27 | // passed in array of track names that should be excluded (unchecked) 28 | const excludedTrackNames = this.navParams.get('excludedTracks'); 29 | 30 | this.confData.getTracks().subscribe((tracks: any[]) => { 31 | tracks.forEach(track => { 32 | this.tracks.push({ 33 | name: track.name, 34 | icon: track.icon, 35 | isChecked: (excludedTrackNames.indexOf(track.name) === -1) 36 | }); 37 | }); 38 | }); 39 | } 40 | 41 | selectAll(check: boolean) { 42 | // set all to checked or unchecked 43 | this.tracks.forEach(track => { 44 | track.isChecked = check; 45 | }); 46 | } 47 | 48 | applyFilters() { 49 | // Pass back a new array of track names to exclude 50 | const excludedTrackNames = this.tracks.filter(c => !c.isChecked).map(c => c.name); 51 | this.dismiss(excludedTrackNames); 52 | } 53 | 54 | dismiss(data?: any) { 55 | // using the injected ModalController this page 56 | // can "dismiss" itself and pass back data 57 | this.modalCtrl.dismiss(data); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/app/pages/schedule/schedule-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | 4 | import { SchedulePage } from './schedule'; 5 | 6 | const routes: Routes = [ 7 | { 8 | path: '', 9 | component: SchedulePage 10 | } 11 | ]; 12 | 13 | @NgModule({ 14 | imports: [RouterModule.forChild(routes)], 15 | exports: [RouterModule] 16 | }) 17 | export class SchedulePageRoutingModule { } 18 | -------------------------------------------------------------------------------- /src/app/pages/schedule/schedule.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | All 9 | 10 | 11 | Favorites 12 | 13 | 14 | Schedule 15 | 16 | 17 | 18 | 19 | 20 | 21 | Filter 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | All 32 | 33 | 34 | Favorites 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | Schedule 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | {{group.time}} 55 | 56 | 57 | 58 | 60 | 61 | 62 |

{{session.name}}

63 |

64 | {{session.timeStart}} — {{session.timeEnd}}: {{session.location}} 65 |

66 |
67 |
68 | 69 | 70 | Favorite 71 | 72 | 74 | Remove 75 | 76 | 77 |
78 |
79 |
80 | 81 | 82 | No Sessions Found 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 |
105 | -------------------------------------------------------------------------------- /src/app/pages/schedule/schedule.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FormsModule } from '@angular/forms'; 4 | import { IonicModule } from '@ionic/angular'; 5 | 6 | import { SchedulePage } from './schedule'; 7 | import { ScheduleFilterPage } from '../schedule-filter/schedule-filter'; 8 | import { SchedulePageRoutingModule } from './schedule-routing.module'; 9 | 10 | @NgModule({ 11 | imports: [ 12 | CommonModule, 13 | FormsModule, 14 | IonicModule, 15 | SchedulePageRoutingModule 16 | ], 17 | declarations: [ 18 | SchedulePage, 19 | ScheduleFilterPage 20 | ], 21 | entryComponents: [ 22 | ScheduleFilterPage 23 | ] 24 | }) 25 | export class ScheduleModule { } 26 | -------------------------------------------------------------------------------- /src/app/pages/schedule/schedule.scss: -------------------------------------------------------------------------------- 1 | ion-fab-button { 2 | --background: var(--ion-color-step-150, #fff); 3 | --background-hover: var(--ion-color-step-200, #f2f2f2); 4 | --background-focused: var(--ion-color-step-250, #d9d9d9); 5 | 6 | --color: var(--ion-color-primary, #3880ff); 7 | } 8 | 9 | /* 10 | * Material Design uses the ripple for activated 11 | * so only style the iOS activated background 12 | */ 13 | .ios ion-fab-button { 14 | --background-activated: var(--ion-color-step-250, #d9d9d9); 15 | } 16 | 17 | $categories: ( 18 | ionic: var(--ion-color-primary), 19 | angular: #ac282b, 20 | communication: #8e8d93, 21 | tooling: #fe4c52, 22 | services: #fd8b2d, 23 | design: #fed035, 24 | workshop: #69bb7b, 25 | food: #3bc7c4, 26 | documentation: #b16be3, 27 | navigation: #6600cc 28 | ); 29 | 30 | @each $track, $value in map-remove($categories) { 31 | ion-item-sliding[track='#{$track}'] ion-label { 32 | border-left: 2px solid $value; 33 | padding-left: 10px; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/app/pages/session-detail/session-detail-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | 4 | import { SessionDetailPage } from './session-detail'; 5 | 6 | const routes: Routes = [ 7 | { 8 | path: '', 9 | component: SessionDetailPage 10 | } 11 | ]; 12 | 13 | @NgModule({ 14 | imports: [RouterModule.forChild(routes)], 15 | exports: [RouterModule] 16 | }) 17 | export class SessionDetailPageRoutingModule { } 18 | -------------------------------------------------------------------------------- /src/app/pages/session-detail/session-detail.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |

{{session.name}}

21 | {{track}} 22 |

{{session.description}}

23 | 24 | {{session.timeStart}} – {{session.timeEnd}} 25 |
{{session.location}} 26 |
27 |
28 | 29 | 30 | 31 | Watch 32 | 33 | 34 | Add to Calendar 35 | 36 | 37 | Mark as Unwatched 38 | 39 | 40 | Download Video 41 | 42 | 43 | 44 | Leave Feedback 45 | 46 | 47 |
48 | -------------------------------------------------------------------------------- /src/app/pages/session-detail/session-detail.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | import { SessionDetailPage } from './session-detail'; 5 | import { SessionDetailPageRoutingModule } from './session-detail-routing.module'; 6 | import { IonicModule } from '@ionic/angular'; 7 | 8 | @NgModule({ 9 | imports: [ 10 | CommonModule, 11 | IonicModule, 12 | SessionDetailPageRoutingModule 13 | ], 14 | declarations: [ 15 | SessionDetailPage, 16 | ] 17 | }) 18 | export class SessionDetailModule { } 19 | -------------------------------------------------------------------------------- /src/app/pages/session-detail/session-detail.scss: -------------------------------------------------------------------------------- 1 | .session-track-ionic { 2 | color: var(--ion-color-primary); 3 | } 4 | 5 | .session-track-angular { 6 | color: var(--ion-color-angular); 7 | } 8 | 9 | .session-track-communication { 10 | color: var(--ion-color-communication); 11 | } 12 | 13 | .session-track-tooling { 14 | color: var(--ion-color-tooling); 15 | } 16 | 17 | .session-track-services { 18 | color: var(--ion-color-services); 19 | } 20 | 21 | .session-track-design { 22 | color: var(--ion-color-design); 23 | } 24 | 25 | .session-track-workshop { 26 | color: var(--ion-color-workshop); 27 | } 28 | 29 | .session-track-food { 30 | color: var(--ion-color-food); 31 | } 32 | 33 | .session-track-documentation { 34 | color: var(--ion-color-documentation); 35 | } 36 | 37 | .session-track-navigation { 38 | color: var(--ion-color-navigation); 39 | } 40 | 41 | /* Favorite Icon 42 | * -------------------------------------------------------- 43 | */ 44 | 45 | .show-favorite { 46 | position: relative; 47 | } 48 | 49 | .icon-heart-empty, 50 | .icon-heart { 51 | --border-radius: 50%; 52 | --padding-start: 0; 53 | --padding-end: 0; 54 | 55 | position: absolute; 56 | 57 | top: 5px; 58 | right: 5px; 59 | 60 | width: 48px; 61 | height: 48px; 62 | 63 | font-size: 16px; 64 | 65 | transition: transform 300ms ease; 66 | } 67 | 68 | .icon-heart-empty { 69 | transform: scale(1); 70 | } 71 | 72 | .icon-heart { 73 | transform: scale(0); 74 | } 75 | 76 | .show-favorite .icon-heart { 77 | transform: scale(1); 78 | } 79 | 80 | .show-favorite .icon-heart-empty { 81 | transform: scale(0); 82 | } 83 | 84 | h1 { 85 | margin: 0; 86 | } -------------------------------------------------------------------------------- /src/app/pages/session-detail/session-detail.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | import { ConferenceData } from '../../providers/conference-data'; 4 | import { ActivatedRoute } from '@angular/router'; 5 | import { UserData } from '../../providers/user-data'; 6 | 7 | @Component({ 8 | selector: 'page-session-detail', 9 | styleUrls: ['./session-detail.scss'], 10 | templateUrl: 'session-detail.html' 11 | }) 12 | export class SessionDetailPage { 13 | session: any; 14 | isFavorite = false; 15 | defaultHref = ''; 16 | 17 | constructor( 18 | private dataProvider: ConferenceData, 19 | private userProvider: UserData, 20 | private route: ActivatedRoute 21 | ) { } 22 | 23 | ionViewWillEnter() { 24 | this.dataProvider.load().subscribe((data: any) => { 25 | if (data && data.schedule && data.schedule[0] && data.schedule[0].groups) { 26 | const sessionId = this.route.snapshot.paramMap.get('sessionId'); 27 | for (const group of data.schedule[0].groups) { 28 | if (group && group.sessions) { 29 | for (const session of group.sessions) { 30 | if (session && session.id === sessionId) { 31 | this.session = session; 32 | 33 | this.isFavorite = this.userProvider.hasFavorite( 34 | this.session.name 35 | ); 36 | 37 | break; 38 | } 39 | } 40 | } 41 | } 42 | } 43 | }); 44 | } 45 | 46 | ionViewDidEnter() { 47 | this.defaultHref = `/app/tabs/schedule`; 48 | } 49 | 50 | sessionClick(item: string) { 51 | console.log('Clicked', item); 52 | } 53 | 54 | toggleFavorite() { 55 | if (this.userProvider.hasFavorite(this.session.name)) { 56 | this.userProvider.removeFavorite(this.session.name); 57 | this.isFavorite = false; 58 | } else { 59 | this.userProvider.addFavorite(this.session.name); 60 | this.isFavorite = true; 61 | } 62 | } 63 | 64 | shareSession() { 65 | console.log('Clicked share session'); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/app/pages/signup/signup-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | 4 | import { SignupPage } from './signup'; 5 | 6 | const routes: Routes = [ 7 | { 8 | path: '', 9 | component: SignupPage 10 | } 11 | ]; 12 | 13 | @NgModule({ 14 | imports: [RouterModule.forChild(routes)], 15 | exports: [RouterModule] 16 | }) 17 | export class SignupPageRoutingModule { } 18 | -------------------------------------------------------------------------------- /src/app/pages/signup/signup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Signup 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 |
17 | 18 | 19 | Username 20 | 21 | 22 | 23 | 24 |

25 | Username is required 26 |

27 |
28 | 29 | 30 | Password 31 | 32 | 33 | 34 | 35 |

36 | Password is required 37 |

38 |
39 |
40 | 41 |
42 | Create 43 |
44 |
45 | 46 |
47 | -------------------------------------------------------------------------------- /src/app/pages/signup/signup.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FormsModule } from '@angular/forms'; 4 | import { IonicModule } from '@ionic/angular'; 5 | 6 | import { SignupPage } from './signup'; 7 | import { SignupPageRoutingModule } from './signup-routing.module'; 8 | 9 | @NgModule({ 10 | imports: [ 11 | CommonModule, 12 | FormsModule, 13 | IonicModule, 14 | SignupPageRoutingModule 15 | ], 16 | declarations: [ 17 | SignupPage, 18 | ] 19 | }) 20 | export class SignUpModule { } 21 | -------------------------------------------------------------------------------- /src/app/pages/signup/signup.scss: -------------------------------------------------------------------------------- 1 | .signup-logo { 2 | padding: 20px 0; 3 | min-height: 200px; 4 | text-align: center; 5 | } 6 | 7 | .signup-logo img { 8 | max-width: 150px; 9 | } 10 | 11 | .list { 12 | margin-bottom: 0; 13 | } 14 | -------------------------------------------------------------------------------- /src/app/pages/signup/signup.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { NgForm } from '@angular/forms'; 3 | import { Router } from '@angular/router'; 4 | 5 | import { UserData } from '../../providers/user-data'; 6 | 7 | import { UserOptions } from '../../interfaces/user-options'; 8 | 9 | 10 | 11 | @Component({ 12 | selector: 'page-signup', 13 | templateUrl: 'signup.html', 14 | styleUrls: ['./signup.scss'], 15 | }) 16 | export class SignupPage { 17 | signup: UserOptions = { username: '', password: '' }; 18 | submitted = false; 19 | 20 | constructor( 21 | public router: Router, 22 | public userData: UserData 23 | ) {} 24 | 25 | onSignup(form: NgForm) { 26 | this.submitted = true; 27 | 28 | if (form.valid) { 29 | this.userData.signup(this.signup.username); 30 | this.router.navigateByUrl('/app/tabs/schedule'); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/app/pages/speaker-detail/speaker-detail-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | 4 | import { SpeakerDetailPage } from './speaker-detail'; 5 | 6 | const routes: Routes = [ 7 | { 8 | path: '', 9 | component: SpeakerDetailPage 10 | } 11 | ]; 12 | 13 | @NgModule({ 14 | imports: [RouterModule.forChild(routes)], 15 | exports: [RouterModule] 16 | }) 17 | export class SpeakerDetailPageRoutingModule { } 18 | -------------------------------------------------------------------------------- /src/app/pages/speaker-detail/speaker-detail.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 |

{{speaker?.name}}

21 |
22 | 23 |
24 |

{{speaker?.about}} Say hello on social media!

25 | 26 |
27 | 28 | 29 | 30 | Twitter 31 | 32 | 33 | 34 | 35 | GitHub 36 | 37 | 38 | 39 | 40 | Instagram 41 | 42 |
43 |
44 | -------------------------------------------------------------------------------- /src/app/pages/speaker-detail/speaker-detail.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | import { SpeakerDetailPage } from './speaker-detail'; 5 | import { SpeakerDetailPageRoutingModule } from './speaker-detail-routing.module'; 6 | import { IonicModule } from '@ionic/angular'; 7 | 8 | @NgModule({ 9 | imports: [ 10 | CommonModule, 11 | IonicModule, 12 | SpeakerDetailPageRoutingModule 13 | ], 14 | declarations: [ 15 | SpeakerDetailPage, 16 | ] 17 | }) 18 | export class SpeakerDetailModule { } 19 | -------------------------------------------------------------------------------- /src/app/pages/speaker-detail/speaker-detail.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * Speaker Background 3 | */ 4 | 5 | ion-toolbar { 6 | position: absolute; 7 | 8 | top: 0; 9 | left: 0; 10 | right: 0; 11 | 12 | --background: transparent; 13 | --color: white; 14 | } 15 | 16 | ion-toolbar ion-button, 17 | ion-toolbar ion-back-button, 18 | ion-toolbar ion-menu-button { 19 | --color: white; 20 | } 21 | 22 | .speaker-background { 23 | position: relative; 24 | 25 | display: flex; 26 | 27 | padding-top: var(--ion-safe-area-top); 28 | 29 | align-items: center; 30 | justify-content: center; 31 | 32 | flex-direction: column; 33 | 34 | height: calc(250px + var(--ion-safe-area-top)); 35 | 36 | background: center / cover url(/assets/img/speaker-background.png) no-repeat; 37 | } 38 | 39 | .speaker-background img { 40 | width: 70px; 41 | border-radius: 50%; 42 | margin-top: calc(-1 * var(--ion-safe-area-top)); 43 | } 44 | 45 | .speaker-background h2 { 46 | position: absolute; 47 | 48 | bottom: 10px; 49 | 50 | color: white; 51 | } 52 | 53 | .md .speaker-background { 54 | box-shadow: rgba(0, 0, 0, 0.2) 0px 3px 1px -2px, rgba(0, 0, 0, 0.14) 0px 2px 2px 0px, rgba(0, 0, 0, 0.12) 0px 1px 5px 0px; 55 | } 56 | 57 | .ios .speaker-background { 58 | box-shadow: rgba(0, 0, 0, 0.12) 0px 4px 16px; 59 | } 60 | 61 | /* 62 | * Speaker Details 63 | */ 64 | 65 | .speaker-detail p { 66 | margin-left: 6px; 67 | margin-right: 6px; 68 | } 69 | 70 | .speaker-detail hr { 71 | margin-top: 20px; 72 | margin-bottom: 20px; 73 | 74 | background: var(--ion-color-step-150, #d7d8da); 75 | } 76 | -------------------------------------------------------------------------------- /src/app/pages/speaker-detail/speaker-detail.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { ActivatedRoute } from '@angular/router'; 3 | import { ConferenceData } from '../../providers/conference-data'; 4 | import { ActionSheetController } from '@ionic/angular'; 5 | import { InAppBrowser } from '@ionic-native/in-app-browser/ngx'; 6 | 7 | @Component({ 8 | selector: 'page-speaker-detail', 9 | templateUrl: 'speaker-detail.html', 10 | styleUrls: ['./speaker-detail.scss'], 11 | }) 12 | export class SpeakerDetailPage { 13 | speaker: any; 14 | 15 | constructor( 16 | private dataProvider: ConferenceData, 17 | private route: ActivatedRoute, 18 | public actionSheetCtrl: ActionSheetController, 19 | public confData: ConferenceData, 20 | public inAppBrowser: InAppBrowser, 21 | ) {} 22 | 23 | ionViewWillEnter() { 24 | this.dataProvider.load().subscribe((data: any) => { 25 | const speakerId = this.route.snapshot.paramMap.get('speakerId'); 26 | if (data && data.speakers) { 27 | for (const speaker of data.speakers) { 28 | if (speaker && speaker.id === speakerId) { 29 | this.speaker = speaker; 30 | break; 31 | } 32 | } 33 | } 34 | }); 35 | } 36 | 37 | openExternalUrl(url: string) { 38 | this.inAppBrowser.create( 39 | url, 40 | '_blank' 41 | ); 42 | } 43 | 44 | async openSpeakerShare(speaker: any) { 45 | const actionSheet = await this.actionSheetCtrl.create({ 46 | header: 'Share ' + speaker.name, 47 | buttons: [ 48 | { 49 | text: 'Copy Link', 50 | handler: () => { 51 | console.log( 52 | 'Copy link clicked on https://twitter.com/' + speaker.twitter 53 | ); 54 | if ( 55 | (window as any).cordova && 56 | (window as any).cordova.plugins.clipboard 57 | ) { 58 | (window as any).cordova.plugins.clipboard.copy( 59 | 'https://twitter.com/' + speaker.twitter 60 | ); 61 | } 62 | } 63 | }, 64 | { 65 | text: 'Share via ...' 66 | }, 67 | { 68 | text: 'Cancel', 69 | role: 'cancel' 70 | } 71 | ] 72 | }); 73 | 74 | await actionSheet.present(); 75 | } 76 | 77 | async openContact(speaker: any) { 78 | const mode = 'ios'; // this.config.get('mode'); 79 | 80 | const actionSheet = await this.actionSheetCtrl.create({ 81 | header: 'Contact ' + speaker.name, 82 | buttons: [ 83 | { 84 | text: `Email ( ${speaker.email} )`, 85 | icon: mode !== 'ios' ? 'mail' : null, 86 | handler: () => { 87 | window.open('mailto:' + speaker.email); 88 | } 89 | }, 90 | { 91 | text: `Call ( ${speaker.phone} )`, 92 | icon: mode !== 'ios' ? 'call' : null, 93 | handler: () => { 94 | window.open('tel:' + speaker.phone); 95 | } 96 | }, 97 | { 98 | text: 'Cancel', 99 | role: 'cancel' 100 | } 101 | ] 102 | }); 103 | 104 | await actionSheet.present(); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/app/pages/speaker-list/speaker-list-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | 4 | import { SpeakerListPage } from './speaker-list'; 5 | const routes: Routes = [ 6 | { 7 | path: '', 8 | component: SpeakerListPage 9 | } 10 | ]; 11 | 12 | @NgModule({ 13 | imports: [RouterModule.forChild(routes)], 14 | exports: [RouterModule] 15 | }) 16 | export class SpeakerListPageRoutingModule {} 17 | -------------------------------------------------------------------------------- /src/app/pages/speaker-list/speaker-list.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Speakers 7 | 8 | 9 | 10 | 11 | 12 | 13 | Speakers 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |

{{speaker.name}}

28 |

{{speaker.title}}

29 |
30 |
31 |
32 | 33 | 34 | 35 | 36 | 37 |

{{session.name}}

38 |
39 |
40 | 41 | 42 | 43 |

About {{speaker.name}}

44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 | -------------------------------------------------------------------------------- /src/app/pages/speaker-list/speaker-list.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { IonicModule } from '@ionic/angular'; 4 | 5 | import { SpeakerListPage } from './speaker-list'; 6 | import { SpeakerListPageRoutingModule } from './speaker-list-routing.module'; 7 | 8 | @NgModule({ 9 | imports: [ 10 | CommonModule, 11 | IonicModule, 12 | SpeakerListPageRoutingModule 13 | ], 14 | declarations: [SpeakerListPage], 15 | }) 16 | export class SpeakerListModule {} 17 | -------------------------------------------------------------------------------- /src/app/pages/speaker-list/speaker-list.scss: -------------------------------------------------------------------------------- 1 | .speaker-card { 2 | display: flex; 3 | flex-direction: column; 4 | } 5 | 6 | /* Due to the fact the cards are inside of columns the margins don't overlap 7 | * properly so we want to remove the extra margin between cards 8 | */ 9 | ion-col:not(:last-of-type) .speaker-card { 10 | margin-bottom: 0; 11 | } 12 | 13 | .speaker-card .speaker-item { 14 | --min-height: 85px; 15 | } 16 | 17 | .speaker-card .speaker-item h2 { 18 | font-size: 18px; 19 | font-weight: 500; 20 | letter-spacing: 0.02em; 21 | } 22 | 23 | .speaker-card .speaker-item p { 24 | font-size: 13px; 25 | letter-spacing: 0.02em; 26 | } 27 | 28 | .speaker-card ion-card-header { 29 | padding: 0; 30 | } 31 | 32 | .speaker-card ion-card-content { 33 | flex: 1 1 auto; 34 | 35 | padding: 0; 36 | } 37 | 38 | .ios ion-list { 39 | margin-bottom: 10px; 40 | } 41 | 42 | .md ion-list { 43 | border-top: 1px solid var(--ion-color-step-150, #d7d8da); 44 | 45 | padding: 0; 46 | } -------------------------------------------------------------------------------- /src/app/pages/speaker-list/speaker-list.spec.ts: -------------------------------------------------------------------------------- 1 | import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | import { TestBed, waitForAsync } from '@angular/core/testing'; 4 | import { ActionSheetController } from '@ionic/angular'; 5 | 6 | import { InAppBrowser } from '@ionic-native/in-app-browser/ngx'; 7 | import { SpeakerListPage } from './speaker-list'; 8 | import { ConferenceData } from '../../providers/conference-data'; 9 | 10 | const confDataSub = {}; 11 | 12 | describe('SpeakerListPage', () => { 13 | let fixture, app; 14 | beforeEach(waitForAsync(() => { 15 | const actionSheetSpy = jasmine.createSpyObj('ActionSheetController', [ 16 | 'create' 17 | ]); 18 | const routerSpy = jasmine.createSpyObj('Router', ['navigateByUrl']); 19 | const iabSpy = jasmine.createSpyObj('InAppBrowser', ['create']); 20 | 21 | TestBed.configureTestingModule({ 22 | declarations: [SpeakerListPage], 23 | schemas: [CUSTOM_ELEMENTS_SCHEMA], 24 | providers: [ 25 | { provide: ActionSheetController, useValue: actionSheetSpy }, 26 | { provide: InAppBrowser, useValue: iabSpy }, 27 | { provide: Router, useValue: routerSpy }, 28 | { provide: ConferenceData, useValue: confDataSub } 29 | ] 30 | }).compileComponents(); 31 | })); 32 | beforeEach(() => { 33 | fixture = TestBed.createComponent(SpeakerListPage); 34 | app = fixture.debugElement.componentInstance; 35 | }); 36 | it('should create the speaker list page', () => { 37 | expect(app).toBeTruthy(); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /src/app/pages/speaker-list/speaker-list.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { ConferenceData } from '../../providers/conference-data'; 3 | 4 | @Component({ 5 | selector: 'page-speaker-list', 6 | templateUrl: 'speaker-list.html', 7 | styleUrls: ['./speaker-list.scss'], 8 | }) 9 | export class SpeakerListPage { 10 | speakers: any[] = []; 11 | 12 | constructor(public confData: ConferenceData) {} 13 | 14 | ionViewDidEnter() { 15 | this.confData.getSpeakers().subscribe((speakers: any[]) => { 16 | this.speakers = speakers; 17 | }); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/app/pages/support/support-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | 4 | import { SupportPage } from './support'; 5 | 6 | const routes: Routes = [ 7 | { 8 | path: '', 9 | component: SupportPage 10 | } 11 | ]; 12 | 13 | @NgModule({ 14 | imports: [RouterModule.forChild(routes)], 15 | exports: [RouterModule] 16 | }) 17 | export class SupportPageRoutingModule { } 18 | -------------------------------------------------------------------------------- /src/app/pages/support/support.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Support 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 |
17 | 18 | 19 | Enter your support message below 20 | 21 | 22 | 23 | 24 | 25 |

26 | Support message is required 27 |

28 |
29 | 30 |
31 | Submit 32 |
33 |
34 |
35 | -------------------------------------------------------------------------------- /src/app/pages/support/support.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FormsModule } from '@angular/forms'; 4 | import { IonicModule } from '@ionic/angular'; 5 | import { SupportPage } from './support'; 6 | import { SupportPageRoutingModule } from './support-routing.module'; 7 | 8 | @NgModule({ 9 | imports: [ 10 | CommonModule, 11 | FormsModule, 12 | IonicModule, 13 | SupportPageRoutingModule 14 | ], 15 | declarations: [ 16 | SupportPage, 17 | ] 18 | }) 19 | export class SupportModule { } 20 | -------------------------------------------------------------------------------- /src/app/pages/support/support.scss: -------------------------------------------------------------------------------- 1 | .support-logo { 2 | padding: 20px 0; 3 | min-height: 200px; 4 | text-align: center; 5 | } 6 | 7 | .support-logo img { 8 | max-width: 150px; 9 | } 10 | 11 | .list { 12 | margin-bottom: 0; 13 | } 14 | -------------------------------------------------------------------------------- /src/app/pages/support/support.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { NgForm } from '@angular/forms'; 3 | 4 | import { AlertController, ToastController } from '@ionic/angular'; 5 | 6 | 7 | @Component({ 8 | selector: 'page-support', 9 | templateUrl: 'support.html', 10 | styleUrls: ['./support.scss'], 11 | }) 12 | export class SupportPage { 13 | submitted = false; 14 | supportMessage: string; 15 | 16 | constructor( 17 | public alertCtrl: AlertController, 18 | public toastCtrl: ToastController 19 | ) { } 20 | 21 | async ionViewDidEnter() { 22 | const toast = await this.toastCtrl.create({ 23 | message: 'This does not actually send a support request.', 24 | duration: 3000 25 | }); 26 | await toast.present(); 27 | } 28 | 29 | async submit(form: NgForm) { 30 | this.submitted = true; 31 | 32 | if (form.valid) { 33 | this.supportMessage = ''; 34 | this.submitted = false; 35 | 36 | const toast = await this.toastCtrl.create({ 37 | message: 'Your support request has been sent.', 38 | duration: 3000 39 | }); 40 | await toast.present(); 41 | } 42 | } 43 | 44 | // If the user enters text in the support question and then navigates 45 | // without submitting first, ask if they meant to leave the page 46 | // async ionViewCanLeave(): Promise { 47 | // // If the support message is empty we should just navigate 48 | // if (!this.supportMessage || this.supportMessage.trim().length === 0) { 49 | // return true; 50 | // } 51 | 52 | // return new Promise((resolve: any, reject: any) => { 53 | // const alert = await this.alertCtrl.create({ 54 | // title: 'Leave this page?', 55 | // message: 'Are you sure you want to leave this page? Your support message will not be submitted.', 56 | // buttons: [ 57 | // { text: 'Stay', handler: reject }, 58 | // { text: 'Leave', role: 'cancel', handler: resolve } 59 | // ] 60 | // }); 61 | 62 | // await alert.present(); 63 | // }); 64 | // } 65 | } 66 | -------------------------------------------------------------------------------- /src/app/pages/tabs-page/tabs-page-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | import { TabsPage } from './tabs-page'; 4 | import { SchedulePage } from '../schedule/schedule'; 5 | 6 | 7 | const routes: Routes = [ 8 | { 9 | path: 'tabs', 10 | component: TabsPage, 11 | children: [ 12 | { 13 | path: 'schedule', 14 | children: [ 15 | { 16 | path: '', 17 | component: SchedulePage, 18 | }, 19 | { 20 | path: 'session/:sessionId', 21 | loadChildren: () => import('../session-detail/session-detail.module').then(m => m.SessionDetailModule) 22 | } 23 | ] 24 | }, 25 | { 26 | path: 'speakers', 27 | children: [ 28 | { 29 | path: '', 30 | loadChildren: () => import('../speaker-list/speaker-list.module').then(m => m.SpeakerListModule) 31 | }, 32 | { 33 | path: 'session/:sessionId', 34 | loadChildren: () => import('../session-detail/session-detail.module').then(m => m.SessionDetailModule) 35 | }, 36 | { 37 | path: 'speaker-details/:speakerId', 38 | loadChildren: () => import('../speaker-detail/speaker-detail.module').then(m => m.SpeakerDetailModule) 39 | } 40 | ] 41 | }, 42 | { 43 | path: 'map', 44 | children: [ 45 | { 46 | path: '', 47 | loadChildren: () => import('../map/map.module').then(m => m.MapModule) 48 | } 49 | ] 50 | }, 51 | { 52 | path: 'about', 53 | children: [ 54 | { 55 | path: '', 56 | loadChildren: () => import('../about/about.module').then(m => m.AboutModule) 57 | } 58 | ] 59 | }, 60 | { 61 | path: '', 62 | redirectTo: '/app/tabs/schedule', 63 | pathMatch: 'full' 64 | } 65 | ] 66 | } 67 | ]; 68 | 69 | @NgModule({ 70 | imports: [RouterModule.forChild(routes)], 71 | exports: [RouterModule] 72 | }) 73 | export class TabsPageRoutingModule { } 74 | 75 | -------------------------------------------------------------------------------- /src/app/pages/tabs-page/tabs-page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Schedule 7 | 8 | 9 | 10 | 11 | Speakers 12 | 13 | 14 | 15 | 16 | Map 17 | 18 | 19 | 20 | 21 | About 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/app/pages/tabs-page/tabs-page.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { IonicModule } from '@ionic/angular'; 4 | 5 | import { TabsPage } from './tabs-page'; 6 | import { TabsPageRoutingModule } from './tabs-page-routing.module'; 7 | 8 | import { AboutModule } from '../about/about.module'; 9 | import { MapModule } from '../map/map.module'; 10 | import { ScheduleModule } from '../schedule/schedule.module'; 11 | import { SessionDetailModule } from '../session-detail/session-detail.module'; 12 | import { SpeakerDetailModule } from '../speaker-detail/speaker-detail.module'; 13 | import { SpeakerListModule } from '../speaker-list/speaker-list.module'; 14 | 15 | @NgModule({ 16 | imports: [ 17 | AboutModule, 18 | CommonModule, 19 | IonicModule, 20 | MapModule, 21 | ScheduleModule, 22 | SessionDetailModule, 23 | SpeakerDetailModule, 24 | SpeakerListModule, 25 | TabsPageRoutingModule 26 | ], 27 | declarations: [ 28 | TabsPage, 29 | ] 30 | }) 31 | export class TabsModule { } 32 | -------------------------------------------------------------------------------- /src/app/pages/tabs-page/tabs-page.scss: -------------------------------------------------------------------------------- 1 | .tabbar { 2 | justify-content: center; 3 | } 4 | 5 | .tab-button { 6 | max-width: 200px; 7 | } -------------------------------------------------------------------------------- /src/app/pages/tabs-page/tabs-page.spec.ts: -------------------------------------------------------------------------------- 1 | import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; 2 | import { TestBed, waitForAsync } from '@angular/core/testing'; 3 | 4 | import { TabsPage } from './tabs-page'; 5 | 6 | describe('TabsPage', () => { 7 | beforeEach(waitForAsync(() => { 8 | TestBed.configureTestingModule({ 9 | declarations: [TabsPage], 10 | schemas: [CUSTOM_ELEMENTS_SCHEMA] 11 | }).compileComponents(); 12 | })); 13 | 14 | it('should create the tabs page', () => { 15 | const fixture = TestBed.createComponent(TabsPage); 16 | const app = fixture.debugElement.componentInstance; 17 | expect(app).toBeTruthy(); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /src/app/pages/tabs-page/tabs-page.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | templateUrl: 'tabs-page.html' 5 | }) 6 | export class TabsPage {} 7 | -------------------------------------------------------------------------------- /src/app/pages/tutorial/tutorial-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | 4 | import { TutorialPage } from './tutorial'; 5 | 6 | const routes: Routes = [ 7 | { 8 | path: '', 9 | component: TutorialPage 10 | } 11 | ]; 12 | 13 | @NgModule({ 14 | imports: [RouterModule.forChild(routes)], 15 | exports: [RouterModule] 16 | }) 17 | export class TutorialPageRoutingModule { } 18 | -------------------------------------------------------------------------------- /src/app/pages/tutorial/tutorial.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Skip 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |

14 | Welcome to 15 | ICA 16 |

17 |

18 | The 19 | ionic conference app is a practical preview of the ionic framework in action, and a demonstration of proper code 20 | use. 21 |

22 |
23 | 24 | 25 | 26 |

What is Ionic?

27 |

28 | Ionic Framework is an open source SDK that enables developers to build high quality mobile apps with web technologies 29 | like HTML, CSS, and JavaScript.

30 |
31 | 32 | 33 | 34 |

What is Ionic Appflow?

35 |

36 | Ionic Appflow is a powerful set of services and features built on top of Ionic Framework that brings a totally new 37 | level of app development agility to mobile dev teams.

38 |
39 | 40 | 41 | 42 |

Ready to Play?

43 | 44 | Continue 45 | 46 | 47 |
48 |
49 |
50 | -------------------------------------------------------------------------------- /src/app/pages/tutorial/tutorial.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { IonicModule } from '@ionic/angular'; 4 | import { SwiperModule } from 'swiper/angular'; 5 | 6 | import { TutorialPage } from './tutorial'; 7 | import { TutorialPageRoutingModule } from './tutorial-routing.module'; 8 | 9 | @NgModule({ 10 | imports: [ 11 | CommonModule, 12 | IonicModule, 13 | TutorialPageRoutingModule, 14 | SwiperModule 15 | ], 16 | declarations: [TutorialPage], 17 | entryComponents: [TutorialPage], 18 | }) 19 | export class TutorialModule {} 20 | -------------------------------------------------------------------------------- /src/app/pages/tutorial/tutorial.scss: -------------------------------------------------------------------------------- 1 | ion-toolbar { 2 | --background: transparent; 3 | --border-color: transparent; 4 | } 5 | 6 | .slide-title { 7 | margin-top: 2.8rem; 8 | } 9 | 10 | .slide-image { 11 | max-height: 50%; 12 | max-width: 60%; 13 | margin: 36px 0; 14 | pointer-events: none; 15 | } 16 | 17 | b { 18 | font-weight: 500; 19 | } 20 | 21 | p { 22 | padding: 0 40px; 23 | font-size: 14px; 24 | line-height: 1.5; 25 | color: var(--ion-color-step-600, #60646b); 26 | 27 | b { 28 | color: var(--ion-text-color, #000000); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/app/pages/tutorial/tutorial.spec.ts: -------------------------------------------------------------------------------- 1 | import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | import { TestBed, waitForAsync } from '@angular/core/testing'; 4 | import { MenuController } from '@ionic/angular'; 5 | 6 | import { TutorialPage } from './tutorial'; 7 | 8 | import { IonicStorageModule } from '@ionic/storage'; 9 | describe('TutorialPage', () => { 10 | let fixture, app; 11 | beforeEach(waitForAsync(() => { 12 | const menuSpy = jasmine.createSpyObj('MenuController', [ 13 | 'toggle', 14 | 'enable' 15 | ]); 16 | const routerSpy = jasmine.createSpyObj('Router', ['navigateByUrl']); 17 | 18 | TestBed.configureTestingModule({ 19 | declarations: [TutorialPage], 20 | schemas: [CUSTOM_ELEMENTS_SCHEMA], 21 | imports: [IonicStorageModule.forRoot()], 22 | providers: [ 23 | { provide: MenuController, useValue: menuSpy }, 24 | { provide: Router, useValue: routerSpy } 25 | ] 26 | }).compileComponents(); 27 | })); 28 | 29 | beforeEach(() => { 30 | fixture = TestBed.createComponent(TutorialPage); 31 | app = fixture.debugElement.componentInstance; 32 | }); 33 | it('should create the tutorial page', () => { 34 | expect(app).toBeTruthy(); 35 | }); 36 | 37 | it('should check the tutorial status', async () => { 38 | const didTuts = await app.storage.get('ion_did_tutorial'); 39 | expect(didTuts).toBeFalsy(); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /src/app/pages/tutorial/tutorial.ts: -------------------------------------------------------------------------------- 1 | import { Component, ChangeDetectorRef } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | 4 | import { MenuController } from '@ionic/angular'; 5 | 6 | import { Storage } from '@ionic/storage'; 7 | import Swiper from 'swiper'; 8 | 9 | @Component({ 10 | selector: 'page-tutorial', 11 | templateUrl: 'tutorial.html', 12 | styleUrls: ['./tutorial.scss'], 13 | }) 14 | export class TutorialPage { 15 | showSkip = true; 16 | private slides: Swiper; 17 | 18 | constructor( 19 | public menu: MenuController, 20 | public router: Router, 21 | public storage: Storage, 22 | private cd: ChangeDetectorRef 23 | ) {} 24 | 25 | startApp() { 26 | this.router 27 | .navigateByUrl('/app/tabs/schedule', { replaceUrl: true }) 28 | .then(() => this.storage.set('ion_did_tutorial', true)); 29 | } 30 | 31 | setSwiperInstance(swiper: Swiper) { 32 | this.slides = swiper; 33 | } 34 | 35 | onSlideChangeStart() { 36 | this.showSkip = !this.slides.isEnd; 37 | this.cd.detectChanges(); 38 | } 39 | 40 | ionViewWillEnter() { 41 | this.storage.get('ion_did_tutorial').then(res => { 42 | if (res === true) { 43 | this.router.navigateByUrl('/app/tabs/schedule', { replaceUrl: true }); 44 | } 45 | }); 46 | 47 | this.menu.enable(false); 48 | } 49 | 50 | ionViewDidLeave() { 51 | // enable the root left menu when leaving the tutorial page 52 | this.menu.enable(true); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/app/providers/check-tutorial.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { CanLoad, Router } from '@angular/router'; 3 | import { Storage } from '@ionic/storage'; 4 | @Injectable({ 5 | providedIn: 'root' 6 | }) 7 | export class CheckTutorial implements CanLoad { 8 | constructor(private storage: Storage, private router: Router) {} 9 | 10 | canLoad() { 11 | return this.storage.get('ion_did_tutorial').then(res => { 12 | if (res) { 13 | this.router.navigate(['/app', 'tabs', 'schedule']); 14 | return false; 15 | } else { 16 | return true; 17 | } 18 | }); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/app/providers/user-data.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Storage } from '@ionic/storage'; 3 | 4 | 5 | @Injectable({ 6 | providedIn: 'root' 7 | }) 8 | export class UserData { 9 | favorites: string[] = []; 10 | HAS_LOGGED_IN = 'hasLoggedIn'; 11 | HAS_SEEN_TUTORIAL = 'hasSeenTutorial'; 12 | 13 | constructor( 14 | public storage: Storage 15 | ) { } 16 | 17 | hasFavorite(sessionName: string): boolean { 18 | return (this.favorites.indexOf(sessionName) > -1); 19 | } 20 | 21 | addFavorite(sessionName: string): void { 22 | this.favorites.push(sessionName); 23 | } 24 | 25 | removeFavorite(sessionName: string): void { 26 | const index = this.favorites.indexOf(sessionName); 27 | if (index > -1) { 28 | this.favorites.splice(index, 1); 29 | } 30 | } 31 | 32 | login(username: string): Promise { 33 | return this.storage.set(this.HAS_LOGGED_IN, true).then(() => { 34 | this.setUsername(username); 35 | return window.dispatchEvent(new CustomEvent('user:login')); 36 | }); 37 | } 38 | 39 | signup(username: string): Promise { 40 | return this.storage.set(this.HAS_LOGGED_IN, true).then(() => { 41 | this.setUsername(username); 42 | return window.dispatchEvent(new CustomEvent('user:signup')); 43 | }); 44 | } 45 | 46 | logout(): Promise { 47 | return this.storage.remove(this.HAS_LOGGED_IN).then(() => { 48 | return this.storage.remove('username'); 49 | }).then(() => { 50 | window.dispatchEvent(new CustomEvent('user:logout')); 51 | }); 52 | } 53 | 54 | setUsername(username: string): Promise { 55 | return this.storage.set('username', username); 56 | } 57 | 58 | getUsername(): Promise { 59 | return this.storage.get('username').then((value) => { 60 | return value; 61 | }); 62 | } 63 | 64 | isLoggedIn(): Promise { 65 | return this.storage.get(this.HAS_LOGGED_IN).then((value) => { 66 | return value === true; 67 | }); 68 | } 69 | 70 | checkHasSeenTutorial(): Promise { 71 | return this.storage.get(this.HAS_SEEN_TUTORIAL).then((value) => { 72 | return value; 73 | }); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/assets/img/about/Archive.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/src/assets/img/about/Archive.zip -------------------------------------------------------------------------------- /src/assets/img/about/austin.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/src/assets/img/about/austin.jpg -------------------------------------------------------------------------------- /src/assets/img/about/chicago.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/src/assets/img/about/chicago.jpg -------------------------------------------------------------------------------- /src/assets/img/about/madison.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/src/assets/img/about/madison.jpg -------------------------------------------------------------------------------- /src/assets/img/about/seattle.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/src/assets/img/about/seattle.jpg -------------------------------------------------------------------------------- /src/assets/img/appicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/src/assets/img/appicon.png -------------------------------------------------------------------------------- /src/assets/img/appicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/assets/img/ica-slidebox-img-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/src/assets/img/ica-slidebox-img-1.png -------------------------------------------------------------------------------- /src/assets/img/ica-slidebox-img-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/src/assets/img/ica-slidebox-img-2.png -------------------------------------------------------------------------------- /src/assets/img/ica-slidebox-img-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/src/assets/img/ica-slidebox-img-3.png -------------------------------------------------------------------------------- /src/assets/img/ica-slidebox-img-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/src/assets/img/ica-slidebox-img-4.png -------------------------------------------------------------------------------- /src/assets/img/ionic-logo-white.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/assets/img/speaker-background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/src/assets/img/speaker-background.png -------------------------------------------------------------------------------- /src/assets/img/speakers/bear.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/src/assets/img/speakers/bear.jpg -------------------------------------------------------------------------------- /src/assets/img/speakers/cheetah.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/src/assets/img/speakers/cheetah.jpg -------------------------------------------------------------------------------- /src/assets/img/speakers/duck.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/src/assets/img/speakers/duck.jpg -------------------------------------------------------------------------------- /src/assets/img/speakers/eagle.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/src/assets/img/speakers/eagle.jpg -------------------------------------------------------------------------------- /src/assets/img/speakers/elephant.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/src/assets/img/speakers/elephant.jpg -------------------------------------------------------------------------------- /src/assets/img/speakers/giraffe.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/src/assets/img/speakers/giraffe.jpg -------------------------------------------------------------------------------- /src/assets/img/speakers/iguana.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/src/assets/img/speakers/iguana.jpg -------------------------------------------------------------------------------- /src/assets/img/speakers/kitten.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/src/assets/img/speakers/kitten.jpg -------------------------------------------------------------------------------- /src/assets/img/speakers/lion.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/src/assets/img/speakers/lion.jpg -------------------------------------------------------------------------------- /src/assets/img/speakers/mouse.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/src/assets/img/speakers/mouse.jpg -------------------------------------------------------------------------------- /src/assets/img/speakers/puppy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/src/assets/img/speakers/puppy.jpg -------------------------------------------------------------------------------- /src/assets/img/speakers/rabbit.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/src/assets/img/speakers/rabbit.jpg -------------------------------------------------------------------------------- /src/assets/img/speakers/turtle.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionic-team/ionic-e2e-example/fffed0d77fad002953ec108d2b40416e8d1e1766/src/assets/img/speakers/turtle.jpg -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // The file contents for the current environment will overwrite these during build. 2 | // The build system defaults to the dev environment which uses `environment.ts`, but if you do 3 | // `ng build --env=prod` then `environment.prod.ts` will be used instead. 4 | // The list of which env maps to which file can be found in `.angular-cli.json`. 5 | export const environment = { 6 | production: false 7 | }; 8 | 9 | /* 10 | * In development mode, to ignore zone related error stack frames such as 11 | * `zone.run`, `zoneDelegate.invokeTask` for easier debugging, you can 12 | * import the following file, but please comment it out in production mode 13 | * because it will have performance impact when throw error 14 | */ 15 | // import 'zone.js/plugins/zone-error'; // Included with Angular CLI. 16 | -------------------------------------------------------------------------------- /src/global.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * App Global CSS 3 | * ---------------------------------------------------------------------------- 4 | * Put style rules here that you want to apply globally. These styles are for 5 | * the entire app and not just one component. Additionally, this file can be 6 | * used as an entry point to import other CSS/Sass files to be included in the 7 | * output CSS. 8 | * For more information on global stylesheets, visit the documentation: 9 | * https://ionicframework.com/docs/layout/global-stylesheets 10 | */ 11 | 12 | /* Core CSS required for Ionic components to work properly */ 13 | @import "~@ionic/angular/css/core.css"; 14 | 15 | /* Basic CSS for apps built with Ionic */ 16 | @import "~@ionic/angular/css/normalize.css"; 17 | @import "~@ionic/angular/css/structure.css"; 18 | @import "~@ionic/angular/css/typography.css"; 19 | 20 | /* Optional CSS utils that can be commented out */ 21 | @import "~@ionic/angular/css/padding.css"; 22 | @import "~@ionic/angular/css/float-elements.css"; 23 | @import "~@ionic/angular/css/text-alignment.css"; 24 | @import "~@ionic/angular/css/text-transformation.css"; 25 | @import "~@ionic/angular/css/flex-utils.css"; 26 | 27 | 28 | /* 29 | * App CSS 30 | * ---------------------------------------------------------------------------- 31 | * Imports a file that can contain Sass/CSS that should be used throughout 32 | * the entire app. 33 | */ 34 | 35 | @import "./app/app.scss"; 36 | 37 | @import '~swiper/scss'; 38 | @import '~@ionic/angular/css/ionic-swiper'; 39 | 40 | // placed here instead of tutorial.scss due to slide els not getting ng scoping attribute 41 | .swiper-slide { 42 | display: block; 43 | } -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Ionic Conference App 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | process.env.CHROME_BIN = require('puppeteer').executablePath(); 5 | 6 | module.exports = function (config) { 7 | config.set({ 8 | basePath: '', 9 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 10 | plugins: [ 11 | require('karma-jasmine'), 12 | require('karma-chrome-launcher'), 13 | require('karma-jasmine-html-reporter'), 14 | require('karma-coverage-istanbul-reporter'), 15 | require('@angular-devkit/build-angular/plugins/karma') 16 | ], 17 | client: { 18 | clearContext: false // leave Jasmine Spec Runner output visible in browser 19 | }, 20 | coverageIstanbulReporter: { 21 | dir: require('path').join(__dirname, 'coverage'), 22 | reports: ['html', 'lcovonly'], 23 | fixWebpackSourcePaths: true 24 | }, 25 | reporters: ['progress', 'kjhtml'], 26 | port: 9876, 27 | colors: true, 28 | logLevel: config.LOG_INFO, 29 | autoWatch: true, 30 | browsers: ['Chrome', 'ChromeHeadless'], 31 | customLaunchers: { 32 | ChromeHeadlessCI: { 33 | base: 'ChromeHeadless', 34 | flags: ['--no-sandbox', '--disable-gpu'] 35 | } 36 | }, 37 | singleRun: false 38 | }); 39 | }; 40 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import '@angular/compiler'; 2 | 3 | import { enableProdMode } from '@angular/core'; 4 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 5 | 6 | import { AppModule } from './app/app.module'; 7 | import { environment } from './environments/environment'; 8 | 9 | if (environment.production) { 10 | enableProdMode(); 11 | } 12 | 13 | platformBrowserDynamic().bootstrapModule(AppModule) 14 | .catch(err => console.log(err)); 15 | -------------------------------------------------------------------------------- /src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Ionic Conference", 3 | "short_name": "Ionic Conference", 4 | "start_url": "index.html", 5 | "display": "standalone", 6 | "icons": [ 7 | { 8 | "src": "assets/img/appicon.png", 9 | "sizes": "512x512", 10 | "type": "image/png" 11 | } 12 | ], 13 | "background_color": "#387ef5", 14 | "theme_color": "#387ef5" 15 | } 16 | -------------------------------------------------------------------------------- /src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | // BROWSER POLYFILLS 18 | // --------------------------------------------------------------------------------- 19 | 20 | // IE9, IE10 and IE11 requires all of the following polyfills. 21 | // import 'core-js/es6/symbol'; 22 | // import 'core-js/es6/object'; 23 | // import 'core-js/es6/function'; 24 | // import 'core-js/es6/parse-int'; 25 | // import 'core-js/es6/parse-float'; 26 | // import 'core-js/es6/number'; 27 | // import 'core-js/es6/math'; 28 | // import 'core-js/es6/string'; 29 | // import 'core-js/es6/date'; 30 | // import 'core-js/es6/array'; 31 | // import 'core-js/es6/regexp'; 32 | // import 'core-js/es6/map'; 33 | // import 'core-js/es6/weak-map'; 34 | // import 'core-js/es6/set'; 35 | 36 | /** IE10 and IE11 requires the following for the Reflect API. */ 37 | // import 'core-js/es6/reflect'; 38 | 39 | // Evergreen browsers require these. 40 | // Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove. 41 | 42 | 43 | // Required to support Web Animations `@angular/platform-browser/animations`. 44 | 45 | // Zone JS is required by Angular itself. 46 | import 'zone.js'; // Included with Angular CLI. 47 | 48 | // APPLICATION IMPORTS 49 | // ------------------------------------------------------------------------ 50 | 51 | /** 52 | * Date, currency, decimal and percent pipes. 53 | * Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10 54 | */ 55 | // import 'intl'; // Run `npm install --save intl`. 56 | /** 57 | * Need to import at least one locale-data with intl. 58 | */ 59 | // import 'intl/locale-data/jsonp/en'; 60 | -------------------------------------------------------------------------------- /src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: any; 11 | 12 | // First, initialize the Angular testing environment. 13 | getTestBed().initTestEnvironment( 14 | BrowserDynamicTestingModule, 15 | platformBrowserDynamicTesting(), { 16 | teardown: { destroyAfterEach: false } 17 | } 18 | ); 19 | // Then we find all the tests. 20 | const context = require.context('./', true, /\.spec\.ts$/); 21 | // And load the modules. 22 | context.keys().map(context); 23 | -------------------------------------------------------------------------------- /src/zone-flags.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Prevents Angular change detection from 3 | * running with certain Web Component callbacks 4 | */ 5 | (window as any).__Zone_disable_customElements = true; 6 | -------------------------------------------------------------------------------- /tests/config/wdio.android.config.ts: -------------------------------------------------------------------------------- 1 | import config from './wdio.shared.appium.config'; 2 | 3 | // ============ 4 | // Capabilities 5 | // ============ 6 | // 7 | // For all capabilities please check 8 | // http://appium.io/docs/en/writing-running-appium/caps/#general-capabilities 9 | // 10 | config.capabilities = [ 11 | { 12 | // The defaults you need to have in your config 13 | platformName: 'Android', 14 | maxInstances: 1, 15 | // For W3C the appium capabilities need to have an extension prefix 16 | // http://appium.io/docs/en/writing-running-appium/caps/ 17 | // This is `appium:` for all Appium Capabilities which can be found here 18 | 'appium:deviceName': 'Pixel_3_11.0', 19 | 'appium:platformVersion': '11', 20 | 'appium:orientation': 'PORTRAIT', 21 | 'appium:automationName': 'UiAutomator2', 22 | // The path to the app 23 | 'appium:app': 'platforms/android/app/build/outputs/apk/debug/app-debug.apk', 24 | 'appium:appWaitActivity': 'com.ionicframework.conferenceapp.MainActivity', 25 | 'appium:newCommandTimeout': 240, 26 | // This will automatically start the iOS app in a webview context, 27 | // if your app starts in a native context then please put this to false and handle your own 28 | // context switching 29 | 'appium:autoWebview': true, 30 | // Read the reset strategies very well, they differ per platform, see 31 | // http://appium.io/docs/en/writing-running-appium/other/reset-strategies/ 32 | // When enabling the noReset the App will NOT be re-installed between sessions 33 | // This means that every test needs to maintain it's own state 34 | // `"appium:noReset":false` means that the app is removed and installed 35 | // between each test 36 | 'appium:noReset': true, 37 | // This will prevent appium to restart the app between the sessions, 38 | // meaning between each spec file 39 | // @ts-ignore 40 | 'appium:dontStopAppOnReset': true, 41 | }, 42 | ]; 43 | 44 | exports.config = config; 45 | -------------------------------------------------------------------------------- /tests/config/wdio.ios.config.ts: -------------------------------------------------------------------------------- 1 | import config from './wdio.shared.appium.config'; 2 | 3 | // ============ 4 | // Capabilities 5 | // ============ 6 | // 7 | // For all capabilities please check 8 | // http://appium.io/docs/en/writing-running-appium/caps/#general-capabilities 9 | // 10 | config.capabilities = [ 11 | { 12 | platformName: 'iOS', 13 | maxInstances: 1, 14 | // For W3C the appium capabilities need to have an extension prefix 15 | // This is `appium:` for all Appium Capabilities which can be found here 16 | // http://appium.io/docs/en/writing-running-appium/caps/ 17 | 'appium:deviceName': 'iPhone 13 Pro Max', 18 | 'appium:platformVersion': '15.2', 19 | 'appium:orientation': 'PORTRAIT', 20 | 'appium:automationName': 'XCUITest', 21 | // The path to the app 22 | 'appium:app': './platforms/ios/build/emulator/Ionic Conference App.app', 23 | 'appium:newCommandTimeout': 240, 24 | // This will automatically start the iOS app in a webview context, 25 | // if your app starts in a native context then please put this to false and handle your own 26 | // context switching 27 | 'appium:autoWebview': true, 28 | // Read the reset strategies very well, they differ per platform, see 29 | // http://appium.io/docs/en/writing-running-appium/other/reset-strategies/ 30 | // When enabling the noReset the App will NOT be re-installed between sessions 31 | // This means that every test needs to maintain it's own state 32 | // `"appium:noReset":false` means that the app is removed and installed 33 | // between each test 34 | 'appium:noReset': true, 35 | }, 36 | ]; 37 | config.maxInstances = 1; 38 | 39 | exports.config = config; 40 | -------------------------------------------------------------------------------- /tests/config/wdio.shared.appium.config.ts: -------------------------------------------------------------------------------- 1 | import { config } from './wdio.shared.config'; 2 | 3 | // 4 | // ===================== 5 | // Server Configurations 6 | // ===================== 7 | // 8 | // The server port Appium is running on 9 | // 10 | config.port = 4723; 11 | 12 | // 13 | // ================ 14 | // Services: Appium 15 | // ================ 16 | // 17 | config.services = (config.services ? config.services : []).concat([ 18 | [ 19 | 'appium', 20 | { 21 | // This will use the globally installed version of Appium 22 | command: 'appium', 23 | args: { 24 | // This is needed to tell Appium that we can execute local ADB commands 25 | // and to automatically download the latest version of ChromeDriver 26 | relaxedSecurity: true, 27 | // Only log Appium logs in verbose mode 28 | ...(process.env.VERBOSE === 'true' ? { log: './appium.log' } : {}), 29 | }, 30 | }, 31 | ], 32 | ]); 33 | 34 | export default config; 35 | -------------------------------------------------------------------------------- /tests/config/wdio.web.config.ts: -------------------------------------------------------------------------------- 1 | import { config } from './wdio.shared.config'; 2 | 3 | // 4 | // ===== 5 | // Specs 6 | // ===== 7 | // 8 | // Normally the specs are written like 9 | // specs: ["./tests/**/*.spec.ts"], 10 | // but for watch mode we want to run all specs in 1 instance so 11 | // the specs are an array in an array 12 | config.specs = [['./tests/**/*.spec.ts']]; 13 | config.filesToWatch = ['./tests/**/*.spec.ts']; 14 | 15 | // 16 | // ================ 17 | // Services: Chrome 18 | // ================ 19 | // 20 | config.services = (config.services ? config.services : []).concat([ 21 | [ 22 | 'chromedriver', 23 | { 24 | args: [ 25 | '--use-fake-ui-for-media-stream', 26 | '--use-fake-device-for-media-stream', 27 | ], 28 | }, 29 | ], 30 | ]); 31 | 32 | // ============ 33 | // Capabilities 34 | // ============ 35 | // 36 | // For all capabilities please check 37 | // http://appium.io/docs/en/writing-running-appium/caps/#general-capabilities 38 | // 39 | config.capabilities = [ 40 | { 41 | maxInstances: 1, 42 | browserName: 'chrome', 43 | 'goog:chromeOptions': { 44 | args: ['--window-size=500,1000'], 45 | // See https://chromedriver.chromium.org/mobile-emulation 46 | // For more details 47 | mobileEmulation: { 48 | deviceMetrics: { width: 393, height: 851, pixelRatio: 3 }, 49 | userAgent: 50 | 'Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Mobile Safari/537.36', 51 | }, 52 | prefs: { 53 | 'profile.default_content_setting_values.media_stream_camera': 1, 54 | 'profile.default_content_setting_values.media_stream_mic': 1, 55 | 'profile.default_content_setting_values.notifications': 1, 56 | }, 57 | }, 58 | }, 59 | ]; 60 | 61 | exports.config = config; 62 | -------------------------------------------------------------------------------- /tests/helpers/browser.ts: -------------------------------------------------------------------------------- 1 | export async function getUrl(): Promise { 2 | return new URL(await browser.getUrl()); 3 | } 4 | -------------------------------------------------------------------------------- /tests/helpers/definitions.ts: -------------------------------------------------------------------------------- 1 | export interface ElementActionOptions { 2 | /** 3 | * How long to wait (in ms) for the element to be visible before 4 | * the test fails. Default: 5000 ms 5 | */ 6 | visibilityTimeout?: number; 7 | } 8 | 9 | export interface ElementSelector { 10 | text?: string; 11 | } 12 | -------------------------------------------------------------------------------- /tests/helpers/element.ts: -------------------------------------------------------------------------------- 1 | import { ElementActionOptions } from './definitions'; 2 | 3 | export async function waitForElement(selector: string, { visibilityTimeout = 5000 }: ElementActionOptions = {}) { 4 | const el = await $(selector); 5 | await el.waitForDisplayed({ timeout: visibilityTimeout }); 6 | return el; 7 | } 8 | 9 | export async function blur(selector: string, { visibilityTimeout = 5000 }: ElementActionOptions = {}) { 10 | return browser.execute((sel) => { 11 | const el = document.querySelector(sel); 12 | if (el) { 13 | (el as any).blur(); 14 | } 15 | }, selector); 16 | } 17 | 18 | export async function tryAcceptAlert() { 19 | try { 20 | return driver.acceptAlert(); 21 | } catch (e) { 22 | console.warn('No alert to accept'); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/helpers/index.ts: -------------------------------------------------------------------------------- 1 | import type { Expect } from 'expect-webdriverio'; 2 | export { Expect }; 3 | 4 | export * from './definitions'; 5 | export * from './platform/index'; 6 | export * from './element'; 7 | export * from './gestures'; 8 | export * from './browser'; 9 | export * from './storage'; 10 | 11 | export * from './ionic'; 12 | -------------------------------------------------------------------------------- /tests/helpers/ionic/components/alert.ts: -------------------------------------------------------------------------------- 1 | import { IonicComponent } from './component'; 2 | 3 | export class IonicAlert extends IonicComponent { 4 | constructor(selector?: string | WebdriverIO.Element) { 5 | super(selector ?? 'ion-alert'); 6 | } 7 | 8 | get input() { 9 | return $(this.selector).$(`.alert-input`); 10 | } 11 | 12 | async button(buttonTitle: string) { 13 | return $(this.selector).$(`button=${buttonTitle}`); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/helpers/ionic/components/button.ts: -------------------------------------------------------------------------------- 1 | import { Ionic$ } from '..'; 2 | import { ElementActionOptions } from '../..'; 3 | import { IonicComponent } from './component'; 4 | 5 | export interface TapButtonOptions extends ElementActionOptions { 6 | /** 7 | * Whether to scroll the element into view first. Default: true 8 | */ 9 | scroll?: boolean; 10 | } 11 | 12 | export class IonicButton extends IonicComponent { 13 | constructor(selector: string) { 14 | super(selector); 15 | } 16 | 17 | static withTitle(buttonTitle: string): IonicButton { 18 | return new IonicButton(`ion-button=${buttonTitle}`); 19 | } 20 | 21 | async tap({ 22 | visibilityTimeout = 5000, 23 | scroll = true, 24 | }: TapButtonOptions = {}) { 25 | const button = await Ionic$.$(this.selector as string); 26 | await button.waitForDisplayed({ timeout: visibilityTimeout }); 27 | if (scroll) { 28 | await button.scrollIntoView(); 29 | } 30 | return button.click(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tests/helpers/ionic/components/component.ts: -------------------------------------------------------------------------------- 1 | export class IonicComponent { 2 | constructor(public selector: string | WebdriverIO.Element) { 3 | } 4 | 5 | get $() { 6 | return import('./page').then(async ({ IonicPage }) => { 7 | if (typeof this.selector === 'string') { 8 | const activePage = await IonicPage.active(); 9 | return activePage.$(this.selector); 10 | } 11 | 12 | return this.selector; 13 | }); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/helpers/ionic/components/content.ts: -------------------------------------------------------------------------------- 1 | import { IonicComponent } from './component'; 2 | 3 | export class IonicContent extends IonicComponent { 4 | constructor(selector: string) { 5 | super(selector); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/helpers/ionic/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from './button'; 2 | export * from './content'; 3 | export * from './input'; 4 | export * from './item'; 5 | export * from './menu'; 6 | export * from './page'; 7 | export * from './segment'; 8 | export * from './select'; 9 | export * from './slides'; 10 | export * from './textarea'; 11 | export * from './toast'; 12 | -------------------------------------------------------------------------------- /tests/helpers/ionic/components/input.ts: -------------------------------------------------------------------------------- 1 | import { IonicComponent } from './component'; 2 | import { Ionic$ } from '..'; 3 | import { ElementActionOptions } from '../..'; 4 | 5 | export class IonicInput extends IonicComponent { 6 | constructor(selector: string) { 7 | super(selector); 8 | } 9 | 10 | async setValue( 11 | value: string, 12 | { visibilityTimeout = 5000 }: ElementActionOptions = {} 13 | ) { 14 | const el = await Ionic$.$(this.selector as string); 15 | await el.waitForDisplayed({ timeout: visibilityTimeout }); 16 | 17 | const ionTags = ['ion-input', 'ion-textarea']; 18 | if (ionTags.indexOf(await el.getTagName()) >= 0) { 19 | const input = await el.$('input,textarea'); 20 | await input.setValue(value); 21 | } else { 22 | return el.setValue(value); 23 | } 24 | } 25 | 26 | async getValue({ visibilityTimeout = 5000 }: ElementActionOptions = {}) { 27 | const el = await Ionic$.$(this.selector as string); 28 | await el.waitForDisplayed({ timeout: visibilityTimeout }); 29 | 30 | const ionTags = ['ion-input', 'ion-textarea']; 31 | if (ionTags.indexOf(await el.getTagName()) >= 0) { 32 | const input = await el.$('input,textarea'); 33 | return input.getValue(); 34 | } else { 35 | return el.getValue(); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/helpers/ionic/components/item.ts: -------------------------------------------------------------------------------- 1 | import { TapButtonOptions } from '.'; 2 | import { Ionic$ } from '..'; 3 | import { IonicComponent } from './component'; 4 | 5 | export class IonicItem extends IonicComponent { 6 | constructor(selector: string) { 7 | super(selector); 8 | } 9 | 10 | static withTitle(buttonTitle: string): IonicItem { 11 | return new IonicItem(`ion-item=${buttonTitle}`); 12 | } 13 | 14 | async tap({ 15 | visibilityTimeout = 5000, 16 | scroll = true, 17 | }: TapButtonOptions = {}) { 18 | const button = await Ionic$.$(this.selector as string); 19 | await button.waitForDisplayed({ timeout: visibilityTimeout }); 20 | if (scroll) { 21 | await button.scrollIntoView(); 22 | } 23 | return button.click(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/helpers/ionic/components/menu.ts: -------------------------------------------------------------------------------- 1 | import { Ionic$ } from '..'; 2 | import { ElementActionOptions } from '../..'; 3 | import { IonicComponent } from './component'; 4 | 5 | export interface OpenMenuOptions extends ElementActionOptions { 6 | delayForAnimation?: boolean; 7 | } 8 | 9 | export class IonicMenu extends IonicComponent { 10 | constructor(selector?: string) { 11 | super(selector || 'ion-menu'); 12 | } 13 | 14 | get menuButton() { 15 | return Ionic$.$('.ion-page:not(.ion-page-hidden) ion-menu-button'); 16 | } 17 | 18 | async open({ 19 | delayForAnimation = true, 20 | visibilityTimeout = 5000, 21 | }: OpenMenuOptions = {}) { 22 | await ( 23 | await this.menuButton 24 | ).waitForDisplayed({ timeout: visibilityTimeout }); 25 | await (await this.menuButton).click(); 26 | 27 | // Let the menu animate open/closed 28 | if (delayForAnimation) { 29 | return driver.pause(500); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tests/helpers/ionic/components/page.ts: -------------------------------------------------------------------------------- 1 | import { IonicComponent } from './component'; 2 | 3 | export class IonicPage extends IonicComponent { 4 | static async active() { 5 | await driver.waitUntil( 6 | async () => { 7 | const currentPages = await $$('.ion-page:not(.ion-page-hidden)'); 8 | for (const page of currentPages) { 9 | if ((await page.isDisplayed())) { 10 | return true; 11 | } 12 | } 13 | return false; 14 | }, { 15 | timeout: 10000, 16 | timeoutMsg: 'Unable to find any visible pages', 17 | interval: 500, 18 | } 19 | ); 20 | 21 | const allPages = await $$('.ion-page:not(.ion-page-hidden)'); 22 | 23 | const pages: WebdriverIO.Element[] = []; 24 | 25 | // Collect visible pages 26 | for (const page of allPages) { 27 | if ((await page.isDisplayed())) { 28 | pages.push(page); 29 | } 30 | } 31 | 32 | // Collect all the visible pages in the app 33 | const pagesAndParents: WebdriverIO.Element[][] = []; 34 | for (const page of pages) { 35 | const path = await this.getPath(page); 36 | pagesAndParents.push(path); 37 | } 38 | 39 | // Reverse sort the pages by the ones that have the most parent elements first, since 40 | // we assume pages deeper in the tree are more likely to be "active" than ones higher up 41 | const activePage = pagesAndParents.sort((a, b) => b.length - a.length)[0][0]; 42 | 43 | return activePage; 44 | } 45 | 46 | static async getPath(el: WebdriverIO.Element) { 47 | const path = [el]; 48 | 49 | let p = el; 50 | while (p) { 51 | p = await p.parentElement(); 52 | if (p.error) { 53 | break; 54 | } 55 | path.push(p); 56 | } 57 | 58 | return path; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /tests/helpers/ionic/components/segment.ts: -------------------------------------------------------------------------------- 1 | import { IonicComponent } from './component'; 2 | 3 | import { TapButtonOptions } from './button'; 4 | import { Ionic$ } from '..'; 5 | 6 | export class IonicSegment extends IonicComponent { 7 | constructor(selector: string | WebdriverIO.Element) { 8 | super(selector); 9 | } 10 | 11 | async button(buttonTitle: string) { 12 | const segmentButtons = await (await this.$).$$('ion-segment-button'); 13 | for (const button of segmentButtons) { 14 | if ( 15 | (await button.getText()).toLocaleLowerCase() === 16 | buttonTitle.toLocaleLowerCase() 17 | ) { 18 | return new IonicSegmentButton(button); 19 | } 20 | } 21 | return Promise.resolve(null); 22 | } 23 | } 24 | 25 | export class IonicSegmentButton extends IonicComponent { 26 | async tap({ 27 | visibilityTimeout = 5000, 28 | scroll = true, 29 | }: TapButtonOptions = {}) { 30 | const button = await Ionic$.$(this.selector as string); 31 | await button.waitForDisplayed({ timeout: visibilityTimeout }); 32 | if (scroll) { 33 | await button.scrollIntoView(); 34 | } 35 | return button.click(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tests/helpers/ionic/components/select.ts: -------------------------------------------------------------------------------- 1 | import { pause, waitForElement } from '../..'; 2 | import { IonicComponent } from './component'; 3 | 4 | export class IonicSelect extends IonicComponent { 5 | constructor(selector: string) { 6 | super(selector); 7 | } 8 | 9 | async open() { 10 | await (await this.$).click(); 11 | // Wait for the alert to popup 12 | return pause(1000); 13 | } 14 | 15 | async select(n: number) { 16 | const options = await $$('.select-interface-option'); 17 | 18 | return options[n]?.click(); 19 | } 20 | 21 | async cancel() { 22 | const cancel = await waitForElement('.alert-button-role-cancel'); 23 | await cancel.click(); 24 | // Allow alert to close 25 | return cancel.waitForDisplayed({ reverse: true }); 26 | } 27 | 28 | async ok() { 29 | const ok = await waitForElement( 30 | '.alert-button:not(.alert-button-role-cancel)' 31 | ); 32 | await ok.click(); 33 | // Allow alert to close 34 | return ok.waitForDisplayed({ reverse: true }); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/helpers/ionic/components/slides.ts: -------------------------------------------------------------------------------- 1 | import type { RectReturn } from '@wdio/protocols/build/types'; 2 | 3 | import { IonicComponent } from './component'; 4 | import { Ionic$ } from '..'; 5 | import { Gestures } from '../..'; 6 | 7 | export class IonicSlides extends IonicComponent { 8 | rects: RectReturn | null = null; 9 | 10 | constructor(selector: string) { 11 | super(selector); 12 | } 13 | 14 | /** 15 | * Swipe the Swiper to the LEFT (from right to left) 16 | */ 17 | async swipeLeft() { 18 | // Determine the rectangles of the Swiper 19 | const SwiperRectangles = await this.getSwiperRectangles(); 20 | // We need to determine the center position of the Swiper on the screen. This can be done by taking the 21 | // starting position (SwiperRectangles.y) and add half of the height of the Swiper to it. 22 | const y = Math.round(SwiperRectangles.y + SwiperRectangles.height / 2); 23 | 24 | // Execute the gesture by providing a starting position and an end position 25 | return Gestures.swipe( 26 | // Here we start on the right of the Swiper. To make sure that we don't touch the outer most right 27 | // part of the screen we take 10% of the x-position. The y-position has already been determined. 28 | { 29 | x: Math.round(SwiperRectangles.width - SwiperRectangles.width * 0.1), 30 | y, 31 | }, 32 | // Here we end on the left of the Swiper. To make sure that we don't touch the outer most left 33 | // part of the screen we add 10% to the x-position. The y-position has already been determined. 34 | { x: Math.round(SwiperRectangles.x + SwiperRectangles.width * 0.1), y } 35 | ); 36 | } 37 | 38 | /** 39 | * Swipe the Swiper to the RIGHT (from left to right) 40 | */ 41 | async swipeRight() { 42 | // Determine the rectangles of the Swiper 43 | const SwiperRectangles = await this.getSwiperRectangles(); 44 | // We need to determine the center position of the Swiper on the screen. This can be done by taking the 45 | // starting position (SwiperRectangles.y) and add half of the height of the Swiper to it. 46 | const y = Math.round(SwiperRectangles.y + SwiperRectangles.height / 2); 47 | 48 | // Execute the gesture by providing a starting position and an end position 49 | return Gestures.swipe( 50 | // Here we start on the left of the Swiper. To make sure that we don't touch the outer most left 51 | // part of the screen we add 10% to the x-position. The y-position has already been determined. 52 | { x: Math.round(SwiperRectangles.x + SwiperRectangles.width * 0.1), y }, 53 | // Here we end on the right of the Swiper. To make sure that we don't touch the outer most right 54 | // part of the screen we take 10% of the x-position. The y-position has already been determined. 55 | { 56 | x: Math.round(SwiperRectangles.width - SwiperRectangles.width * 0.1), 57 | y, 58 | } 59 | ); 60 | } 61 | 62 | /** 63 | * Get the Swiper position and size 64 | */ 65 | async getSwiperRectangles(): Promise { 66 | const slides2 = await Ionic$.$(this.selector as string); 67 | // Get the rectangles of the Swiper and store it in a global that will be used for a next call. 68 | // We dont want ask for the rectangles of the Swiper if we already know them. 69 | // This will save unneeded webdriver calls. 70 | this.rects = this.rects || (await driver.getElementRect(slides2.elementId)); 71 | 72 | return this.rects; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /tests/helpers/ionic/components/textarea.ts: -------------------------------------------------------------------------------- 1 | import { IonicComponent } from './component'; 2 | 3 | export class IonicTextarea extends IonicComponent { 4 | constructor(selector: string) { 5 | super(selector); 6 | } 7 | 8 | setValue(value: string) { 9 | return browser.execute( 10 | (selector: string, valueString: string) => { 11 | const el = document.querySelector(selector); 12 | if (el) { 13 | (el as any).value = valueString; 14 | } 15 | }, 16 | this.selector, 17 | value 18 | ); 19 | } 20 | 21 | getValue() { 22 | return browser.execute((selector: string) => { 23 | const el = document.querySelector(selector); 24 | if (el) { 25 | return (el as any).value; 26 | } 27 | return null; 28 | }, this.selector); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/helpers/ionic/components/toast.ts: -------------------------------------------------------------------------------- 1 | import { IonicComponent } from './component'; 2 | 3 | export class IonicToast extends IonicComponent { 4 | constructor() { 5 | super('ion-toast'); 6 | } 7 | 8 | getText() { 9 | return $(this.selector).getText(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /tests/helpers/ionic/index.ts: -------------------------------------------------------------------------------- 1 | import { IonicPage } from './components'; 2 | 3 | 4 | export class Ionic$ { 5 | static async $(selector: string): Promise { 6 | const activePage = await IonicPage.active(); 7 | return activePage.$(selector); 8 | } 9 | 10 | static async $$(selector: string): Promise { 11 | const activePage = await IonicPage.active(); 12 | return activePage.$$(selector); 13 | } 14 | } 15 | 16 | export * from './components'; 17 | -------------------------------------------------------------------------------- /tests/helpers/platform/android.ts: -------------------------------------------------------------------------------- 1 | import { ElementSelector } from '../definitions'; 2 | 3 | export function findElementAndroid({ text }: ElementSelector) { 4 | if (text) { 5 | return $(`android=new UiSelector().text("${text}")`); 6 | } else { 7 | throw new Error('Unknown selector strategy'); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/helpers/platform/index.ts: -------------------------------------------------------------------------------- 1 | import WebView, { CONTEXT_REF } from '../webview'; 2 | 3 | export * from './android'; 4 | export * from './ios'; 5 | 6 | export async function waitForLoad() { 7 | if (isWeb()) { 8 | return Promise.resolve(); 9 | } 10 | return WebView.waitForWebsiteLoaded(); 11 | } 12 | 13 | export async function switchToNative() { 14 | if (isWeb()) { 15 | return Promise.resolve(); 16 | } 17 | 18 | return WebView.switchToContext(CONTEXT_REF.NATIVE); 19 | } 20 | 21 | export async function switchToWeb() { 22 | if (isWeb()) { 23 | return Promise.resolve(); 24 | } 25 | 26 | return WebView.switchToContext(CONTEXT_REF.WEBVIEW); 27 | } 28 | 29 | export async function getContexts() { 30 | if (isWeb()) { 31 | return Promise.resolve(['WEBVIEW']); 32 | } 33 | 34 | return driver.getContexts(); 35 | } 36 | 37 | export function getContext() { 38 | if (isWeb()) { 39 | return Promise.resolve('WEBVIEW'); 40 | } 41 | 42 | return driver.getContext(); 43 | } 44 | 45 | export async function url(newUrl: string) { 46 | const currentUrl = await browser.getUrl(); 47 | 48 | if (newUrl[0] === '/') { 49 | // Simulate baseUrl by grabbing the current url and navigating relative 50 | // to that 51 | try { 52 | const fullUrl = new URL(newUrl, currentUrl); 53 | return browser.url(fullUrl.href); 54 | } catch (e) {} 55 | } 56 | 57 | return browser.url(newUrl); 58 | } 59 | 60 | export function pause(ms: number) { 61 | return driver.pause(ms); 62 | } 63 | 64 | export function hideKeyboard() { 65 | return driver.hideKeyboard(); 66 | } 67 | 68 | export function onWeb(fn: () => Promise) { 69 | if (isWeb()) { 70 | return fn(); 71 | } 72 | } 73 | 74 | export function onIOS(fn: () => Promise) { 75 | if (isIOS()) { 76 | return fn(); 77 | } 78 | } 79 | export function onAndroid(fn: () => Promise) { 80 | if (isAndroid()) { 81 | return fn(); 82 | } 83 | } 84 | 85 | export function isIOS() { 86 | return driver.isIOS; 87 | } 88 | 89 | export function isAndroid() { 90 | return driver.isAndroid; 91 | } 92 | 93 | export function isWeb() { 94 | return !driver.isMobile; 95 | } 96 | 97 | export async function setLocation(lat: number, lng: number) { 98 | if (isWeb()) { 99 | // Not available on web 100 | return Promise.resolve(); 101 | } 102 | 103 | return driver.setGeoLocation({ 104 | latitude: '' + lat, 105 | longitude: '' + lat, 106 | altitude: '94.23', 107 | }); 108 | } 109 | 110 | export async function restartApp(urlPath: string) { 111 | // this is needed to set the "default" url on web so the DB can be cleared 112 | if (isWeb()) { 113 | return url(urlPath); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /tests/helpers/platform/ios.ts: -------------------------------------------------------------------------------- 1 | import { ElementSelector } from '../definitions'; 2 | 3 | export function findElementIOS({ text }: ElementSelector) { 4 | if (text) { 5 | return $( 6 | `-ios class chain:**/XCUIElementTypeAny[\`label == "${text}" OR value == "${text}"\`]` 7 | ); 8 | } else { 9 | throw new Error('Unknown selector strategy'); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /tests/helpers/storage.ts: -------------------------------------------------------------------------------- 1 | import { pause } from '.'; 2 | 3 | export async function clearIndexedDB(dbName: string) { 4 | await browser.execute((name) => { 5 | indexedDB.deleteDatabase(name); 6 | // Needed to reload the page for the DB to be reloaded 7 | // for mobile devices 8 | document.location.reload(); 9 | }, dbName); 10 | 11 | return pause(500); 12 | } 13 | -------------------------------------------------------------------------------- /tests/helpers/webview.ts: -------------------------------------------------------------------------------- 1 | export const CONTEXT_REF = { 2 | NATIVE: 'native', 3 | WEBVIEW: 'webview', 4 | }; 5 | const DOCUMENT_READY_STATE = { 6 | COMPLETE: 'complete', 7 | INTERACTIVE: 'interactive', 8 | LOADING: 'loading', 9 | }; 10 | 11 | class WebView { 12 | /** 13 | * Wait for the webview context to be loaded 14 | * 15 | * By default you have `NATIVE_APP` as the current context. If a webview is loaded it will be 16 | * added to the current contexts and will looks something like this for iOS 17 | * `["NATIVE_APP","WEBVIEW_28158.2"]` 18 | * The number behind `WEBVIEW` will be a random number in random order. 19 | * 20 | * For Android you can get something like 21 | * ["NATIVE_APP","WEBVIEW_com.wdiodemoapp", "WEBVIEW_com.chrome"]`. 22 | * The string behind `WEBVIEW` will the package name of the app that holds 23 | * the webview 24 | */ 25 | async waitForWebViewContextLoaded() { 26 | const context = await driver.waitUntil( 27 | async () => { 28 | const currentContexts = await this.getCurrentContexts(); 29 | 30 | return ( 31 | currentContexts.length > 1 && 32 | currentContexts.find((currentContext) => 33 | currentContext.toLowerCase().includes(CONTEXT_REF.WEBVIEW) 34 | ) !== 'undefined' 35 | ); 36 | }, 37 | { 38 | // Wait a max of 45 seconds. Reason for this high amount is that loading 39 | // a webview for iOS might take longer 40 | timeout: 45000, 41 | timeoutMsg: 'Webview context not loaded', 42 | interval: 100, 43 | } 44 | ); 45 | 46 | return context; 47 | } 48 | 49 | /** 50 | * Switch to native or webview context 51 | */ 52 | async switchToContext(context: string) { 53 | // The first context will always be the NATIVE_APP, 54 | // the second one will always be the WebdriverIO web page 55 | const currentContexts = await this.getCurrentContexts(); 56 | return driver.switchContext( 57 | currentContexts[context === CONTEXT_REF.NATIVE ? 0 : 1] 58 | ); 59 | } 60 | 61 | /** 62 | * Returns an object with the list of all available contexts 63 | */ 64 | getCurrentContexts(): Promise { 65 | return Promise.resolve(driver.getContexts()); 66 | } 67 | 68 | /** 69 | * Wait for the document to be fully loaded 70 | */ 71 | waitForDocumentFullyLoaded() { 72 | return driver.waitUntil( 73 | // A webpage can have multiple states, the ready state is the one we need to have. 74 | // This looks like the same implementation as for the w3c implementation for `browser.url('https://webdriver.io')` 75 | // That command also waits for the readiness of the page, see also the w3c specs 76 | // https://www.w3.org/TR/webdriver/#dfn-waiting-for-the-navigation-to-complete 77 | async () => 78 | (await driver.execute(() => document.readyState)) === 79 | DOCUMENT_READY_STATE.COMPLETE, 80 | { 81 | timeout: 15000, 82 | timeoutMsg: 'Website not loaded', 83 | interval: 100, 84 | } 85 | ); 86 | } 87 | 88 | /** 89 | * Wait for the website in the webview to be loaded 90 | */ 91 | async waitForWebsiteLoaded() { 92 | await this.waitForWebViewContextLoaded(); 93 | await this.switchToContext(CONTEXT_REF.WEBVIEW); 94 | await this.waitForDocumentFullyLoaded(); 95 | await this.switchToContext(CONTEXT_REF.NATIVE); 96 | } 97 | 98 | async waitForWebViewIsDisplayedByXpath( 99 | isShown = true 100 | ): Promise { 101 | const selector = browser.isAndroid 102 | ? '*//android.webkit.WebView' 103 | : '*//XCUIElementTypeWebView'; 104 | (await $(selector)).waitForDisplayed({ 105 | timeout: 45000, 106 | reverse: !isShown, 107 | }); 108 | } 109 | } 110 | 111 | export default new WebView(); 112 | -------------------------------------------------------------------------------- /tests/pageobjects/about.page.ts: -------------------------------------------------------------------------------- 1 | import { Ionic$, IonicButton, IonicSelect } from '../helpers'; 2 | 3 | import Page from './page'; 4 | 5 | class About extends Page { 6 | get popoverButton() { 7 | return new IonicButton('ion-buttons > ion-button'); 8 | } 9 | get headerImage() { 10 | return Ionic$.$('.about-header'); 11 | } 12 | get madisonImage() { 13 | return Ionic$.$('.madison'); 14 | } 15 | get austinImage() { 16 | return Ionic$.$('.austin'); 17 | } 18 | get chicagoImage() { 19 | return Ionic$.$('.chicago'); 20 | } 21 | get seattleImage() { 22 | return Ionic$.$('.seattle'); 23 | } 24 | get locationSelect() { 25 | return new IonicSelect('ion-select'); 26 | } 27 | } 28 | 29 | export default new About(); 30 | -------------------------------------------------------------------------------- /tests/pageobjects/account.page.ts: -------------------------------------------------------------------------------- 1 | import { Ionic$, IonicItem } from '../helpers'; 2 | import { IonicAlert } from '../helpers/ionic/components/alert'; 3 | import Page from './page'; 4 | 5 | class Account extends Page { 6 | get updatePictureButton() { 7 | return IonicItem.withTitle('Update Picture'); 8 | } 9 | get changeUsernameButton() { 10 | return IonicItem.withTitle('Change Username'); 11 | } 12 | get changePasswordButton() { 13 | return IonicItem.withTitle('Change Password'); 14 | } 15 | get supportButton() { 16 | return IonicItem.withTitle('Support'); 17 | } 18 | get logoutButton() { 19 | return IonicItem.withTitle('Logout'); 20 | } 21 | get changeUsernameAlert() { 22 | return new IonicAlert(); 23 | } 24 | get usernameLabel() { 25 | return Ionic$.$('h2'); 26 | } 27 | } 28 | 29 | export default new Account(); 30 | -------------------------------------------------------------------------------- /tests/pageobjects/login.page.ts: -------------------------------------------------------------------------------- 1 | import { IonicButton, IonicInput } from '../helpers'; 2 | import Page from './page'; 3 | 4 | class Login extends Page { 5 | get username() { 6 | return new IonicInput('ion-input [name="username"]'); 7 | } 8 | get password() { 9 | return new IonicInput('ion-input [name="password"]'); 10 | } 11 | get loginButton() { 12 | return IonicButton.withTitle('Login'); 13 | } 14 | 15 | async login(username: string, password: string) { 16 | await this.username.setValue(username); 17 | await this.password.setValue(password); 18 | return this.loginButton.tap(); 19 | } 20 | } 21 | 22 | export default new Login(); 23 | -------------------------------------------------------------------------------- /tests/pageobjects/map.page.ts: -------------------------------------------------------------------------------- 1 | import { Ionic$ } from '../helpers'; 2 | import Page from './page'; 3 | 4 | class Map extends Page { 5 | get map() { 6 | return Ionic$.$('.map-canvas'); 7 | } 8 | } 9 | 10 | export default new Map(); 11 | -------------------------------------------------------------------------------- /tests/pageobjects/page.ts: -------------------------------------------------------------------------------- 1 | export default class Page {} 2 | -------------------------------------------------------------------------------- /tests/pageobjects/schedule-filter.page.ts: -------------------------------------------------------------------------------- 1 | import Page from './page'; 2 | 3 | class ScheduleFilter extends Page {} 4 | 5 | export default new ScheduleFilter(); 6 | -------------------------------------------------------------------------------- /tests/pageobjects/schedule.page.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IonicButton, 3 | IonicContent, 4 | IonicMenu, 5 | IonicSegment, 6 | isIOS, 7 | } from '../helpers'; 8 | import Page from './page'; 9 | 10 | class Schedule extends Page { 11 | get menu() { 12 | return new IonicMenu(); 13 | } 14 | get filterButton() { 15 | return new IonicButton( 16 | `ion-buttons[slot="end"] > ion-button:nth-child(${isIOS() ? 1 : 2})` 17 | ); 18 | } 19 | get segment() { 20 | return new IonicSegment('ion-segment'); 21 | } 22 | get content() { 23 | return new IonicContent('ion-content'); 24 | } 25 | 26 | async openMenu() { 27 | return this.menu.open(); 28 | } 29 | } 30 | 31 | export default new Schedule(); 32 | -------------------------------------------------------------------------------- /tests/pageobjects/session-detail.page.ts: -------------------------------------------------------------------------------- 1 | import { Ionic$, IonicButton } from '../helpers'; 2 | import Page from './page'; 3 | 4 | class SessionDetail extends Page { 5 | get sessionTitle() { 6 | return Ionic$.$('h1'); 7 | } 8 | get backButton() { 9 | return new IonicButton('ion-back-button'); 10 | } 11 | get favoriteButton() { 12 | return new IonicButton('ion-buttons > ion-button:nth-child(1)'); 13 | } 14 | get shareButton() { 15 | return new IonicButton('ion-buttons > ion-button:nth-child(2)'); 16 | } 17 | } 18 | 19 | export default new SessionDetail(); 20 | -------------------------------------------------------------------------------- /tests/pageobjects/signup.page.ts: -------------------------------------------------------------------------------- 1 | import { IonicButton, IonicInput } from '../helpers'; 2 | import Page from './page'; 3 | 4 | class Signup extends Page { 5 | get username() { 6 | return new IonicInput('ion-input [name="username"]'); 7 | } 8 | get password() { 9 | return new IonicInput('ion-input [name="password"]'); 10 | } 11 | get signupButton() { 12 | return IonicButton.withTitle('Create'); 13 | } 14 | 15 | async signup(username: string, password: string) { 16 | await this.username.setValue(username); 17 | await this.password.setValue(password); 18 | return this.signupButton.tap(); 19 | } 20 | } 21 | 22 | export default new Signup(); 23 | -------------------------------------------------------------------------------- /tests/pageobjects/speaker-detail.page.ts: -------------------------------------------------------------------------------- 1 | import { Ionic$ } from '../helpers'; 2 | import Page from './page'; 3 | 4 | class SpeakerDetail extends Page { 5 | get speakerName() { 6 | return Ionic$.$('h2'); 7 | } 8 | } 9 | 10 | export default new SpeakerDetail(); 11 | -------------------------------------------------------------------------------- /tests/pageobjects/speaker-list.page.ts: -------------------------------------------------------------------------------- 1 | import { Ionic$ } from '../helpers'; 2 | import Page from './page'; 3 | 4 | class SpeakerList extends Page { 5 | get speakers() { 6 | return Ionic$.$$('ion-col'); 7 | } 8 | } 9 | 10 | export default new SpeakerList(); 11 | -------------------------------------------------------------------------------- /tests/pageobjects/support.page.ts: -------------------------------------------------------------------------------- 1 | import { IonicButton, IonicTextarea, IonicToast } from '../helpers'; 2 | import Page from './page'; 3 | 4 | class Support extends Page { 5 | get messageInput() { return new IonicTextarea('ion-textarea[name="supportQuestion"]'); } 6 | get submitButton() { return IonicButton.withTitle('Submit'); } 7 | get toast() { return new IonicToast(); } 8 | 9 | submitMessage() { 10 | return this.submitButton.tap(); 11 | } 12 | } 13 | 14 | export default new Support(); 15 | -------------------------------------------------------------------------------- /tests/pageobjects/tutorial.page.ts: -------------------------------------------------------------------------------- 1 | import { IonicButton, IonicSlides } from '../helpers'; 2 | import Page from './page'; 3 | 4 | class Tutorial extends Page { 5 | get slides() { 6 | return new IonicSlides('swiper'); 7 | } 8 | get skipButton() { 9 | return IonicButton.withTitle('Skip'); 10 | } 11 | get continueButton() { 12 | return IonicButton.withTitle('Continue'); 13 | } 14 | 15 | async swipeLeft() { 16 | return this.slides.swipeLeft(); 17 | } 18 | 19 | async swipeRight() { 20 | return this.slides.swipeRight(); 21 | } 22 | 23 | async skip() { 24 | return this.skipButton.tap(); 25 | } 26 | 27 | async continue() { 28 | return this.continueButton.tap(); 29 | } 30 | } 31 | 32 | export default new Tutorial(); 33 | -------------------------------------------------------------------------------- /tests/specs/app.about.spec.ts: -------------------------------------------------------------------------------- 1 | import { clearIndexedDB, pause, restartApp, url } from '../helpers'; 2 | 3 | import About from '../pageobjects/about.page'; 4 | 5 | describe('About', () => { 6 | beforeEach(async () => { 7 | await restartApp('/app/tabs/about'); 8 | await clearIndexedDB('_ionicstorage'); 9 | await url('/app/tabs/about'); 10 | await pause(500); 11 | }); 12 | 13 | it('Should switch location', async () => { 14 | const location = await About.locationSelect; 15 | 16 | await location.open(); 17 | await location.select(1); 18 | await location.ok(); 19 | const austinImage = await About.austinImage; 20 | await pause(500); 21 | await expect((await austinImage.getCSSProperty('opacity')).value).toEqual( 22 | 1 23 | ); 24 | 25 | await location.open(); 26 | await location.select(2); 27 | await location.ok(); 28 | const chicagoImage = await About.chicagoImage; 29 | await pause(500); 30 | await expect((await chicagoImage.getCSSProperty('opacity')).value).toEqual( 31 | 1 32 | ); 33 | 34 | await location.open(); 35 | await location.select(3); 36 | await location.ok(); 37 | const seattleImage = await About.seattleImage; 38 | await pause(500); 39 | await expect((await seattleImage.getCSSProperty('opacity')).value).toEqual( 40 | 1 41 | ); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /tests/specs/app.account.spec.ts: -------------------------------------------------------------------------------- 1 | import { clearIndexedDB, pause, restartApp, url } from '../helpers'; 2 | 3 | import Account from '../pageobjects/account.page'; 4 | import Login from '../pageobjects/login.page'; 5 | 6 | describe('Account', () => { 7 | beforeEach(async () => { 8 | await restartApp('/login'); 9 | await clearIndexedDB('_ionicstorage'); 10 | await url('/login'); 11 | await pause(500); 12 | await Login.login('test', 'test'); 13 | await pause(500); 14 | await url('/account'); 15 | }); 16 | 17 | it('Should open change username alert', async () => { 18 | await Account.changeUsernameButton.tap(); 19 | await pause(500); 20 | const input = await Account.changeUsernameAlert.input; 21 | await input.setValue('newusername'); 22 | const okButton = await Account.changeUsernameAlert.button('Ok'); 23 | await okButton.click(); 24 | await url('/login'); 25 | await pause(500); 26 | await url('/account'); 27 | const usernameLabel = await Account.usernameLabel; 28 | expect(await usernameLabel.getText()).toBe('newusername'); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /tests/specs/app.login.spec.ts: -------------------------------------------------------------------------------- 1 | import { clearIndexedDB, pause, restartApp, url } from '../helpers'; 2 | 3 | import Login from '../pageobjects/login.page'; 4 | 5 | describe('Login', () => { 6 | beforeEach(async () => { 7 | await restartApp('/login'); 8 | await clearIndexedDB('_ionicstorage'); 9 | await url('/login'); 10 | await pause(500); 11 | }); 12 | 13 | it('Should login', async () => { 14 | await Login.login('test', 'test'); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /tests/specs/app.map.spec.ts: -------------------------------------------------------------------------------- 1 | import { clearIndexedDB, pause, restartApp, url } from '../helpers'; 2 | 3 | import Map from '../pageobjects/map.page'; 4 | 5 | describe('Map', () => { 6 | beforeEach(async () => { 7 | await restartApp('/app/tabs/map'); 8 | await clearIndexedDB('_ionicstorage'); 9 | await url('/app/tabs/map'); 10 | await pause(500); 11 | }); 12 | 13 | it('Should load map', async () => { 14 | await expect(Map.map).toBeDisplayed(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /tests/specs/app.schedule-filter.spec.ts: -------------------------------------------------------------------------------- 1 | import { clearIndexedDB, pause, restartApp, url } from '../helpers'; 2 | 3 | import schedulePage from '../pageobjects/schedule.page'; 4 | 5 | describe('Schedule Filter', () => { 6 | beforeEach(async () => { 7 | await restartApp('/app/tabs/schedule'); 8 | await clearIndexedDB('_ionicstorage'); 9 | await url('/app/tabs/schedule'); 10 | await pause(200); 11 | await schedulePage.filterButton.tap(); 12 | await pause(400); 13 | }); 14 | 15 | it('Should load filters', async () => { 16 | const items = await $$('page-schedule-filter ion-item'); 17 | expect(items.length).toBe(10); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /tests/specs/app.schedule.spec.ts: -------------------------------------------------------------------------------- 1 | import { clearIndexedDB, pause, restartApp, url } from '../helpers'; 2 | 3 | import Schedule from '../pageobjects/schedule.page'; 4 | 5 | describe('Schedule', () => { 6 | beforeEach(async () => { 7 | await restartApp('/app/tabs/schedule'); 8 | await clearIndexedDB('_ionicstorage'); 9 | await url('/app/tabs/schedule'); 10 | await pause(500); 11 | }); 12 | 13 | it('Should switch to favorites', async () => { 14 | const favoritesButton = await Schedule.segment.button('Favorites'); 15 | if (favoritesButton) { 16 | await (await favoritesButton.$).waitForDisplayed(); 17 | await favoritesButton.tap(); 18 | } else { 19 | throw new Error('Favorites button not found'); 20 | } 21 | const listHeader = await (await Schedule.content.$).$('ion-list-header'); 22 | await listHeader.waitForDisplayed(); 23 | await expect(listHeader).toHaveText('No Sessions Found'); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /tests/specs/app.session-detail.spec.ts: -------------------------------------------------------------------------------- 1 | import { clearIndexedDB, pause, restartApp, url } from '../helpers'; 2 | 3 | import Schedule from '../pageobjects/schedule.page'; 4 | import SessionDetail from '../pageobjects/session-detail.page'; 5 | 6 | describe('Session Detail', () => { 7 | beforeEach(async () => { 8 | await restartApp('/app/tabs/schedule/session/1'); 9 | await clearIndexedDB('_ionicstorage'); 10 | await url('/app/tabs/schedule/session/1'); 11 | await pause(500); 12 | }); 13 | 14 | it('Should load first session', async () => { 15 | const title = await SessionDetail.sessionTitle; 16 | await expect(await title.getText()).toBe('Breakfast'); 17 | }); 18 | 19 | it('Should favorite session', async () => { 20 | const button = await SessionDetail.favoriteButton; 21 | await button.tap(); 22 | const backButton = await SessionDetail.backButton; 23 | await backButton.tap(); 24 | 25 | const favoritesButton = await Schedule.segment.button('Favorites'); 26 | if (favoritesButton) { 27 | await (await favoritesButton.$).waitForDisplayed(); 28 | await favoritesButton.tap(); 29 | } else { 30 | throw new Error('Favorites button not found'); 31 | } 32 | const item = await (await Schedule.content.$).$('ion-item'); 33 | const h3 = await item.$('h3'); 34 | await expect(h3).toHaveText('Breakfast'); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /tests/specs/app.signup.spec.ts: -------------------------------------------------------------------------------- 1 | import { clearIndexedDB, getUrl, pause, restartApp, url } from '../helpers'; 2 | 3 | import Signup from '../pageobjects/signup.page'; 4 | 5 | describe('Signup', () => { 6 | beforeEach(async () => { 7 | await restartApp('/signup'); 8 | await clearIndexedDB('_ionicstorage'); 9 | await url('/signup'); 10 | await pause(500); 11 | }); 12 | 13 | it('Should signup', async () => { 14 | await Signup.signup('test', 'test'); 15 | await pause(500); 16 | await expect((await getUrl()).pathname).toBe('/app/tabs/schedule'); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /tests/specs/app.speaker-detail.spec.ts: -------------------------------------------------------------------------------- 1 | import { clearIndexedDB, pause, restartApp, url } from '../helpers'; 2 | 3 | import SpeakerDetail from '../pageobjects/speaker-detail.page'; 4 | 5 | describe('Speaker Detail', () => { 6 | beforeEach(async () => { 7 | await restartApp('/app/tabs/speakers/speaker-details/3'); 8 | await clearIndexedDB('_ionicstorage'); 9 | await url('/app/tabs/speakers/speaker-details/3'); 10 | await pause(500); 11 | }); 12 | 13 | it('Should load speaker', async () => { 14 | const speakerName = await SpeakerDetail.speakerName; 15 | await expect(await speakerName.getText()).toBe('Donald Duck'); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /tests/specs/app.speaker-list.spec.ts: -------------------------------------------------------------------------------- 1 | import { 2 | clearIndexedDB, 3 | pause, 4 | restartApp, 5 | url, 6 | } from '../helpers'; 7 | 8 | import SpeakerList from '../pageobjects/speaker-list.page'; 9 | 10 | describe('Speaker List', () => { 11 | beforeEach(async () => { 12 | await restartApp('/app/tabs/speakers'); 13 | await clearIndexedDB('_ionicstorage'); 14 | await url('/app/tabs/speakers'); 15 | await pause(500); 16 | }); 17 | 18 | it('Should load speakers', async () => { 19 | const speakers = await SpeakerList.speakers; 20 | 21 | expect(speakers.length).toBe(13); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /tests/specs/app.support.spec.ts: -------------------------------------------------------------------------------- 1 | import { clearIndexedDB, pause, restartApp, url } from '../helpers'; 2 | 3 | import Support from '../pageobjects/support.page'; 4 | 5 | describe('Support', () => { 6 | beforeEach(async () => { 7 | await restartApp('/support'); 8 | await clearIndexedDB('_ionicstorage'); 9 | await url('/support'); 10 | await pause(500); 11 | }); 12 | 13 | it('Should submit support request', async () => { 14 | await pause(5000); 15 | const input = Support.messageInput; 16 | await input.setValue('I am very happy with the app'); 17 | await Support.submitMessage(); 18 | 19 | const toast = await Support.toast; 20 | await expect(await toast.getText()).toBe( 21 | 'Your support request has been sent.' 22 | ); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /tests/specs/app.tutorial.spec.ts: -------------------------------------------------------------------------------- 1 | import { clearIndexedDB, pause, getUrl, restartApp, url } from '../helpers'; 2 | 3 | import Tutorial from '../pageobjects/tutorial.page'; 4 | 5 | describe('Tutorial', () => { 6 | beforeEach(async () => { 7 | await restartApp('/tutorial'); 8 | await clearIndexedDB('_ionicstorage'); 9 | await url('/tutorial'); 10 | }); 11 | 12 | it('Should load swiper', async () => { 13 | await expect(await Tutorial.slides.$).toBeDisplayed(); 14 | }); 15 | 16 | it('Should get to schedule', async () => { 17 | await Tutorial.slides.swipeLeft(); 18 | await Tutorial.slides.swipeLeft(); 19 | await Tutorial.slides.swipeLeft(); 20 | 21 | await Tutorial.continue(); 22 | 23 | await pause(1000); 24 | 25 | await expect((await getUrl()).pathname).toBe('/app/tabs/schedule'); 26 | }); 27 | 28 | it('Should skip to schedule', async () => { 29 | await Tutorial.skip(); 30 | 31 | await pause(1000); 32 | 33 | await expect((await getUrl()).pathname).toBe('/app/tabs/schedule'); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /tests/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../.tsbuild/", 5 | "sourceMap": false, 6 | "target": "es2019", 7 | "module": "commonjs", 8 | "removeComments": true, 9 | "noImplicitAny": true, 10 | "esModuleInterop": true, 11 | "strictPropertyInitialization": true, 12 | "strictNullChecks": true, 13 | "types": [ 14 | "node", 15 | "webdriverio/async", 16 | "@wdio/mocha-framework", 17 | "expect-webdriverio" 18 | ] 19 | } 20 | } -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/app", 5 | "types": [] 6 | }, 7 | "files": [ 8 | "src/main.ts", 9 | "src/polyfills.ts" 10 | ], 11 | "include": [ 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "downlevelIteration": true, 4 | "importHelpers": true, 5 | "module": "es2020", 6 | "outDir": "./dist/out-tsc", 7 | "sourceMap": true, 8 | "declaration": false, 9 | "moduleResolution": "node", 10 | "emitDecoratorMetadata": true, 11 | "experimentalDecorators": true, 12 | "target": "ES5", 13 | "skipLibCheck": true, 14 | "lib": [ 15 | "es2017", 16 | "dom" 17 | ] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts", 12 | "src/zone-flags.ts", 13 | "src/polyfills.ts" 14 | ], 15 | "include": [ 16 | "src/**/*.spec.ts", 17 | "src/**/*.d.ts" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:recommended", 3 | "rulesDirectory": [ 4 | "codelyzer" 5 | ], 6 | "rules": { 7 | "align": { 8 | "options": [ 9 | "parameters", 10 | "statements" 11 | ] 12 | }, 13 | "array-type": false, 14 | "arrow-parens": false, 15 | "arrow-return-shorthand": true, 16 | "deprecation": { 17 | "severity": "warn" 18 | }, 19 | "import-blacklist": [ 20 | true, 21 | "rxjs/Rx" 22 | ], 23 | "curly": true, 24 | "interface-name": false, 25 | "max-classes-per-file": false, 26 | "max-line-length": [ 27 | true, 28 | 140 29 | ], 30 | "eofline": true, 31 | "member-access": false, 32 | "import-spacing": true, 33 | "indent": { 34 | "options": [ 35 | "spaces" 36 | ] 37 | }, 38 | "member-ordering": [ 39 | true, 40 | { 41 | "order": [ 42 | "static-field", 43 | "instance-field", 44 | "static-method", 45 | "instance-method" 46 | ] 47 | } 48 | ], 49 | "no-consecutive-blank-lines": false, 50 | "no-console": [ 51 | true, 52 | "debug", 53 | "info", 54 | "time", 55 | "timeEnd", 56 | "trace" 57 | ], 58 | "no-empty": false, 59 | "no-inferrable-types": [ 60 | true, 61 | "ignore-params" 62 | ], 63 | "no-non-null-assertion": true, 64 | "no-redundant-jsdoc": true, 65 | "no-switch-case-fall-through": true, 66 | "no-use-before-define": true, 67 | "no-var-requires": false, 68 | "object-literal-key-quotes": [ 69 | true, 70 | "as-needed" 71 | ], 72 | "object-literal-sort-keys": false, 73 | "ordered-imports": false, 74 | "quotemark": [ 75 | true, 76 | "single" 77 | ], 78 | "trailing-comma": false, 79 | "no-output-on-prefix": true, 80 | "no-inputs-metadata-property": true, 81 | "no-host-metadata-property": true, 82 | "no-input-rename": true, 83 | "no-output-rename": true, 84 | "use-lifecycle-interface": true, 85 | "use-pipe-transform-interface": true, 86 | "one-variable-per-declaration": false, 87 | "component-class-suffix": [ 88 | true, 89 | "Page", 90 | "Component" 91 | ], 92 | "semicolon": { 93 | "options": [ 94 | "always" 95 | ] 96 | }, 97 | "space-before-function-paren": { 98 | "options": { 99 | "anonymous": "never", 100 | "asyncArrow": "always", 101 | "constructor": "never", 102 | "method": "never", 103 | "named": "never" 104 | } 105 | }, 106 | "directive-class-suffix": true, 107 | "typedef-whitespace": { 108 | "options": [ 109 | { 110 | "call-signature": "nospace", 111 | "index-signature": "nospace", 112 | "parameter": "nospace", 113 | "property-declaration": "nospace", 114 | "variable-declaration": "nospace" 115 | }, 116 | { 117 | "call-signature": "onespace", 118 | "index-signature": "onespace", 119 | "parameter": "onespace", 120 | "property-declaration": "onespace", 121 | "variable-declaration": "onespace" 122 | } 123 | ] 124 | }, 125 | "directive-selector": [ 126 | true, 127 | "attribute", 128 | "app", 129 | "camelCase" 130 | ], 131 | "component-selector": [ 132 | true, 133 | "element", 134 | "app", 135 | "page", 136 | "kebab-case" 137 | ], 138 | "variable-name": { 139 | "options": [ 140 | "ban-keywords", 141 | "check-format", 142 | "allow-pascal-case" 143 | ] 144 | }, 145 | "whitespace": { 146 | "options": [ 147 | "check-branch", 148 | "check-decl", 149 | "check-operator", 150 | "check-separator", 151 | "check-type", 152 | "check-typecast" 153 | ] 154 | } 155 | } 156 | } 157 | --------------------------------------------------------------------------------