├── .editorconfig
├── .gitignore
├── .nvmrc
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── config.xml
├── config
├── gmap-mock.js
├── helpers.js
├── karma-shim.js
├── karma.conf.js
├── webpack.config.js
└── webpack.test.js
├── coverage
└── .gitignore
├── ionic.config.json
├── ionic.project
├── karma.conf.js
├── 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-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
├── icon.png
├── ios
│ ├── icon
│ │ ├── icon-40.png
│ │ ├── icon-40@2x.png
│ │ ├── icon-40@3x.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-small.png
│ │ ├── icon-small@2x.png
│ │ ├── icon-small@3x.png
│ │ ├── icon.png
│ │ └── icon@2x.png
│ └── splash
│ │ ├── Default-568h@2x~iphone.png
│ │ ├── Default-667h.png
│ │ ├── Default-736h.png
│ │ ├── Default-Portrait@2x~ipad.png
│ │ ├── Default-Portrait~ipad.png
│ │ ├── Default@2x~iphone.png
│ │ └── Default~iphone.png
└── splash.png
├── src
├── app
│ ├── app.component.ts
│ ├── app.mock.spec.ts
│ ├── app.module.ts
│ ├── app.template.html
│ └── main.ts
├── assets
│ ├── icon
│ │ └── favicon.ico
│ └── img
│ │ └── taxi.png
├── components
│ └── map
│ │ ├── map.spec.ts
│ │ └── map.ts
├── declarations.d.ts
├── index.html
├── ionic-mock.ts
├── manifest.json
├── pages
│ ├── about
│ │ ├── about.html
│ │ ├── about.scss
│ │ └── about.ts
│ ├── base-page.ts
│ ├── confirmation
│ │ ├── confirmation.scss
│ │ ├── confirmation.spec.ts
│ │ ├── confirmation.tpl.html
│ │ └── confirmation.ts
│ ├── home
│ │ ├── home.scss
│ │ ├── home.spec.ts
│ │ ├── home.tpl.html
│ │ └── home.ts
│ ├── ride-list
│ │ ├── ride-list.scss
│ │ ├── ride-list.spec.ts
│ │ ├── ride-list.tpl.html
│ │ └── ride-list.ts
│ └── search
│ │ ├── search.spec.ts
│ │ ├── search.tpl.html
│ │ └── search.ts
├── providers
│ ├── map
│ │ ├── geocoder-mock.service.ts
│ │ ├── geocoder.service.ts
│ │ ├── map-mock.service.ts
│ │ ├── map.constants.ts
│ │ └── map.service.ts
│ └── ride
│ │ ├── ride-mock.service.ts
│ │ ├── ride.model.ts
│ │ └── ride.service.ts
├── service-worker.js
└── theme
│ └── variables.scss
├── tsconfig.json
├── tslint.json
├── typedoc.json
├── www
└── .gitignore
└── yarn.lock
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent coding styles between different editors and IDEs
2 | # editorconfig.org
3 |
4 | root = true
5 |
6 | [*]
7 | indent_style = space
8 | indent_size = 2
9 |
10 | # We recommend you to keep these unchanged
11 | end_of_line = lf
12 | charset = utf-8
13 | trim_trailing_whitespace = true
14 | insert_final_newline = true
15 |
16 | [*.md]
17 | trim_trailing_whitespace = false
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Specifies intentionally untracked files to ignore when using Git
2 | # http://git-scm.com/docs/gitignore
3 |
4 | # Webstorm/Vscode/Sublime
5 | .idea/
6 | *.sublime-project
7 | *.sublime-workspace
8 | .vscode/
9 |
10 | # Logs and trash
11 | npm-debug.log*
12 | *~
13 | *.sw[mnpcod]
14 | *.log
15 | *.tmp
16 | log.txt
17 | $RECYCLE.BIN/
18 | tsconfig.tmp.json
19 | .DS_Store
20 | Thumbs.db
21 | UserInterfaceState.xcuserstate
22 |
23 | # Build folders
24 | .sass-cache/
25 | .tmp/
26 | *.tmp.*
27 |
28 | # Coverege folders
29 | config/coverage
30 | coverage/*
31 | dist/
32 | tmp/
33 | temp/
34 |
35 | # Doc
36 | doc/
37 |
38 | # Dependencies
39 | node_modules/
40 | platforms/
41 | plugins/
42 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | 7.5.0
2 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | language: node_js
3 | node_js:
4 | - "6.3"
5 |
6 | notifications:
7 | email: true
8 |
9 | branches:
10 | only:
11 | - master
12 |
13 | script:
14 | - npm run lint
15 | - npm test
16 |
17 | before_install:
18 | - export CHROME_BIN=chromium-browser
19 | - export DISPLAY=:99.0
20 | - sh -e /etc/init.d/xvfb start
21 | - sleep 3
22 | - npm i -g npm@^3
23 | - npm install -g cordova ionic
24 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4 |
5 |
6 | # [0.1.0](https://github.com/ddellamico/ionic2-taxi-app/compare/v0.0.1...v0.1.0) (2017-03-14)
7 |
8 |
9 | ### Features
10 |
11 | * **dependency management:** add yarn to manage dependency management ([fb41b14](https://github.com/ddellamico/ionic2-taxi-app/commit/fb41b14))
12 | * **ionic:** update to ionic 2 final ([491257e](https://github.com/ddellamico/ionic2-taxi-app/commit/491257e))
13 |
14 |
15 |
16 |
17 | ## 0.0.1 (2016-09-07)
18 |
19 |
20 | ### Features
21 |
22 | * **app:** base app structure ([678b882](https://github.com/ddellamico/ionic2-taxi-app/commit/678b882))
23 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright 2015 Drifty Co.
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Ionic 2 Taxi App
2 |
3 | [](https://travis-ci.org/ddellamico/ionic2-taxi-app) [](http://commitizen.github.io/cz-cli/) [](http://makeapullrequest.com)
4 | [](http://opensource.org/licenses/MIT) [](https://david-dm.org/ddellamico/ionic2-taxi-app) [](https://david-dm.org/ddellamico/ionic2-taxi-app#info=devDependencies)
5 |
6 | The main purpose for this project, it's provide a simple starting point for building 'on-demand/rideshare taxi' ionic application ( something like Uber, Lyft or Sidecar .. ) or more generally, applications using extensively Google Maps JavaScript API.
7 |
8 | When you start the app, you see a map centering around your location, with a marker permanently fixed to the center of map.
9 | When the user stop moving the map, an InfoWindow shows the new position. To achieve this, I use google maps geocoder service along with rxjs, that let you easily handle 'OVER_QUERY_LIMIT' response ( API Usage Limits ).
10 |
11 | ** Build configuration and test setup is heavily inspired from this great Ionic 2 boilerplate, take a look [here](https://github.com/marcoturi/ionic2-boilerplate). **
12 |
13 | If you are looking for a more complex and complete Ionic 2 app sample, [check out here](https://github.com/ddellamico/ionic-conference-app).
14 |
15 | **Note: This project is under development.**
16 |
17 | ## App Preview
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | ## Features
29 | * Ionic 2 Final:
30 | * [TypeScript](http://www.typescriptlang.org/)
31 | * [RxJS](https://github.com/Reactive-Extensions/RxJS)
32 | * [Webpack](http://webpack.github.io/)
33 | * [Yarn](https://github.com/yarnpkg/yarn) for fast, reliable, and secure dependency management.
34 | * [BetterScripts](https://github.com/benoror/better-npm-run) for better NPM scripts.
35 | * [tslint](https://github.com/palantir/tslint)
36 | * [Codelyzer](https://github.com/mgechev/codelyzer)
37 | * [Typedoc](https://github.com/TypeStrong/typedoc)
38 | * [NVM](https://github.com/creationix/nvm) to manage multiple active node.js versions
39 |
40 | ## Install
41 | **Make sure you have Node version >= 6.X and NPM >= 3** (node.js version used 7.5.0 and NPM v. 4.1.2)
42 |
43 | ```bash
44 | # Clone the repo
45 | $ git clone https://github.com/ddellamico/ionic2-taxi-app
46 | -----
47 | # change directory to our repo
48 | cd ionic2-taxi-app
49 | -----
50 | # install the repo with yarn
51 | yarn
52 | -----
53 | # restore plugins and platforms
54 | cordova prepare
55 | -----
56 | # start the server (webpack-dev-server)
57 | npm run dev
58 | ```
59 |
60 | go to [http://0.0.0.0:8100](http://0.0.0.0:8100) or [http://localhost:8100](http://localhost:8100) in your browser
61 |
62 | ## Commands
63 | ```bash
64 | $ npm run dev --> Run ionic serve ( development )
65 | $ npm run build --> build files inside www folder ( production )
66 | $ npm run test --> run test with Karma
67 | $ npm run ios:dev --> start ios simulator (ionic run ios)
68 | $ npm run ios:release --> build files for ios platform and generate xcodeproj (ionic build ios)
69 | $ npm run android:dev --> start android simulator (ionic run android)
70 | $ npm run android:release --> build files for android platform and generate apk (ionic build android)
71 | ```
72 |
73 | ## Commit:
74 |
75 | Follows [AngularJS's commit message convention](https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md#-git-commit-guidelines)
76 | ```sh
77 | # Lint and execute tests before committing code.
78 | npm run commit
79 | # OR
80 | # use git commit directly with correct message convention.
81 | git commit -m "chore(ghooks): Add pre-commit and commit-msg ghook"
82 | ```
83 |
84 | ## Tests
85 |
86 | ```sh
87 | $ npm test
88 | ```
89 |
90 | ## Changelog
91 |
92 | You can check the changelog [here](https://github.com/ddellamico/ionic2-taxi-app/blob/master/CHANGELOG.md)
93 |
94 | ## Todo
95 |
96 | * Add more test with karma
97 | * Add protractor (E2E testing)
98 | * Add HMR
99 |
100 | ## License
101 |
102 | MIT
103 |
--------------------------------------------------------------------------------
/config.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Ionic Taxi
4 |
5 | Ionic Taxi App.
6 |
7 |
8 | Damien Dell'Amico
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/config/gmap-mock.js:
--------------------------------------------------------------------------------
1 | // CONVERTED FROM : https://raw.githubusercontent.com/sgruhier/google_maps_mock/master/google_maps_mock.coffee
2 |
3 | var GoogleMapsMock,
4 | extend = function (child, parent) {
5 | for (var key in parent) {
6 | if (hasProp.call(parent, key)) child[key] = parent[key];
7 | }
8 | function ctor() {
9 | this.constructor = child;
10 | }
11 |
12 | ctor.prototype = parent.prototype;
13 | child.prototype = new ctor();
14 | child.__super__ = parent.prototype;
15 | return child;
16 | },
17 | hasProp = {}.hasOwnProperty;
18 |
19 | this.google = {
20 | maps: {
21 | event: {
22 | addDomListener: function () {
23 | },
24 | addDomListenerOnce: function () {
25 | },
26 | addListener: function () {
27 | },
28 | addListenerOnce: function () {
29 | },
30 | bind: function () {
31 | },
32 | clearInstanceListeners: function () {
33 | },
34 | clearListeners: function () {
35 | },
36 | forward: function () {
37 | },
38 | removeListener: function () {
39 | },
40 | trigger: function () {
41 | },
42 | vf: function () {
43 | }
44 | }
45 | }
46 | };
47 |
48 | GoogleMapsMock = (function () {
49 | function GoogleMapsMock() {
50 | }
51 |
52 | GoogleMapsMock.prototype.setMap = function () {
53 | };
54 |
55 | GoogleMapsMock.prototype.getMap = function () {
56 | };
57 |
58 | return GoogleMapsMock;
59 |
60 | })();
61 |
62 | google.maps.LatLng = (function (superClass) {
63 | extend(LatLng, superClass);
64 |
65 | function LatLng(lat, lng) {
66 | this.latitude = parseFloat(lat);
67 | this.longitude = parseFloat(lng);
68 | }
69 |
70 | LatLng.prototype.lat = function () {
71 | return this.latitude;
72 | };
73 |
74 | LatLng.prototype.lng = function () {
75 | return this.longitude;
76 | };
77 |
78 | return LatLng;
79 |
80 | })(GoogleMapsMock);
81 |
82 | google.maps.LatLngBounds = (function (superClass) {
83 | extend(LatLngBounds, superClass);
84 |
85 | function LatLngBounds(ne, sw) {
86 | this.ne = ne;
87 | this.sw = sw;
88 | }
89 |
90 | LatLngBounds.prototype.getSouthWest = function () {
91 | return this.sw;
92 | };
93 |
94 | LatLngBounds.prototype.getNorthEast = function () {
95 | return this.ne;
96 | };
97 |
98 | return LatLngBounds;
99 |
100 | })(GoogleMapsMock);
101 |
102 | google.maps.OverlayView = (function (superClass) {
103 | extend(OverlayView, superClass);
104 |
105 | function OverlayView() {
106 | return OverlayView.__super__.constructor.apply(this, arguments);
107 | }
108 |
109 | return OverlayView;
110 |
111 | })(GoogleMapsMock);
112 |
113 | google.maps.Marker = (function (superClass) {
114 | extend(Marker, superClass);
115 |
116 | function Marker() {
117 | return Marker.__super__.constructor.apply(this, arguments);
118 | }
119 |
120 | Marker.prototype.getAnimation = function () {
121 | };
122 |
123 | Marker.prototype.getClickable = function () {
124 | };
125 |
126 | Marker.prototype.getCursor = function () {
127 | };
128 |
129 | Marker.prototype.getDraggable = function () {
130 | };
131 |
132 | Marker.prototype.getFlat = function () {
133 | };
134 |
135 | Marker.prototype.getIcon = function () {
136 | };
137 |
138 | Marker.prototype.getPosition = function () {
139 | };
140 |
141 | Marker.prototype.getShadow = function () {
142 | };
143 |
144 | Marker.prototype.getShape = function () {
145 | };
146 |
147 | Marker.prototype.getTitle = function () {
148 | };
149 |
150 | Marker.prototype.getVisible = function () {
151 | };
152 |
153 | Marker.prototype.getZIndex = function () {
154 | };
155 |
156 | Marker.prototype.setAnimation = function () {
157 | };
158 |
159 | Marker.prototype.setClickable = function () {
160 | };
161 |
162 | Marker.prototype.setCursor = function () {
163 | };
164 |
165 | Marker.prototype.setDraggable = function () {
166 | };
167 |
168 | Marker.prototype.setFlat = function () {
169 | };
170 |
171 | Marker.prototype.setIcon = function () {
172 | };
173 |
174 | Marker.prototype.setPosition = function () {
175 | };
176 |
177 | Marker.prototype.setShadow = function () {
178 | };
179 |
180 | Marker.prototype.setShape = function () {
181 | };
182 |
183 | Marker.prototype.setTitle = function () {
184 | };
185 |
186 | Marker.prototype.setVisible = function () {
187 | };
188 |
189 | Marker.prototype.setZIndex = function () {
190 | };
191 |
192 | Marker.prototype.setMap = function () {
193 | };
194 |
195 | Marker.prototype.getMap = function () {
196 | };
197 |
198 | return Marker;
199 |
200 | })(GoogleMapsMock);
201 |
202 | google.maps.MarkerImage = (function (superClass) {
203 | extend(MarkerImage, superClass);
204 |
205 | function MarkerImage() {
206 | return MarkerImage.__super__.constructor.apply(this, arguments);
207 | }
208 |
209 | return MarkerImage;
210 |
211 | })(GoogleMapsMock);
212 |
213 | google.maps.Map = (function (superClass) {
214 | extend(Map, superClass);
215 |
216 | function Map() {
217 | return Map.__super__.constructor.apply(this, arguments);
218 | }
219 |
220 | Map.prototype.setCenter = function () {
221 | };
222 |
223 | Map.prototype.getCenter = function () {
224 | return new google.maps.LatLng(4, 12);
225 | };
226 |
227 | Map.prototype.setZoom = function () {
228 | };
229 |
230 | Map.prototype.getDiv = function () {
231 | };
232 |
233 | return Map;
234 |
235 | })(GoogleMapsMock);
236 |
237 | google.maps.Point = (function (superClass) {
238 | extend(Point, superClass);
239 |
240 | function Point() {
241 | return Point.__super__.constructor.apply(this, arguments);
242 | }
243 |
244 | return Point;
245 |
246 | })(GoogleMapsMock);
247 |
248 | google.maps.Size = (function (superClass) {
249 | extend(Size, superClass);
250 |
251 | function Size() {
252 | return Size.__super__.constructor.apply(this, arguments);
253 | }
254 |
255 | return Size;
256 |
257 | })(GoogleMapsMock);
258 |
259 | google.maps.InfoWindow = (function (superClass) {
260 | extend(InfoWindow, superClass);
261 |
262 | function InfoWindow() {
263 | return InfoWindow.__super__.constructor.apply(this, arguments);
264 | }
265 |
266 | return InfoWindow;
267 |
268 | })(GoogleMapsMock);
269 |
--------------------------------------------------------------------------------
/config/helpers.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | // Helper functions
4 | const ROOT = path.resolve(__dirname, '..');
5 |
6 | function root(args) {
7 | args = Array.prototype.slice.call(arguments, 0);
8 | return path.join.apply(path, [ROOT].concat(args));
9 | }
10 |
11 | exports.root = root;
12 |
--------------------------------------------------------------------------------
/config/karma-shim.js:
--------------------------------------------------------------------------------
1 | /*
2 | * When testing with webpack and ES6, we have to do some extra
3 | * things to get testing to work right. Because we are gonna write tests
4 | * in ES6 too, we have to compile those as well. That's handled in
5 | * karma.conf.js with the karma-webpack plugin. This is the entry
6 | * file for webpack test. Just like webpack will create a bundle.js
7 | * file for our client, when we run test, it will compile and bundle them
8 | * all here! Crazy huh. So we need to do some setup
9 | */
10 | Error.stackTraceLimit = Infinity;
11 |
12 | require('core-js/client/shim');
13 | require('reflect-metadata');
14 |
15 | // Typescript emit helpers polyfill
16 | require('ts-helpers');
17 |
18 | require('zone.js/dist/zone');
19 | require('zone.js/dist/long-stack-trace-zone');
20 | require('zone.js/dist/proxy'); // since zone.js 0.6.15
21 | require('zone.js/dist/sync-test');
22 | require('zone.js/dist/jasmine-patch'); // put here since zone.js 0.6.14
23 | require('zone.js/dist/async-test');
24 | require('zone.js/dist/fake-async-test');
25 |
26 | // RxJS
27 | require('rxjs/Rx');
28 |
29 | var testing = require('@angular/core/testing');
30 | var browser = require('@angular/platform-browser-dynamic/testing');
31 |
32 | testing.TestBed.initTestEnvironment(
33 | browser.BrowserDynamicTestingModule,
34 | browser.platformBrowserDynamicTesting()
35 | );
36 |
37 | /*
38 | * Ok, this is kinda crazy. We can use the context method on
39 | * require that webpack created in order to tell webpack
40 | * what files we actually want to require or import.
41 | * Below, context will be a function/object with file names as keys.
42 | * Using that regex we are saying look in ../src then find
43 | * any file that ends with spec.ts and get its path. By passing in true
44 | * we say do this recursively
45 | */
46 | var testContext = require.context('../src', true, /\.spec\.ts/);
47 |
48 | /*
49 | * get all the files, for each file, call the context function
50 | * that will require the file and load it up here. Context will
51 | * loop and require those spec files here
52 | */
53 | function requireAll(requireContext) {
54 | return requireContext.keys().map(requireContext);
55 | }
56 |
57 | // requires and returns all modules that match
58 | var modules = requireAll(testContext);
59 |
--------------------------------------------------------------------------------
/config/karma.conf.js:
--------------------------------------------------------------------------------
1 | module.exports = (config) => {
2 |
3 | var testWebpackConfig = require('./webpack.test.js')({});
4 |
5 | const configuration = {
6 |
7 | // base path that will be used to resolve all patterns (e.g. files, exclude)
8 | basePath: '',
9 | frameworks: ['jasmine'],
10 | files: [
11 | { pattern: './config/karma-shim.js', watched: false },
12 | { pattern: './config/gmap-mock.js', watched: false },
13 | { pattern: './src/assets/**/*', watched: false, included: false, served: true, nocache: false }],
14 | preprocessors: { './config/karma-shim.js': ['coverage', 'webpack', 'sourcemap'] },
15 | webpack: testWebpackConfig,
16 | webpackMiddleware: {
17 | // webpack-dev-middleware configuration
18 | // i.e.
19 | noInfo: true,
20 | // and use stats to turn off verbose output
21 | stats: {
22 | // options i.e.
23 | chunks: false
24 | }
25 | },
26 | reporters: ['mocha'],
27 | port: 9876,
28 | colors: true,
29 | client: {
30 | captureConsole: false
31 | },
32 | /*
33 | * By default all assets are served at http://localhost:[PORT]/base/
34 | */
35 | proxies: {
36 | "/assets/": "/base/src/assets/"
37 | },
38 | /*
39 | * level of logging
40 | * possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
41 | */
42 | logLevel: config.LOG_WARN,
43 | // enable / disable watching file and executing tests whenever any file changes
44 | autoWatch: false,
45 | /*
46 | * start these browsers
47 | * available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
48 | */
49 | browsers: [
50 | 'PhantomJS'
51 | ],
52 | singleRun: true
53 | };
54 |
55 | if (process.env.NO_COVERAGE !== 'true') {
56 | configuration.reporters.push('coverage', 'remap-coverage');
57 | configuration.coverageReporter = {
58 | type: 'in-memory'
59 | };
60 |
61 | configuration.remapCoverageReporter = {
62 | 'text-summary': null,
63 | html: './coverage/istanbul'
64 | };
65 | }
66 |
67 |
68 | config.set(configuration);
69 | };
70 |
--------------------------------------------------------------------------------
/config/webpack.config.js:
--------------------------------------------------------------------------------
1 | // reference : https://github.com/marcoturi/ionic2-boilerplate/blob/master/config/webpack.config.js
2 |
3 | /**
4 | * If you are not using webpack to build your ionic app, this configuration will not affect your code,
5 | * se rollup.config.js instead.
6 | */
7 |
8 | const webpackConfig = require('../node_modules/@ionic/app-scripts/config/webpack.config');
9 | const webpack = require('webpack');
10 |
11 | const nodeEnv = JSON.stringify(process.env.NODE_ENV) || JSON.stringify('development');
12 | const platform = JSON.stringify(process.env.PLATFORM) || JSON.stringify('android');
13 |
14 | /**
15 | * Plugin: DefinePlugin
16 | * Description: Define free variables.
17 | * Useful for having development builds with debug logging or adding global constants.
18 | *
19 | * Environment helpers
20 | *
21 | * See: https://webpack.github.io/docs/list-of-plugins.html#defineplugin
22 | */
23 |
24 | webpackConfig.plugins.push(
25 | new webpack.DefinePlugin({
26 | 'process.env.NODE_ENV': nodeEnv,
27 | 'process.env.PLATFORM': platform
28 | })
29 | );
30 |
--------------------------------------------------------------------------------
/config/webpack.test.js:
--------------------------------------------------------------------------------
1 | // reference : https://github.com/marcoturi/ionic2-boilerplate/blob/master/config/webpack.test.js
2 |
3 | /**
4 | * This config file was inspired by https://github.com/AngularClass/angular2-webpack-starter/
5 | * all credits go to them!
6 | */
7 |
8 | const helpers = require('./helpers');
9 | const path = require('path');
10 |
11 | const ProvidePlugin = require('webpack/lib/ProvidePlugin');
12 | const DefinePlugin = require('webpack/lib/DefinePlugin');
13 | const LoaderOptionsPlugin = require('webpack/lib/LoaderOptionsPlugin');
14 | const ContextReplacementPlugin = require('webpack/lib/ContextReplacementPlugin');
15 |
16 | const ENV = process.env.ENV = process.env.NODE_ENV = 'test';
17 | const coverageEnabled = process.env.NO_COVERAGE !== 'true';
18 |
19 |
20 | /**
21 | * Webpack configuration
22 | *
23 | * See: http://webpack.github.io/docs/configuration.html#cli
24 | */
25 | module.exports = (options) => {
26 |
27 | /**
28 | * Instruments JS files with Istanbul for subsequent code coverage reporting.
29 | * Instrument only testing sources.
30 | *
31 | * See: https://github.com/deepsweet/istanbul-instrumenter-loader
32 | *
33 | * @hack: Disabling coverage if NO_COVERAGE env var is set to 'true'.
34 | * This is useful for karma debug.
35 | *
36 | * See: https://github.com/AngularClass/angular2-webpack-starter/issues/361?_pjax=%23js-repo-pjax-container
37 | * See: https://github.com/gotwarlost/istanbul/issues/212
38 | *
39 | */
40 | let postLoaders = {};
41 | if (coverageEnabled) {
42 | postLoaders = {
43 | enforce: 'post',
44 | test: /\.(js|ts)$/, loader: 'istanbul-instrumenter-loader',
45 | include: helpers.root('src'),
46 | exclude: [
47 | /\.(e2e|spec)\.ts$/,
48 | /node_modules/
49 | ]
50 | };
51 |
52 | }
53 |
54 | return {
55 |
56 | /**
57 | * Source map for Karma from the help of karma-sourcemap-loader & karma-webpack
58 | *
59 | * Do not change, leave as is or it wont work.
60 | * See: https://github.com/webpack/karma-webpack#source-maps
61 | */
62 | devtool: 'inline-source-map',
63 |
64 | /**
65 | * Options affecting the resolving of modules.
66 | *
67 | * See: http://webpack.github.io/docs/configuration.html#resolve
68 | */
69 | resolve: {
70 |
71 | /**
72 | * An array of extensions that should be used to resolve modules.
73 | *
74 | * See: http://webpack.github.io/docs/configuration.html#resolve-extensions
75 | */
76 | extensions: ['.ts', '.js'],
77 |
78 | /**
79 | * Make sure root is src
80 | */
81 | modules: [ path.resolve(__dirname, 'src'), 'node_modules' ]
82 |
83 | },
84 |
85 | /**
86 | * Options affecting the normal modules.
87 | *
88 | * See: http://webpack.github.io/docs/configuration.html#module
89 | */
90 | module: {
91 |
92 | rules: [
93 |
94 | /**
95 | * Tslint loader support for *.ts files
96 | *
97 | * See: https://github.com/wbuchwalter/tslint-loader
98 | */
99 | {
100 | enforce: 'pre',
101 | test: /\.ts$/,
102 | loader: 'tslint-loader',
103 | exclude: [helpers.root('node_modules')]
104 | },
105 |
106 | /**
107 | * Source map loader support for *.js files
108 | * Extracts SourceMaps for source files that as added as sourceMappingURL comment.
109 | *
110 | * See: https://github.com/webpack/source-map-loader
111 | */
112 | {
113 | enforce: 'pre',
114 | test: /\.js$/,
115 | loader: 'source-map-loader',
116 | exclude: [
117 | // these packages have problems with their sourcemaps
118 | helpers.root('node_modules/rxjs'),
119 | helpers.root('node_modules/@angular'),
120 | helpers.root('node_modules/ionic-angular'),
121 | helpers.root('node_modules/ionic-native')
122 | ]
123 | },
124 |
125 | /**
126 | * Typescript loader support for .ts and Angular 2 async routes via .async.ts
127 | *
128 | * See: https://github.com/s-panferov/awesome-typescript-loader
129 | */
130 | {
131 | test: /\.ts$/,
132 |
133 | loaders: [
134 | `awesome-typescript-loader?sourceMap=${!coverageEnabled},inlineSourceMap=${coverageEnabled},module=commonjs,noEmitHelpers=true,compilerOptions{}=removeComments:true`,
135 | 'angular2-template-loader'
136 | ],
137 | // query: {
138 | // // use inline sourcemaps for "karma-remap-coverage" reporter
139 | // sourceMap: !coverageEnabled,
140 | // inlineSourceMap: coverageEnabled,
141 | // module: 'commonjs',
142 | // noEmitHelpers: true,
143 | // compilerOptions: {
144 | // removeComments: true
145 | // }
146 | // },
147 | exclude: [/\.e2e\.ts$/]
148 | },
149 |
150 | /**
151 | * Json loader support for *.json files.
152 | *
153 | * See: https://github.com/webpack/json-loader
154 | */
155 | {
156 | test: /\.json$/,
157 | loader: 'json-loader',
158 | exclude: [helpers.root('src/index.html')]
159 | },
160 |
161 | /**
162 | * Raw loader support for *.css files
163 | * Returns file content as string
164 | *
165 | * See: https://github.com/webpack/raw-loader
166 | */
167 | {
168 | test: /\.css$/,
169 | loaders: ['to-string-loader', 'css-loader'],
170 | exclude: [helpers.root('src/index.html')]
171 | },
172 |
173 | /**
174 | * Raw loader support for *.html
175 | * Returns file content as string
176 | *
177 | * See: https://github.com/webpack/raw-loader
178 | */
179 | {
180 | test: /\.html$/,
181 | loader: 'raw-loader',
182 | exclude: [helpers.root('src/index.html')]
183 | },
184 |
185 | postLoaders
186 | ]
187 | },
188 |
189 | /**
190 | * Add additional plugins to the compiler.
191 | *
192 | * See: http://webpack.github.io/docs/configuration.html#plugins
193 | */
194 | plugins: [
195 |
196 | /**
197 | * Plugin: DefinePlugin
198 | * Description: Define free variables.
199 | * Useful for having development builds with debug logging or adding global constants.
200 | *
201 | * Environment helpers
202 | *
203 | * See: https://webpack.github.io/docs/list-of-plugins.html#defineplugin
204 | */
205 | // NOTE: when adding more properties make sure you include them in custom-typings.d.ts
206 | new DefinePlugin({
207 | 'ENV': JSON.stringify(ENV),
208 | 'process.env': {
209 | 'ENV': JSON.stringify(ENV),
210 | 'NODE_ENV': JSON.stringify(ENV)
211 | }
212 | }),
213 |
214 | /**
215 | * Plugin: ContextReplacementPlugin
216 | * Description: Provides context to Angular's use of System.import
217 | *
218 | * See: https://webpack.github.io/docs/list-of-plugins.html#contextreplacementplugin
219 | * See: https://github.com/angular/angular/issues/11580
220 | */
221 | new ContextReplacementPlugin(
222 | // The (\\|\/) piece accounts for path separators in *nix and Windows
223 | /angular(\\|\/)core(\\|\/)(esm(\\|\/)src|src)(\\|\/)linker/,
224 | helpers.root('src') // location of your src
225 | ),
226 |
227 | /**
228 | * Plugin LoaderOptionsPlugin (experimental)
229 | *
230 | * See: https://gist.github.com/sokra/27b24881210b56bbaff7
231 | */
232 | new LoaderOptionsPlugin({
233 | debug: true,
234 | options: {
235 |
236 | /**
237 | * Static analysis linter for TypeScript advanced options configuration
238 | * Description: An extensible linter for the TypeScript language.
239 | *
240 | * See: https://github.com/wbuchwalter/tslint-loader
241 | */
242 | tslint: {
243 | emitErrors: false,
244 | failOnHint: false,
245 | resourcePath: 'src'
246 | },
247 |
248 | }
249 | }),
250 |
251 | ],
252 |
253 | /**
254 | * Disable performance hints
255 | *
256 | * See: https://github.com/a-tarasyuk/rr-boilerplate/blob/master/webpack/dev.config.babel.js#L41
257 | */
258 | performance: {
259 | hints: false
260 | },
261 |
262 |
263 | /**
264 | * Include polyfills or mocks for various node stuff
265 | * Description: Node configuration
266 | *
267 | * See: https://webpack.github.io/docs/configuration.html#node
268 | */
269 | node: {
270 | global: true,
271 | process: false,
272 | crypto: 'empty',
273 | module: false,
274 | clearImmediate: false,
275 | setImmediate: false
276 | }
277 |
278 | };
279 | };
280 |
--------------------------------------------------------------------------------
/coverage/.gitignore:
--------------------------------------------------------------------------------
1 | # Ignore everything in this directory
2 | *
3 | # Except this file
4 | !.gitignore
5 | !protractor
6 |
--------------------------------------------------------------------------------
/ionic.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ionic2-taxi-app",
3 | "app_id": "",
4 | "typescript": true,
5 | "v2": true
6 | }
7 |
--------------------------------------------------------------------------------
/ionic.project:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ionic2-taxi-app",
3 | "app_id": ""
4 | }
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | module.exports = require('./config/karma.conf.js');
2 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ionic2-taxi-app",
3 | "version": "0.1.0",
4 | "description": "Ionic 2 Taxi App - A simple starting point for building 'on-demand/rideshare taxi' ionic application ( something like Uber, Lyft or Sidecar .. )",
5 | "keywords": [
6 | "ionic",
7 | "ionic2",
8 | "angular2",
9 | "google-maps",
10 | "webpack",
11 | "typescript",
12 | "yarn"
13 | ],
14 | "author": {
15 | "name": "Damien Dell'Amico",
16 | "email": "damien.dellamico@gmail.com",
17 | "url": "https://github.com/ddellamico"
18 | },
19 | "license": "MIT",
20 | "repository": {
21 | "type": "git",
22 | "url": "https://github.com/ddellamico/ionic2-taxi-app.git"
23 | },
24 | "scripts": {
25 | "dev": "bnr dev",
26 | "build": "bnr build",
27 | "build-dev": "bnr build-dev",
28 | "clean": "bnr clean",
29 | "test": "bnr test",
30 | "test:watch": "bnr test-watch",
31 | "ios:dev": "bnr ios:dev",
32 | "ios:release": "bnr ios:release",
33 | "android:dev": "bnr android:dev",
34 | "android:release": "bnr android:release",
35 | "commit": "git-cz",
36 | "release": "standard-version --no-verify",
37 | "push": "git push --follow-tags origin master",
38 | "lint": "tslint \"src/**/*.ts\" --exclude=src/**/*.d.ts",
39 | "docs": "typedoc --options typedoc.json --exclude '**/*+(e2e|spec|index).ts' ./src/",
40 | "outdated": "yarn outdated",
41 | "precommit": "npm run lint",
42 | "prepush": "npm test"
43 | },
44 | "betterScripts": {
45 | "dev": {
46 | "command": "ionic-app-scripts serve",
47 | "env": {
48 | "NODE_ENV": "development",
49 | "PLATFORM": "web"
50 | }
51 | },
52 | "build": {
53 | "command": "ionic-app-scripts build --prod",
54 | "env": {
55 | "NODE_ENV": "production"
56 | }
57 | },
58 | "build-dev": {
59 | "command": "ionic-app-scripts build --dev",
60 | "env": {
61 | "NODE_ENV": "development"
62 | }
63 | },
64 | "clean": {
65 | "command": "ionic-app-scripts clean"
66 | },
67 | "test": {
68 | "command": "karma start",
69 | "env": {
70 | "NODE_ENV": "test"
71 | }
72 | },
73 | "test-watch": {
74 | "command": "karma start --auto-watch --no-single-run",
75 | "env": {
76 | "NODE_ENV": "test",
77 | "NO_COVERAGE": true
78 | }
79 | },
80 | "ios:dev": {
81 | "command": "ionic run ios --livereload",
82 | "env": {
83 | "NODE_ENV": "development",
84 | "PLATFORM": "ios"
85 | }
86 | },
87 | "ios:release": {
88 | "command": "ionic build ios --prod",
89 | "env": {
90 | "NODE_ENV": "production",
91 | "PLATFORM": "ios"
92 | }
93 | },
94 | "android:dev": {
95 | "command": "ionic run android --livereload",
96 | "env": {
97 | "NODE_ENV": "development",
98 | "PLATFORM": "android"
99 | }
100 | },
101 | "android:release": {
102 | "command": "ionic build android --prod",
103 | "env": {
104 | "NODE_ENV": "production",
105 | "PLATFORM": "android"
106 | }
107 | }
108 | },
109 | "dependencies": {
110 | "@angular/common": "2.4.8",
111 | "@angular/compiler": "2.4.8",
112 | "@angular/compiler-cli": "2.4.8",
113 | "@angular/core": "2.4.8",
114 | "@angular/forms": "2.4.8",
115 | "@angular/http": "2.4.8",
116 | "@angular/platform-browser": "2.4.8",
117 | "@angular/platform-browser-dynamic": "2.4.8",
118 | "@angular/platform-server": "2.4.8",
119 | "@angular/tsc-wrapped": "~0.5.2",
120 | "@ionic/storage": "2.0.0",
121 | "ionic-angular": "2.2.0",
122 | "ionic-native": "2.4.1",
123 | "ionicons": "3.0.0",
124 | "lodash-es": "^4.17.4",
125 | "node-uuid": "^1.4.7",
126 | "rxjs": "5.0.1",
127 | "sw-toolbox": "3.4.0",
128 | "uuid": "^2.0.2",
129 | "zone.js": "0.7.2"
130 | },
131 | "devDependencies": {
132 | "@ionic/app-scripts": "1.1.4",
133 | "@types/googlemaps": "^3.26.2",
134 | "@types/jasmine": "2.5.41",
135 | "@types/lodash": "ts2.0",
136 | "@types/node-uuid": "0.0.28",
137 | "angular2-template-loader": "^0.6.0",
138 | "awesome-typescript-loader": "^3.0.8",
139 | "better-npm-run": "0.0.14",
140 | "codelyzer": "2.0.1",
141 | "commitizen": "^2.9.5",
142 | "css-loader": "^0.26.1",
143 | "cz-conventional-changelog": "^2.0.0",
144 | "finalhandler": "^1.0.0",
145 | "husky": "^0.13.1",
146 | "istanbul-instrumenter-loader": "0.2.0",
147 | "jasmine-core": "^2.5.2",
148 | "jasmine-spec-reporter": "^3.2.0",
149 | "json-loader": "^0.5.4",
150 | "karma": "^1.4.0",
151 | "karma-chrome-launcher": "^2.0.0",
152 | "karma-coverage": "^1.1.1",
153 | "karma-jasmine": "^1.1.0",
154 | "karma-mocha-reporter": "^2.2.2",
155 | "karma-phantomjs-launcher": "^1.0.2",
156 | "karma-remap-coverage": "0.1.2",
157 | "karma-sourcemap-loader": "^0.3.7",
158 | "karma-webpack": "2.0.2",
159 | "phantomjs-prebuilt": "^2.1.14",
160 | "raw-loader": "0.5.1",
161 | "serve-static": "^1.11.2",
162 | "source-map-loader": "^0.1.6",
163 | "standard-version": "^4.0.0",
164 | "to-string-loader": "^1.1.4",
165 | "ts-helpers": "^1.1.1",
166 | "ts-node": "^2.1.0",
167 | "tslint": "4.0.0",
168 | "tslint-loader": "^3.4.2",
169 | "typedoc": "^0.5.7",
170 | "typescript": "~2.0.10",
171 | "validate-commit-msg": "^2.11.1",
172 | "webpack": "2.2.1",
173 | "xml2js": "^0.4.17"
174 | },
175 | "config": {
176 | "commitizen": {
177 | "path": "./node_modules/cz-conventional-changelog"
178 | },
179 | "ionic_webpack": "./config/webpack.config.js"
180 | },
181 | "engines": {
182 | "node": ">= 6.0.0",
183 | "npm": ">= 3"
184 | }
185 | }
186 |
--------------------------------------------------------------------------------
/resources/android/icon/drawable-hdpi-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ddellamico/ionic2-taxi-app/a0f81c59270a5126c3fb527334dfca614d32e027/resources/android/icon/drawable-hdpi-icon.png
--------------------------------------------------------------------------------
/resources/android/icon/drawable-ldpi-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ddellamico/ionic2-taxi-app/a0f81c59270a5126c3fb527334dfca614d32e027/resources/android/icon/drawable-ldpi-icon.png
--------------------------------------------------------------------------------
/resources/android/icon/drawable-mdpi-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ddellamico/ionic2-taxi-app/a0f81c59270a5126c3fb527334dfca614d32e027/resources/android/icon/drawable-mdpi-icon.png
--------------------------------------------------------------------------------
/resources/android/icon/drawable-xhdpi-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ddellamico/ionic2-taxi-app/a0f81c59270a5126c3fb527334dfca614d32e027/resources/android/icon/drawable-xhdpi-icon.png
--------------------------------------------------------------------------------
/resources/android/icon/drawable-xxhdpi-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ddellamico/ionic2-taxi-app/a0f81c59270a5126c3fb527334dfca614d32e027/resources/android/icon/drawable-xxhdpi-icon.png
--------------------------------------------------------------------------------
/resources/android/icon/drawable-xxxhdpi-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ddellamico/ionic2-taxi-app/a0f81c59270a5126c3fb527334dfca614d32e027/resources/android/icon/drawable-xxxhdpi-icon.png
--------------------------------------------------------------------------------
/resources/android/splash/drawable-port-hdpi-screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ddellamico/ionic2-taxi-app/a0f81c59270a5126c3fb527334dfca614d32e027/resources/android/splash/drawable-port-hdpi-screen.png
--------------------------------------------------------------------------------
/resources/android/splash/drawable-port-ldpi-screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ddellamico/ionic2-taxi-app/a0f81c59270a5126c3fb527334dfca614d32e027/resources/android/splash/drawable-port-ldpi-screen.png
--------------------------------------------------------------------------------
/resources/android/splash/drawable-port-mdpi-screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ddellamico/ionic2-taxi-app/a0f81c59270a5126c3fb527334dfca614d32e027/resources/android/splash/drawable-port-mdpi-screen.png
--------------------------------------------------------------------------------
/resources/android/splash/drawable-port-xhdpi-screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ddellamico/ionic2-taxi-app/a0f81c59270a5126c3fb527334dfca614d32e027/resources/android/splash/drawable-port-xhdpi-screen.png
--------------------------------------------------------------------------------
/resources/android/splash/drawable-port-xxhdpi-screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ddellamico/ionic2-taxi-app/a0f81c59270a5126c3fb527334dfca614d32e027/resources/android/splash/drawable-port-xxhdpi-screen.png
--------------------------------------------------------------------------------
/resources/android/splash/drawable-port-xxxhdpi-screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ddellamico/ionic2-taxi-app/a0f81c59270a5126c3fb527334dfca614d32e027/resources/android/splash/drawable-port-xxxhdpi-screen.png
--------------------------------------------------------------------------------
/resources/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ddellamico/ionic2-taxi-app/a0f81c59270a5126c3fb527334dfca614d32e027/resources/icon.png
--------------------------------------------------------------------------------
/resources/ios/icon/icon-40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ddellamico/ionic2-taxi-app/a0f81c59270a5126c3fb527334dfca614d32e027/resources/ios/icon/icon-40.png
--------------------------------------------------------------------------------
/resources/ios/icon/icon-40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ddellamico/ionic2-taxi-app/a0f81c59270a5126c3fb527334dfca614d32e027/resources/ios/icon/icon-40@2x.png
--------------------------------------------------------------------------------
/resources/ios/icon/icon-40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ddellamico/ionic2-taxi-app/a0f81c59270a5126c3fb527334dfca614d32e027/resources/ios/icon/icon-40@3x.png
--------------------------------------------------------------------------------
/resources/ios/icon/icon-50.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ddellamico/ionic2-taxi-app/a0f81c59270a5126c3fb527334dfca614d32e027/resources/ios/icon/icon-50.png
--------------------------------------------------------------------------------
/resources/ios/icon/icon-50@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ddellamico/ionic2-taxi-app/a0f81c59270a5126c3fb527334dfca614d32e027/resources/ios/icon/icon-50@2x.png
--------------------------------------------------------------------------------
/resources/ios/icon/icon-60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ddellamico/ionic2-taxi-app/a0f81c59270a5126c3fb527334dfca614d32e027/resources/ios/icon/icon-60.png
--------------------------------------------------------------------------------
/resources/ios/icon/icon-60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ddellamico/ionic2-taxi-app/a0f81c59270a5126c3fb527334dfca614d32e027/resources/ios/icon/icon-60@2x.png
--------------------------------------------------------------------------------
/resources/ios/icon/icon-60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ddellamico/ionic2-taxi-app/a0f81c59270a5126c3fb527334dfca614d32e027/resources/ios/icon/icon-60@3x.png
--------------------------------------------------------------------------------
/resources/ios/icon/icon-72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ddellamico/ionic2-taxi-app/a0f81c59270a5126c3fb527334dfca614d32e027/resources/ios/icon/icon-72.png
--------------------------------------------------------------------------------
/resources/ios/icon/icon-72@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ddellamico/ionic2-taxi-app/a0f81c59270a5126c3fb527334dfca614d32e027/resources/ios/icon/icon-72@2x.png
--------------------------------------------------------------------------------
/resources/ios/icon/icon-76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ddellamico/ionic2-taxi-app/a0f81c59270a5126c3fb527334dfca614d32e027/resources/ios/icon/icon-76.png
--------------------------------------------------------------------------------
/resources/ios/icon/icon-76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ddellamico/ionic2-taxi-app/a0f81c59270a5126c3fb527334dfca614d32e027/resources/ios/icon/icon-76@2x.png
--------------------------------------------------------------------------------
/resources/ios/icon/icon-83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ddellamico/ionic2-taxi-app/a0f81c59270a5126c3fb527334dfca614d32e027/resources/ios/icon/icon-83.5@2x.png
--------------------------------------------------------------------------------
/resources/ios/icon/icon-small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ddellamico/ionic2-taxi-app/a0f81c59270a5126c3fb527334dfca614d32e027/resources/ios/icon/icon-small.png
--------------------------------------------------------------------------------
/resources/ios/icon/icon-small@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ddellamico/ionic2-taxi-app/a0f81c59270a5126c3fb527334dfca614d32e027/resources/ios/icon/icon-small@2x.png
--------------------------------------------------------------------------------
/resources/ios/icon/icon-small@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ddellamico/ionic2-taxi-app/a0f81c59270a5126c3fb527334dfca614d32e027/resources/ios/icon/icon-small@3x.png
--------------------------------------------------------------------------------
/resources/ios/icon/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ddellamico/ionic2-taxi-app/a0f81c59270a5126c3fb527334dfca614d32e027/resources/ios/icon/icon.png
--------------------------------------------------------------------------------
/resources/ios/icon/icon@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ddellamico/ionic2-taxi-app/a0f81c59270a5126c3fb527334dfca614d32e027/resources/ios/icon/icon@2x.png
--------------------------------------------------------------------------------
/resources/ios/splash/Default-568h@2x~iphone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ddellamico/ionic2-taxi-app/a0f81c59270a5126c3fb527334dfca614d32e027/resources/ios/splash/Default-568h@2x~iphone.png
--------------------------------------------------------------------------------
/resources/ios/splash/Default-667h.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ddellamico/ionic2-taxi-app/a0f81c59270a5126c3fb527334dfca614d32e027/resources/ios/splash/Default-667h.png
--------------------------------------------------------------------------------
/resources/ios/splash/Default-736h.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ddellamico/ionic2-taxi-app/a0f81c59270a5126c3fb527334dfca614d32e027/resources/ios/splash/Default-736h.png
--------------------------------------------------------------------------------
/resources/ios/splash/Default-Portrait@2x~ipad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ddellamico/ionic2-taxi-app/a0f81c59270a5126c3fb527334dfca614d32e027/resources/ios/splash/Default-Portrait@2x~ipad.png
--------------------------------------------------------------------------------
/resources/ios/splash/Default-Portrait~ipad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ddellamico/ionic2-taxi-app/a0f81c59270a5126c3fb527334dfca614d32e027/resources/ios/splash/Default-Portrait~ipad.png
--------------------------------------------------------------------------------
/resources/ios/splash/Default@2x~iphone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ddellamico/ionic2-taxi-app/a0f81c59270a5126c3fb527334dfca614d32e027/resources/ios/splash/Default@2x~iphone.png
--------------------------------------------------------------------------------
/resources/ios/splash/Default~iphone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ddellamico/ionic2-taxi-app/a0f81c59270a5126c3fb527334dfca614d32e027/resources/ios/splash/Default~iphone.png
--------------------------------------------------------------------------------
/resources/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ddellamico/ionic2-taxi-app/a0f81c59270a5126c3fb527334dfca614d32e027/resources/splash.png
--------------------------------------------------------------------------------
/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Damien Dell'Amico
3 | * @copyright Copyright (c) 2017
4 | * @license GPL-3.0
5 | */
6 |
7 | import { Component, ViewChild } from '@angular/core';
8 | import { Platform, Nav } from 'ionic-angular';
9 | import { StatusBar, Splashscreen } from 'ionic-native';
10 | import { HomePage } from '../pages/home/home';
11 | import { AboutPage } from '../pages/about/about';
12 | import { RideListPage } from '../pages/ride-list/ride-list';
13 |
14 | export interface PageInterface {
15 | title: string;
16 | component: any;
17 | icon: string;
18 | logsOut?: boolean;
19 | index?: number;
20 | tabComponent?: any;
21 | }
22 |
23 | @Component({
24 | templateUrl: 'app.template.html',
25 | })
26 | export class TaxiApp {
27 |
28 | appPages: PageInterface[] = [
29 | {title: 'Map', component: HomePage, index: 1, icon: 'map'},
30 | {title: 'Taxi rides', component: RideListPage, index: 2, icon: 'car'},
31 | {title: 'About', component: AboutPage, index: 3, icon: 'information-circle'},
32 | ];
33 |
34 | rootPage: any = HomePage;
35 |
36 | // the root nav is a child of the root app component
37 | // @ViewChild(Nav) gets a reference to the app's root nav
38 | @ViewChild(Nav) private nav: Nav;
39 |
40 | constructor(private platform: Platform) {
41 | // Call any initial plugins when ready
42 | platform.ready().then(() => {
43 | // Okay, so the platform is ready and our plugins are available.
44 | // Here you can do any higher level native things you might need.
45 | StatusBar.styleDefault();
46 | Splashscreen.hide();
47 | // https://github.com/apache/cordova-plugin-inappbrowser
48 | // The cordova.InAppBrowser.open() function is defined to be a drop-in replacement for the window.open()
49 | // function. Existing window.open() calls can use the InAppBrowser window, by replacing window.open:
50 | if ((window).cordova && (window).cordova.InAppBrowser) {
51 | window.open = (window).cordova.InAppBrowser.open;
52 | }
53 | });
54 | }
55 |
56 | openPage(page: PageInterface) {
57 | // the nav component was found using @ViewChild(Nav)
58 | // reset the nav to remove previous pages and only have this page
59 | // we wouldn't want the back button to show in this scenario
60 | if (page.index) {
61 | this.nav.setRoot(page.component, { tabIndex: page.index });
62 | } else {
63 | this.nav.setRoot(page.component).catch(() => {
64 | console.log("Didn't set nav root");
65 | });
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/app/app.mock.spec.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Damien Dell'Amico
3 | * @copyright Copyright (c) 2016
4 | * @license GPL-3.0
5 | */
6 |
7 | import { Platform } from 'ionic-angular';
8 | import { TaxiApp } from './app.component';
9 | import { TestBed, ComponentFixture, async } from '@angular/core/testing';
10 | import { CUSTOM_ELEMENTS_SCHEMA, DebugElement } from '@angular/core';
11 | import { PlatformMock } from '../ionic-mock';
12 | import { } from 'jasmine';
13 |
14 | describe('TaxiApp', () => {
15 |
16 | let comp: TaxiApp;
17 | let fixture: ComponentFixture;
18 | let de: DebugElement;
19 | let el: HTMLElement;
20 |
21 | beforeEach(async(() => {
22 | TestBed.configureTestingModule({
23 | schemas: [CUSTOM_ELEMENTS_SCHEMA],
24 | declarations: [TaxiApp],
25 | providers: [
26 | {provide: Platform, useClass: PlatformMock}
27 | ]
28 | }).compileComponents();
29 | }));
30 |
31 | beforeEach(() => {
32 |
33 | fixture = TestBed.createComponent(TaxiApp);
34 | comp = fixture.componentInstance;
35 | de = fixture.debugElement;
36 |
37 | // #trick
38 | // If you want to trigger ionViewWillEnter automatically de-comment the following line
39 | // fixture.componentInstance.ionViewWillEnter();
40 |
41 | fixture.detectChanges();
42 | });
43 |
44 | afterEach(() => {
45 | fixture.destroy();
46 | comp = null;
47 | de = null;
48 | el = null;
49 | });
50 |
51 | it('is created', () => {
52 | expect(fixture).toBeTruthy();
53 | expect(comp).toBeTruthy();
54 | });
55 |
56 | it('should initialize with an app', () => {
57 | expect(comp['app']).not.toBe(null);
58 | });
59 |
60 | it('should have a root page', () => {
61 | expect(comp['rootPage']).not.toBe(null);
62 | });
63 |
64 | it('should have 2 main pages', () => {
65 | expect(comp.appPages.length).toEqual(3);
66 | });
67 |
68 | });
69 |
--------------------------------------------------------------------------------
/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Damien Dell'Amico
3 | * @copyright Copyright (c) 2017
4 | * @license GPL-3.0
5 | */
6 |
7 | import { NgModule } from '@angular/core';
8 | import { IonicApp, IonicModule } from 'ionic-angular';
9 | import { TaxiApp } from './app.component';
10 | import { AboutPage } from '../pages/about/about';
11 | import { HomePage } from '../pages/home/home';
12 | import { RideListPage } from '../pages/ride-list/ride-list';
13 | import { ConfirmationPage } from '../pages/confirmation/confirmation';
14 |
15 | import { MapComponent } from '../components/map/map';
16 | import { SearchPage } from '../pages/search/search';
17 |
18 | import { RideService } from '../providers/ride/ride.service';
19 | import { GeocoderService } from '../providers/map/geocoder.service';
20 | import { MapService } from '../providers/map/map.service';
21 |
22 | @NgModule({
23 | declarations: [
24 | TaxiApp,
25 | AboutPage,
26 | HomePage,
27 | RideListPage,
28 | MapComponent,
29 | SearchPage,
30 | ConfirmationPage
31 | ],
32 | imports: [
33 | IonicModule.forRoot(TaxiApp),
34 | ],
35 | bootstrap: [IonicApp],
36 | entryComponents: [
37 | TaxiApp,
38 | AboutPage,
39 | HomePage,
40 | RideListPage,
41 | SearchPage,
42 | ConfirmationPage
43 | ],
44 | providers: [RideService, GeocoderService, MapService],
45 | })
46 | export class AppModule {
47 | }
48 |
--------------------------------------------------------------------------------
/src/app/app.template.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Menu
5 |
6 |
7 |
8 |
9 |
10 | Navigate
11 |
12 |
13 |
14 | {{p.title}}
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/app/main.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Damien Dell'Amico
3 | * @copyright Copyright (c) 2016
4 | * @license GPL-3.0
5 | */
6 |
7 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
8 |
9 | // RxJS
10 | import 'rxjs/add/observable/throw';
11 | import 'rxjs/add/operator/catch';
12 | import 'rxjs/add/operator/map';
13 | import 'rxjs/add/operator/mergeMap';
14 | import 'rxjs/add/operator/switchMap';
15 | import 'rxjs/add/operator/debounceTime';
16 | import 'rxjs/add/operator/retry';
17 | import 'rxjs/add/operator/distinctUntilChanged';
18 | import 'rxjs/add/operator/do';
19 | import 'rxjs/add/observable/of';
20 | import 'rxjs/add/observable/from';
21 |
22 | import { AppModule } from './app.module';
23 |
24 | platformBrowserDynamic().bootstrapModule(AppModule);
25 |
--------------------------------------------------------------------------------
/src/assets/icon/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ddellamico/ionic2-taxi-app/a0f81c59270a5126c3fb527334dfca614d32e027/src/assets/icon/favicon.ico
--------------------------------------------------------------------------------
/src/assets/img/taxi.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ddellamico/ionic2-taxi-app/a0f81c59270a5126c3fb527334dfca614d32e027/src/assets/img/taxi.png
--------------------------------------------------------------------------------
/src/components/map/map.spec.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Damien Dell'Amico
3 | * @copyright Copyright (c) 2016
4 | * @license GPL-3.0
5 | */
6 |
7 | import { ViewController, AlertController } from 'ionic-angular';
8 | import { TestBed, ComponentFixture, async } from '@angular/core/testing';
9 | import { CUSTOM_ELEMENTS_SCHEMA, DebugElement } from '@angular/core';
10 | import { NavController } from 'ionic-angular';
11 | import { TaxiApp } from '../../app/app.component';
12 | import { MapComponent } from './map';
13 | import { GeocoderServiceMock } from '../../providers/map/geocoder-mock.service';
14 | import { GeocoderService } from '../../providers/map/geocoder.service';
15 | import { MapService } from '../../providers/map/map.service';
16 | import { MapServiceMock } from '../../providers/map/map-mock.service';
17 | import { NavMock, ViewControllerMock, AlertControllerMock } from '../../ionic-mock';
18 | import { RideService } from '../../providers/ride/ride.service';
19 | import { RideServiceMock } from '../../providers/ride/ride-mock.service';
20 |
21 | describe('Component: Map', () => {
22 |
23 | let comp: MapComponent;
24 | let fixture: ComponentFixture;
25 | let de: DebugElement;
26 | let el: HTMLElement;
27 | let mapService: MapService;
28 | let rideService: RideService;
29 | let geocoderService: GeocoderServiceMock;
30 |
31 | beforeEach(async(() => {
32 | TestBed.configureTestingModule({
33 | schemas: [CUSTOM_ELEMENTS_SCHEMA],
34 | declarations: [TaxiApp, MapComponent],
35 | providers: [
36 | {provide: NavController, useClass: NavMock},
37 | {provide: ViewController, useClass: ViewControllerMock},
38 | {provide: AlertController, useClass: AlertControllerMock},
39 | {provide: RideService, useClass: RideServiceMock},
40 | {provide: MapService, useClass: MapServiceMock},
41 | {provide: GeocoderService, useClass: GeocoderServiceMock}
42 | ]
43 | }).compileComponents();
44 | }));
45 |
46 | beforeEach(() => {
47 | fixture = TestBed.createComponent(MapComponent);
48 | comp = fixture.componentInstance;
49 | de = fixture.debugElement;
50 |
51 | mapService = TestBed.get(MapService);
52 | rideService = TestBed.get(RideService);
53 | geocoderService = TestBed.get(GeocoderService);
54 | });
55 |
56 | afterEach(() => {
57 | fixture.destroy();
58 | comp = null;
59 | de = null;
60 | el = null;
61 | });
62 |
63 | it('is created', () => {
64 | expect(fixture).toBeTruthy();
65 | expect(comp).toBeTruthy();
66 | });
67 |
68 | it('should create a new map', () => {
69 | const _response = new Promise((resolve: Function) => {
70 | resolve(true);
71 | });
72 |
73 | spyOn(mapService, 'createMap').and.returnValue(_response);
74 |
75 | const instance = fixture.componentInstance;
76 | instance.ngAfterViewInit().then(() => {
77 | expect(mapService.createMap).toHaveBeenCalled();
78 | expect(instance.map).toBeTruthy();
79 | });
80 | });
81 | });
82 |
--------------------------------------------------------------------------------
/src/components/map/map.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Damien Dell'Amico
3 | * @copyright Copyright (c) 2016
4 | * @license GPL-3.0
5 | */
6 |
7 | import { Component, Output, EventEmitter, AfterViewInit, ViewChild } from '@angular/core';
8 | import { MapService } from '../../providers/map/map.service';
9 |
10 | @Component({
11 | selector: 'it-map',
12 | template: `
`
13 | })
14 |
15 | export class MapComponent implements AfterViewInit {
16 |
17 | @Output() onMapReady = new EventEmitter();
18 | @Output() onMapIdle = new EventEmitter();
19 | @Output() onCenterChanged = new EventEmitter();
20 | @Output() onDragStart = new EventEmitter();
21 |
22 | @ViewChild('map') mapCanvas: any;
23 | map: any = null;
24 |
25 | constructor(private mapService: MapService) {
26 | }
27 |
28 | ngAfterViewInit() {
29 | const mapElem = this.mapCanvas.nativeElement;
30 | return this.mapService.createMap(mapElem).then((map) => {
31 | this.map = map;
32 | this.bindMapEvents(mapElem);
33 | });
34 | }
35 |
36 | private bindMapEvents(mapEl: HTMLElement): void {
37 | // Stop the side bar from dragging when mousedown/tapdown on the map
38 | google.maps.event.addDomListener(mapEl, 'mousedown', (e: any) => {
39 | e.preventDefault();
40 | });
41 |
42 | google.maps.event.addListenerOnce(this.map, 'idle', () => {
43 | this.onMapReady.emit({
44 | value: this.map
45 | });
46 | });
47 |
48 | google.maps.event.addListenerOnce(this.map, 'center_changed', () => {
49 | this.onCenterChanged.emit({
50 | value: this.map
51 | });
52 | });
53 |
54 | google.maps.event.addListener(this.map, 'idle', () => {
55 | this.onMapIdle.emit({
56 | value: this.map
57 | });
58 | });
59 |
60 | google.maps.event.addListener(this.map, 'dragstart', () => {
61 | this.onDragStart.emit({
62 | value: this.map
63 | });
64 | });
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/declarations.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * ref: https://github.com/AngularClass/angular2-webpack-starter
3 | * Custom Type Definitions
4 | * When including 3rd party modules you also need to include the type definition for the module
5 | * if they don't provide one within the module. You can try to install it with @types
6 | * npm install @types/moment
7 | * npm install @types/lodash
8 | * If you can't find the type definition in the registry we can make an ambient/global definition in
9 | * this file for now. For example
10 | * declare module 'my-module' {
11 | * export function doesSomething(value: string): string;
12 | * }
13 | * If you are using a CommonJS module that is using module.exports then you will have to write your
14 | * types using export = yourObjectOrFunction with a namespace above it
15 | * notice how we have to create a namespace that is equal to the function we're
16 | * assigning the export to
17 | * declare module 'jwt-decode' {
18 | * function jwtDecode(token: string): any;
19 | * namespace jwtDecode {}
20 | * export = jwtDecode;
21 | * }
22 | *
23 | * If you're prototying and you will fix the types later you can also declare it as type any
24 | *
25 | * declare var assert: any;
26 | * declare var _: any;
27 | * declare var $: any;
28 | *
29 | * If you're importing a module that uses Node.js modules which are CommonJS you need to import as
30 | * in the files such as main.browser.ts or any file within app/
31 | *
32 | * import * as _ from 'lodash'
33 | * You can include your type definitions in this file until you create one for the @types
34 | *
35 | */
36 |
37 | declare module '*.json'
38 |
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Ionic App
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/src/ionic-mock.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Damien Dell'Amico
3 | * @copyright Copyright (c) 2016
4 | * @license GPL-3.0
5 | */
6 |
7 | export class NavMock {
8 |
9 | public pop(): any {
10 | return new Promise(function (resolve: Function): void {
11 | resolve();
12 | });
13 | }
14 |
15 | public push(): any {
16 | return new Promise(function (resolve: Function): void {
17 | resolve();
18 | });
19 | }
20 |
21 | public getActive(): any {
22 | return {
23 | 'instance': {
24 | 'model': 'something',
25 | },
26 | };
27 | }
28 |
29 | public setRoot(): any {
30 | return true;
31 | }
32 | }
33 |
34 | export class PlatformMock {
35 | public ready(): any {
36 | return new Promise((resolve: Function) => {
37 | resolve();
38 | });
39 | }
40 | }
41 |
42 | export class MenuMock {
43 | public close(): any {
44 | return new Promise((resolve: Function) => {
45 | resolve();
46 | });
47 | }
48 | }
49 |
50 | export class LoadingControllerMock {
51 | create(opts?: any) {
52 | return {
53 | present: () => {
54 | },
55 | dismiss: () => {
56 | }
57 | };
58 | };
59 | }
60 |
61 | export class ViewControllerMock {
62 | dismiss(data?: any, role?: any): Promise {
63 | return new Promise(function (resolve: Function): void {
64 | resolve();
65 | });
66 | }
67 | }
68 |
69 | export class AlertControllerMock {
70 | create(): any {
71 | return {};
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Ionic Taxi App",
3 | "short_name": "Ionic Taxi",
4 | "start_url": "index.html",
5 | "display": "standalone",
6 | "icons": [{
7 | "src": "assets/img/taxi.png",
8 | "sizes": "512x512",
9 | "type": "image/png"
10 | }],
11 | "background_color": "#387ef5",
12 | "theme_color": "#387ef5"
13 | }
14 |
--------------------------------------------------------------------------------
/src/pages/about/about.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | About
7 |
8 |
9 |
10 |
11 |
14 |
19 |
20 |
--------------------------------------------------------------------------------
/src/pages/about/about.scss:
--------------------------------------------------------------------------------
1 | .about-header {
2 | background-color: #434954;
3 | padding: 16px;
4 | width: 100%;
5 | min-height: 150px;
6 | text-align: center;
7 | }
8 |
9 | .about-header img {
10 | max-width: 200px;
11 | min-height: 115px;
12 | margin-left: -15px;
13 | padding: 25px 0 20px 0;
14 | }
15 |
16 | .about-info p {
17 | color: #697072;
18 | text-align: left;
19 | }
20 |
21 | .about-info ion-icon {
22 | color: color($colors, primary);
23 | width: 20px;
24 | }
25 |
26 | .md,
27 | .wp {
28 | .about-info [text-right] {
29 | margin-right: 0;
30 | }
31 | }
32 |
33 | .ios .about-info {
34 | text-align: center;
35 | }
36 |
37 | .ios .about-info ion-icon {
38 | width: auto;
39 | margin-right: 10px;
40 | }
41 |
--------------------------------------------------------------------------------
/src/pages/about/about.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Damien Dell'Amico
3 | * @copyright Copyright (c) 2016
4 | * @license GPL-3.0
5 | */
6 |
7 | import { Component } from '@angular/core';
8 |
9 | @Component({
10 | templateUrl: 'about.html'
11 | })
12 | export class AboutPage {
13 |
14 | constructor() {
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/pages/base-page.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Damien Dell'Amico
3 | * @copyright Copyright (c) 2016
4 | * @license GPL-3.0
5 | */
6 |
7 | import { AlertController } from 'ionic-angular';
8 |
9 | export class BasePage {
10 |
11 | constructor(protected alertCtrl: AlertController) {
12 | }
13 |
14 | displayErrorAlert(): void {
15 | const prompt = this.alertCtrl.create({
16 | title: 'Ionic Taxi',
17 | message: 'Unknown error, please try again later',
18 | buttons: ['OK']
19 | });
20 | prompt.present();
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/pages/confirmation/confirmation.scss:
--------------------------------------------------------------------------------
1 | .confirmation {
2 |
3 | }
4 |
--------------------------------------------------------------------------------
/src/pages/confirmation/confirmation.spec.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Damien Dell'Amico
3 | * @copyright Copyright (c) 2016
4 | * @license GPL-3.0
5 | */
6 |
7 | import { IonicModule, Platform } from 'ionic-angular';
8 | import { TestBed, ComponentFixture, async } from '@angular/core/testing';
9 | import { CUSTOM_ELEMENTS_SCHEMA, DebugElement } from '@angular/core';
10 | import { NavController } from 'ionic-angular';
11 | import { TaxiApp } from '../../app/app.component';
12 | import { ConfirmationPage } from './confirmation';
13 | import { GeocoderServiceMock } from '../../providers/map/geocoder-mock.service';
14 | import { GeocoderService } from '../../providers/map/geocoder.service';
15 | import { MapService } from '../../providers/map/map.service';
16 | import { MapServiceMock } from '../../providers/map/map-mock.service';
17 | import { NavMock } from '../../ionic-mock';
18 | import { RideService } from '../../providers/ride/ride.service';
19 | import { RideServiceMock } from '../../providers/ride/ride-mock.service';
20 |
21 | describe('Page: ConfirmationPage', () => {
22 |
23 | let comp: ConfirmationPage;
24 | let fixture: ComponentFixture;
25 | let de: DebugElement;
26 | let el: HTMLElement;
27 | let mapService: MapService;
28 | let rideService: RideService;
29 | let geocoderService: GeocoderServiceMock;
30 |
31 | beforeEach(async(() => {
32 | TestBed.configureTestingModule({
33 | schemas: [CUSTOM_ELEMENTS_SCHEMA],
34 | declarations: [TaxiApp, ConfirmationPage],
35 | providers: [
36 | {provide: NavController, useClass: NavMock},
37 | {provide: RideService, useClass: RideServiceMock},
38 | {provide: MapService, useClass: MapServiceMock},
39 | {provide: GeocoderService, useClass: GeocoderServiceMock}
40 | ],
41 | imports: [
42 | IonicModule.forRoot(TaxiApp)
43 | ]
44 | }).compileComponents();
45 | }));
46 |
47 | beforeEach(() => {
48 |
49 | fixture = TestBed.createComponent(ConfirmationPage);
50 | comp = fixture.componentInstance;
51 | de = fixture.debugElement;
52 |
53 | mapService = TestBed.get(MapService);
54 | rideService = TestBed.get(RideService);
55 | geocoderService = TestBed.get(GeocoderService);
56 | });
57 |
58 | afterEach(() => {
59 | fixture.destroy();
60 | comp = null;
61 | de = null;
62 | el = null;
63 | });
64 |
65 | it('is created', () => {
66 | expect(fixture).toBeTruthy();
67 | expect(comp).toBeTruthy();
68 | });
69 |
70 | it('should render `ion-content`', () => {
71 | fixture.detectChanges();
72 | expect(de.nativeElement.querySelectorAll('ion-content').length).toBe(1);
73 | });
74 |
75 | /* it('should get address when google map idle event is emitted', () => {
76 | spyOn(geocoderService, 'addressForlatLng').and.callThrough();
77 |
78 | const _response = 'fake address';
79 | geocoderService.setResponse(_response);
80 |
81 | const instance = fixture.componentInstance;
82 | fixture.detectChanges();
83 |
84 | expect(geocoderService.addressForlatLng).toHaveBeenCalled();
85 | expect(instance.departure).toBe(_response);
86 | });*/
87 | });
88 |
--------------------------------------------------------------------------------
/src/pages/confirmation/confirmation.tpl.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Confirmation
4 |
5 |
6 |
7 |
8 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/pages/confirmation/confirmation.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Damien Dell'Amico
3 | * @copyright Copyright (c) 2016
4 | * @license GPL-3.0
5 | */
6 |
7 | import { Component } from '@angular/core';
8 | import { AlertController, NavController } from 'ionic-angular';
9 | import { FormGroup, FormBuilder, Validators } from '@angular/forms';
10 | import { GeocoderService } from '../../providers/map/geocoder.service';
11 | import { RideModel } from '../../providers/ride/ride.model';
12 | import { RideService } from '../../providers/ride/ride.service';
13 | import { BasePage } from '../base-page';
14 | import { RideListPage } from '../ride-list/ride-list';
15 | import { MapService } from '../../providers/map/map.service';
16 |
17 | @Component({
18 | templateUrl: 'confirmation.tpl.html'
19 | })
20 | export class ConfirmationPage extends BasePage {
21 | form: FormGroup;
22 | position: google.maps.LatLng;
23 |
24 | departure: string = null;
25 | destination: string = null;
26 |
27 | constructor(private nav: NavController,
28 | private fb: FormBuilder,
29 | private rideService: RideService,
30 | private mapService: MapService,
31 | private geocoderService: GeocoderService,
32 | protected alertCtrl: AlertController) {
33 | super(alertCtrl);
34 |
35 | this.form = this.fb.group({
36 | departure: ['', [Validators.required]],
37 | destination: ['', [Validators.required]]
38 | });
39 |
40 | this.position = this.mapService.mapCenter;
41 |
42 | if (this.position) {
43 | this.geocoderService.addressForlatLng(this.position.lat(), this.position.lng())
44 | .subscribe((address: string) => {
45 | this.departure = address;
46 | }, (error) => {
47 | this.displayErrorAlert();
48 | console.error(error);
49 | });
50 | }
51 | }
52 |
53 | onConfirm(model: RideModel, isValid: boolean): void {
54 | if (!isValid) {
55 | this.displayErrorAlert();
56 | return;
57 | }
58 | const prompt = this.alertCtrl.create({
59 | title: 'Confirmation',
60 | message: 'Taxi will pick you up within 5 minutes. Do you want to confirm ?',
61 | buttons: [{
62 | text: 'Cancel'
63 | }, {
64 | text: 'Confirm',
65 | handler: () => {
66 | this.rideService.addRide(model.departure, model.destination);
67 | this.nav.setRoot(RideListPage);
68 | }
69 | }]
70 | });
71 | prompt.present();
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/pages/home/home.scss:
--------------------------------------------------------------------------------
1 | .map-page {
2 | background: rgb(229, 227, 223);
3 |
4 | #gmaps {
5 | width: 100%;
6 | height: 100%;
7 | opacity: 0;
8 | transition: opacity 150ms ease-in
9 | }
10 |
11 | #gmaps.show-map {
12 | opacity: 1;
13 | }
14 |
15 | #centerMarker {
16 | width: 22px;
17 | height: 40px;
18 | display: block;
19 | content: ' ';
20 | position: absolute;
21 | top: 50%;
22 | left: 50%;
23 | margin: -40px 0 0 -11px;
24 | background: url('https://maps.gstatic.com/mapfiles/api-3/images/spotlight-poi_hdpi.png');
25 | background-size: 22px 40px; /* Since I used the HiDPI marker version this compensates for the 2x size */
26 | pointer-events: none; /* This disables clicks on the marker. Not fully supported by all major browsers, though */
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/pages/home/home.spec.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Damien Dell'Amico
3 | * @copyright Copyright (c) 2016
4 | * @license GPL-3.0
5 | */
6 |
7 | import { IonicModule, Platform } from 'ionic-angular';
8 | import { TestBed, ComponentFixture, async } from '@angular/core/testing';
9 | import { CUSTOM_ELEMENTS_SCHEMA, DebugElement } from '@angular/core';
10 | import { NavController } from 'ionic-angular';
11 | import { TaxiApp } from '../../app/app.component';
12 | import { HomePage } from './home';
13 | import { GeocoderServiceMock } from '../../providers/map/geocoder-mock.service';
14 | import { GeocoderService } from '../../providers/map/geocoder.service';
15 | import { MapService } from '../../providers/map/map.service';
16 | import { MapServiceMock } from '../../providers/map/map-mock.service';
17 | import { NavMock } from '../../ionic-mock';
18 |
19 | describe('Page: HomePage', () => {
20 |
21 | let comp: HomePage;
22 | let fixture: ComponentFixture;
23 | let de: DebugElement;
24 | let el: HTMLElement;
25 | let mapService: MapService;
26 | let geocoderService: GeocoderServiceMock;
27 |
28 | beforeEach(async(() => {
29 | TestBed.configureTestingModule({
30 | schemas: [CUSTOM_ELEMENTS_SCHEMA],
31 | declarations: [TaxiApp, HomePage],
32 | providers: [
33 | {provide: NavController, useClass: NavMock},
34 | {provide: GeocoderService, useClass: GeocoderServiceMock},
35 | {provide: MapService, useClass: MapServiceMock}
36 | ],
37 | imports: [
38 | IonicModule.forRoot(TaxiApp)
39 | ]
40 | }).compileComponents();
41 | }));
42 |
43 | beforeEach(() => {
44 |
45 | fixture = TestBed.createComponent(HomePage);
46 | comp = fixture.componentInstance;
47 | de = fixture.debugElement;
48 |
49 | mapService = TestBed.get(MapService);
50 | geocoderService = TestBed.get(GeocoderService);
51 |
52 | // #trick
53 | // If you want to trigger ionViewWillEnter automatically de-comment the following line
54 | // fixture.componentInstance.ionViewWillEnter();
55 | });
56 |
57 | afterEach(() => {
58 | fixture.destroy();
59 | comp = null;
60 | de = null;
61 | el = null;
62 | });
63 |
64 | it('is created', () => {
65 | expect(fixture).toBeTruthy();
66 | expect(comp).toBeTruthy();
67 | });
68 |
69 | it('should render `ion-content`', () => {
70 | fixture.detectChanges();
71 | expect(de.nativeElement.querySelectorAll('ion-content').length).toBe(1);
72 | });
73 |
74 | it('should call platform ready when google map is ready', () => {
75 | const platform = de.injector.get(Platform);
76 | spyOn(platform, 'ready').and.callThrough();
77 |
78 | const instance = fixture.componentInstance;
79 | instance.onMapReady().then(() => {
80 | expect(platform.ready).toHaveBeenCalled();
81 | });
82 | });
83 |
84 | it('should localize after google map is ready', () => {
85 | const _response = new Promise((resolve: Function) => {
86 | resolve({
87 | latitude: 0,
88 | longitude: 0
89 | });
90 | });
91 |
92 | spyOn(mapService, 'setPosition').and.returnValue(_response);
93 |
94 | const instance = fixture.componentInstance;
95 | fixture.detectChanges();
96 | instance.onMapReady().then(() => {
97 | expect(mapService.setPosition).toHaveBeenCalled();
98 | expect(instance.localized).toBe(true);
99 | });
100 | });
101 |
102 | it('should get address when google map idle event is emitted', () => {
103 | spyOn(geocoderService, 'addressForlatLng').and.callThrough();
104 |
105 | const _response = 'fake address';
106 | geocoderService.setResponse(_response);
107 |
108 | const instance = fixture.componentInstance;
109 |
110 | instance.localized = true;
111 | instance.onMapIdle();
112 | fixture.detectChanges();
113 |
114 | expect(geocoderService.addressForlatLng).toHaveBeenCalled();
115 | });
116 |
117 | it('should close info window when `onDragStart` is emitted', () => {
118 | spyOn(mapService, 'closeInfoWindow').and.callThrough();
119 |
120 | const _response = 'fake address';
121 | geocoderService.setResponse(_response);
122 |
123 | const instance = fixture.componentInstance;
124 | instance.onDragStart();
125 | fixture.detectChanges();
126 |
127 | expect(mapService.closeInfoWindow).toHaveBeenCalled();
128 | });
129 | });
130 |
--------------------------------------------------------------------------------
/src/pages/home/home.tpl.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Ionic Taxi
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/pages/home/home.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Damien Dell'Amico
3 | * @copyright Copyright (c) 2016
4 | * @license GPL-3.0
5 | */
6 |
7 | import { Component } from '@angular/core';
8 | import { MapComponent } from '../../components/map/map';
9 | import { Vibration } from 'ionic-native';
10 | import { MapService } from '../../providers/map/map.service';
11 | import { LoadingController, AlertController, ModalController, NavController, Platform } from 'ionic-angular';
12 | import { GeocoderService } from '../../providers/map/geocoder.service';
13 | import { SearchPage } from '../search/search';
14 | import { ConfirmationPage } from '../confirmation/confirmation';
15 | import { BasePage } from '../base-page';
16 |
17 | @Component({
18 | templateUrl: 'home.tpl.html'
19 | })
20 | export class HomePage extends BasePage {
21 |
22 | localized: boolean = false;
23 |
24 | constructor(private platform: Platform,
25 | private nav: NavController,
26 | private geocoderService: GeocoderService,
27 | private mapService: MapService,
28 | private modalCtrl: ModalController,
29 | private loadingCtrl: LoadingController,
30 | protected alertCtrl: AlertController) {
31 | super(alertCtrl);
32 | }
33 |
34 | /***
35 | * This event is fired when map has fully loaded
36 | */
37 | onMapReady(): Promise {
38 | // I must wait platform.ready() to use plugins ( in this case Geolocation plugin ).
39 | return this.platform.ready().then(() => {
40 | return this.locate().then(() => {
41 | const mapElement: Element = this.mapService.mapElement;
42 | if (mapElement) {
43 | mapElement.classList.add('show-map');
44 | this.mapService.resizeMap();
45 | }
46 | });
47 | });
48 | }
49 |
50 | /***
51 | * This event is fired when the map becomes idle after panning or zooming.
52 | */
53 | onMapIdle(): void {
54 | if (!this.localized) return;
55 | const position = this.mapService.mapCenter;
56 | this.geocoderService.addressForlatLng(position.lat(), position.lng())
57 | .subscribe((address: string) => {
58 |
59 | const content = `${address}
`;
60 | this.mapService.createInfoWindow(content, position);
61 |
62 | }, (error) => {
63 | this.displayErrorAlert();
64 | console.error(error);
65 | });
66 | }
67 |
68 | /***
69 | * This event is fired when the user starts dragging the map.
70 | */
71 | onDragStart(): void {
72 | this.mapService.closeInfoWindow();
73 | }
74 |
75 | openModal(): void {
76 | const searchModal = this.modalCtrl.create(SearchPage);
77 | searchModal.present();
78 | }
79 |
80 | goToConfirmation(): void {
81 | this.nav.push(ConfirmationPage);
82 | }
83 |
84 | /**
85 | * Get the current position
86 | */
87 | private locate(): Promise {
88 | const loader = this.loadingCtrl.create({
89 | content: 'Please wait...',
90 | });
91 | loader.present();
92 | return this.mapService.setPosition().then(() => {
93 | this.localized = true;
94 | // Vibrate the device for a second
95 | Vibration.vibrate(1000);
96 | }).catch(error => {
97 | this.alertNoGps();
98 | console.warn(error);
99 | }).then(() => {
100 | // TODO why dismiss not working without setTimeout ?
101 | setTimeout(() => {
102 | loader.dismiss();
103 | }, 1000);
104 | });
105 | }
106 |
107 | private alertNoGps() {
108 | const alert = this.alertCtrl.create({
109 | title: 'Ionic Taxi',
110 | subTitle: 'Gps and network locations are unavailable. Click OK to retry',
111 | enableBackdropDismiss: false,
112 | buttons: [{
113 | text: 'OK',
114 | handler: () => {
115 | setTimeout(() => this.locate(), 1500);
116 | }
117 | }],
118 | });
119 | alert.present();
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/src/pages/ride-list/ride-list.scss:
--------------------------------------------------------------------------------
1 | .ride-list {
2 |
3 |
4 | }
5 |
--------------------------------------------------------------------------------
/src/pages/ride-list/ride-list.spec.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Damien Dell'Amico
3 | * @copyright Copyright (c) 2016
4 | * @license GPL-3.0
5 | */
6 |
7 | import { IonicModule, Platform } from 'ionic-angular';
8 | import { TestBed, ComponentFixture, async } from '@angular/core/testing';
9 | import { CUSTOM_ELEMENTS_SCHEMA, DebugElement } from '@angular/core';
10 | import { NavController } from 'ionic-angular';
11 | import { TaxiApp } from '../../app/app.component';
12 | import { RideListPage } from './ride-list';
13 | import { GeocoderServiceMock } from '../../providers/map/geocoder-mock.service';
14 | import { GeocoderService } from '../../providers/map/geocoder.service';
15 | import { MapService } from '../../providers/map/map.service';
16 | import { MapServiceMock } from '../../providers/map/map-mock.service';
17 | import { NavMock } from '../../ionic-mock';
18 | import { RideService } from '../../providers/ride/ride.service';
19 | import { RideServiceMock } from '../../providers/ride/ride-mock.service';
20 |
21 | describe('Page: RideListPage', () => {
22 |
23 | let comp: RideListPage;
24 | let fixture: ComponentFixture;
25 | let de: DebugElement;
26 | let el: HTMLElement;
27 | let mapService: MapService;
28 | let rideService: RideService;
29 | let geocoderService: GeocoderServiceMock;
30 |
31 | beforeEach(async(() => {
32 | TestBed.configureTestingModule({
33 | schemas: [CUSTOM_ELEMENTS_SCHEMA],
34 | declarations: [TaxiApp, RideListPage],
35 | providers: [
36 | {provide: NavController, useClass: NavMock},
37 | {provide: RideService, useClass: RideServiceMock},
38 | {provide: MapService, useClass: MapServiceMock},
39 | {provide: GeocoderService, useClass: GeocoderServiceMock}
40 | ],
41 | imports: [
42 | IonicModule.forRoot(TaxiApp)
43 | ]
44 | }).compileComponents();
45 | }));
46 |
47 | beforeEach(() => {
48 |
49 | fixture = TestBed.createComponent(RideListPage);
50 | comp = fixture.componentInstance;
51 | de = fixture.debugElement;
52 |
53 | mapService = TestBed.get(MapService);
54 | rideService = TestBed.get(RideService);
55 | geocoderService = TestBed.get(GeocoderService);
56 | });
57 |
58 | afterEach(() => {
59 | fixture.destroy();
60 | comp = null;
61 | de = null;
62 | el = null;
63 | });
64 |
65 | it('is created', () => {
66 | expect(fixture).toBeTruthy();
67 | expect(comp).toBeTruthy();
68 | });
69 |
70 | it('should render `ion-content`', () => {
71 | fixture.detectChanges();
72 | expect(de.nativeElement.querySelectorAll('ion-content').length).toBe(1);
73 | });
74 |
75 | it('should fetch all taxi rides at page load', () => {
76 | spyOn(rideService, 'getRides').and.returnValue(Promise.resolve([]));
77 |
78 | const instance = fixture.componentInstance;
79 | instance.ionViewDidEnter();
80 |
81 | expect(rideService.getRides).toHaveBeenCalled();
82 | expect(instance.rides).not.toBeUndefined();
83 | });
84 | });
85 |
--------------------------------------------------------------------------------
/src/pages/ride-list/ride-list.tpl.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Taxi rides
7 |
8 |
9 |
10 |
11 | No rides found
12 |
13 |
14 |
15 | {{ ride.departure }}
16 |
17 |
18 |
19 | {{ ride.destination }}
20 |
21 | {{ ride.rideDate | date:'medium' }}
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/src/pages/ride-list/ride-list.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Damien Dell'Amico
3 | * @copyright Copyright (c) 2016
4 | * @license GPL-3.0
5 | */
6 |
7 | import { Component } from '@angular/core';
8 | import { AlertController } from 'ionic-angular';
9 | import { RideService } from '../../providers/ride/ride.service';
10 | import { BasePage } from '../base-page';
11 | import { RideModel } from '../../providers/ride/ride.model';
12 |
13 | @Component({
14 | templateUrl: 'ride-list.tpl.html'
15 | })
16 | export class RideListPage extends BasePage {
17 | rides: Array = [];
18 |
19 | constructor(private rideService: RideService,
20 | protected alertCtrl: AlertController) {
21 | super(alertCtrl);
22 | }
23 |
24 | ionViewDidEnter() {
25 | this.rides = this.rideService.getRides();
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/pages/search/search.spec.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Damien Dell'Amico
3 | * @copyright Copyright (c) 2016
4 | * @license GPL-3.0
5 | */
6 |
7 | import { IonicModule, Platform, ViewController, AlertController } from 'ionic-angular';
8 | import { TestBed, ComponentFixture, async } from '@angular/core/testing';
9 | import { CUSTOM_ELEMENTS_SCHEMA, DebugElement } from '@angular/core';
10 | import { NavController } from 'ionic-angular';
11 | import { TaxiApp } from '../../app/app.component';
12 | import { SearchPage } from './search';
13 | import { GeocoderServiceMock } from '../../providers/map/geocoder-mock.service';
14 | import { GeocoderService } from '../../providers/map/geocoder.service';
15 | import { MapService } from '../../providers/map/map.service';
16 | import { MapServiceMock } from '../../providers/map/map-mock.service';
17 | import { NavMock, ViewControllerMock, AlertControllerMock } from '../../ionic-mock';
18 | import { RideService } from '../../providers/ride/ride.service';
19 | import { RideServiceMock } from '../../providers/ride/ride-mock.service';
20 |
21 | describe('Page: SearchPage', () => {
22 |
23 | let comp: SearchPage;
24 | let fixture: ComponentFixture;
25 | let de: DebugElement;
26 | let el: HTMLElement;
27 | let mapService: MapService;
28 | let rideService: RideService;
29 | let geocoderService: GeocoderServiceMock;
30 |
31 | beforeEach(async(() => {
32 | TestBed.configureTestingModule({
33 | schemas: [CUSTOM_ELEMENTS_SCHEMA],
34 | declarations: [TaxiApp, SearchPage],
35 | providers: [
36 | {provide: NavController, useClass: NavMock},
37 | {provide: ViewController, useClass: ViewControllerMock},
38 | {provide: AlertController, useClass: AlertControllerMock},
39 | {provide: RideService, useClass: RideServiceMock},
40 | {provide: MapService, useClass: MapServiceMock},
41 | {provide: GeocoderService, useClass: GeocoderServiceMock}
42 | ]
43 | }).compileComponents();
44 | }));
45 |
46 | beforeEach(() => {
47 | fixture = TestBed.createComponent(SearchPage);
48 | comp = fixture.componentInstance;
49 | de = fixture.debugElement;
50 |
51 | mapService = TestBed.get(MapService);
52 | rideService = TestBed.get(RideService);
53 | geocoderService = TestBed.get(GeocoderService);
54 | });
55 |
56 | afterEach(() => {
57 | fixture.destroy();
58 | comp = null;
59 | de = null;
60 | el = null;
61 | });
62 |
63 | it('is created', () => {
64 | expect(fixture).toBeTruthy();
65 | expect(comp).toBeTruthy();
66 | });
67 |
68 | it('should render `ion-content`', () => {
69 | fixture.detectChanges();
70 | expect(de.nativeElement.querySelectorAll('ion-content').length).toBe(1);
71 | });
72 |
73 | it('should setup autocomplete and load nearby places', () => {
74 | spyOn(mapService, 'createAutocomplete').and.callThrough();
75 | spyOn(mapService, 'loadNearbyPlaces').and.callThrough();
76 |
77 | fixture.componentInstance.ionViewDidLoad();
78 |
79 | fixture.detectChanges();
80 | expect(mapService.createAutocomplete).toHaveBeenCalled();
81 | expect(mapService.loadNearbyPlaces).toHaveBeenCalled();
82 | });
83 | });
84 |
--------------------------------------------------------------------------------
/src/pages/search/search.tpl.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Cancel
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | {{ place.name }}
20 | {{ place.vicinity }}
21 | Distance: {{ place.distance }} meters
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/pages/search/search.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Damien Dell'Amico
3 | * @copyright Copyright (c) 2016
4 | * @license GPL-3.0
5 | */
6 |
7 | import { Component, ViewChild, ElementRef, NgZone } from '@angular/core';
8 | import { ViewController, AlertController } from 'ionic-angular';
9 | import { MapService } from '../../providers/map/map.service';
10 | import { BasePage } from '../base-page';
11 |
12 | @Component({
13 | templateUrl: 'search.tpl.html'
14 | })
15 |
16 | export class SearchPage extends BasePage {
17 |
18 | // reference : https://github.com/driftyco/ionic/issues/7223
19 | @ViewChild('searchbar', {read: ElementRef}) searchbar: ElementRef;
20 |
21 | private nearbyPlaces: Array = [];
22 | private addressElement: HTMLInputElement = null;
23 |
24 | constructor(private mapService: MapService,
25 | private zone: NgZone,
26 | protected alertCtrl: AlertController,
27 | private viewCtrl: ViewController) {
28 | super(alertCtrl);
29 | }
30 |
31 | ionViewDidLoad() {
32 | this.initAutocomplete();
33 | this.loadNearbyPlaces();
34 | }
35 |
36 | dismiss(location?: google.maps.LatLng) {
37 | if (location) {
38 | this.mapService.mapCenter = location;
39 | }
40 | if (this.addressElement) {
41 | this.addressElement.value = '';
42 | }
43 | this.viewCtrl.dismiss();
44 | }
45 |
46 | /***
47 | * Place item has been selected
48 | */
49 | selectPlace(place: any) {
50 | this.dismiss(place.geometry.location);
51 | }
52 |
53 | private initAutocomplete(): void {
54 | // reference : https://github.com/driftyco/ionic/issues/7223
55 | this.addressElement = this.searchbar.nativeElement.querySelector('.searchbar-input');
56 | this.mapService.createAutocomplete(this.addressElement).subscribe((location) => {
57 | this.dismiss(location);
58 | }, (error) => {
59 | this.displayErrorAlert();
60 | console.error(error);
61 | });
62 | }
63 |
64 | private loadNearbyPlaces(): void {
65 | this.nearbyPlaces = [];
66 | this.mapService.loadNearbyPlaces().subscribe((_nearbyPlaces) => {
67 | // force NgZone to detect changes
68 | this.zone.run(() => {
69 | this.nearbyPlaces.push.apply(this.nearbyPlaces, _nearbyPlaces);
70 | });
71 | }, (error) => {
72 | this.displayErrorAlert();
73 | console.error(error);
74 | });
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/providers/map/geocoder-mock.service.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Damien Dell'Amico
3 | * @copyright Copyright (c) 2016
4 | * @license GPL-3.0
5 | */
6 |
7 | import { Observable } from 'rxjs/Observable';
8 |
9 | export class GeocoderServiceMock {
10 |
11 | public fakeResponse: any = null;
12 |
13 | public addressForlatLng(): Observable {
14 | return Observable.of(this.fakeResponse);
15 | }
16 |
17 | public setResponse(data: any): void {
18 | this.fakeResponse = data;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/providers/map/geocoder.service.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Damien Dell'Amico
3 | * @copyright Copyright (c) 2016
4 | * @license GPL-3.0
5 | */
6 |
7 | import { Observable } from 'rxjs/Observable';
8 | import { Injectable } from '@angular/core';
9 |
10 | import 'rxjs/add/observable/throw';
11 | import 'rxjs/add/operator/catch';
12 | import 'rxjs/add/operator/map';
13 | import 'rxjs/add/operator/mergeMap';
14 | import 'rxjs/add/operator/switchMap';
15 | import 'rxjs/add/operator/debounceTime';
16 | import 'rxjs/add/operator/retry';
17 | import 'rxjs/add/operator/distinctUntilChanged';
18 | import 'rxjs/add/operator/do';
19 | import 'rxjs/add/observable/of';
20 | import 'rxjs/add/observable/from';
21 |
22 | // Amount of time (in milliseconds) to pause between each trip to the
23 | // Geocoding API, which places limits on frequency.
24 | const QUERY_PAUSE = 500;
25 |
26 | @Injectable()
27 | export class GeocoderService {
28 |
29 | geocoder: google.maps.Geocoder = null;
30 |
31 | constructor() {
32 | }
33 |
34 | /***
35 | * Convert coordinates to address
36 | * @param lat
37 | * @param lng
38 | * @returns {Observable}
39 | */
40 | public addressForlatLng(lat: number, lng: number): Observable {
41 | if (!this.geocoder) {
42 | this.geocoder = new google.maps.Geocoder();
43 | }
44 | const latlng = new google.maps.LatLng(lat, lng);
45 | return this.geocode(latlng)
46 | .debounceTime(QUERY_PAUSE)
47 | .distinctUntilChanged()
48 | .map((res: any) => res.formatted_address)
49 | .retry(3);
50 | }
51 |
52 | private geocode(latlng: google.maps.LatLng): Observable {
53 | return new Observable((sub: any) => {
54 | this.geocoder.geocode({location: latlng}, (result: google.maps.GeocoderResult[], status: any) => {
55 | if (status === google.maps.GeocoderStatus.OK) {
56 | sub.next(result[0]);
57 | sub.complete();
58 | } else if (status === google.maps.GeocoderStatus.ZERO_RESULTS) {
59 | sub.error({
60 | type: 'ZERO',
61 | message: `Zero results for geocoding location: ${location}`
62 | });
63 | } else if (status === google.maps.GeocoderStatus.OVER_QUERY_LIMIT) {
64 | sub.error({
65 | type: 'OVER_QUERY_LIMIT',
66 | message: `OVER_QUERY_LIMIT. location: ${location}`
67 | });
68 | } else if (status === google.maps.GeocoderStatus.REQUEST_DENIED) {
69 | sub.error({
70 | type: 'DENIED',
71 | message: `Request denied for geocoding location: ${location}`
72 | });
73 | } else {
74 | sub.error({
75 | type: 'INVALID',
76 | message: `Invalid request for geocoding: status: ${status}, location: ${location}`
77 | });
78 | }
79 | });
80 | });
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/providers/map/map-mock.service.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Damien Dell'Amico
3 | * @copyright Copyright (c) 2016
4 | * @license GPL-3.0
5 | */
6 |
7 | import { Observable } from 'rxjs/Observable';
8 | import { MapConst } from './map.constants';
9 |
10 | interface IMapOptions {
11 | lat: number;
12 | lon: number;
13 | zoom: number;
14 | }
15 |
16 | export class MapServiceMock {
17 |
18 | constructor() {
19 | }
20 |
21 | public createMap(mapEl: Element, opts: IMapOptions = {
22 | lat: MapConst.DEFAULT_LAT,
23 | lon: MapConst.DEFAULT_LNG,
24 | zoom: MapConst.DEFAULT_ZOOM
25 | }): Promise {
26 | return Promise.resolve(new google.maps.Map(mapEl));
27 | }
28 |
29 | public get mapCenter(): google.maps.LatLng {
30 | return new google.maps.LatLng(MapConst.DEFAULT_LAT, MapConst.DEFAULT_LNG);
31 | }
32 |
33 | public set mapCenter(location: google.maps.LatLng) {
34 | }
35 |
36 | public get mapElement(): Element {
37 | return new Element();
38 | }
39 |
40 | public createInfoWindow(content: string, position: google.maps.LatLng): void {
41 | }
42 |
43 | public closeInfoWindow(): void {
44 | }
45 |
46 | public createAutocomplete(addressEl: HTMLInputElement): Observable {
47 | return Observable.of(new google.maps.LatLng(MapConst.DEFAULT_LAT, MapConst.DEFAULT_LNG));
48 | }
49 |
50 | public setPosition(): Promise {
51 | return Promise.resolve();
52 | }
53 |
54 | public resizeMap(): void {
55 | }
56 |
57 | public loadMap(): Promise {
58 | return Promise.resolve();
59 | }
60 |
61 | public loadNearbyPlaces(): Observable {
62 | return Observable.of([]);
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/providers/map/map.constants.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Damien Dell'Amico
3 | * @copyright Copyright (c) 2016
4 | * @license GPL-3.0
5 | */
6 |
7 | export class MapConst {
8 | public static get DEFAULT_LAT(): number {
9 | return 42;
10 | }
11 |
12 | public static get DEFAULT_LNG(): number {
13 | return 12.8;
14 | }
15 |
16 | public static get DEFAULT_ZOOM(): number {
17 | return 5;
18 | }
19 |
20 | public static get MAX_ZOOM(): number {
21 | return 18;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/providers/map/map.service.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Damien Dell'Amico
3 | * @copyright Copyright (c) 2016
4 | * @license GPL-3.0
5 | */
6 |
7 | import { Injectable } from '@angular/core';
8 | import { Observable } from 'rxjs/Observable';
9 | import { Geolocation, Geoposition } from 'ionic-native';
10 | import { MapConst } from './map.constants';
11 | import { } from '@types/googlemaps';
12 |
13 | interface IMapOptions {
14 | lat: number;
15 | lon: number;
16 | zoom: number;
17 | }
18 |
19 | @Injectable()
20 | export class MapService {
21 |
22 | private map: google.maps.Map = null;
23 | private infoWindow: google.maps.InfoWindow = null;
24 |
25 | constructor() {
26 | }
27 |
28 | public createMap(mapEl: Element, opts: IMapOptions = {
29 | lat: MapConst.DEFAULT_LAT,
30 | lon: MapConst.DEFAULT_LNG,
31 | zoom: MapConst.DEFAULT_ZOOM
32 | }): Promise {
33 |
34 | return this.loadMap().then(() => {
35 | const myLatLng = new google.maps.LatLng(opts.lat, opts.lon);
36 | const styleArray = [
37 | {
38 | featureType: 'poi',
39 | elementType: 'labels',
40 | stylers: [
41 | {visibility: 'off'}
42 | ]
43 | }
44 | ];
45 |
46 | const mapOptions: google.maps.MapOptions = {
47 | zoom: opts.zoom,
48 | minZoom: opts.zoom,
49 | center: myLatLng,
50 | mapTypeId: google.maps.MapTypeId.ROADMAP,
51 | disableDefaultUI: true,
52 | scaleControl: false,
53 | styles: styleArray,
54 | zoomControl: false,
55 | zoomControlOptions: {
56 | position: google.maps.ControlPosition.BOTTOM_CENTER
57 | }
58 | };
59 |
60 | this.map = new google.maps.Map(mapEl, mapOptions);
61 | return this.map;
62 | });
63 | }
64 |
65 | /**
66 | * return the coordinates of the center of map
67 | * @returns {LatLng}
68 | */
69 | public get mapCenter(): google.maps.LatLng {
70 | return this.map.getCenter();
71 | }
72 |
73 | public set mapCenter(location: google.maps.LatLng) {
74 | this.map.setCenter(location);
75 | }
76 |
77 | /***
78 | * return map html element
79 | * @returns {Element}
80 | */
81 | public get mapElement(): Element {
82 | return this.map.getDiv();
83 | }
84 |
85 | /***
86 | * create an infoWindow and display it in the map
87 | * @param content - the content to display inside the infoWindow
88 | * @param position
89 | */
90 | public createInfoWindow(content: string, position: google.maps.LatLng): void {
91 | this.closeInfoWindow();
92 | const opt: google.maps.InfoWindowOptions = {
93 | content,
94 | position,
95 | pixelOffset: new google.maps.Size(0, -50),
96 | disableAutoPan: true
97 | };
98 | this.infoWindow = new google.maps.InfoWindow(opt);
99 | setTimeout(() => this.infoWindow.open(this.map), 100);
100 | }
101 |
102 | /***
103 | * close the current infoWindow
104 | */
105 | public closeInfoWindow(): void {
106 | if (this.infoWindow) {
107 | this.infoWindow.close();
108 | }
109 | }
110 |
111 | /***
112 | * create Place Autocomplete
113 | * ref: https://developers.google.com/maps/documentation/javascript/examples/places-autocomplete
114 | * @param addressEl
115 | * @returns {Observable}
116 | */
117 | public createAutocomplete(addressEl: HTMLInputElement): Observable {
118 | const autocomplete = new google.maps.places.Autocomplete(addressEl);
119 | autocomplete.bindTo('bounds', this.map);
120 | return new Observable((sub: any) => {
121 | google.maps.event.addListener(autocomplete, 'place_changed', () => {
122 | const place = autocomplete.getPlace();
123 | if (!place.geometry) {
124 | sub.error({
125 | message: 'Autocomplete returned place with no geometry'
126 | });
127 | } else {
128 | sub.next(place.geometry.location);
129 | sub.complete();
130 | }
131 | });
132 | });
133 | }
134 |
135 | /***
136 | * set map position and the relative center and zoom
137 | * @returns {Promise}
138 | */
139 | public setPosition(): Promise {
140 | return this.getCurrentPosition().then((coords: Coordinates) => {
141 | if (!coords) {
142 | console.warn('invalid coordinates: ', coords);
143 | return null;
144 | }
145 | const myLatLng = new google.maps.LatLng(coords.latitude, coords.longitude);
146 | this.map.setCenter(myLatLng);
147 | this.map.setZoom(MapConst.MAX_ZOOM);
148 | return this.mapCenter;
149 | });
150 | }
151 |
152 | /***
153 | * trigger map resize event
154 | */
155 | public resizeMap(): void {
156 | if (this.map) {
157 | google.maps.event.trigger(this.map, 'resize');
158 | }
159 | }
160 |
161 | /***
162 | * google map place searches
163 | * @returns {Observable}
164 | */
165 | public loadNearbyPlaces(): Observable {
166 | const position: google.maps.LatLng = this.mapCenter;
167 |
168 | const placesService = new google.maps.places.PlacesService(this.map);
169 | const request: google.maps.places.PlaceSearchRequest = {
170 | location: position,
171 | radius: 500
172 | };
173 |
174 | return new Observable((sub: any) => {
175 | placesService.nearbySearch(request, (results, status) => {
176 | const _nearbyPlaces: Array = [];
177 | if (status === google.maps.places.PlacesServiceStatus.OK) {
178 | for (let i = 0; i < results.length; i++) {
179 | const place: any = results[i];
180 | const distance: number =
181 | google.maps.geometry.spherical.computeDistanceBetween(position, place.geometry.location);
182 | place.distance = distance.toFixed(2);
183 | _nearbyPlaces.push(place);
184 | }
185 | sub.next(_nearbyPlaces);
186 | sub.complete();
187 | } else {
188 | sub.error({
189 | message: `Invalid response status from nearbySearch : ${status}`
190 | });
191 | }
192 | });
193 | });
194 | }
195 |
196 | /***
197 | * Load Google Map Api in async mode
198 | * @returns {Promise}
199 | */
200 | private loadMap(): Promise {
201 | return new Promise((resolve: Function, reject: Function) => {
202 | if ((window).google && (window).google.maps) {
203 | resolve();
204 | } else {
205 | this.loadGoogleMapApi().then(() => resolve()).catch(reason => {
206 | reject(reason);
207 | });
208 | }
209 | });
210 | }
211 |
212 | /***
213 | * get the current location using Geolocation cordova plugin
214 | * @param maximumAge
215 | * @returns {Promise}
216 | */
217 | private getCurrentPosition(maximumAge: number = 10000): Promise {
218 | const options = {
219 | timeout: 10000,
220 | enableHighAccuracy: true,
221 | maximumAge
222 | };
223 | return Geolocation.getCurrentPosition(options).then((pos: Geoposition) => {
224 | return pos.coords;
225 | });
226 | }
227 |
228 | /***
229 | * Create a script element to insert into the page
230 | * @returns {Promise}
231 | * @private
232 | */
233 | private loadGoogleMapApi(): Promise {
234 | const _loadScript = () => {
235 | const script = document.createElement('script');
236 | // tslint:disable-next-line
237 | script.src = `https://maps.googleapis.com/maps/api/js?libraries=places,geometry&language=it&components=country:IT&callback=initMap`;
238 | script.type = 'text/javascript';
239 | script.async = true;
240 | const s = document.getElementsByTagName('script')[0];
241 | s.parentNode.insertBefore(script, s);
242 | };
243 |
244 | return new Promise((resolve: Function) => {
245 | (window).initMap = () => {
246 | return resolve();
247 | };
248 | _loadScript();
249 | });
250 | }
251 | }
252 |
--------------------------------------------------------------------------------
/src/providers/ride/ride-mock.service.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Damien Dell'Amico
3 | * @copyright Copyright (c) 2016
4 | * @license GPL-3.0
5 | */
6 |
7 | export class RideServiceMock {
8 |
9 | public fakeResponse: any = null;
10 |
11 | public getRides(): Array {
12 | return [];
13 | }
14 |
15 | public addRide(): void {
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/providers/ride/ride.model.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Damien Dell'Amico
3 | * @copyright Copyright (c) 2016
4 | * @license GPL-3.0
5 | */
6 |
7 | export class RideModel {
8 | constructor(public _id: string,
9 | public departure: string,
10 | public destination: string,
11 | public rideDate: Date = new Date()) {
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/providers/ride/ride.service.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Damien Dell'Amico
3 | * @copyright Copyright (c) 2016
4 | * @license GPL-3.0
5 | */
6 |
7 | import * as uuid from 'node-uuid';
8 | import { Injectable } from '@angular/core';
9 | import { Events } from 'ionic-angular';
10 | import { RideModel } from './ride.model';
11 |
12 | @Injectable()
13 | export class RideService {
14 | _rides: Array = [];
15 |
16 | constructor(public events: Events) {
17 | }
18 |
19 | addRide(departure: string, destination: string): void {
20 | const model = new RideModel(uuid.v4(), departure, destination);
21 | this._rides.push(model);
22 | }
23 |
24 | getRides(): Array {
25 | return this._rides;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/service-worker.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Check out https://googlechrome.github.io/sw-toolbox/docs/master/index.html for
3 | * more info on how to use sw-toolbox to custom configure your service worker.
4 | */
5 |
6 |
7 | 'use strict';
8 | importScripts('./build/sw-toolbox.js');
9 |
10 | self.toolbox.options.cache = {
11 | name: 'ionic-cache'
12 | };
13 |
14 | // pre-cache our key assets
15 | self.toolbox.precache(
16 | [
17 | './build/main.js',
18 | './build/main.css',
19 | './build/polyfills.js',
20 | 'index.html',
21 | 'manifest.json'
22 | ]
23 | );
24 |
25 | // dynamically cache any other local assets
26 | self.toolbox.router.any('/*', self.toolbox.cacheFirst);
27 |
28 | // for any other requests go to the network, cache,
29 | // and then only use that cached resource if your user goes offline
30 | self.toolbox.router.default = self.toolbox.networkFirst;
--------------------------------------------------------------------------------
/src/theme/variables.scss:
--------------------------------------------------------------------------------
1 | // Ionic Variables and Theming. For more info, please see:
2 | // http://ionicframework.com/docs/v2/theming/
3 |
4 | // Font path is used to include ionicons,
5 | // roboto, and noto sans fonts
6 | $font-path: "../assets/fonts";
7 |
8 | @import "ionic.globals";
9 |
10 |
11 | // Shared Variables
12 | // --------------------------------------------------
13 | // To customize the look and feel of this app, you can override
14 | // the Sass variables found in Ionic's source scss files.
15 | // To view all the possible Ionic variables, see:
16 | // http://ionicframework.com/docs/v2/theming/overriding-ionic-variables/
17 |
18 | $headings-font-weight: 300;
19 |
20 |
21 | // Named Color Variables
22 | // --------------------------------------------------
23 | // Named colors makes it easy to reuse colors on various components.
24 | // It's highly recommended to change the default colors
25 | // to match your app's branding. Ionic uses a Sass map of
26 | // colors so you can add, rename and remove colors as needed.
27 | // The "primary" color is the only required color in the map.
28 |
29 | $colors: (
30 | // primary: #478aff,
31 | primary: #ffc900,
32 | secondary: #32db64,
33 | danger: #f53d3d,
34 | light: #f4f4f4,
35 | dark: #222,
36 | favorite: #69BB7B,
37 | twitter: #1da1f4,
38 | google: #dc4a38,
39 | vimeo: #23b6ea,
40 | facebook: #3b5998
41 | );
42 |
43 |
44 | // App iOS Variables
45 | // --------------------------------------------------
46 | // iOS only Sass variables can go here
47 |
48 |
49 |
50 | // App Material Design Variables
51 | // --------------------------------------------------
52 | // Material Design only Sass variables can go here
53 |
54 | // Use the primary color as the background for the toolbar-md-background
55 | $toolbar-md-background: color($colors, primary);
56 |
57 | // Change the color of the segment button text
58 | $toolbar-md-active-color: #fff;
59 |
60 |
61 | // App Windows Variables
62 | // --------------------------------------------------
63 | // Windows only Sass variables can go here
64 |
65 |
66 |
67 | // App Theme
68 | // --------------------------------------------------
69 | // Ionic apps can have different themes applied, which can
70 | // then be future customized. This import comes last
71 | // so that the above variables are used and Ionic's
72 | // default are overridden.
73 |
74 | @import "ionic.theme.default";
75 |
76 |
77 | // Ionicons
78 | // --------------------------------------------------
79 | // The premium icon font for Ionic. For more info, please see:
80 | // http://ionicframework.com/docs/v2/ionicons/
81 |
82 | @import "ionic.ionicons";
83 |
84 |
85 | // Fonts
86 | // --------------------------------------------------
87 | // Roboto font is used by default for Material Design. Noto sans
88 | // is used by default for Windows.
89 |
90 | @import "roboto";
91 | @import "noto-sans";
92 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "module": "es2015",
5 | "moduleResolution": "node",
6 | "emitDecoratorMetadata": true,
7 | "experimentalDecorators": true,
8 | "allowSyntheticDefaultImports": true,
9 | "declaration": false,
10 | "sourceMap": true,
11 | "noUnusedParameters": false,
12 | "lib": [
13 | "dom",
14 | "es2015"
15 | ]
16 | },
17 | "types": [
18 | "jasmine"
19 | ],
20 | "include": [
21 | "src/**/*.ts"
22 | ],
23 | "exclude": [
24 | "node_modules"
25 | ],
26 | "compileOnSave": false,
27 | "atom": {
28 | "rewriteTsconfig": false
29 | },
30 | "awesomeTypescriptLoaderOptions": {
31 | "forkChecker": true,
32 | "useWebpackText": true
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "rulesDirectory": [
3 | "node_modules/codelyzer"
4 | ],
5 | "rules": {
6 | "align": [true, "statements"],
7 | "ban": false,
8 | "class-name": true,
9 | "comment-format": [true, "check-space"],
10 | "curly": false,
11 | "eofline": true,
12 | "forin": true,
13 | "indent": [true, "spaces"],
14 | "interface-name": false,
15 | "jsdoc-format": true,
16 | "label-position": true,
17 | "max-line-length": [true, 120],
18 | "member-access": false,
19 | "member-ordering": [true, "public-before-private", "static-before-instance", "variables-before-functions"],
20 | "no-any": false,
21 | "no-arg": true,
22 | "no-bitwise": true,
23 | "no-conditional-assignment": true,
24 | "no-consecutive-blank-lines": true,
25 | "no-console": [true, "debug", "info", "time", "timeEnd", "trace"],
26 | "no-construct": true,
27 | "no-debugger": true,
28 | "no-duplicate-variable": true,
29 | "no-empty": false,
30 | "no-eval": true,
31 | "no-inferrable-types": false,
32 | "no-internal-module": true,
33 | "no-null-keyword": false,
34 | "no-require-imports": true,
35 | "no-shadowed-variable": true,
36 | "no-string-literal": false,
37 | "no-switch-case-fall-through": true,
38 | "no-trailing-whitespace": true,
39 | "no-unused-expression": true,
40 | "no-use-before-declare": true,
41 | "no-var-keyword": true,
42 | "no-var-requires": false,
43 | "object-literal-sort-keys": false,
44 | "one-line": [true, "check-open-brace", "check-catch", "check-else", "check-finally", "check-whitespace"],
45 | "quotemark": [true, "single", "avoid-escape"],
46 | "radix": true,
47 | "semicolon": [true, "always"],
48 | "switch-default": true,
49 | "trailing-comma": false,
50 | "triple-equals": [true, "allow-null-check"],
51 | "typedef": false,
52 | "typedef-whitespace": [true,
53 | {"call-signature": "nospace", "index-signature": "nospace", "parameter": "nospace", "property-declaration": "nospace", "variable-declaration": "nospace"},
54 | {"call-signature": "space", "index-signature": "space", "parameter": "space", "property-declaration": "space", "variable-declaration": "space"}],
55 | "variable-name": [true, "check-format", "allow-leading-underscore", "ban-keywords"],
56 | "whitespace": [true, "check-branch", "check-decl", "check-operator", "check-separator", "check-type"],
57 |
58 | "directive-selector": [true, "attribute", "it", "camelCase"],
59 | "component-selector": [true, "element", "it", "kebab-case"],
60 | "use-input-property-decorator": true,
61 | "use-output-property-decorator": true,
62 | "use-host-property-decorator": true,
63 | "no-input-rename": true,
64 | "no-output-rename": true,
65 | "use-life-cycle-interface": true,
66 | "use-pipe-transform-interface": true,
67 | "directive-class-suffix": true,
68 | "import-destructuring-spacing": true,
69 | "templates-use-public": true,
70 | "no-access-missing-member": true,
71 | "invoke-injectable": true
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/typedoc.json:
--------------------------------------------------------------------------------
1 | {
2 | "mode": "modules",
3 | "out": "doc",
4 | "theme": "default",
5 | "ignoreCompilerErrors": "true",
6 | "experimentalDecorators": "true",
7 | "emitDecoratorMetadata": "true",
8 | "target": "ES5",
9 | "moduleResolution": "node",
10 | "preserveConstEnums": "true",
11 | "stripInternal": "true",
12 | "suppressExcessPropertyErrors": "true",
13 | "suppressImplicitAnyIndexErrors": "true",
14 | "module": "commonjs"
15 | }
16 |
--------------------------------------------------------------------------------
/www/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------