├── .editorconfig ├── .gitignore ├── README.md ├── config.xml ├── ionic.config.json ├── package.json ├── resources ├── android │ ├── icon │ │ ├── drawable-hdpi-icon.png │ │ ├── drawable-ldpi-icon.png │ │ ├── drawable-mdpi-icon.png │ │ ├── drawable-xhdpi-icon.png │ │ ├── drawable-xxhdpi-icon.png │ │ └── drawable-xxxhdpi-icon.png │ └── splash │ │ ├── drawable-land-hdpi-screen.png │ │ ├── drawable-land-ldpi-screen.png │ │ ├── drawable-land-mdpi-screen.png │ │ ├── drawable-land-xhdpi-screen.png │ │ ├── drawable-land-xxhdpi-screen.png │ │ ├── drawable-land-xxxhdpi-screen.png │ │ ├── drawable-port-hdpi-screen.png │ │ ├── drawable-port-ldpi-screen.png │ │ ├── drawable-port-mdpi-screen.png │ │ ├── drawable-port-xhdpi-screen.png │ │ ├── drawable-port-xxhdpi-screen.png │ │ └── drawable-port-xxxhdpi-screen.png ├── 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-Landscape-736h.png │ │ ├── Default-Landscape@2x~ipad.png │ │ ├── Default-Landscape@~ipadpro.png │ │ ├── Default-Landscape~ipad.png │ │ ├── Default-Portrait@2x~ipad.png │ │ ├── Default-Portrait@~ipadpro.png │ │ ├── Default-Portrait~ipad.png │ │ ├── Default@2x~iphone.png │ │ └── Default~iphone.png └── splash.png ├── src ├── app │ ├── app.component.ts │ ├── app.global.ts │ ├── app.html │ ├── app.module.ts │ ├── app.scss │ ├── main.ts │ └── shared.module.ts ├── assets │ ├── audio │ │ ├── heavy_swing.mp3 │ │ ├── heavy_swing_2.mp3 │ │ ├── intro.mp3 │ │ ├── intro.wav │ │ ├── jedi_off.mp3 │ │ ├── jedi_on.mp3 │ │ ├── light_swing.mp3 │ │ ├── light_swing.wav │ │ ├── light_swing_2.mp3 │ │ └── lightsaber-on.mp3 │ ├── data │ │ ├── trivia_tt0076759.json │ │ ├── trivia_tt0080684.json │ │ ├── trivia_tt0086190.json │ │ ├── trivia_tt0120915.json │ │ ├── trivia_tt0121765.json │ │ ├── trivia_tt0121766.json │ │ ├── trivia_tt2488496.json │ │ ├── trivia_tt2527336.json │ │ ├── tt0076759.json │ │ ├── tt0080684.json │ │ ├── tt0086190.json │ │ ├── tt0120915.json │ │ ├── tt0121765.json │ │ ├── tt0121766.json │ │ ├── tt2488496.json │ │ └── tt2527336.json │ ├── fonts │ │ ├── DistrictPro.otf │ │ ├── icomoon.eot │ │ ├── icomoon.svg │ │ ├── icomoon.ttf │ │ └── icomoon.woff │ ├── icon │ │ └── favicon.ico │ ├── img │ │ ├── avatar │ │ │ ├── darth_vader.jpg │ │ │ └── luke.jpg │ │ ├── covers │ │ │ ├── episode_1.jpg │ │ │ ├── episode_2.jpg │ │ │ ├── episode_3.jpg │ │ │ ├── episode_4.jpg │ │ │ ├── episode_5.jpg │ │ │ ├── episode_6.jpg │ │ │ ├── episode_7.jpg │ │ │ └── episode_8.jpg │ │ ├── galaxy.jpg │ │ ├── logo.svg │ │ ├── logo │ │ │ ├── icon-128x128.png │ │ │ ├── icon-144x144.png │ │ │ ├── icon-152x152.png │ │ │ ├── icon-192x192.png │ │ │ ├── icon-384x384.png │ │ │ ├── icon-512x512.png │ │ │ ├── icon-72x72.png │ │ │ └── icon-96x96.png │ │ └── scenes │ │ │ ├── episode_1.jpg │ │ │ ├── episode_2.jpg │ │ │ ├── episode_3.jpg │ │ │ ├── episode_4.jpg │ │ │ ├── episode_5.jpg │ │ │ ├── episode_6.jpg │ │ │ ├── episode_7.jpg │ │ │ └── episode_8.jpg │ └── maps │ │ ├── m1.png │ │ ├── m2.png │ │ ├── m3.png │ │ ├── m4.png │ │ ├── m5.png │ │ ├── pin-dark-1.png │ │ ├── pin-dark-2.png │ │ ├── pin-dark-3.png │ │ ├── pin-dark-4.png │ │ ├── pin-dark-5.png │ │ ├── pin-dark-6.png │ │ ├── pin-dark-7.png │ │ ├── pin-dark-8.png │ │ ├── pin-light-1.png │ │ ├── pin-light-2.png │ │ ├── pin-light-3.png │ │ ├── pin-light-4.png │ │ ├── pin-light-5.png │ │ ├── pin-light-6.png │ │ ├── pin-light-7.png │ │ ├── pin-light-8.png │ │ └── pin-light-9.png ├── components │ ├── overlaying-view │ │ ├── overlaying-view.html │ │ ├── overlaying-view.module.ts │ │ ├── overlaying-view.scss │ │ └── overlaying-view.ts │ └── timer │ │ ├── timer.html │ │ ├── timer.module.ts │ │ ├── timer.scss │ │ └── timer.ts ├── declarations.d.ts ├── index.html ├── manifest.json ├── pages │ ├── film-detail │ │ ├── avatars.ts │ │ ├── film-detail.html │ │ ├── film-detail.module.ts │ │ ├── film-detail.scss │ │ └── film-detail.ts │ ├── home │ │ ├── home.html │ │ ├── home.module.ts │ │ ├── home.scss │ │ └── home.ts │ ├── list │ │ ├── list.html │ │ ├── list.module.ts │ │ ├── list.scss │ │ └── list.ts │ ├── menu │ │ ├── menu.html │ │ ├── menu.module.ts │ │ ├── menu.scss │ │ ├── menu.ts │ │ └── shift-transition.ts │ ├── world-map │ │ ├── world-map.html │ │ ├── world-map.module.ts │ │ ├── world-map.scss │ │ └── world-map.ts │ └── youtube-modal │ │ ├── youtube-modal.html │ │ ├── youtube-modal.module.ts │ │ ├── youtube-modal.scss │ │ └── youtube-modal.ts ├── pipes │ ├── pipes.module.ts │ ├── romanize │ │ └── romanize.ts │ └── species │ │ └── species.ts ├── providers │ ├── alert │ │ └── alert.ts │ ├── audio-service │ │ └── audio-service.ts │ ├── connectivity │ │ └── connectivity.ts │ ├── firebase-data │ │ └── firebase-data.ts │ ├── google-images │ │ └── google-images.ts │ ├── motion │ │ └── motion.ts │ ├── movie-info │ │ └── movie-info.ts │ └── swapi │ │ └── swapi.ts ├── service-worker.js └── theme │ ├── darkside.theme.scss │ ├── fonts.scss │ ├── lightside.theme.scss │ ├── mixins.scss │ ├── splash.scss │ └── variables.scss ├── tsconfig.json └── tslint.json /.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 | *~ 5 | *.sw[mnpcod] 6 | *.log 7 | *.tmp 8 | *.tmp.* 9 | log.txt 10 | *.sublime-project 11 | *.sublime-workspace 12 | .vscode/ 13 | npm-debug.log* 14 | 15 | .idea/ 16 | .sass-cache/ 17 | .tmp/ 18 | .versions/ 19 | coverage/ 20 | dist/ 21 | node_modules/ 22 | tmp/ 23 | temp/ 24 | hooks/ 25 | platforms/ 26 | plugins/ 27 | plugins/android.json 28 | plugins/ios.json 29 | www/ 30 | $RECYCLE.BIN/ 31 | 32 | .DS_Store 33 | Thumbs.db 34 | UserInterfaceState.xcuserstate 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MAY THE FOURTH BE WITH YOU 2 | 3 | So the ionic team came up with this amazing event called [Ionic Jedi Hackster](http://blog.ionic.io/become-an-ionic-jedi-hackster/), which aimed at engaging their developers to create a Star Wars themed app in under 48 hours using the latest and greatest [Ionic](https://ionicframework.com) and the recently released [Ionic CLI v3](http://blog.ionic.io/announcing-ionic-cli-v3/). 4 | 5 | Of course I had to contribute, to see if I could *become a Jedi Hackster*. 6 | 7 | My name is Yann Braga, a passionate fullstack developer based in Fortaleza, Brazil. Today I'll be showing you how StarWarnic was developed. 8 | 9 | ## The Inspiration 10 | _important note: I do not own any rights to Star Wars brand of imagery, Star Wars was just a theme used for the challenge._ 11 | 12 | To be honest, I’m not a huge Star Wars fan, but I’ve always had interest in knowing more about its movies. I started thinking in making a card game, where each player would get a set of seven cards and compete statuses with each other. Unfortunately my weekend was rushy, but I still wanted to be able to complete the challenge and make a fun app, so I had to change the initial idea to a very simple app: Listing Star Wars movies and showing some info about them. 13 | One thing I did that was fun (and might confuse people) was listing the movies chronologically with their respective sequence number. This might be weird, as the movies were released in a completely non-ordered sequence: IV, II, I, III, VI, V, VII then VIII. 14 | _Go figure George Lucas’ head_. 15 | 16 | When I learned [my app had actually won](http://blog.ionic.io/and-the-1st-ionic-jedi-hackster-is/), I got so excited I decided to improve the project! Even better, turn it into a PWA! Finally, I had a bit more time to actually add some features I wanted: 17 | - Better work on UI 18 | - Enhance support for browser (audio and accelerometer) 19 | - Add a trivia section 20 | - Embed youtube videos in the app 21 | - Integrate the app with firebase and have a real time updated map! 22 | - Publish the app as PWA (Progressive web app) 23 | 24 | ## Technologies Used: 25 | 26 | - [SWAPI (Star wars API)](https://swapi.co/) 27 | - [Ionic 3.3.0](https://github.com/driftyco/ionic) 28 | - [Firebase 3.9](https://firebase.google.com/) 29 | - [Ionic Cache 2.0.1](https://github.com/Nodonisko/ionic-cache) 30 | - [Google Maps API v3](https://developers.google.com/maps/documentation/javascript/) 31 | - [Ionic Native plugins](https://ionicframework.com/docs/native/): Shake, Geolocation, Device, Flashlight, Native Audio, Network 32 | - [Cheerio](https://github.com/cheeriojs/cheerio) 33 | 34 | ## The App’s Features 35 | 36 | - Animated(ish) splash screen 37 | - Custom icons 38 | - Custom side menu transition 39 | - Switching sides (app theme) 40 | - Fetching data 41 | - Sound effects and Jedi Mode 42 | - Overlaying View with Trivia 43 | - Firebase and The Force map 44 | - Publish as PWA 45 | 46 | ## Animated(ish) Splash Icon 47 | 48 | As the app is about Star Wars, it was almost obligatory to have a classic Star Wars intro. Note that this is very experimental, but it was fun to see how it ended up. 49 | 50 | Basically, I set the app to not have a splashscreen. Then, added a `div` which contains the Star Wars logo and a background image of a galaxy. On the app start, I show that div and after a few seconds delay, fade it out. To make that logo sliding effect just like the Star Wars movie does, I used css and `@keyframes` animations that would change the logo’s perspective and add a sliding animation, from bottom to top: 51 | 52 | https://media.giphy.com/media/DDYYooPOfr5Cw/giphy.gif 53 | 54 | To be honest this experience gave me a few issues when in a PWA, as the splash is played every time the user updates the url and it gets a bit annoying. I had to make a few workarounds, so I recommend trying another approach. 55 | 56 | ## Custom icons 57 | 58 | In this project, I added custom icons in order to make it cooler, following the Star Wars theme. 59 | 60 | To do so, I downloaded a few SVG icons from [The Noun Project](https://thenounproject.com/)(amazing website, you should definitely check it out) and created my own font by using the online app [Icon Moon](https://icomoon.io/app/). 61 | 62 | Right after, I downloaded my custom font and added into the project's assets, then got into the scss and extended the default `ion-icon` so that I could use the same element to either use default icons or my custom icons, such as ``. 63 | 64 | An example can be seen here: 65 | https://yannbraga.com/static/custom_icons.png 66 | 67 | You can check the code out [here](https://github.com/yannbf/may-the-4th/blob/master/src/theme/variables.scss#L76) 68 | 69 | ## Custom side menu transitions 70 | 71 | If you take a closer look at ionic’s [source code](https://github.com/driftyco/ionic/blob/53113366e239a48d83bb70789ed64d0637f150e5/src/components/menu/menu-types.ts), you’ll notice something like this: 72 | 73 | ``` 74 | class MenuRevealType extends MenuType { 75 | constructor(menu: Menu, plt: Platform) { 76 | super(plt); 77 | 78 | const openedX = (menu.width() * (menu.isRightSide ? -1 : 1)) + 'px'; 79 | const contentOpen = new Animation(plt, menu.getContentElement()); 80 | contentOpen.fromTo('translateX', '0px', openedX); 81 | this.ani.add(contentOpen); 82 | } 83 | } 84 | MenuController.registerType('reveal', MenuRevealType); 85 | ``` 86 | 87 | Did you know this is all the code used to create that sweet slide to reveal animation on the sidemenu? So with the help of [EbilPanda](https://github.com/EbilPanda) I used a similar approach and was able to create this very elegant custom effect: 88 | 89 | https://media.giphy.com/media/6C8XLpqQDf8ic/giphy.gif 90 | 91 | ## Switching side (app theme) 92 | 93 | The app offers you the option to be either on the light side or the dark side. Upon picking your side, the whole UI is changed dynamically! 94 | 95 | Dynamically switching the app’s theme may seem a bit complicated, but it’s actually quite simple! 96 | 97 | This feature can be seen in detail [here](https://github.com/yannbf/ionic3-components/tree/master/src/pages/theming), but I ‘ll try to quickly explain it: 98 | 99 | I had to create a `Global State` class, that would share any information across every page whenever needed. This way, I could set info about the theme just like: 100 | ``` 101 | changeTheme(theme) { 102 | this.global.set('theme', theme); 103 | } 104 | ``` 105 | 106 | Which could be instantly accessed anywhere with `this.global.get('theme')`. 107 | 108 | 109 | Then, wrap the whole app in a div such as: 110 | ``` 111 |
112 | // app.html code... 113 |
114 | ``` 115 | 116 | After that, also create as many themes in separate scss files each. For each theme, indicate specific stylings for each element you want to be affected. In this project, I created `theme-darkside.scss` and `theme-lightside.scss`, obviously. 117 | 118 | and _boom_, whenever the property ‘theme’ was changed, the whole app’s looks would change instantly! 119 | 120 | https://media.giphy.com/media/xUPGcuVozZdmY9WYwg/giphy.gif 121 | 122 | _P.S. I have a [Github repository](https://github.com/yannbf/ionic3-components) that has a lot of custom components and samples of many cool stuff, and theme switching is one of them._ 123 | 124 | ## Fetching Data 125 | 126 | The SWAPI was used to gather Star Wars specific data, like which characters appear in which movie. 127 | 128 | To support faster loading of data and offline support, I used Ionic Cache, a module for request caching which uses Ionic Storage (thus supporting IndexedDB, SQLite and WebSQL). This allowed me to enhance the app's performance as the data would only be fetched remotely once, and later on would be loaded instantly. 129 | 130 | I also wanted to have more info about the movies, such as duration, plot, nominations, trivia, etc. To do so, I used the [ODBM (Open Movie Database) API](http://www.omdbapi.com/). 131 | The API is quite good, as it fetches info from IMDb, which is what I wanted. However, a really unexpected thing happened: a few days ago they shut down their open api and only opened for their patreons. It was quite frustrating, but encouraged me to do something way more interesting: get the info myself with a web scraper! It was even better because I also wanted to have a trivia section with lots of interesting facts, and I didn't find any API with this data. To sum up, a web scraper is basically a sort of bot that accesses a web page, extracts its information, and from there you can do whatever you want with it. 132 | 133 | In order to populate the movie data, I wrote a [simple web scraper in node](https://github.com/yannbf/imdb-scrapper/blob/master/index.js) that accesses the IMDb url of a given movie and uses [Cheerio](https://github.com/cheeriojs/cheerio) to read the DOM elements just like people are used to do with jQuery selectors. It is as simple as: `$('.movie').text()`. If you never thought of writing a webscrapper, the project can be a good reference, feel free to use it! It's quite fun :) 134 | 135 | 136 | ## Sound effects and Jedi Mode 137 | 138 | I was hoping to get some sound effects on the app to make it more fun, and what could be cooler than lightsaber sound effects?? 139 | 140 | With that in mind, I created a provider that uses Ionic Native Audio plugin to store and play the sounds natively, and also falls back to HTML5 Audio, in case the user is on the browser platform. 141 | 142 | https://youtu.be/CG81BrBB1Ag 143 | 144 | These sounds would be played in two occasions: When switching sides or when Jedi mode is on. 145 | 146 | The Jedi mode is a feature that unfortunately doesn't work on the PWA, but you can check it out by installing the apk/ipa on your app. What it does is it turns your flashlight on with a nice lightsaber sound and every time you swing the phone, the app reproduces a lightsaber swinging sound. This way you can play around and feel like a Jedi! 147 | 148 | ## Overlaying View with Trivia 149 | 150 | Most of you might know about Josh Morony, and as I really like his tutorials I wanted to add some feature from one of his blog posts. I then implemented an adapted version of his sliding drawer, where there is a kind of footer element, but it's actually a whole ion-content that overlays the current view upon clicking: 151 | 152 | https://media.giphy.com/media/xUA7aMDSWoOm2G7y7u/giphy.gif 153 | 154 | ## Firebase and The Force map 155 | 156 | This was the feature I most wanted to do. I wanted to have a map that could show all users and which side they are on, so we could see which would prevail. 157 | 158 | https://media.giphy.com/media/8SxGru3XzElqg/giphy.gif 159 | 160 | To do so, I created a project on firebase and integrated it on the app, and on the maps page I request and register the user information (name and location). Then data is fetched from Firebase database, and each person gets an avatar (all made by [Jory Raphael](http://symbolicons.com/). If you choose the force, you get to be one of the good guys(C-3PO, Princess Leia..), otherwise, one of the bad guys (Darth Vader, Stormtrooper..). Along with that, there are two lightsabers dueling as the number of users grow. This all happens in real time, so you can see the users dropping on the map and the numbers going up. 161 | 162 | On this video, I simulated many people registering at the same time for demonstration purposes: 163 | 164 | https://media.giphy.com/media/3ohzdWmi3voKoftIZ2/giphy.gif 165 | 166 | ## Publish as a PWA (Progressive Web App) 167 | 168 | A Progressive Web App is a website that behaves just like a native app, and makes it much easier to users access your app without the need of going to the stores and installing your app. They could simply open a url and _boom_, it works. 169 | 170 | Fortunately, Ionic supports PWA right out of the box! It offers service workers and a manifest file by default. The service worker is a script that allows PWA functionality such as push notifications, background sync, offline support and much more. The manifest file is processed by the browser and provides metadata such as name, theme, and icon, and enables the browser to add the app to the home screen so that the user can access it later on just like any installed app! 171 | 172 | Better yet, if you take a look at `index.html`, there's even a commented code that already does the work of registering the service worker for you: 173 | 174 | ``` 175 | 182 | ``` 183 | 184 | Everything was always there and we never noticed! Just remove the comment and you're good to go. The project uses `sw-toolbox` by chrome, so in case you want to understand what you can do with it, [hit their website](https://googlechrome.github.io/sw-toolbox/). 185 | 186 | _Okay, sure.. but how to deploy a production version of my app as PWA?_ 187 | 188 | This is something that I have seen plenty of people asking about on the community and the answer is quite simple: just run `npm run ionic:build --prod` and deploy the generated `/www` folder! 189 | 190 | https://media0.giphy.com/media/11sBLVxNs7v6WA/giphy.gif 191 | 192 | _Sure, but what are the steps of deploying a PWA?_ 193 | Well, there are a few ways you can achieve it: you could either host your app in your private server or use Firebase hosting services. Firebase offers free hosting, which makes interesting if you are just trying stuff out. It's also really quick from setup to deploy, like a matter of a few minutes. There is a great [step by step guide](https://firebase.google.com/docs/hosting/deploying) on their website. 194 | 195 | *Important note!* 196 | 197 | Nowadays every website is being served over https protocol, so there's a great secure layer that protects the privacy and integrity of the website's data whilst securing the website from upcoming attacks. This means that every request in the app has to be done through *https*, otherwise it will be flagged as non-secure and therefore get canceled. SWAPI is great but gave me a few headaches because of that. As their docs doesn't mention https, I was unsure if they supported it. Even though I had renamed my request url to https (such as `https://swapi.co/api/people/1`), the calls were being redirected as http. After a few good hours the problem was solved in an unexpected way: I just had to add a slash to the end of every request ¯\_(ツ)_/¯. 198 | 199 | ## May the force be with ALL of us 200 | 201 | I was really flattered to have won the challenge but I'm even more grateful to have the opportunity to share my experience with you all. Ionic is an amazing and versatile platform that has given me many wonderful opportunities ever since the first day I started using it (it was still ionic 2 beta!). 202 | 203 | I wish this was interesting and brought inspiration to you! If you are interested in knowing more about this project, have any questions or just want to chat, feel free to hit me on [Ionic's worldwide slack](https://ionicworldwide.herokuapp.com/) or contact me through [my website](https://yannbraga.com/). 204 | 205 | Without further do, you can access the app on https://starwarnic.yannbraga.com, or on Ionic View with id 6e8bd472, or even building the app yourself on the repository: https://github.com/yannbf/may-the-4th. 206 | 207 | Let's see which side will win! What side are you on? 208 | _spoiler: The one I chose offered me cookies_. 209 | -------------------------------------------------------------------------------- /config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Star Warnic 4 | Cool app about star wars :D 5 | Yann Braga 6 | 7 | 8 | 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 | 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /ionic.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Star Warnic", 3 | "app_id": "6e8bd472", 4 | "type": "ionic-angular" 5 | } 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "StarWarnic", 3 | "version": "0.2.0", 4 | "author": "Yann Braga", 5 | "homepage": "https://yannbraga.com/", 6 | "private": true, 7 | "scripts": { 8 | "clean": "ionic-app-scripts clean", 9 | "build": "ionic-app-scripts build", 10 | "ionic:build": "ionic-app-scripts build", 11 | "ionic:serve": "ionic-app-scripts serve" 12 | }, 13 | "dependencies": { 14 | "@angular/common": "4.1.2", 15 | "@angular/compiler": "4.1.2", 16 | "@angular/compiler-cli": "4.1.2", 17 | "@angular/core": "4.1.2", 18 | "@angular/forms": "4.1.2", 19 | "@angular/http": "4.1.2", 20 | "@angular/platform-browser": "4.1.2", 21 | "@angular/platform-browser-dynamic": "4.1.2", 22 | "@ionic-native/core": "3.9.1", 23 | "@ionic-native/device": "^3.10.3", 24 | "@ionic-native/flashlight": "^3.8.0", 25 | "@ionic-native/geolocation": "^3.8.1", 26 | "@ionic-native/in-app-browser": "^3.6.1", 27 | "@ionic-native/native-audio": "^3.6.1", 28 | "@ionic-native/network": "^3.8.1", 29 | "@ionic-native/shake": "^3.6.1", 30 | "@ionic-native/splash-screen": "3.6.1", 31 | "@ionic-native/status-bar": "3.6.1", 32 | "@ionic-native/youtube-video-player": "^3.6.1", 33 | "@ionic/app-scripts": "1.3.7", 34 | "@ionic/storage": "^2.0.1", 35 | "@types/google-maps": "3.2.0", 36 | "angularfire2": "^4.0.0-rc.0", 37 | "fingerprintjs2": "^1.5.1", 38 | "firebase": "^4.1.0", 39 | "ionic-angular": "3.3.0", 40 | "ionic-cache": "^2.0.1", 41 | "ionicons": "3.0.0", 42 | "node-js-marker-clusterer": "^1.0.0", 43 | "rxjs": "5.1.1", 44 | "sw-toolbox": "3.6.0", 45 | "zone.js": "0.8.10" 46 | }, 47 | "devDependencies": { 48 | "@ionic/app-scripts": "1.3.7", 49 | "@ionic/cli-plugin-cordova": "1.3.0", 50 | "@ionic/cli-plugin-ionic-angular": "1.3.0", 51 | "typescript": "2.3.3" 52 | }, 53 | "description": "An Amazing Ionic project" 54 | } 55 | -------------------------------------------------------------------------------- /resources/android/icon/drawable-hdpi-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/resources/android/icon/drawable-hdpi-icon.png -------------------------------------------------------------------------------- /resources/android/icon/drawable-ldpi-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/resources/android/icon/drawable-ldpi-icon.png -------------------------------------------------------------------------------- /resources/android/icon/drawable-mdpi-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/resources/android/icon/drawable-mdpi-icon.png -------------------------------------------------------------------------------- /resources/android/icon/drawable-xhdpi-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/resources/android/icon/drawable-xhdpi-icon.png -------------------------------------------------------------------------------- /resources/android/icon/drawable-xxhdpi-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/resources/android/icon/drawable-xxhdpi-icon.png -------------------------------------------------------------------------------- /resources/android/icon/drawable-xxxhdpi-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/resources/android/icon/drawable-xxxhdpi-icon.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-land-hdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/resources/android/splash/drawable-land-hdpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-land-ldpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/resources/android/splash/drawable-land-ldpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-land-mdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/resources/android/splash/drawable-land-mdpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-land-xhdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/resources/android/splash/drawable-land-xhdpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-land-xxhdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/resources/android/splash/drawable-land-xxhdpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-land-xxxhdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/resources/android/splash/drawable-land-xxxhdpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-port-hdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/resources/android/splash/drawable-port-hdpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-port-ldpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/resources/android/splash/drawable-port-ldpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-port-mdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/resources/android/splash/drawable-port-mdpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-port-xhdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/resources/android/splash/drawable-port-xhdpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-port-xxhdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/resources/android/splash/drawable-port-xxhdpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-port-xxxhdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/resources/android/splash/drawable-port-xxxhdpi-screen.png -------------------------------------------------------------------------------- /resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/resources/icon.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/resources/ios/icon/icon-40.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/resources/ios/icon/icon-40@2x.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/resources/ios/icon/icon-40@3x.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/resources/ios/icon/icon-50.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-50@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/resources/ios/icon/icon-50@2x.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/resources/ios/icon/icon-60.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/resources/ios/icon/icon-60@2x.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/resources/ios/icon/icon-60@3x.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/resources/ios/icon/icon-72.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-72@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/resources/ios/icon/icon-72@2x.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/resources/ios/icon/icon-76.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/resources/ios/icon/icon-76@2x.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/resources/ios/icon/icon-83.5@2x.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/resources/ios/icon/icon-small.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-small@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/resources/ios/icon/icon-small@2x.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-small@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/resources/ios/icon/icon-small@3x.png -------------------------------------------------------------------------------- /resources/ios/icon/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/resources/ios/icon/icon.png -------------------------------------------------------------------------------- /resources/ios/icon/icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/resources/ios/icon/icon@2x.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-568h@2x~iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/resources/ios/splash/Default-568h@2x~iphone.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-667h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/resources/ios/splash/Default-667h.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-736h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/resources/ios/splash/Default-736h.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-Landscape-736h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/resources/ios/splash/Default-Landscape-736h.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-Landscape@2x~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/resources/ios/splash/Default-Landscape@2x~ipad.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-Landscape@~ipadpro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/resources/ios/splash/Default-Landscape@~ipadpro.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-Landscape~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/resources/ios/splash/Default-Landscape~ipad.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-Portrait@2x~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/resources/ios/splash/Default-Portrait@2x~ipad.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-Portrait@~ipadpro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/resources/ios/splash/Default-Portrait@~ipadpro.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-Portrait~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/resources/ios/splash/Default-Portrait~ipad.png -------------------------------------------------------------------------------- /resources/ios/splash/Default@2x~iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/resources/ios/splash/Default@2x~iphone.png -------------------------------------------------------------------------------- /resources/ios/splash/Default~iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/resources/ios/splash/Default~iphone.png -------------------------------------------------------------------------------- /resources/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/resources/splash.png -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { MenuShiftType } from '../pages/menu/shift-transition'; 2 | import { AppState } from './app.global'; 3 | import { Component, ViewChild } from '@angular/core'; 4 | import { MenuController, Nav, Platform } from 'ionic-angular'; 5 | import { StatusBar } from '@ionic-native/status-bar'; 6 | import { SplashScreen } from '@ionic-native/splash-screen'; 7 | import { Storage } from '@ionic/storage'; 8 | import Fingerprint2 from 'fingerprintjs2'; 9 | 10 | @Component({ 11 | templateUrl: 'app.html' 12 | }) 13 | export class MyApp { 14 | @ViewChild(Nav) nav: Nav; 15 | 16 | rootPage: any = 'MenuPage'; 17 | 18 | constructor(public platform: Platform, 19 | public statusBar: StatusBar, 20 | public splashScreen: SplashScreen, 21 | public global: AppState, 22 | public storage: Storage) { 23 | this.initializeApp(); 24 | } 25 | 26 | initializeApp() { 27 | this.platform.ready().then(() => { 28 | this.storage.get('uuid').then(uuid => { 29 | if(uuid){ 30 | this.global.set('uuid', uuid); 31 | } else { 32 | new Fingerprint2().get((result, components) => { 33 | this.global.set('uuid', result); 34 | this.storage.set('uuid', result); 35 | }); 36 | } 37 | }); 38 | this.global.set('side', 'light'); 39 | 40 | MenuController.registerType('shift', MenuShiftType); 41 | 42 | this.statusBar.styleDefault(); 43 | this.splashScreen.hide(); 44 | }); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/app/app.global.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | @Injectable() 3 | export class AppState { 4 | _state = {} 5 | constructor() { 6 | } 7 | // already return a clone of the current state 8 | get state() { 9 | return this._state = this._clone(this._state); 10 | } 11 | // never allow mutation 12 | set state(value) { 13 | throw new Error('do not mutate the `.state` directly'); 14 | } 15 | get(prop?: any) { 16 | // use our state getter for the clone 17 | const state = this.state; 18 | return state.hasOwnProperty(prop) ? state[prop] : state; 19 | } 20 | set(prop: string, value: any) { 21 | // internally mutate our state 22 | return this._state[prop] = value; 23 | } 24 | _clone(object) { 25 | // simple object clone 26 | return JSON.parse(JSON.stringify(object)); 27 | } 28 | } -------------------------------------------------------------------------------- /src/app/app.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |
-------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { AppState } from './app.global'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { ErrorHandler, NgModule } from '@angular/core'; 4 | import { HttpModule } from '@angular/http'; 5 | import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular'; 6 | 7 | import { MyApp } from './app.component'; 8 | 9 | // Ionic native providers 10 | import { StatusBar } from '@ionic-native/status-bar'; 11 | import { SplashScreen } from '@ionic-native/splash-screen'; 12 | import { Shake } from '@ionic-native/shake'; 13 | import { NativeAudio } from '@ionic-native/native-audio'; 14 | import { InAppBrowser } from '@ionic-native/in-app-browser'; 15 | import { YoutubeVideoPlayer } from '@ionic-native/youtube-video-player'; 16 | import { Geolocation } from '@ionic-native/geolocation'; 17 | import { Flashlight } from '@ionic-native/flashlight'; 18 | import { IonicStorageModule } from '@ionic/storage'; 19 | 20 | // Custom providers 21 | import { AudioService } from '../providers/audio-service/audio-service'; 22 | import { SwapiProvider } from '../providers/swapi/swapi'; 23 | import { GoogleImagesProvider } from '../providers/google-images/google-images'; 24 | import { FirebaseDataProvider } from '../providers/firebase-data/firebase-data'; 25 | import { MotionProvider } from '../providers/motion/motion'; 26 | import { ConnectivityProvider } from '../providers/connectivity/connectivity'; 27 | import { AlertService } from '../providers/alert/alert'; 28 | import { CacheModule } from "ionic-cache"; 29 | import { Network } from '@ionic-native/network'; 30 | import { MovieInfoProvider } from '../providers/movie-info/movie-info'; 31 | 32 | import { AngularFireModule } from 'angularfire2'; 33 | import { AngularFireDatabaseModule } from 'angularfire2/database'; 34 | import { AngularFireAuthModule } from 'angularfire2/auth'; 35 | 36 | export const firebaseConfig = { 37 | apiKey: "AIzaSyAK2DSpw4LHsYS6tPzgfHmZnVM32uQaQn4", 38 | authDomain: "star-warnic.firebaseapp.com", 39 | databaseURL: "https://star-warnic.firebaseio.com", 40 | projectId: "star-warnic", 41 | storageBucket: "star-warnic.appspot.com", 42 | messagingSenderId: "459136741480" 43 | }; 44 | 45 | @NgModule({ 46 | declarations: [ 47 | MyApp, 48 | ], 49 | imports: [ 50 | BrowserModule, 51 | HttpModule, 52 | IonicModule.forRoot(MyApp, { 53 | preloadModules: true 54 | }), 55 | CacheModule.forRoot(), 56 | IonicStorageModule.forRoot(), 57 | AngularFireModule.initializeApp(firebaseConfig), 58 | AngularFireAuthModule, 59 | AngularFireDatabaseModule 60 | ], 61 | bootstrap: [IonicApp], 62 | entryComponents: [ 63 | MyApp, 64 | ], 65 | providers: [ 66 | AppState, 67 | StatusBar, 68 | SplashScreen, 69 | Shake, 70 | NativeAudio, 71 | GoogleImagesProvider, 72 | InAppBrowser, 73 | YoutubeVideoPlayer, 74 | FirebaseDataProvider, 75 | Flashlight, 76 | Geolocation, 77 | Network, 78 | {provide: ErrorHandler, useClass: IonicErrorHandler}, 79 | 80 | AudioService, 81 | SwapiProvider, 82 | AlertService, 83 | MotionProvider, 84 | ConnectivityProvider, 85 | MovieInfoProvider, 86 | ] 87 | }) 88 | export class AppModule {} 89 | -------------------------------------------------------------------------------- /src/app/app.scss: -------------------------------------------------------------------------------- 1 | // http://ionicframework.com/docs/v2/theming/ 2 | 3 | 4 | // App Global Sass 5 | // -------------------------------------------------- 6 | // Put style rules here that you want to apply globally. These 7 | // styles are for the entire app and not just one component. 8 | // Additionally, this file can be also used as an entry point 9 | // to import other Sass files to be included in the output CSS. 10 | // 11 | // Shared Sass variables, which can be used to adjust Ionic's 12 | // default Sass variables, belong in "theme/variables.scss". 13 | // 14 | // To declare rules for a specific mode, create a child rule 15 | // for the .md, .ios, or .wp mode classes. The mode class is 16 | // automatically applied to the element in the app. 17 | @import '../theme/darkside.theme'; 18 | @import '../theme/lightside.theme'; 19 | @import '../theme/mixins'; 20 | @import "../theme/fonts"; 21 | @import "../theme/splash"; 22 | 23 | ion-card { 24 | cursor: -webkit-grab; 25 | .card-header { 26 | background: rgba(255,255,255,.05) !important; 27 | } 28 | ion-avatar img { 29 | max-height: 160px !important; 30 | // max-width: 160px !important; 31 | padding: 2px !important; 32 | } 33 | } 34 | 35 | .darkside-button .button-inner{ 36 | color: $darkside-red; 37 | } 38 | 39 | .item, p, h3 { 40 | color: #333; 41 | } 42 | 43 | ion-nav { 44 | box-shadow: -8px 8px 19px rgba(0,0,0,0.45); 45 | } 46 | 47 | .item, 48 | ion-label, 49 | ion-item .item-inner { 50 | background-color: transparent !important; 51 | } 52 | -------------------------------------------------------------------------------- /src/app/main.ts: -------------------------------------------------------------------------------- 1 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 2 | import { enableProdMode } from '@angular/core'; 3 | 4 | import { AppModule } from './app.module'; 5 | 6 | enableProdMode(); 7 | platformBrowserDynamic().bootstrapModule(AppModule); 8 | -------------------------------------------------------------------------------- /src/app/shared.module.ts: -------------------------------------------------------------------------------- 1 | import { PipesModule } from '../pipes/pipes.module'; 2 | import { NgModule } from '@angular/core'; 3 | 4 | @NgModule({ 5 | declarations: [ 6 | ], 7 | imports: [ 8 | PipesModule, 9 | ], 10 | exports: [ 11 | PipesModule, 12 | ] 13 | }) 14 | 15 | export class SharedModule { } 16 | -------------------------------------------------------------------------------- /src/assets/audio/heavy_swing.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/src/assets/audio/heavy_swing.mp3 -------------------------------------------------------------------------------- /src/assets/audio/heavy_swing_2.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/src/assets/audio/heavy_swing_2.mp3 -------------------------------------------------------------------------------- /src/assets/audio/intro.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/src/assets/audio/intro.mp3 -------------------------------------------------------------------------------- /src/assets/audio/intro.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/src/assets/audio/intro.wav -------------------------------------------------------------------------------- /src/assets/audio/jedi_off.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/src/assets/audio/jedi_off.mp3 -------------------------------------------------------------------------------- /src/assets/audio/jedi_on.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/src/assets/audio/jedi_on.mp3 -------------------------------------------------------------------------------- /src/assets/audio/light_swing.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/src/assets/audio/light_swing.mp3 -------------------------------------------------------------------------------- /src/assets/audio/light_swing.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/src/assets/audio/light_swing.wav -------------------------------------------------------------------------------- /src/assets/audio/light_swing_2.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/src/assets/audio/light_swing_2.mp3 -------------------------------------------------------------------------------- /src/assets/audio/lightsaber-on.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/src/assets/audio/lightsaber-on.mp3 -------------------------------------------------------------------------------- /src/assets/data/trivia_tt2527336.json: -------------------------------------------------------------------------------- 1 | { 2 | "common": [ 3 | "\nBenicio Del Toro was announced to be playing a villain in the film. He was previously cast as Darth Maul, the primary antagonist of Star Wars: Episode I - The Phantom Menace (1999), but dropped out after George Lucas removed most of Maul's lines from the script. ", 4 | "\nJoaquin Phoenix turned down the role that eventually went to Benicio Del Toro. ", 5 | "\nIt was confirmed in 2016 that John Williams will compose the music for Episode VIII. This will be the 8th Star Wars film he composes. ", 6 | "\nEwan McGregor has expressed his interest to reprise his role as Obi-Wan Kenobi in this film. ", 7 | "\nEarly filming took place with Mark Hamill and Daisy Ridley on Skellig Michael off the coast of County Kerry, Ireland, in late September 2015. On February 15, 2016, LucasFilm posted a short production-announcement video, which included some footage and behind-the-scenes shots, and which picks up immediately after the final scene of Star Wars: Episode VII - The Force Awakens (2015). Main photography was scheduled to begin in early January 2016, but was delayed a month reportedly due to script revisions, and began on February 15. ", 8 | "\nFirst film in the series in which Kenny Baker was not involved in the portrayal of R2-D2. Jimmy Vee was given the job, shortly before Baker's death. ", 9 | "\nThe production name for Episode VIII was \"Space Bear\". ", 10 | "\nOn December 2015 the production team of this film and Rogue One (2016) went to Mexico City looking for filming locations, specifically at Iztapalapa and Tlahuac boroughs. It was later confirmed that some scenes for both films would be filmed in Mexico City, but due to some trouble with the local government the filming was cancelled. ", 11 | "\nThe film will be released over 40 years after Star Wars: Episode IV - A New Hope (1977) and will mark the 40th anniversary of the Star Wars film franchise. ", 12 | "\nLawrence Kasdan initially wrote a story outline for the film, but was called away to work on Star Wars: Episode VII - The Force Awakens (2015) when problems arose with that film's initial script. When Rian Johnson signed on as director he requested to be allowed to scrap Kasdan's story and write his own script from scratch, which the producers consented to, as Kasdan's outline no longer matched up with the finished storyline of the previous film. ", 13 | "\nDirector Rian Johnson has said that the film took inspiration from Twelve O'Clock High (1949), Letter Never Sent (1960), The Bridge on the River Kwai (1957) and Three Outlaw Samurai (1964). ", 14 | "\nSome fans have noted that the titles of this film and its predecessor form a sentence. Namely, \"The Force awakens the last Jedi\". ", 15 | "\nRian Johnson was influenced by Twelve O'Clock High (1949) and Letter Never Sent (1960) (known as \"Letter Never Sent\" in English-speaking countries) and arranged for screenings for the crew prior to filming. ", 16 | "\nRian Johnson was originally considered to direct Star Wars: Episode VII - The Force Awakens (2015). ", 17 | "\nThe Irish location filming in Donegal was intended to follow on from the dark Skellig ending of \"The Force Awakens\". However the weather was unseasonably good and shooting took longer than expected. ", 18 | "\nThe original release date was May 26, 2017. However, in January of 2016, the film's release was rescheduled for December 15, 2017, based on how successful Star Wars: Episode VII - The Force Awakens (2015) was releasing in December.. ", 19 | "\nThe title of \"Star Wars VIII The Last Jedi\" was first published in Spanish and translated to \"The Last Jedis\", meaning the title is plural. ", 20 | "\nThe second Star Wars film that's filmed selected sequences with IMAX cameras. ", 21 | "\nNone of Carrie Fisher's scenes in the movie are cut out. ", 22 | "\nKelly Marie Tran didn't tell anyone that she was doing the movie and lied that she was doing an indie movie in Canada. At one point, she actually got some maple syrup to bring back to her parents, so they actually thought she was in Canada. ", 23 | "\nHayden Christensen expressed his interest in future Star Wars films. ", 24 | "\nOn January 24th 2017, the title of the film was announced as Star Wars: The Last Jedi. ", 25 | "\nBillie Lourd who played Lieutenant Connix is the previous film will have a bigger role in this film. Billie is also the Daughter of Carrie Fisher. ", 26 | "\nCarrie Fisher was also a well-known writer, whose services were often called upon to act as script doctor for other films. She later began declining such assignments when producers would solicit her story ideas, then hire someone else to actually change the script, using her ideas without paying her. However, Rian Johnson revealed that Fisher helped with the writing of the script for this film. ", 27 | "\nThe \"STAR WARS\" logo is red. Possibly meaning that \"THE LAST JEDI\" is coinciding the ruby anniversary year, celebrating 40 years of the franchise since the original movie was released in 1977. ", 28 | "\nAs with Star Wars: Episode V - The Empire Strikes Back (1980), Star Wars: Episode VI - Return of the Jedi (1983) and Star Wars: Episode VII - The Force Awakens (2015), the episode number will not be included in the official marketing of the film. However, it will appear in the opening title crawl. ", 29 | "\nCarrie Fisher credited Miguel Ferrer for preparing her for her original audition for the role of Leia. Ferrer later became known for playing Agent Albert Rosenfeld in 'Twin Peaks (1990)(TV)'. Like Fisher, Ferrer ended up returning to that role decades later in 'Twin Peaks (2017)(TV)', but also died before its premiere. Laura Dern appears in both Twin Peaks and The Last Jedi. ", 30 | "\nCarrie Fisher's final film due to her death in December 2016. ", 31 | "\nDuring the trailer, there is a shot of what appears to be several books old on a shelf. We then see a shot of a gloved hand running over a faded tome with the symbol of the jedi on it. In the Force Awakens we are told Luke was searching for the first Jedi Temple. It is possible in the caves of the planet Ahch-to, he found the ancient texts of those first force users. A parallel may be drawn in real life to the Dead Sea Scrolls, the founding documents of Christianity (through which, Lucas drew inspiration for the original Star Wars) being found in caves in Israel and the Middle East ", 32 | "\nBenicio Del Toro is the fourth Academy Award winner to star in a Star Wars film, after Alec Guinness, Natalie Portman, and Lupita Nyong'o. ", 33 | "\nTom Hardy, who makes a cameo appearance as a storm trooper, made one of his earliest film appearances in Star Trek: Nemesis (2002). He also appeared in Black Hawk Down (2001) with Ewan McGregor. He and Mark Hamill have also both appeared in the Batman franchise. Hardy played Bane in The Dark Knight Rises (2012), while Hamill voiced the Joker. ", 34 | "\nPrince William and Prince Harry visited the set of the film. ", 35 | "\nThe title of this film, \"The Last Jedi,\" was revealed on January 23, 2017. The phrase appears in the opening crawl of \"The Force Awakens\", making this the first time the title of a Star Wars film appeared in an opening crawl. ", 36 | "\nOn April 4th 2016, it was rumored Adrian Edmondson would be starring in the film. ", 37 | "\nAustralian actor Mahesh Jadu turned down the role of Poe Dameron (now portrayed by Oscar Isaac) in the Star Wars saga, for his role of The Spanish Soldier in Pirates of the Caribbean: Dead Men Tell No Tales (2017). ", 38 | "\nThe first teaser trailer was released on April 14, 2017. ", 39 | "\nFootage was shown to Disney shareholders.[March 2017] ", 40 | "\nSecond Star Wars movie to take place directly after the events of the previous movie. A New Hope took place directly after the events of Rogue One - A Star Wars Story. ", 41 | "\nLaura Dern has starred as Sandy Williams in David Lynch's classic thriller Blue Velvet (1986). David Lynch also directed Dune (1984) based on the science fiction fantasy novel by Frank Herbert. It had been acknowledged and speculated that Dune was one of George Lucas's influences behind the Star Wars saga and David Lynch had turned down the offer to direct Star Wars: Episode VI - Return of the Jedi (1983) to direct Dune. ", 42 | "\nReleased the same day as 20th Century Fox's Ferdinand (2017), similar to how Star Wars: Episode VII - The Force Awakens (2015) released the same day as 20th Century Fox's Alvin and the Chipmunks: The Road Chip (2015). ", 43 | "\nThe previous films in the series all took place at least a year after the previous film. This film takes up right where Star Wars: Episode VII - The Force Awakens (2015) left off, including a re-creation of the final scene and a funeral for Han Solo. ", 44 | "\nIt is rumored that Frank Oz, Ewan McGregor, and Hayden Christensen would reprise their roles as Yoda, Obi-Wan Kenobi and Anakin Skywalker respectively. ", 45 | "\nAn image of the toy box layout for Episode 8 toys leaked in early February. Poe and Finn look much the same, however Rey's hair is down, and it appears as if she has a scar above her left eye. It is unclear if the scar was a result of training, or if it is meant to be reminiscent of Anakin Skywalker's scar from episode 3. ", 46 | "\nMark Hamill let it slip that it was a great experience to play Luke Skywalker once again and how emotional it was during the films wrapping. This speculated that Luke would most likely die in the film as he (Hamill) did not say he would reprise the role for Episode IX. Hamill did later comment that it was not what he meant. ", 47 | "\nLiam Neeson has expressed interest in returning to the Star Wars franchise as Qui-Gon Jinn, either as a spin off solo movie, or a ghost in episode 8 or 9. ", 48 | "\nThe first Star Wars film which does not take place 3 - 4 years, not even a decade after the previous film. The film is a direct continuation of Star Wars: The Force Awakens (2015) and takes place shortly after Rey arrives on the island on the oceanic planet and finds Luke Skywalker and presents to him his old lightsaber. ", 49 | "\nAs with Episode VII, \"script leaks\" abound. So far the most prominent theory is that VIII will have some similarities to Episode V, the first sequel, but none as close as Episode VII had to Episode IV. ", 50 | "\nDirector Rian Johnsin has said that Luke Skywalker is the last Jedi. ", 51 | "\nDirector Rian Johnson admits that he moved Kylo Ren's facial injury. In Star Wars: The Force Awakens (2015), Rey slices his face with her light saber, giving him a laceration that goes across the bridge of his nose. In this film, the injury has scabbed over, but is now over his right eye. Johnson says a scab over his nose didn't look right. Ironically, the placement of the scar matches a similar injury on Ren's grandfather, Anakin Skywalker, in Star Wars: Revenge of the Sith (2005). " 52 | ], 53 | "cameo": [], 54 | "spoiler": [ 55 | "\nThe previous films in the series all took place at least a year after the previous film. This film takes up right where Star Wars: Episode VII - The Force Awakens (2015) left off, including a re-creation of the final scene and a funeral for Han Solo. ", 56 | "\nIt is rumored that Frank Oz, Ewan McGregor, and Hayden Christensen would reprise their roles as Yoda, Obi-Wan Kenobi and Anakin Skywalker respectively. ", 57 | "\nAn image of the toy box layout for Episode 8 toys leaked in early February. Poe and Finn look much the same, however Rey's hair is down, and it appears as if she has a scar above her left eye. It is unclear if the scar was a result of training, or if it is meant to be reminiscent of Anakin Skywalker's scar from episode 3. ", 58 | "\nMark Hamill let it slip that it was a great experience to play Luke Skywalker once again and how emotional it was during the films wrapping. This speculated that Luke would most likely die in the film as he (Hamill) did not say he would reprise the role for Episode IX. Hamill did later comment that it was not what he meant. ", 59 | "\nLiam Neeson has expressed interest in returning to the Star Wars franchise as Qui-Gon Jinn, either as a spin off solo movie, or a ghost in episode 8 or 9. ", 60 | "\nThe first Star Wars film which does not take place 3 - 4 years, not even a decade after the previous film. The film is a direct continuation of Star Wars: The Force Awakens (2015) and takes place shortly after Rey arrives on the island on the oceanic planet and finds Luke Skywalker and presents to him his old lightsaber. ", 61 | "\nAs with Episode VII, \"script leaks\" abound. So far the most prominent theory is that VIII will have some similarities to Episode V, the first sequel, but none as close as Episode VII had to Episode IV. ", 62 | "\nDirector Rian Johnsin has said that Luke Skywalker is the last Jedi. ", 63 | "\nDirector Rian Johnson admits that he moved Kylo Ren's facial injury. In Star Wars: The Force Awakens (2015), Rey slices his face with her light saber, giving him a laceration that goes across the bridge of his nose. In this film, the injury has scabbed over, but is now over his right eye. Johnson says a scab over his nose didn't look right. Ironically, the placement of the scar matches a similar injury on Ren's grandfather, Anakin Skywalker, in Star Wars: Revenge of the Sith (2005). " 64 | ] 65 | } -------------------------------------------------------------------------------- /src/assets/data/tt0076759.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Star Wars", 3 | "storyLine": "The Imperial Forces, under orders from cruel Darth Vader, hold Princess Leia hostage in their efforts to quell the rebellion against the Galactic Empire. Luke Skywalker and Han Solo, captain of the Millennium Falcon, work together with the companionable droid duo R2-D2 and C-3PO to rescue the beautiful princess, help the Rebel Alliance and restore freedom and justice to the Galaxy.", 4 | "imdbRating": "8.7", 5 | "releaseDate": "1978-01-30", 6 | "duration": "2h 1min", 7 | "awards": "Won 6 Oscars. Another 50 wins & 28 nominations." 8 | } -------------------------------------------------------------------------------- /src/assets/data/tt0080684.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Star Wars: Episode V - The Empire Strikes Back", 3 | "storyLine": "Luke Skywalker, Han Solo, Princess Leia and Chewbacca face attack by the Imperial forces and its AT-AT walkers on the ice planet Hoth. While Han and Leia escape in the Millennium Falcon, Luke travels to Dagobah in search of Yoda. Only with the Jedi master's help will Luke survive when the dark side of the Force beckons him into the ultimate duel with Darth Vader.", 4 | "imdbRating": "8.8", 5 | "releaseDate": "1980-07-21", 6 | "duration": "2h 4min", 7 | "awards": "Won 1 Oscar. Another 21 wins & 19 nominations" 8 | } -------------------------------------------------------------------------------- /src/assets/data/tt0086190.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Star Wars: Episode VI - Return of the Jedi", 3 | "storyLine": "Luke Skywalker battles horrible Jabba the Hut and cruel Darth Vader to save his comrades in the Rebel Alliance and triumph over the Galactic Empire. Han Solo and Princess Leia reaffirm their love and team with Chewbacca, Lando Calrissian, the Ewoks and the androids C-3PO and R2-D2 to aid in the disruption of the Dark Side and the defeat of the evil emperor.", 4 | "imdbRating": "8.4", 5 | "releaseDate": "1983-10-06", 6 | "duration": "2h 11min", 7 | "awards": "Nominated for 4 Oscars. Another 20 wins & 16 nominations." 8 | } -------------------------------------------------------------------------------- /src/assets/data/tt0120915.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Star Wars: Episode I - The Phantom Menace", 3 | "storyLine": "The evil Trade Federation, led by Nute Gunray is planning to take over the peaceful world of Naboo. Jedi Knights Qui-Gon Jinn and Obi-Wan Kenobi are sent to confront the leaders. But not everything goes to plan. The two Jedi escape, and along with their new Gungan friend, Jar Jar Binks head to Naboo to warn Queen Amidala, but droids have already started to capture Naboo and the Queen is not safe there. Eventually, they land on Tatooine, where they become friends with a young boy known as Anakin Skywalker. Qui-Gon is curious about the boy, and sees a bright future for him. The group must now find a way of getting to Coruscant and to finally solve this trade dispute, but there is someone else hiding in the shadows. Are the Sith really extinct? Is the Queen really who she says she is? And what's so special about this young boy?", 4 | "imdbRating": "6.5", 5 | "releaseDate": "1999-06-24", 6 | "duration": "2h 16min", 7 | "awards": "Nominated for 3 Oscars. Another 25 wins & 60 nominations." 8 | } -------------------------------------------------------------------------------- /src/assets/data/tt0121765.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Star Wars: Episode II - Attack of the Clones", 3 | "storyLine": "Ten years after the invasion of Naboo, the Galactic Republic is facing a Separatist movement and the former queen and now Senator Padmé Amidala travels to Coruscant to vote on a project to create an army to help the Jedi to protect the Republic. Upon arrival, she escapes from an attempt to kill her, and Obi-Wan Kenobi and his Padawan Anakin Skywalker are assigned to protect her. They chase the shape-shifter Zam Wessell but she is killed by a poisoned dart before revealing who hired her. The Jedi Council assigns Obi-Wan Kenobi to discover who has tried to kill Amidala and Anakin to protect her in Naboo. Obi-Wan discovers that the dart is from the planet Kamino, and he heads to the remote planet. He finds an army of clones that has been under production for years for the Republic and that the bounty hunter Jango Fett was the matrix for the clones. Meanwhile Anakin and Amidala fall in love with each other, and he has nightmarish visions of his mother.", 4 | "imdbRating": "6.6", 5 | "releaseDate": "2002-07-01", 6 | "duration": "2h 22min", 7 | "awards": "Nominated for 1 Oscar. Another 16 wins & 56 nominations." 8 | } -------------------------------------------------------------------------------- /src/assets/data/tt0121766.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Star Wars: Episode III - Revenge of the Sith", 3 | "storyLine": "Near the end of the Clone Wars, Darth Sidious has revealed himself and is ready to execute the last part of his plan to rule the galaxy. Sidious is ready for his new apprentice, Darth Vader, to step into action and kill the remaining Jedi. Vader, however, struggles to choose the dark side and save his wife or remain loyal to the Jedi order.", 4 | "imdbRating": "7.6", 5 | "releaseDate": "2005-05-19", 6 | "duration": "2h 20min", 7 | "awards": "Nominated for 1 Oscar. Another 25 wins & 56 nominations." 8 | } -------------------------------------------------------------------------------- /src/assets/data/tt2488496.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Star Wars: Episode VII - The Force Awakens", 3 | "storyLine": "30 years after the defeat of Darth Vader and the Empire, Rey, a scavenger from the planet Jakku, finds a BB-8 droid that knows the whereabouts of the long lost Luke Skywalker. Rey, as well as a rogue stormtrooper and two smugglers, are thrown into the middle of a battle between the Resistance and the daunting legions of the First Order.", 4 | "imdbRating": "8.1", 5 | "releaseDate": "2015-12-17", 6 | "duration": "2h 16min", 7 | "awards": "Nominated for 5 Oscars. Another 51 wins & 115 nominations." 8 | } -------------------------------------------------------------------------------- /src/assets/data/tt2527336.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Star Wars: The Last Jedi", 3 | "storyLine": "Having taken her first steps into a larger world in Star Wars: O Despertar da Força (2015), Rey continues her epic journey with Finn, Poe and Luke Skywalker in the next chapter of the saga.", 4 | "imdbRating": "", 5 | "releaseDate": "2017-12-14", 6 | "duration": "", 7 | "awards": "1 nomination." 8 | } -------------------------------------------------------------------------------- /src/assets/fonts/DistrictPro.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/src/assets/fonts/DistrictPro.otf -------------------------------------------------------------------------------- /src/assets/fonts/icomoon.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/src/assets/fonts/icomoon.eot -------------------------------------------------------------------------------- /src/assets/fonts/icomoon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Generated by IcoMoon 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/assets/fonts/icomoon.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/src/assets/fonts/icomoon.ttf -------------------------------------------------------------------------------- /src/assets/fonts/icomoon.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/src/assets/fonts/icomoon.woff -------------------------------------------------------------------------------- /src/assets/icon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/src/assets/icon/favicon.ico -------------------------------------------------------------------------------- /src/assets/img/avatar/darth_vader.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/src/assets/img/avatar/darth_vader.jpg -------------------------------------------------------------------------------- /src/assets/img/avatar/luke.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/src/assets/img/avatar/luke.jpg -------------------------------------------------------------------------------- /src/assets/img/covers/episode_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/src/assets/img/covers/episode_1.jpg -------------------------------------------------------------------------------- /src/assets/img/covers/episode_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/src/assets/img/covers/episode_2.jpg -------------------------------------------------------------------------------- /src/assets/img/covers/episode_3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/src/assets/img/covers/episode_3.jpg -------------------------------------------------------------------------------- /src/assets/img/covers/episode_4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/src/assets/img/covers/episode_4.jpg -------------------------------------------------------------------------------- /src/assets/img/covers/episode_5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/src/assets/img/covers/episode_5.jpg -------------------------------------------------------------------------------- /src/assets/img/covers/episode_6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/src/assets/img/covers/episode_6.jpg -------------------------------------------------------------------------------- /src/assets/img/covers/episode_7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/src/assets/img/covers/episode_7.jpg -------------------------------------------------------------------------------- /src/assets/img/covers/episode_8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/src/assets/img/covers/episode_8.jpg -------------------------------------------------------------------------------- /src/assets/img/galaxy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/src/assets/img/galaxy.jpg -------------------------------------------------------------------------------- /src/assets/img/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml 50 | 51 | 54 | 56 | 61 | 62 | 64 | 69 | 70 | 72 | 77 | 78 | 80 | 85 | 86 | 88 | 93 | 94 | 96 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /src/assets/img/logo/icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/src/assets/img/logo/icon-128x128.png -------------------------------------------------------------------------------- /src/assets/img/logo/icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/src/assets/img/logo/icon-144x144.png -------------------------------------------------------------------------------- /src/assets/img/logo/icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/src/assets/img/logo/icon-152x152.png -------------------------------------------------------------------------------- /src/assets/img/logo/icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/src/assets/img/logo/icon-192x192.png -------------------------------------------------------------------------------- /src/assets/img/logo/icon-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/src/assets/img/logo/icon-384x384.png -------------------------------------------------------------------------------- /src/assets/img/logo/icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/src/assets/img/logo/icon-512x512.png -------------------------------------------------------------------------------- /src/assets/img/logo/icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/src/assets/img/logo/icon-72x72.png -------------------------------------------------------------------------------- /src/assets/img/logo/icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/src/assets/img/logo/icon-96x96.png -------------------------------------------------------------------------------- /src/assets/img/scenes/episode_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/src/assets/img/scenes/episode_1.jpg -------------------------------------------------------------------------------- /src/assets/img/scenes/episode_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/src/assets/img/scenes/episode_2.jpg -------------------------------------------------------------------------------- /src/assets/img/scenes/episode_3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/src/assets/img/scenes/episode_3.jpg -------------------------------------------------------------------------------- /src/assets/img/scenes/episode_4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/src/assets/img/scenes/episode_4.jpg -------------------------------------------------------------------------------- /src/assets/img/scenes/episode_5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/src/assets/img/scenes/episode_5.jpg -------------------------------------------------------------------------------- /src/assets/img/scenes/episode_6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/src/assets/img/scenes/episode_6.jpg -------------------------------------------------------------------------------- /src/assets/img/scenes/episode_7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/src/assets/img/scenes/episode_7.jpg -------------------------------------------------------------------------------- /src/assets/img/scenes/episode_8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/src/assets/img/scenes/episode_8.jpg -------------------------------------------------------------------------------- /src/assets/maps/m1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/src/assets/maps/m1.png -------------------------------------------------------------------------------- /src/assets/maps/m2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/src/assets/maps/m2.png -------------------------------------------------------------------------------- /src/assets/maps/m3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/src/assets/maps/m3.png -------------------------------------------------------------------------------- /src/assets/maps/m4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/src/assets/maps/m4.png -------------------------------------------------------------------------------- /src/assets/maps/m5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/src/assets/maps/m5.png -------------------------------------------------------------------------------- /src/assets/maps/pin-dark-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/src/assets/maps/pin-dark-1.png -------------------------------------------------------------------------------- /src/assets/maps/pin-dark-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/src/assets/maps/pin-dark-2.png -------------------------------------------------------------------------------- /src/assets/maps/pin-dark-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/src/assets/maps/pin-dark-3.png -------------------------------------------------------------------------------- /src/assets/maps/pin-dark-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/src/assets/maps/pin-dark-4.png -------------------------------------------------------------------------------- /src/assets/maps/pin-dark-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/src/assets/maps/pin-dark-5.png -------------------------------------------------------------------------------- /src/assets/maps/pin-dark-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/src/assets/maps/pin-dark-6.png -------------------------------------------------------------------------------- /src/assets/maps/pin-dark-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/src/assets/maps/pin-dark-7.png -------------------------------------------------------------------------------- /src/assets/maps/pin-dark-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/src/assets/maps/pin-dark-8.png -------------------------------------------------------------------------------- /src/assets/maps/pin-light-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/src/assets/maps/pin-light-1.png -------------------------------------------------------------------------------- /src/assets/maps/pin-light-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/src/assets/maps/pin-light-2.png -------------------------------------------------------------------------------- /src/assets/maps/pin-light-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/src/assets/maps/pin-light-3.png -------------------------------------------------------------------------------- /src/assets/maps/pin-light-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/src/assets/maps/pin-light-4.png -------------------------------------------------------------------------------- /src/assets/maps/pin-light-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/src/assets/maps/pin-light-5.png -------------------------------------------------------------------------------- /src/assets/maps/pin-light-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/src/assets/maps/pin-light-6.png -------------------------------------------------------------------------------- /src/assets/maps/pin-light-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/src/assets/maps/pin-light-7.png -------------------------------------------------------------------------------- /src/assets/maps/pin-light-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/src/assets/maps/pin-light-8.png -------------------------------------------------------------------------------- /src/assets/maps/pin-light-9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yannbf/may-the-4th/0bee23ddbc26d7cd1c1e8db302d851aa5e034abb/src/assets/maps/pin-light-9.png -------------------------------------------------------------------------------- /src/components/overlaying-view/overlaying-view.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | 5 | 6 | 10 | 11 | {{ title }} 12 | 13 |
14 | 15 | 16 | -------------------------------------------------------------------------------- /src/components/overlaying-view/overlaying-view.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { IonicPageModule } from 'ionic-angular'; 3 | import { OverlayingViewComponent } from './overlaying-view'; 4 | 5 | @NgModule({ 6 | declarations: [ 7 | OverlayingViewComponent, 8 | ], 9 | imports: [ 10 | IonicPageModule.forChild(OverlayingViewComponent), 11 | ], 12 | exports: [ 13 | OverlayingViewComponent 14 | ] 15 | }) 16 | export class OverlayingViewComponentModule {} 17 | -------------------------------------------------------------------------------- /src/components/overlaying-view/overlaying-view.scss: -------------------------------------------------------------------------------- 1 | overlaying-view { 2 | width: 100%; 3 | height: 100%; 4 | position: absolute; 5 | z-index: 10 !important; 6 | 7 | .header-top { 8 | transition: all 1s; 9 | background-color: $lightside-blue; 10 | height: 13px; 11 | border-radius: 50% / 100%; 12 | border-bottom-left-radius: 0; 13 | border-bottom-right-radius: 0; 14 | box-shadow: 0px -4px 22px -8px rgba(0,0,0,0.75); 15 | 16 | &.active { 17 | border-radius: 0; 18 | } 19 | } 20 | 21 | ion-header .toolbar-title{ 22 | text-align: center; 23 | } 24 | 25 | ion-content { 26 | background: rgba(128, 128, 128, 0.6); 27 | } 28 | 29 | p { 30 | font-weight: bold !important; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/components/overlaying-view/overlaying-view.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, ElementRef, Renderer } from '@angular/core'; 2 | import { Platform, DomController } from 'ionic-angular'; 3 | 4 | @Component({ 5 | selector: 'overlaying-view', 6 | templateUrl: 'overlaying-view.html' 7 | }) 8 | export class OverlayingViewComponent { 9 | @Input('options') options: any; 10 | @Input('header') header: any; 11 | 12 | title: string = ''; 13 | handleHeight: number = 50; 14 | hasScrolledUp: boolean = false; 15 | headerElement: any; 16 | 17 | constructor(public element: ElementRef, 18 | public renderer: Renderer, 19 | public domCtrl: DomController, 20 | public platform: Platform) { 21 | } 22 | 23 | toggleScroll(){ 24 | if(this.hasScrolledUp){ 25 | this.scrollDown(); 26 | } else { 27 | this.scrollUp(); 28 | } 29 | } 30 | 31 | scrollUp(){ 32 | this.domCtrl.write(() => { 33 | this.renderer.setElementStyle(this.element.nativeElement, 'transition', 'top 0.5s'); 34 | this.renderer.setElementStyle(this.element.nativeElement, 'top', '0px'); 35 | }); 36 | 37 | this.hasScrolledUp = true; 38 | } 39 | 40 | scrollDown(){ 41 | this.domCtrl.write(() => { 42 | this.renderer.setElementStyle(this.element.nativeElement, 'transition', 'top 0.5s'); 43 | this.renderer.setElementStyle(this.element.nativeElement, 'top', this.platform.height() - this.handleHeight + 'px'); 44 | }); 45 | 46 | this.hasScrolledUp = false; 47 | } 48 | 49 | ngAfterViewInit() { 50 | this.title = this.options.title || ''; 51 | 52 | this.renderer.setElementStyle(this.element.nativeElement, 'top', this.platform.height() - this.handleHeight + 'px'); 53 | this.renderer.setElementStyle(this.element.nativeElement, 'padding-top', this.handleHeight + 'px'); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/components/timer/timer.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | {{ format(remainingDays) }} 6 | 7 | 8 | {{ format(remainingHours) }} 9 | 10 | 11 | {{ format(remainingMinutes) }} 12 | 13 | 14 | {{ format(remainingSeconds) }} 15 | 16 | 17 | 18 | 19 | DAYS 20 | 21 | 22 | HOURS 23 | 24 | 25 | MINUTES 26 | 27 | 28 | SECONDS 29 | 30 | 31 | 32 |

On theaters by December 15th, 2017

33 |
-------------------------------------------------------------------------------- /src/components/timer/timer.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; 2 | import { TimerComponent } from './timer'; 3 | import { CommonModule } from '@angular/common'; 4 | import { IonicModule } from 'ionic-angular'; 5 | 6 | @NgModule({ 7 | declarations: [ 8 | TimerComponent, 9 | ], 10 | imports: [ 11 | CommonModule, 12 | IonicModule 13 | ], 14 | schemas: [CUSTOM_ELEMENTS_SCHEMA], 15 | exports: [ 16 | TimerComponent 17 | ] 18 | }) 19 | export class TimerComponentModule {} 20 | -------------------------------------------------------------------------------- /src/components/timer/timer.scss: -------------------------------------------------------------------------------- 1 | #timer { 2 | color: white; 3 | ion-grid { 4 | text-align: center; 5 | } 6 | .numbers .col { 7 | font-size: x-large; 8 | font-family: "District Pro"; 9 | font-weight: bold; 10 | } 11 | .titles .col { 12 | font-family: "District Pro"; 13 | font-weight: normal; 14 | padding: 0; 15 | } 16 | 17 | h3 { 18 | color: white; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/components/timer/timer.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'timer', 5 | templateUrl: 'timer.html' 6 | }) 7 | export class TimerComponent { 8 | 9 | public date: any; 10 | public displayTime: any; 11 | public diffSeconds: any; 12 | public isOver: boolean; 13 | 14 | constructor() { 15 | this.date = new Date('2017/12/15'); 16 | } 17 | 18 | ngOnInit() { 19 | let tzoffset = ((new Date()).getTimezoneOffset() * 60000) //offset in milliseconds 20 | let now = (new Date(Date.now() - tzoffset - 60000)).toISOString().slice(0, -1); 21 | this.diffSeconds = (Date.parse(this.date) - Date.parse(now)) / 1000; 22 | 23 | if (this.diffSeconds > 0) { 24 | this.displayTime = this.getRemainingTime(this.diffSeconds); 25 | this.startTimer(); 26 | } 27 | } 28 | 29 | startTimer() { 30 | let intervalId = setInterval(() => { 31 | let tzoffset = ((new Date()).getTimezoneOffset() * 60000) //offset in milliseconds 32 | let now = (new Date(Date.now() - tzoffset - 60000)).toISOString().slice(0, -1); 33 | this.diffSeconds = (Date.parse(this.date) - Date.parse(now)) / 1000; 34 | //this.diffSeconds = (Date.parse(this.event.date) - Date.now()) / 1000; 35 | if (this.diffSeconds > 0) { 36 | this.displayTime = this.getRemainingTime(this.diffSeconds); 37 | } else { 38 | clearInterval(intervalId); 39 | } 40 | }, 1000); 41 | } 42 | 43 | remainingDays; 44 | remainingHours; 45 | remainingMinutes; 46 | remainingSeconds; 47 | 48 | getRemainingTime(t) { 49 | this.remainingDays = Math.floor(t / 86400); 50 | t -= this.remainingDays * 86400; 51 | 52 | this.remainingHours = Math.floor(t / 3600) % 24; 53 | t -= this.remainingHours * 3600; 54 | 55 | this.remainingMinutes = Math.floor(t / 60) % 60; 56 | t -= this.remainingMinutes * 60 57 | 58 | this.remainingSeconds = Math.floor(t % 60); 59 | } 60 | 61 | format(number){ 62 | return Array(Math.max(2 - String(number).length + 1, 0)).join('0') + number; 63 | } 64 | } -------------------------------------------------------------------------------- /src/declarations.d.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Declaration files are how the Typescript compiler knows about the type information(or shape) of an object. 3 | They're what make intellisense work and make Typescript know all about your code. 4 | 5 | A wildcard module is declared below to allow third party libraries to be used in an app even if they don't 6 | provide their own type declarations. 7 | 8 | To learn more about using third party libraries in an Ionic app, check out the docs here: 9 | http://ionicframework.com/docs/v2/resources/third-party-libs/ 10 | 11 | For more info on type definition files, check out the Typescript docs here: 12 | https://www.typescriptlang.org/docs/handbook/declaration-files/introduction.html 13 | */ 14 | declare module '*'; -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Star Warnic 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Star Warnic", 3 | "short_name": "Star Warnic", 4 | "theme_color": "#000000", 5 | "background_color": "#000000", 6 | "display": "standalone", 7 | "icons": [ 8 | { 9 | "src": "assets/img/logo/icon-72x72.png", 10 | "sizes": "72x72", 11 | "type": "image/png" 12 | }, 13 | { 14 | "src": "assets/img/logo/icon-96x96.png", 15 | "sizes": "96x96", 16 | "type": "image/png" 17 | }, 18 | { 19 | "src": "assets/img/logo/icon-128x128.png", 20 | "sizes": "128x128", 21 | "type": "image/png" 22 | }, 23 | { 24 | "src": "assets/img/logo/icon-144x144.png", 25 | "sizes": "144x144", 26 | "type": "image/png" 27 | }, 28 | { 29 | "src": "assets/img/logo/icon-152x152.png", 30 | "sizes": "152x152", 31 | "type": "image/png" 32 | }, 33 | { 34 | "src": "assets/img/logo/icon-192x192.png", 35 | "sizes": "192x192", 36 | "type": "image/png" 37 | }, 38 | { 39 | "src": "assets/img/logo/icon-384x384.png", 40 | "sizes": "384x384", 41 | "type": "image/png" 42 | }, 43 | { 44 | "src": "assets/img/logo/icon-512x512.png", 45 | "sizes": "512x512", 46 | "type": "image/png" 47 | } 48 | ], 49 | "splash_pages": null 50 | } 51 | -------------------------------------------------------------------------------- /src/pages/film-detail/film-detail.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 |
9 |
10 | {{ movieTitle }} 11 | {{ film.title }} 12 |
13 |
14 | 15 | 18 |
19 |
20 | 21 | 22 |
23 |
24 |
25 |
26 |

Release date

27 |

{{ film.release_date | date}}

28 |

Length

29 |

{{ filmData.duration }}

30 |

Plot

31 |

{{ filmData.storyLine }}

32 |

Awards

33 |

{{ filmData.awards }}

34 |
35 | IMDb 36 | {{ filmData.imdbRating }} 37 |
38 |
39 |
40 |
41 |

Characters in movie:

42 | 43 | 44 | 45 | 46 |

{{ character.name }}

47 |
48 | 49 | 50 | 52 | 53 |

Weight: {{ character.mass }} Kg

54 |

Gender: {{ character.gender }}

55 |

Birth year: {{ character.birth_year }}

56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | 64 | 65 |
66 | 67 |

{{trivia}}

68 |
69 |
70 |
-------------------------------------------------------------------------------- /src/pages/film-detail/film-detail.module.ts: -------------------------------------------------------------------------------- 1 | import { OverlayingViewComponentModule } from '../../components/overlaying-view/overlaying-view.module'; 2 | import { TimerComponentModule } from '../../components/timer/timer.module'; 3 | import { SharedModule } from '../../app/shared.module'; 4 | import { NgModule } from '@angular/core'; 5 | import { IonicPageModule } from 'ionic-angular'; 6 | import { FilmDetailPage } from './film-detail'; 7 | 8 | @NgModule({ 9 | declarations: [ 10 | FilmDetailPage, 11 | ], 12 | imports: [ 13 | IonicPageModule.forChild(FilmDetailPage), 14 | TimerComponentModule, 15 | OverlayingViewComponentModule, 16 | SharedModule, 17 | ], 18 | exports: [ 19 | FilmDetailPage 20 | ] 21 | }) 22 | export class FilmDetailPageModule {} 23 | -------------------------------------------------------------------------------- /src/pages/film-detail/film-detail.scss: -------------------------------------------------------------------------------- 1 | page-film-detail { 2 | .toolbar button.bar-button span, .toolbar-title { 3 | color: white; 4 | } 5 | 6 | .scroll-content { 7 | padding: 0 !important; 8 | } 9 | 10 | overlaying-view .scroll-content { 11 | padding: 16px !important; 12 | margin-top: 0 !important; 13 | } 14 | 15 | overlaying-view { 16 | transition: 1s all; 17 | transform: translateY(50px); 18 | } 19 | 20 | .cover { 21 | position: relative; 22 | height: 70vh; 23 | background-size: cover !important; 24 | background-repeat: no-repeat !important; 25 | background-position: 50% 50% !important; 26 | clip-path: polygon(0 0, 100% 0, 100% 85%, 0 100%); 27 | } 28 | 29 | .cover::after { 30 | content: ''; 31 | display: block; 32 | position: relative; 33 | background-image: linear-gradient(to bottom, transparent 50%, black 100%); 34 | width: 100%; 35 | height: 70vh; 36 | } 37 | 38 | .footer-btn { 39 | width: 100%; 40 | min-height: 4.85rem; 41 | border-radius: 0; 42 | margin: 0; 43 | font-size: 1.2em; 44 | font-weight: bold; 45 | text-align: center; 46 | } 47 | 48 | .imdb-info { 49 | cursor: pointer; 50 | background: #ffd700; 51 | padding: 6px; 52 | color: #333; 53 | font-weight: bold; 54 | border-radius: 4px; 55 | } 56 | 57 | .imdb-rating { 58 | font-size: 18px; 59 | font-weight: bold; 60 | } 61 | 62 | .trailer-icon { 63 | color: black; 64 | font-weight: bold; 65 | background : $darkside-red; 66 | position : absolute; 67 | right : 5%; 68 | top : 55%; 69 | height : 70px; 70 | width : 70px; 71 | ion-icon { 72 | padding-left: 4px; 73 | font-size: 26px; 74 | color: white; 75 | } 76 | } 77 | 78 | .cover .title { 79 | position: absolute; 80 | bottom: 10%; 81 | padding-left: 1rem; 82 | color: white; 83 | z-index: 2; 84 | 85 | .main-title { 86 | display: block; 87 | font-size: 3rem; 88 | margin-bottom: .8rem; 89 | } 90 | 91 | .subtitle { 92 | display: block; 93 | font-size: 1.7rem; 94 | } 95 | } 96 | 97 | ion-content.galaxy .scroll-content{ 98 | background: url('../assets/img/galaxy.jpg'); 99 | } 100 | 101 | .countdown { 102 | height: 50vh; 103 | text-align: center; 104 | ion-icon::before { 105 | color: rgba(232,232,232,.8); 106 | font-size: 186px !important; 107 | } 108 | } 109 | 110 | .characters { 111 | margin-bottom: 8rem; 112 | } 113 | 114 | .character-slides { 115 | height: 60vh; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/pages/film-detail/film-detail.ts: -------------------------------------------------------------------------------- 1 | import { RomanizePipe } from '../../pipes/romanize/romanize'; 2 | import { SwapiProvider } from '../../providers/swapi/swapi'; 3 | import { Component, ViewChild, Renderer } from '@angular/core'; 4 | import { 5 | Content, 6 | IonicPage, 7 | LoadingController, 8 | ModalController, 9 | NavController, 10 | NavParams, 11 | Platform, 12 | App 13 | } from 'ionic-angular'; 14 | import { InAppBrowser } from '@ionic-native/in-app-browser'; 15 | import { YoutubeVideoPlayer } from '@ionic-native/youtube-video-player'; 16 | import { MovieInfoProvider } from '../../providers/movie-info/movie-info'; 17 | import { AVATARS } from './avatars'; 18 | 19 | @IonicPage() 20 | @Component({ 21 | selector: 'page-film-detail', 22 | templateUrl: 'film-detail.html', 23 | }) 24 | export class FilmDetailPage { 25 | 26 | film : any; 27 | movieTitle : string; 28 | image : any; 29 | filmData : any = {}; 30 | characters = []; 31 | triviaData = []; 32 | @ViewChild('view') view; 33 | @ViewChild('movieCharacters') movieCharacters; 34 | @ViewChild(Content) content: Content; 35 | 36 | movieData = [ 37 | { 38 | imdb: 'tt0120915', 39 | name: 'Episode I: The Phantom Menace', 40 | id: 'bD7bpG-zDJQ', 41 | },{ 42 | imdb: 'tt0121765', 43 | name: 'Episode II: Attack of the Clones', 44 | id: 'CO2OLQ2kiq8' 45 | },{ 46 | imdb: 'tt0121766', 47 | name: 'Episode III: Revenge of the Sith ', 48 | id: '5UnjrG_N8hU', 49 | },{ 50 | imdb: 'tt0076759', 51 | name: 'Episode IV: A New Hope', 52 | id: 'vZ734NWnAHA', 53 | },{ 54 | imdb: 'tt0080684', 55 | name: 'Episode V: The Empire Strikes Back', 56 | id: 'JNwNXF9Y6kY', 57 | },{ 58 | imdb: 'tt0086190', 59 | name: 'Episode VI - Return Of The Jedi', 60 | id: 'CsDwpF3uiZI', 61 | },{ 62 | imdb: 'tt2488496', 63 | name: 'Episode VII - The Force Awakens', 64 | id: 'sGbxmsDFVnE', 65 | }, { 66 | imdb: 'tt2527336', 67 | name: 'Episode VIII - The Last Jedi', 68 | id: 'zB4I68XVPzQ' 69 | } 70 | ] 71 | 72 | viewOptions = { 73 | title: 'Did you know?', 74 | handleHeight: 50, 75 | thresholdFromBottom: 200, 76 | thresholdFromTop: 200, 77 | bounceBack: true 78 | }; 79 | 80 | constructor(public navCtrl: NavController, 81 | public navParams: NavParams, 82 | public swapi: SwapiProvider, 83 | public movieInfo: MovieInfoProvider, 84 | public iab: InAppBrowser, 85 | private youtube: YoutubeVideoPlayer, 86 | public loadingCtrl: LoadingController, 87 | public renderer: Renderer, 88 | public modalCtrl: ModalController, 89 | private platform: Platform, 90 | private app: App) { 91 | this.film = navParams.data; 92 | } 93 | 94 | onScrollEnd($event){ 95 | if( $event.scrollTop + this.platform.height() >= $event.scrollHeight) { 96 | this.renderer.setElementStyle(this.view.element.nativeElement, 'transition', 'all 1s'); 97 | this.renderer.setElementStyle(this.view.element.nativeElement, 'transform', 'translateY(0)'); 98 | } else if ($event.scrollTop <= 100) { 99 | this.renderer.setElementStyle(this.view.element.nativeElement, 'transition', 'all 1s'); 100 | this.renderer.setElementStyle(this.view.element.nativeElement, 'transform', 'translateY(56px)'); 101 | } 102 | } 103 | 104 | ngAfterViewInit() { 105 | if(this.film){ 106 | this.image = `assets/img/covers/episode_${this.film.episode_id}.jpg`; 107 | this.movieTitle = 'Episode ' + new RomanizePipe().transform(this.film.episode_id); 108 | 109 | this.loadCharacters(); 110 | this.loadFilmData(); 111 | } 112 | } 113 | 114 | adjustCardSizes() { 115 | var elements = document.getElementsByClassName('sw-character'); 116 | 117 | var elementHeights = Array.prototype.map.call(elements, (el) => { 118 | return el.clientHeight; 119 | }); 120 | 121 | var maxHeight = Math.max(...elementHeights) 122 | 123 | Array.prototype.forEach.call(elements, (el) => el.style.height = `${maxHeight}px`); 124 | } 125 | 126 | loadFilmData() { 127 | if(this.film.episode_id != 8){ 128 | let id = this.movieData[this.film.episode_id-1].imdb; 129 | let loading = this.loadingCtrl.create({ 130 | content: 'Loading...' 131 | }); 132 | loading.present(); 133 | this.movieInfo.getMovieInfo(id).subscribe(data => { 134 | this.filmData = data; 135 | loading.dismiss(); 136 | }); 137 | this.movieInfo.getTrivia(id).subscribe(data => { 138 | this.triviaData = data; 139 | }); 140 | } 141 | } 142 | 143 | loadCharacters() { 144 | if(this.film){ 145 | for(let i in this.film.characters) { 146 | let character = this.film.characters[i].split('/')[5]; 147 | this.swapi.getPerson(character).subscribe(characterData => { 148 | characterData.photo = this.getAvatar(characterData); 149 | this.characters.push(characterData); 150 | }); 151 | } 152 | } 153 | } 154 | 155 | openIMDB() { 156 | let id = this.movieData[this.film.episode_id-1].imdb; 157 | let url = 'https://www.imdb.com/title/' + id; 158 | this.openOnBrowser(url); 159 | } 160 | 161 | openOnBrowser(url){ 162 | let browser = this.iab.create(url); 163 | browser.show(); 164 | } 165 | 166 | getAvatar(character) { 167 | let index = character.url.split('/')[5]; 168 | index = parseInt(index) - 1; 169 | return AVATARS[index] ? AVATARS[index].photo : ''; 170 | } 171 | 172 | openTrailer () { 173 | let id = this.movieData[this.film.episode_id-1].id; 174 | let modal = this.modalCtrl.create('YoutubeModalPage', {id: id}); 175 | // let url = `https://www.youtube.com/watch?v=${id}`; 176 | modal.present(); 177 | // if(this.platform.is('cordova')){ 178 | // this.youtube.openVideo(id); 179 | // } else { 180 | // this.openOnBrowser(url); 181 | // } 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/pages/home/home.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | Home 7 | 8 | 9 | 10 | 11 |
14 |
15 |

{{ film.title }}

16 |
17 |
18 | {{ film.episode_id | romanize }} 19 |
20 |
21 |
22 | -------------------------------------------------------------------------------- /src/pages/home/home.module.ts: -------------------------------------------------------------------------------- 1 | import { SharedModule } from '../../app/shared.module'; 2 | import { HomePage } from './home'; 3 | import { NgModule } from '@angular/core'; 4 | import { IonicPageModule } from 'ionic-angular'; 5 | 6 | @NgModule({ 7 | declarations: [HomePage], 8 | imports: [ 9 | IonicPageModule.forChild(HomePage), 10 | SharedModule, 11 | ] 12 | }) 13 | 14 | export class HomePageModule { }; 15 | -------------------------------------------------------------------------------- /src/pages/home/home.scss: -------------------------------------------------------------------------------- 1 | page-home { 2 | .movie { 3 | cursor: pointer; 4 | height: 30vh; 5 | background: #333; 6 | color: white; 7 | position: relative; 8 | background-size: cover !important; 9 | background-repeat: no-repeat !important; 10 | background-position: 50% 50% !important; 11 | 12 | .title, .episode, .date { 13 | position : absolute; 14 | z-index: 3; 15 | } 16 | 17 | .title { 18 | h1 { 19 | font-weight: normal !important; 20 | } 21 | left: 3%; 22 | bottom : 5%; 23 | } 24 | 25 | .episode { 26 | top : 0; 27 | right : 3%; 28 | color: #ffe405; 29 | font-size: 9rem; 30 | text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black; 31 | span { 32 | font-family: serif !important; 33 | } 34 | } 35 | } 36 | 37 | .movie::after { 38 | content: ''; 39 | display: block; 40 | position: relative; 41 | background-image: linear-gradient(to bottom, transparent 20%, black 100%); 42 | // background: transparentize(black, 0.9); 43 | width: 100%; 44 | height: 30vh; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/pages/home/home.ts: -------------------------------------------------------------------------------- 1 | import { SwapiProvider } from '../../providers/swapi/swapi'; 2 | import { Component } from '@angular/core'; 3 | import { IonicPage, MenuController, NavController } from 'ionic-angular'; 4 | 5 | @IonicPage() 6 | @Component({ 7 | selector: 'page-home', 8 | templateUrl: 'home.html' 9 | }) 10 | export class HomePage { 11 | 12 | films = []; 13 | last = { 14 | episode_id: 8, 15 | title: 'The last Jedi' 16 | } 17 | 18 | constructor(public navCtrl: NavController, 19 | public swapi: SwapiProvider, 20 | public menu: MenuController) { 21 | menu.swipeEnable(true, 'menu'); 22 | this.loadData(); 23 | } 24 | 25 | loadData() { 26 | this.swapi.getFilms().subscribe(data => { 27 | this.films = data.results; 28 | this.films.push(this.last); 29 | }); 30 | } 31 | 32 | viewFilmDetail(film) { 33 | this.navCtrl.push('FilmDetailPage', film); 34 | } 35 | 36 | getCover(id){ 37 | return `url(assets/img/scenes/episode_${id}.jpg)`; 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/pages/list/list.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | List 7 | 8 | 9 | 10 | 11 | 12 | 19 | 20 |
21 | You navigated here from {{selectedItem.title}} 22 |
23 |
24 | -------------------------------------------------------------------------------- /src/pages/list/list.module.ts: -------------------------------------------------------------------------------- 1 | import { ListPage } from './list'; 2 | import { NgModule } from '@angular/core'; 3 | import { IonicPageModule } from 'ionic-angular'; 4 | 5 | @NgModule({ 6 | declarations: [ 7 | ListPage, 8 | ], 9 | imports: [ 10 | IonicPageModule.forChild(ListPage), 11 | ], 12 | exports: [ 13 | ListPage 14 | ] 15 | }) 16 | 17 | export class ListPageModule { }; 18 | -------------------------------------------------------------------------------- /src/pages/list/list.scss: -------------------------------------------------------------------------------- 1 | page-list { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /src/pages/list/list.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { NavController, NavParams, IonicPage } from 'ionic-angular'; 3 | 4 | @IonicPage() 5 | @Component({ 6 | selector: 'page-list', 7 | templateUrl: 'list.html' 8 | }) 9 | export class ListPage { 10 | selectedItem: any; 11 | icons: string[]; 12 | items: Array<{title: string, note: string, icon: string}>; 13 | 14 | constructor(public navCtrl: NavController, public navParams: NavParams) { 15 | // If we navigated to this page, we will have an item available as a nav param 16 | this.selectedItem = navParams.get('item'); 17 | 18 | // Let's populate this page with some filler content for funzies 19 | this.icons = ['flask', 'wifi', 'beer', 'football', 'basketball', 'paper-plane', 20 | 'american-football', 'boat', 'bluetooth', 'build']; 21 | 22 | this.items = []; 23 | for (let i = 1; i < 11; i++) { 24 | this.items.push({ 25 | title: 'Item ' + i, 26 | note: 'This is item #' + i, 27 | icon: this.icons[Math.floor(Math.random() * this.icons.length)] 28 | }); 29 | } 30 | } 31 | 32 | itemTapped(event, item) { 33 | // That's right, we're pushing to ourselves! 34 | this.navCtrl.push(ListPage, { 35 | item: item 36 | }); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/pages/menu/menu.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |
6 |
7 |
8 | 9 | 10 | 11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | 21 | 22 | 23 |
24 |
25 |
26 | 27 | 34 | 40 | 41 | Jedi mode 42 | 43 | 44 | 45 |
46 |
47 | 48 | 49 |
-------------------------------------------------------------------------------- /src/pages/menu/menu.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { IonicPageModule } from 'ionic-angular'; 3 | import { MenuPage } from './menu'; 4 | 5 | @NgModule({ 6 | declarations: [ 7 | MenuPage, 8 | ], 9 | imports: [ 10 | IonicPageModule.forChild(MenuPage), 11 | ], 12 | exports: [ 13 | MenuPage 14 | ] 15 | }) 16 | export class MenuPageModule {} 17 | -------------------------------------------------------------------------------- /src/pages/menu/menu.scss: -------------------------------------------------------------------------------- 1 | page-menu { 2 | 3 | ion-menu .scroll-content { 4 | overflow: hidden; 5 | } 6 | 7 | #custom-overlay { 8 | background: url('../assets/img/galaxy.jpg'); 9 | background-size: cover; 10 | position: fixed; 11 | top: 0; 12 | right: 0; 13 | bottom: 0; 14 | left: 0; 15 | z-index: 1000000; 16 | width: 100%; 17 | background-color:transparent; 18 | } 19 | 20 | button.item.item-block { 21 | height: 8rem; 22 | } 23 | 24 | ion-avatar img { 25 | display: block; 26 | margin-top: 12px !important; 27 | margin-bottom: 12px !important; 28 | margin: 0 auto; 29 | border-radius: 50%; 30 | width: 120px; 31 | padding: 5px; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/pages/menu/menu.ts: -------------------------------------------------------------------------------- 1 | import { FirebaseDataProvider } from '../../providers/firebase-data/firebase-data'; 2 | import { IonicPage, Menu, Nav, NavController, Platform } from 'ionic-angular'; 3 | import { AudioService } from '../../providers/audio-service/audio-service'; 4 | import { MotionProvider } from '../../providers/motion/motion'; 5 | import { AlertService } from '../../providers/alert/alert'; 6 | import { SplashScreen } from '@ionic-native/splash-screen'; 7 | import { Geolocation } from '@ionic-native/geolocation'; 8 | import { Flashlight } from '@ionic-native/flashlight'; 9 | import { Component, ViewChild } from '@angular/core'; 10 | import { AppState } from '../../app/app.global'; 11 | import { Storage } from '@ionic/storage'; 12 | import { Subject } from 'rxjs'; 13 | 14 | @IonicPage({ 15 | segment: 'menu' 16 | }) 17 | @Component({ 18 | selector: 'page-menu', 19 | templateUrl: 'menu.html', 20 | }) 21 | 22 | export class MenuPage { 23 | @ViewChild('content') content: Nav; 24 | @ViewChild(Menu) menu: Menu; 25 | 26 | rootPage: any = 'HomePage'; 27 | activePage = new Subject(); 28 | 29 | splash = false; 30 | fade = false; 31 | jediMode = false; 32 | side = 'light'; 33 | onMobile = false; 34 | showContent = true; 35 | 36 | pages: Array<{ title: string, component: any, active: boolean, icon: string }>; 37 | 38 | public menuRoot = 'HomePage'; 39 | constructor(public nav: NavController, 40 | public global: AppState, 41 | public splashScreen: SplashScreen, 42 | public audioCtrl: AudioService, 43 | public flashlight: Flashlight, 44 | public firebaseData: FirebaseDataProvider, 45 | public alertCtrl: AlertService, 46 | public storage: Storage, 47 | public geolocation: Geolocation, 48 | public motionCtrl: MotionProvider, 49 | public platform: Platform) { 50 | this.initialize(); 51 | } 52 | 53 | initialize() { 54 | this.initPages(); 55 | this.onMobile = this.platform.is('cordova'); 56 | // this.onMobile = navigator.userAgent.includes('Android') || navigator.userAgent.includes('iPhone'); 57 | 58 | this.storage.get('splashInfo').then(data => { 59 | if(data) { 60 | var now = new Date(); 61 | var difference = now.getTime() - data.getTime(); 62 | var minutesDifference = Math.round(difference / 60000); 63 | 64 | if(minutesDifference >= 3) { 65 | this.showSplash(); 66 | } 67 | } else { 68 | this.showSplash(); 69 | } 70 | }); 71 | } 72 | 73 | showSplash() { 74 | this.platform.ready().then(() => { 75 | this.audioCtrl.playIntro(); 76 | }); 77 | 78 | this.splash = true; 79 | this.showContent = false; 80 | setTimeout(() => this.showContent = true, 3000); 81 | setTimeout(() => this.fade = true, 7000); 82 | setTimeout(() => this.splash = false, 7800); 83 | this.storage.set('splashInfo', new Date()); 84 | } 85 | 86 | initPages() { 87 | this.pages = [ 88 | { title: 'Movies', component: 'HomePage', active: true, icon: 'sw-logo' }, 89 | { title: 'Force map', component: 'WorldMapPage', active: false, icon: 'sw-death-star' }, 90 | ]; 91 | 92 | this.activePage.subscribe((selectedPage: any) => { 93 | this.pages.map(page => { 94 | page.active = page.title === selectedPage.title; 95 | }); 96 | }); 97 | } 98 | 99 | openPage(page) { 100 | // Reset the content nav to have just this page 101 | // we wouldn't want the back button to show in this scenario 102 | this.content.setRoot(page.component); 103 | this.activePage.next(page); 104 | } 105 | 106 | switchSides(){ 107 | if(this.side === 'dark'){ 108 | this.side = 'light'; 109 | } else { 110 | this.side = 'dark'; 111 | } 112 | this.setSide(this.side); 113 | } 114 | 115 | setSide(side){ 116 | this.global.set('side', side); 117 | this.firebaseData.setSide(side); 118 | this.audioCtrl.play('turnLightSaberOn'); 119 | } 120 | 121 | toggleJediMode(){ 122 | if(this.jediMode){ 123 | if (this.platform.is('cordova')) { 124 | this.flashlight.switchOn(); 125 | } 126 | this.audioCtrl.play('jediOn'); 127 | this.motionCtrl.startWatchingSwings(); 128 | } else { 129 | if (this.platform.is('cordova')) { 130 | this.flashlight.switchOff(); 131 | } 132 | this.audioCtrl.play('jediOff'); 133 | this.motionCtrl.stopWatchingSwings(); 134 | } 135 | } 136 | 137 | } -------------------------------------------------------------------------------- /src/pages/menu/shift-transition.ts: -------------------------------------------------------------------------------- 1 | import { MenuType, Menu, Platform, Animation } from 'ionic-angular'; 2 | 3 | /* 4 | * AMAZING idea by Paul Vetter: 5 | * https://github.com/EbilPanda 6 | */ 7 | 8 | export class MenuShiftType extends MenuType { 9 | constructor(private menu: Menu, private plt: Platform) { 10 | super(plt); 11 | 12 | let contentOpenedX: string, menuClosedX: string, menuOpenedX: string; 13 | 14 | contentOpenedX = menu.width() + 'px'; 15 | menuOpenedX = '0px'; 16 | menuClosedX = -menu.width() + 'px'; 17 | 18 | let menuAni = new Animation(plt, menu.getMenuElement()); 19 | menuAni.fromTo('translateX', menuClosedX, menuOpenedX); 20 | this.ani.add(menuAni); 21 | 22 | let contentApi = new Animation(plt, menu.getContentElement()); 23 | let scale = 0.85; 24 | contentApi.fromTo('translateX', '0px', contentOpenedX); 25 | contentApi.fromTo('scale', '1', `${scale}`); 26 | contentApi.beforeAddClass('opening'); 27 | contentApi.afterRemoveClass('opening'); 28 | this.ani.add(contentApi); 29 | } 30 | } -------------------------------------------------------------------------------- /src/pages/world-map/world-map.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | Force map 7 | 8 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |

Please connect to the Internet...

18 |
19 | 20 |
21 |
22 | 23 | 24 |
25 | Dark: {{ darkSideCount }} 26 | Light: {{ lightSideCount }} 27 |
28 |
29 |
-------------------------------------------------------------------------------- /src/pages/world-map/world-map.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { IonicPageModule } from 'ionic-angular'; 3 | import { WorldMapPage } from './world-map'; 4 | 5 | @NgModule({ 6 | declarations: [ 7 | WorldMapPage, 8 | ], 9 | imports: [ 10 | IonicPageModule.forChild(WorldMapPage), 11 | ], 12 | exports: [ 13 | WorldMapPage 14 | ] 15 | }) 16 | export class WorldMapPageModule {} 17 | -------------------------------------------------------------------------------- /src/pages/world-map/world-map.scss: -------------------------------------------------------------------------------- 1 | page-world-map { 2 | #please-connect { 3 | display: none; 4 | position: absolute; 5 | background-color: #000; 6 | opacity: 0.5; 7 | width: 100%; 8 | height: 100%; 9 | z-index: 1; 10 | } 11 | #please-connect p { 12 | color: #fff; 13 | font-weight: bold; 14 | text-align: center; 15 | position: relative; 16 | font-size: 1.6em; 17 | top: 30%; 18 | } 19 | .scroll { 20 | height: 100%; 21 | } 22 | #map { 23 | width: 100%; 24 | height: 100%; 25 | color: #333; 26 | } 27 | .gmnoprint { 28 | display: none; 29 | } 30 | .gm-style-cc { 31 | display: block; 32 | } 33 | #lightsaber-container { 34 | width: 100%; 35 | padding: 20px; 36 | position: absolute; 37 | bottom: 7%; 38 | } 39 | .lightsaber { 40 | height: 8px; 41 | background: white; 42 | border-radius: 2px 2px 2px 2px; 43 | transition: all 1s; 44 | } 45 | .info span { 46 | color: white; 47 | font-weight: bold; 48 | position: absolute; 49 | bottom: 1%; 50 | text-shadow: 1px 1px 1px #000; 51 | &.float-left { 52 | left: 2%; 53 | } 54 | &.float-right { 55 | right: 2%; 56 | } 57 | } 58 | .info { 59 | height: 5%; 60 | background: black; 61 | // background-image: radial-gradient(circle farthest-corner at center, #a966f2 0%, rgb(0, 0, 0) 100%); 62 | } 63 | .dark { 64 | float: right; 65 | box-shadow: 0 0 5px white, 0 0 8px white, 0 0 12px blue, 0 0 15px blue, 0 0 25px blue; 66 | } 67 | .light { 68 | float: left; 69 | box-shadow: 0 0 5px white, 0 0 8px white, 0 0 12px red, 0 0 15px rgba(255, 0, 0, 0.13), 0 0 25px red; 70 | } 71 | } 72 | 73 | -------------------------------------------------------------------------------- /src/pages/world-map/world-map.ts: -------------------------------------------------------------------------------- 1 | import { AppState } from '../../app/app.global'; 2 | import { ConnectivityProvider } from '../../providers/connectivity/connectivity'; 3 | import { FirebaseDataProvider } from '../../providers/firebase-data/firebase-data'; 4 | import { Component, ViewChild, ElementRef, Renderer } from '@angular/core'; 5 | import { IonicPage, MenuController, NavParams, Platform } from 'ionic-angular'; 6 | import { Geolocation } from '@ionic-native/geolocation'; 7 | import { Storage } from '@ionic/storage'; 8 | import { AlertService } from '../../providers/alert/alert'; 9 | 10 | declare var google: any; 11 | 12 | @IonicPage() 13 | @Component({ 14 | selector: 'page-world-map', 15 | templateUrl: 'world-map.html', 16 | }) 17 | export class WorldMapPage { 18 | 19 | // @ViewChild('map') mapElement: ElementRef; 20 | @ViewChild('pleaseConnect') pleaseConnect: ElementRef; 21 | 22 | map: any; 23 | mapElement: any; 24 | mapInitialised: boolean = false; 25 | markers = new Map(); 26 | mapsUrl = 'https://maps.google.com/maps/api/'; 27 | apiKey: string = "AIzaSyDDBFb61Xgfw8RG28BHFB6lMm4D3de-A1U"; 28 | myLocation: any; 29 | users = []; 30 | lightSideCount: any; 31 | darkSideCount: any; 32 | 33 | constructor(public navParams: NavParams, 34 | public renderer: Renderer, 35 | public global: AppState, 36 | public platform: Platform, 37 | public connectivityService: ConnectivityProvider, 38 | public menu: MenuController, 39 | public geolocation: Geolocation, 40 | public firebaseData: FirebaseDataProvider, 41 | public alertCtrl: AlertService, 42 | public storage: Storage,) { 43 | menu.swipeEnable(false, 'menu'); 44 | this.loadGoogleMaps(); 45 | } 46 | 47 | ionViewDidLoad() { 48 | this.markers = new Map(); 49 | this.users = []; 50 | this.loadGoogleMaps(); 51 | } 52 | 53 | isFirstAccess(){ 54 | return this.storage.get('firstAccess').then((data) => { 55 | if(data !== null){ 56 | return data; 57 | } else { 58 | return true; 59 | } 60 | }); 61 | } 62 | 63 | requestUserInfo(){ 64 | this.isFirstAccess().then(isFirst => { 65 | if(isFirst) { 66 | this.alertCtrl.getUserConsent().then(yes => { 67 | if(yes){ 68 | this.getUserInfo(); 69 | } 70 | this.storage.set('firstAccess', false); 71 | }); 72 | } 73 | }); 74 | } 75 | 76 | getUserInfo() { 77 | this.alertCtrl.getUserName().then( name => { 78 | this.geolocation.getCurrentPosition().then(position => { 79 | let user = { 80 | name: name, 81 | side: this.global.get('side'), 82 | uuid: this.global.get('uuid'), 83 | icon: (Math.floor(Math.random() * 6) + 1), 84 | position: { 85 | lat: position.coords.latitude, 86 | lng: position.coords.longitude 87 | } 88 | } 89 | this.firebaseData.writeUserData(user); 90 | }, error => { 91 | console.log('geolocation error'); 92 | }); 93 | }); 94 | } 95 | 96 | loadGoogleMaps() { 97 | this.addConnectivityListeners(); 98 | if (typeof google == "undefined" || typeof google.maps == "undefined") { 99 | this.disableMap(); 100 | 101 | if (this.connectivityService.isOnline()) { 102 | //Load the SDK 103 | window['mapInit'] = () => { 104 | this.initMap(); 105 | this.enableMap(); 106 | } 107 | 108 | if(!document.body.children['googleMaps']){ 109 | let script = document.createElement("script"); 110 | script.id = "googleMaps"; 111 | script.src = `${this.mapsUrl}js?v=3&key=${this.apiKey}&callback=mapInit`; 112 | 113 | document.body.appendChild(script); 114 | } 115 | } 116 | } 117 | else { 118 | if (this.connectivityService.isOnline()) { 119 | this.initMap(); 120 | this.enableMap(); 121 | } 122 | else { 123 | this.disableMap(); 124 | } 125 | } 126 | } 127 | 128 | initMap() { 129 | this.mapInitialised = true; 130 | 131 | let darkMapStyles = [ { "elementType": "geometry", "stylers": [ { "color": "#212121" } ] }, { "elementType": "labels.icon", "stylers": [ { "visibility": "off" } ] }, { "elementType": "labels.text.fill", "stylers": [ { "color": "#757575" } ] }, { "elementType": "labels.text.stroke", "stylers": [ { "color": "#212121" } ] }, { "featureType": "administrative", "elementType": "geometry", "stylers": [ { "color": "#757575" } ] }, { "featureType": "administrative.country", "elementType": "labels.text.fill", "stylers": [ { "color": "#9e9e9e" } ] }, { "featureType": "administrative.land_parcel", "stylers": [ { "visibility": "off" } ] }, { "featureType": "administrative.locality", "elementType": "labels.text.fill", "stylers": [ { "color": "#bdbdbd" } ] }, { "featureType": "poi", "elementType": "labels.text.fill", "stylers": [ { "color": "#757575" } ] }, { "featureType": "poi.business", "stylers": [ { "visibility": "off" } ] }, { "featureType": "poi.park", "elementType": "geometry", "stylers": [ { "color": "#181818" } ] }, { "featureType": "poi.park", "elementType": "labels.text", "stylers": [ { "visibility": "off" } ] }, { "featureType": "poi.park", "elementType": "labels.text.fill", "stylers": [ { "color": "#616161" } ] }, { "featureType": "poi.park", "elementType": "labels.text.stroke", "stylers": [ { "color": "#1b1b1b" } ] }, { "featureType": "road", "elementType": "geometry.fill", "stylers": [ { "color": "#2c2c2c" } ] }, { "featureType": "road", "elementType": "labels.text.fill", "stylers": [ { "color": "#8a8a8a" } ] }, { "featureType": "road.arterial", "elementType": "geometry", "stylers": [ { "color": "#373737" } ] }, { "featureType": "road.arterial", "elementType": "labels", "stylers": [ { "visibility": "off" } ] }, { "featureType": "road.highway", "elementType": "geometry", "stylers": [ { "color": "#3c3c3c" } ] }, { "featureType": "road.highway", "elementType": "labels", "stylers": [ { "visibility": "off" } ] }, { "featureType": "road.highway.controlled_access", "elementType": "geometry", "stylers": [ { "color": "#4e4e4e" } ] }, { "featureType": "road.local", "stylers": [ { "visibility": "off" } ] }, { "featureType": "road.local", "elementType": "labels.text.fill", "stylers": [ { "color": "#616161" } ] }, { "featureType": "transit", "elementType": "labels.text.fill", "stylers": [ { "color": "#757575" } ] }, { "featureType": "water", "elementType": "geometry", "stylers": [ { "color": "#000000" } ] }, { "featureType": "water", "elementType": "labels.text.fill", "stylers": [ { "color": "#3d3d3d" } ] } ]; 132 | let lightMapStyles = [ { "elementType": "geometry", "stylers": [ { "color": "#1d2c4d" } ] }, { "elementType": "labels.text.fill", "stylers": [ { "color": "#8ec3b9" } ] }, { "elementType": "labels.text.stroke", "stylers": [ { "color": "#1a3646" } ] }, { "featureType": "administrative.country", "elementType": "geometry.stroke", "stylers": [ { "color": "#4b6878" } ] }, { "featureType": "administrative.land_parcel", "elementType": "labels.text.fill", "stylers": [ { "color": "#64779e" } ] }, { "featureType": "administrative.province", "elementType": "geometry.stroke", "stylers": [ { "color": "#4b6878" } ] }, { "featureType": "landscape.man_made", "elementType": "geometry.stroke", "stylers": [ { "color": "#334e87" } ] }, { "featureType": "landscape.natural", "elementType": "geometry", "stylers": [ { "color": "#023e58" } ] }, { "featureType": "poi", "elementType": "geometry", "stylers": [ { "color": "#283d6a" } ] }, { "featureType": "poi", "elementType": "labels.text.fill", "stylers": [ { "color": "#6f9ba5" } ] }, { "featureType": "poi", "elementType": "labels.text.stroke", "stylers": [ { "color": "#1d2c4d" } ] }, { "featureType": "poi.business", "stylers": [ { "visibility": "off" } ] }, { "featureType": "poi.park", "elementType": "geometry.fill", "stylers": [ { "color": "#023e58" } ] }, { "featureType": "poi.park", "elementType": "labels.text", "stylers": [ { "visibility": "off" } ] }, { "featureType": "poi.park", "elementType": "labels.text.fill", "stylers": [ { "color": "#3C7680" } ] }, { "featureType": "road", "elementType": "geometry", "stylers": [ { "color": "#304a7d" } ] }, { "featureType": "road", "elementType": "labels.text.fill", "stylers": [ { "color": "#98a5be" } ] }, { "featureType": "road", "elementType": "labels.text.stroke", "stylers": [ { "color": "#1d2c4d" } ] }, { "featureType": "road.highway", "elementType": "geometry", "stylers": [ { "color": "#2c6675" } ] }, { "featureType": "road.highway", "elementType": "geometry.stroke", "stylers": [ { "color": "#255763" } ] }, { "featureType": "road.highway", "elementType": "labels.text.fill", "stylers": [ { "color": "#b0d5ce" } ] }, { "featureType": "road.highway", "elementType": "labels.text.stroke", "stylers": [ { "color": "#023e58" } ] }, { "featureType": "transit", "elementType": "labels.text.fill", "stylers": [ { "color": "#98a5be" } ] }, { "featureType": "transit", "elementType": "labels.text.stroke", "stylers": [ { "color": "#1d2c4d" } ] }, { "featureType": "transit.line", "elementType": "geometry.fill", "stylers": [ { "color": "#283d6a" } ] }, { "featureType": "transit.station", "elementType": "geometry", "stylers": [ { "color": "#3a4762" } ] }, { "featureType": "water", "elementType": "geometry", "stylers": [ { "color": "#0e1626" } ] }, { "featureType": "water", "elementType": "labels.text.fill", "stylers": [ { "color": "#4e6d70" } ] } ] 133 | 134 | let mapOptions = { 135 | center: null, 136 | styles: this.global.get('side') === 'light' ? lightMapStyles : darkMapStyles, 137 | zoom: 4, 138 | streetViewControl: false, 139 | fullscreenControl: false, 140 | mapTypeControl: false, 141 | mapTypeId: google.maps.MapTypeId.ROADMAP 142 | } 143 | 144 | this.geolocation.getCurrentPosition().then((position) => { 145 | let latLng = new google.maps.LatLng(position.coords.latitude, position.coords.longitude); 146 | this.myLocation = latLng; 147 | mapOptions.center = latLng; 148 | this.mapElement = document.getElementById('map'); 149 | this.map = new google.maps.Map(this.mapElement, mapOptions); 150 | this.watchForChanges(); 151 | setImmediate(this.requestUserInfo(),300); 152 | }, error => { 153 | mapOptions.center = new google.maps.LatLng(-31.563910, 147.154312); 154 | this.mapElement = document.getElementById('map'); 155 | this.map = new google.maps.Map(this.mapElement, mapOptions); 156 | this.watchForChanges(); 157 | setImmediate(this.requestUserInfo(),300); 158 | }); 159 | } 160 | 161 | disableMap() { 162 | if (this.pleaseConnect) { 163 | this.renderer.setElementStyle(this.pleaseConnect.nativeElement, 'display', 'block'); 164 | } 165 | } 166 | 167 | enableMap() { 168 | if (this.pleaseConnect) { 169 | this.renderer.setElementStyle(this.pleaseConnect.nativeElement, 'display', 'none'); 170 | } 171 | } 172 | 173 | addConnectivityListeners() { 174 | this.connectivityService.watchOnline().subscribe(() => { 175 | console.log("online"); 176 | setTimeout(() => { 177 | if (typeof google == "undefined" || typeof google.maps == "undefined") { 178 | this.loadGoogleMaps(); 179 | } 180 | else { 181 | if (!this.mapInitialised) { 182 | this.initMap(); 183 | } 184 | this.enableMap(); 185 | } 186 | }, 2000); 187 | }); 188 | 189 | this.connectivityService.watchOffline().subscribe(() => { 190 | console.log("offline"); 191 | this.disableMap(); 192 | }); 193 | } 194 | 195 | addMarker(data) { 196 | let marker = new google.maps.Marker({ 197 | clickable: true, 198 | position: data.position, 199 | animation: google.maps.Animation.DROP, 200 | map: this.map, 201 | icon: `assets/maps/pin-${data.side}-${data.icon}.png`, 202 | }); 203 | 204 | let infowindow = new google.maps.InfoWindow({ 205 | content: data.name 206 | }); 207 | 208 | marker.addListener('click', () => { 209 | infowindow.open(this.map, marker); 210 | }); 211 | 212 | this.markers.set(data.uuid, marker); 213 | this.users.push(data); 214 | } 215 | 216 | saveMarker(data) { 217 | let marker = this.markers.get(data.uuid); 218 | 219 | if(marker){ 220 | this.updateMarker(data, marker); 221 | } else { 222 | this.addMarker(data); 223 | } 224 | this.updateCounters(); 225 | } 226 | 227 | updateMarker(data, marker) { 228 | marker.setPosition(data.position); 229 | marker.setIcon(`assets/maps/pin-${data.side}-${data.icon}.png`); 230 | 231 | let index = this.users.findIndex(user => user.uuid === data.uuid); 232 | if(index != -1) { 233 | this.users[index] = data; 234 | } 235 | } 236 | 237 | watchForChanges() { 238 | this.firebaseData.watchForUpdates().subscribe((updatedUser: any) => { 239 | this.saveMarker(updatedUser); 240 | }); 241 | } 242 | 243 | addUser(user) { 244 | if(this.users.indexOf(user.uuid) === -1){ 245 | this.users.push(user); 246 | } 247 | } 248 | 249 | updateCounters() { 250 | this.lightSideCount = this.users.reduce((total,user) => user.side === 'light' ? total+1 : total, 0); 251 | this.darkSideCount = this.users.length - this.lightSideCount; 252 | } 253 | 254 | centerToMyLocation() { 255 | if (this.myLocation) { 256 | this.map.setZoom(13); 257 | this.map.panTo(this.myLocation); 258 | } else { 259 | this.geolocation.getCurrentPosition().then((position) => { 260 | let latLng = new google.maps.LatLng(position.coords.latitude, position.coords.longitude); 261 | this.myLocation = latLng; 262 | this.map.setZoom(13); 263 | this.map.panTo(latLng); 264 | }).catch(error => { 265 | console.log('geolocation error', error); 266 | }); 267 | } 268 | } 269 | } 270 | -------------------------------------------------------------------------------- /src/pages/youtube-modal/youtube-modal.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 |
8 | 9 | 10 |
11 |
12 | -------------------------------------------------------------------------------- /src/pages/youtube-modal/youtube-modal.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { IonicPageModule } from 'ionic-angular'; 3 | import { YoutubeModalPage } from './youtube-modal'; 4 | 5 | @NgModule({ 6 | declarations: [ 7 | YoutubeModalPage, 8 | ], 9 | imports: [ 10 | IonicPageModule.forChild(YoutubeModalPage), 11 | ], 12 | exports: [ 13 | YoutubeModalPage 14 | ] 15 | }) 16 | export class YoutubeModalPageModule {} 17 | -------------------------------------------------------------------------------- /src/pages/youtube-modal/youtube-modal.scss: -------------------------------------------------------------------------------- 1 | page-youtube-modal { 2 | .toolbar button.bar-button span, .toolbar-title { 3 | color: white; 4 | } 5 | .scroll-content { 6 | background: black; 7 | } 8 | .video-container { 9 | height: 100%; 10 | display: flex; 11 | align-items: center; 12 | justify-content: center; 13 | } 14 | .yt-video { 15 | z-index: 2; 16 | width: 100%; 17 | height: 80%; 18 | } 19 | ion-buttons { 20 | position: absolute; 21 | } 22 | ion-icon { 23 | color: white; 24 | font-size: 2rem; 25 | } 26 | ion-spinner{ 27 | position: absolute !important; 28 | width: 38px !important; 29 | height: 38px !important; 30 | * { 31 | stroke: #444 !important; 32 | fill: white !important; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/pages/youtube-modal/youtube-modal.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { IonicPage, NavController, NavParams, ViewController } from 'ionic-angular'; 3 | import { SafeResourceUrl, DomSanitizer } from '@angular/platform-browser'; 4 | 5 | @IonicPage() 6 | @Component({ 7 | selector: 'page-youtube-modal', 8 | templateUrl: 'youtube-modal.html', 9 | }) 10 | export class YoutubeModalPage { 11 | 12 | url: SafeResourceUrl; 13 | constructor(public navCtrl: NavController, 14 | public navParams: NavParams, 15 | public sanitizer: DomSanitizer, 16 | public viewCtrl: ViewController,) { 17 | this.url = sanitizer.bypassSecurityTrustResourceUrl('https://youtube.com/embed/' + navParams.get('id')); 18 | } 19 | 20 | dismiss() { 21 | this.viewCtrl.dismiss(); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/pipes/pipes.module.ts: -------------------------------------------------------------------------------- 1 | import { SpeciesPipe } from './species/species'; 2 | import { RomanizePipe } from './romanize/romanize'; 3 | import { NgModule } from '@angular/core'; 4 | 5 | @NgModule({ 6 | declarations: [ RomanizePipe, SpeciesPipe ], 7 | exports: [ RomanizePipe, SpeciesPipe ] 8 | }) 9 | export class PipesModule {} 10 | -------------------------------------------------------------------------------- /src/pipes/romanize/romanize.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | 3 | @Pipe({ 4 | name: 'romanize', 5 | }) 6 | export class RomanizePipe implements PipeTransform { 7 | 8 | lookup = { M:1000,CM:900,D:500,CD:400,C:100,XC:90,L:50,XL:40,X:10,IX:9,V:5,IV:4,I:1 }; 9 | 10 | transform(num, ...args) { 11 | let roman = ''; 12 | for (let i in this.lookup ) { 13 | while ( num >= this.lookup[i] ) { 14 | roman += i; 15 | num -= this.lookup[i]; 16 | } 17 | } 18 | return roman; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/pipes/species/species.ts: -------------------------------------------------------------------------------- 1 | import { SwapiProvider } from '../../providers/swapi/swapi'; 2 | import { Pipe, PipeTransform } from '@angular/core'; 3 | 4 | @Pipe({ 5 | name: 'species', 6 | }) 7 | export class SpeciesPipe implements PipeTransform { 8 | constructor(public swapi: SwapiProvider){} 9 | transform(url, ...args) { 10 | return this.swapi.getSpecie(url).subscribe( data => { 11 | return data.name; 12 | }); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/providers/alert/alert.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { AlertController } from 'ionic-angular'; 3 | 4 | @Injectable() 5 | export class AlertService { 6 | constructor(public alertCtrl: AlertController) { } 7 | 8 | presentAlert(title: string, message: string) { 9 | let alert = this.alertCtrl.create( 10 | { 11 | title: title, 12 | subTitle: message, 13 | buttons: [ 14 | { 15 | text: 'OK' 16 | } 17 | ] 18 | }) 19 | 20 | return alert.present(); 21 | } 22 | 23 | presentErrorAlert(message: string) { 24 | return this.presentAlert("An error has occurred.", message); 25 | } 26 | 27 | getUserConsent(): Promise { 28 | return new Promise((resolve, reject) => { 29 | const confirm = this.alertCtrl.create({ 30 | title: 'Welcome to Star Warnic!', 31 | message: ` 32 |

Thanks for using this app!

33 |

We would like to get some basic information of yours in order to keep the app very fun!

34 |

To do so, we'd love to have your name(or nickname) and location. This data is not 35 | used anywhere else other than the maps page and it's completely optional.

36 | `, 37 | buttons: [{ 38 | cssClass: 'darkside-button', 39 | text: 'Never', 40 | role: 'cancel', 41 | handler: () => { 42 | confirm.dismiss().then(() => resolve(false)); 43 | return false; 44 | } 45 | }, { 46 | text: 'Let\'s go!', 47 | handler: () => { 48 | confirm.dismiss().then(() => resolve(true)); 49 | return false; 50 | } 51 | }] 52 | }); 53 | 54 | return confirm.present(); 55 | }); 56 | } 57 | 58 | getUserSide(): Promise { 59 | return new Promise((resolve, reject) => { 60 | const confirm = this.alertCtrl.create({ 61 | title: 'What side are you on?', 62 | message: 'Are you a sith or a jedi? :)', 63 | buttons: [{ 64 | cssClass: 'darkside-button', 65 | text: 'Dark', 66 | role: 'cancel', 67 | handler: () => { 68 | confirm.dismiss().then(() => resolve('dark')); 69 | return false; 70 | } 71 | }, { 72 | text: 'Light', 73 | handler: () => { 74 | confirm.dismiss().then(() => resolve('light')); 75 | return false; 76 | } 77 | }] 78 | }); 79 | 80 | return confirm.present(); 81 | }); 82 | } 83 | 84 | getUserName(){ 85 | let inputs = [ 86 | { 87 | name: 'name', 88 | placeholder: 'Your name or nickname' 89 | }, 90 | ]; 91 | 92 | return this.presentAlertWithInput('What is your name?', inputs, 'John Doe'); 93 | } 94 | 95 | presentAlertWithInput(title: string, inputs: any, defaultValue: any): Promise{ 96 | return new Promise((resolve, reject) => { 97 | const alert = this.alertCtrl.create({ 98 | title: title, 99 | inputs: inputs, 100 | buttons: [ 101 | { 102 | text: 'OK', 103 | handler: data => { 104 | alert.dismiss().then(() => resolve(data.name || defaultValue)); 105 | return false; 106 | } 107 | } 108 | ] 109 | }); 110 | 111 | return alert.present(); 112 | }); 113 | } 114 | } -------------------------------------------------------------------------------- /src/providers/audio-service/audio-service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Platform } from 'ionic-angular'; 3 | import { NativeAudio } from '@ionic-native/native-audio'; 4 | 5 | // Provides a way to work with audio on native 6 | // or fall back to html5 when on browser. 7 | @Injectable() 8 | export class AudioService { 9 | 10 | preloadedAudios: Map; 11 | lightSaberSwings = ['lightSwing', 'lightSwing2', 'heavySwing', 'heavySwing2']; 12 | 13 | constructor(private nativeAudio: NativeAudio, 14 | private platform: Platform) { 15 | 16 | this.platform.ready().then( () => { 17 | this.preloadDefaultAudios(); 18 | }); 19 | } 20 | 21 | preloadDefaultAudios() { 22 | this.preloadedAudios = new Map(); 23 | this.preload('turnLightSaberOn', 'assets/audio/lightsaber-on.mp3'); 24 | this.preload('lightSwing' , 'assets/audio/light_swing.mp3'); 25 | this.preload('lightSwing2' , 'assets/audio/light_swing_2.mp3'); 26 | this.preload('heavySwing' , 'assets/audio/heavy_swing.mp3'); 27 | this.preload('heavySwing2' , 'assets/audio/heavy_swing_2.mp3'); 28 | this.preload('jediOn' , 'assets/audio/jedi_on.mp3'); 29 | this.preload('jediOff' , 'assets/audio/jedi_off.mp3'); 30 | } 31 | 32 | preload(key, path) { 33 | if (this.platform.is('cordova')) { 34 | this.nativeAudio.preloadSimple(key, path); 35 | } else { 36 | this.preloadedAudios.set(key, path); 37 | } 38 | } 39 | 40 | play(key) { 41 | if (this.platform.is('cordova')) { 42 | this.nativeAudio.play(key); 43 | } else { 44 | let audioPath = this.preloadedAudios.get(key); 45 | let audio = new Audio(audioPath); 46 | audio.load(); 47 | audio.play(); 48 | } 49 | } 50 | 51 | playIntro() { 52 | let path = 'assets/audio/intro.mp3'; 53 | if(this.platform.is('cordova')){ 54 | this.nativeAudio.preloadSimple('intro' , path).then(()=>{ 55 | this.nativeAudio.play('intro'); 56 | }).catch(error => { 57 | alert(JSON.stringify(error)); 58 | }); 59 | } else { 60 | let audio = new Audio(path); 61 | audio.load(); 62 | audio.play(); 63 | } 64 | } 65 | 66 | swingLightSaber() { 67 | let index = Math.floor(Math.random() * 4); 68 | let swing = this.lightSaberSwings[index]; 69 | this.play(swing); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/providers/connectivity/connectivity.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Network } from '@ionic-native/network'; 3 | import { Platform } from 'ionic-angular'; 4 | import { Observable } from 'rxjs/Observable'; 5 | 6 | declare var Connection; 7 | 8 | @Injectable() 9 | export class ConnectivityProvider { 10 | 11 | onDevice: boolean; 12 | 13 | constructor(public platform: Platform, public network: Network){ 14 | this.onDevice = this.platform.is('cordova'); 15 | } 16 | 17 | isOnline(): boolean { 18 | return navigator.onLine; 19 | // if(this.onDevice && this.network.connection){ 20 | // return this.network.connection !== Connection.NONE; 21 | // } else { 22 | // return navigator.onLine; 23 | // } 24 | } 25 | 26 | isOffline(): boolean { 27 | return !navigator.onLine; 28 | // if(this.onDevice && this.network.connection){ 29 | // return this.network.connection === Connection.NONE; 30 | // } else { 31 | // return !navigator.onLine; 32 | // } 33 | } 34 | 35 | watchOnline(): Observable { 36 | return this.network.onConnect(); 37 | } 38 | 39 | watchOffline(): Observable { 40 | return this.network.onDisconnect(); 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /src/providers/firebase-data/firebase-data.ts: -------------------------------------------------------------------------------- 1 | import { AngularFireDatabase } from 'angularfire2/database'; 2 | import { AppState } from '../../app/app.global'; 3 | import { Injectable } from '@angular/core'; 4 | import 'rxjs/add/operator/map'; 5 | import firebase from 'firebase'; 6 | import { Subject } from 'rxjs'; 7 | 8 | @Injectable() 9 | export class FirebaseDataProvider { 10 | 11 | usersSubject = new Subject(); 12 | userRef = 'userInfo/'; 13 | 14 | constructor(public global: AppState, 15 | public afDatabase: AngularFireDatabase) { } 16 | 17 | writeUserData(user) { 18 | return firebase.database().ref(this.userRef + user.uuid).set(user); 19 | } 20 | 21 | setSide(side){ 22 | let uuid = this.global.get('uuid'); 23 | var ref = firebase.database().ref(this.userRef + uuid); 24 | ref.once('value', (snapshot) => { 25 | if (snapshot.exists()) { 26 | return firebase.database().ref(this.userRef + uuid + '/side').transaction( () =>{ 27 | return side; 28 | }); 29 | } 30 | }); 31 | } 32 | 33 | fetchUsers() { 34 | return firebase.database().ref(this.userRef).once('value', snapshot => { 35 | return snapshot.val(); 36 | }); 37 | } 38 | 39 | watchForUpdates() { 40 | this.afDatabase.database.ref(this.userRef) 41 | .on('child_added', dataSnapshot => { 42 | var user = dataSnapshot.val(); 43 | this.usersSubject.next(user); 44 | }); 45 | 46 | this.afDatabase.database.ref(this.userRef) 47 | .on('child_changed', dataSnapshot => { 48 | var user = dataSnapshot.val(); 49 | this.usersSubject.next(user); 50 | }); 51 | 52 | return this.usersSubject; 53 | } 54 | 55 | addRandomUser() { 56 | let side = Math.floor(Math.random() * 2); 57 | let user = { 58 | name: 'test', 59 | icon: Math.floor(Math.random() * 6) + 1, 60 | uuid: Math.floor(Math.random() * 125123) + 1, 61 | side: side == 1 ? 'light' : 'dark', 62 | position : { 63 | lat: -(Math.floor(Math.random() * 6) + 1), 64 | lng: Math.floor(Math.random() * 30) + 5 65 | } 66 | } 67 | 68 | return firebase.database().ref(this.userRef + user.uuid).set(user); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/providers/google-images/google-images.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Http, RequestOptions, URLSearchParams } from '@angular/http'; 3 | import { CacheService } from "ionic-cache"; 4 | import 'rxjs/add/operator/map'; 5 | 6 | @Injectable() 7 | export class GoogleImagesProvider { 8 | 9 | private baseUrl = 'https://www.googleapis.com/customsearch/v1'; 10 | private apiKey = 'AIzaSyCzb6SI_JRrp6xLLYV617Ary6n59h36ros'; 11 | private cx = '004286675445984025592:ypgpkv9fjd4'; 12 | private queryParams = { 13 | key : this.apiKey, 14 | cx : this.cx, 15 | filter : '1', 16 | searchType : 'image', 17 | q : '' 18 | } 19 | 20 | constructor(public http: Http, public cache: CacheService) { } 21 | 22 | get(endpoint: string, params?: any, cacheKey?: string, options?: RequestOptions) { 23 | options = new RequestOptions(); 24 | 25 | // Support easy query params for GET requests 26 | let p = new URLSearchParams(); 27 | if (params) { 28 | for(let k in params) { 29 | p.set(k, params[k]); 30 | } 31 | 32 | } 33 | 34 | // Set the search field if we have params and don't already have 35 | // a search field set in options. 36 | options.search = !options.search && p || options.search; 37 | let request = this.http.get(endpoint, options).map(res => res.json()); 38 | return this.cache.loadFromObservable(cacheKey, request); 39 | } 40 | 41 | searchImage(query) { 42 | this.queryParams.q = query; 43 | return this.get(this.baseUrl, this.queryParams, 'image::' + query); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/providers/motion/motion.ts: -------------------------------------------------------------------------------- 1 | import { Shake } from '@ionic-native/shake'; 2 | import { Platform } from 'ionic-angular'; 3 | import { AudioService } from '../audio-service/audio-service'; 4 | import { Injectable } from '@angular/core'; 5 | import { Subject } from 'rxjs'; 6 | 7 | @Injectable() 8 | export class MotionProvider { 9 | axisMovement: number = 0; 10 | watchEvent: any; 11 | movementSubject = new Subject(); 12 | 13 | constructor(public audioCtrl: AudioService, 14 | public platform: Platform, 15 | public shake: Shake) { 16 | } 17 | 18 | startWatchingSwings() { 19 | if (this.platform.is('cordova')) { 20 | this.watchEvent = this.shake.startWatch(10).subscribe(() => { 21 | this.audioCtrl.swingLightSaber(); 22 | }); 23 | } else { 24 | window.ondevicemotion = (event) => { 25 | var accelerationX = Math.abs(event.accelerationIncludingGravity.x); 26 | let swing = Math.abs(this.axisMovement - accelerationX); 27 | 28 | if (swing >= 10) { 29 | this.audioCtrl.swingLightSaber(); 30 | this.axisMovement = accelerationX; 31 | } else { 32 | this.axisMovement = accelerationX; 33 | } 34 | } 35 | } 36 | } 37 | 38 | stopWatchingSwings() { 39 | if (this.platform.is('cordova')) { 40 | if (this.watchEvent) { 41 | this.watchEvent.unsubscribe(); 42 | } 43 | } else { 44 | window.ondevicemotion = null; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/providers/movie-info/movie-info.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Http } from '@angular/http'; 3 | import 'rxjs/add/operator/map'; 4 | 5 | @Injectable() 6 | export class MovieInfoProvider { 7 | 8 | constructor(public http: Http) { } 9 | 10 | getMovieInfo(id){ 11 | let path = 'assets/data/' + id + '.json'; 12 | return this.http.get(path).map(data => data.json()); 13 | } 14 | 15 | getTrivia(id){ 16 | let path = 'assets/data/trivia_' + id + '.json'; 17 | return this.http.get(path).map(data => data.json()); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/providers/swapi/swapi.ts: -------------------------------------------------------------------------------- 1 | import { GoogleImagesProvider } from '../google-images/google-images'; 2 | import { Injectable } from '@angular/core'; 3 | import { Http } from '@angular/http'; 4 | import { CacheService } from "ionic-cache"; 5 | import { Observable } from 'rxjs/Rx'; 6 | import 'rxjs/add/operator/map'; 7 | 8 | @Injectable() 9 | export class SwapiProvider { 10 | private baseUrl = "https://swapi.co/api/"; 11 | 12 | constructor(public http: Http, public cache: CacheService, private gImages: GoogleImagesProvider) { } 13 | 14 | get(endpoint: string) { 15 | let cacheKey = endpoint; 16 | let request = this.http.get(endpoint).map(res => res.json()); 17 | return this.cache.loadFromObservable(cacheKey, request); 18 | } 19 | 20 | getWithImage(endpoint): Observable { 21 | let cacheKey = endpoint; 22 | let request = this.http.get(endpoint) 23 | .map((res: any) => res.json()) 24 | .flatMap((item: any) => { 25 | let query = 'Star wars ' + (item.name || item.title); 26 | return this.gImages.searchImage(query) 27 | .map((res: any) => { 28 | let image = res.items[0].link; 29 | console.log(query, image); 30 | item.image = image; 31 | return item; 32 | }); 33 | }); 34 | 35 | return this.cache.loadFromObservable(cacheKey, request); 36 | } 37 | 38 | getPerson(id): any { 39 | let endpoint = 'people/' + id + '/'; 40 | return this.get(this.baseUrl + endpoint); 41 | } 42 | 43 | getSpecie(id): any { 44 | let endpoint = 'species/' + id; 45 | return this.get(this.baseUrl + endpoint); 46 | } 47 | 48 | getVehicle(id): any { 49 | let endpoint = 'vehicle/' + id; 50 | return this.get(this.baseUrl + endpoint); 51 | } 52 | 53 | getFilm(id): any { 54 | let endpoint = 'films/' + id; 55 | return this.get(this.baseUrl + endpoint); 56 | } 57 | 58 | getFilms(): any { 59 | let endpoint = 'films/'; 60 | return this.get(this.baseUrl + endpoint); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /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/darkside.theme.scss: -------------------------------------------------------------------------------- 1 | .theme-dark { 2 | background-color: #303030; 3 | h1, h2, h3, 4 | h4, h5, h6, 5 | p, .toolbar-title, 6 | .bar-button-default { 7 | color: white; 8 | } 9 | .toolbar-background{ 10 | transition: all 1.1s ease; 11 | background-color: #212121; 12 | } 13 | .item-note, 14 | .swiper-pagination-bullet { 15 | transition: all 1.1s ease; 16 | } 17 | 18 | ion-content { 19 | transition: all .5s ease; 20 | background-color: #303030 !important; 21 | background: #303030 !important; 22 | color: white !important; 23 | } 24 | 25 | .item { 26 | transition: none !important; 27 | background-color: #100815; 28 | color: white; 29 | } 30 | 31 | ion-card { 32 | background: rgba(255, 255, 255, 0.12) !important; 33 | } 34 | 35 | ion-avatar img, .footer-btn { 36 | background: $darkside-red; 37 | } 38 | 39 | .toggle-checked .toggle-icon { 40 | transition: all 1.1s ease; 41 | background-color: #717377; 42 | } 43 | 44 | .toggle-checked .toggle-inner { 45 | background-color: $darkside-red !important; 46 | } 47 | 48 | ion-menu { 49 | transition: all .5s ease; 50 | background: #303030 !important; 51 | } 52 | 53 | ion-menu ion-icon{ 54 | // color:$darkside-red; 55 | } 56 | .active-menu-item ion-icon { 57 | color:$darkside-red; 58 | text-shadow: 0 0 5px #fff, 0 0 10px #fff, 0 0 15px #fff, 0 0 20px $darkside-red, 0 0 35px $darkside-red, 0 0 40px $darkside-red, 0 0 50px $darkside-red, 0 0 75px $darkside-red; 59 | } 60 | 61 | overlaying-view .toolbar-background, 62 | overlaying-view .header-top { 63 | background-color: $darkside-red !important; 64 | } 65 | } 66 | 67 | -------------------------------------------------------------------------------- /src/theme/fonts.scss: -------------------------------------------------------------------------------- 1 | /* 2 | App Wide Fonts and various weights 3 | */ 4 | 5 | @font-face { 6 | font-family: "District Pro"; 7 | src: url("../assets/fonts/DistrictPro.otf") format('opentype'); 8 | } 9 | 10 | .secondary-font { 11 | font-family: "District Pro"; 12 | font-weight: bold; 13 | } 14 | 15 | .bold {font-weight:bold;} 16 | .text-right {text-align: right;} 17 | .text-left {text-align: left;} 18 | .text-center {text-align: center;} 19 | .uppercase {text-transform:uppercase;} 20 | .no-margin {margin:0;} 21 | .text-ellipsis { 22 | text-overflow: ellipsis; 23 | } 24 | .no-underline {text-decoration: none;} -------------------------------------------------------------------------------- /src/theme/lightside.theme.scss: -------------------------------------------------------------------------------- 1 | .theme-light { 2 | ion-content { 3 | transition: all .5s ease; 4 | } 5 | 6 | ion-avatar img { 7 | background: $lightside-blue; 8 | } 9 | 10 | ion-menu { 11 | transition: all .5s ease; 12 | background: white !important; 13 | ion-icon { 14 | color: $lightside-blue; 15 | } 16 | } 17 | 18 | .active-menu-item ion-icon{ 19 | text-shadow: 0 0 10px #fff, 0 0 20px #fff, 0 0 30px #fff, 0 0 40px $lightside-blue, 0 0 70px $lightside-blue, 0 0 80px $lightside-blue, 0 0 100px $lightside-blue, 0 0 150px $lightside-blue; 20 | } 21 | 22 | .item { 23 | transition: none !important; 24 | } 25 | 26 | overlaying-view .toolbar-background, 27 | overlaying-view .header-top { 28 | background-color: $lightside-blue !important; 29 | } 30 | } 31 | 32 | -------------------------------------------------------------------------------- /src/theme/mixins.scss: -------------------------------------------------------------------------------- 1 | .hexagon { 2 | position: relative; 3 | width: 115px; 4 | height: 66.40px; 5 | margin-top: 40px !important; 6 | margin: 0 auto 40px auto; 7 | background-image: url(../assets/img/avatar/darth_vader.jpg); 8 | background-size: auto 121.2436px; 9 | background-position: center; 10 | box-shadow: 0 0 20px rgba(0,0,0,0.7); 11 | border-left: solid 5px $darkside-red; 12 | border-right: solid 5px $darkside-red; 13 | } 14 | 15 | .hexTop, 16 | .hexBottom { 17 | position: absolute; 18 | z-index: 1; 19 | width: 81.32px; 20 | height: 81.32px; 21 | overflow: hidden; 22 | -webkit-transform: scaleY(0.5774) rotate(-45deg); 23 | -ms-transform: scaleY(0.5774) rotate(-45deg); 24 | transform: scaleY(0.5774) rotate(-45deg); 25 | background: inherit; 26 | left: 11.84px; 27 | box-shadow: 0 0 10px rgba(0,0,0,0.7); 28 | } 29 | 30 | /*counter transform the bg image on the caps*/ 31 | .hexTop:after, 32 | .hexBottom:after { 33 | content: ""; 34 | position: absolute; 35 | width: 105.0000px; 36 | height: 60.621778264910716px; 37 | -webkit-transform: rotate(45deg) scaleY(1.7321) translateY(-30.3109px); 38 | -ms-transform: rotate(45deg) scaleY(1.7321) translateY(-30.3109px); 39 | transform: rotate(45deg) scaleY(1.7321) translateY(-30.3109px); 40 | -webkit-transform-origin: 0 0; 41 | -ms-transform-origin: 0 0; 42 | transform-origin: 0 0; 43 | background: inherit; 44 | } 45 | 46 | .hexTop { 47 | top: -40.6586px; 48 | border-top: solid 7.0711px $darkside-red; 49 | border-right: solid 7.0711px $darkside-red; 50 | } 51 | 52 | .hexTop:after { 53 | background-position: center top; 54 | } 55 | 56 | .hexBottom { 57 | bottom: -40.6586px; 58 | border-bottom: solid 7.0711px $darkside-red; 59 | border-left: solid 7.0711px $darkside-red; 60 | } 61 | 62 | .hexBottom:after { 63 | background-position: center bottom; 64 | } 65 | 66 | .hexagon:after { 67 | content: ""; 68 | position: absolute; 69 | top: 2.8868px; 70 | left: 0; 71 | width: 105.0000px; 72 | height: 60.6218px; 73 | z-index: 2; 74 | background: inherit; 75 | } -------------------------------------------------------------------------------- /src/theme/splash.scss: -------------------------------------------------------------------------------- 1 | .fade { 2 | animation: fade 1s forwards; 3 | } 4 | 5 | @keyframes fade { 6 | 0% { opacity: 1; } 7 | 100% { opacity: 0; display: none;} 8 | } 9 | 10 | #perspective 11 | { 12 | // background-color: #000; 13 | position: absolute; 14 | width: 18em; 15 | height: 70em; 16 | bottom: 0; 17 | left: 50%; 18 | margin-left: -9em; 19 | overflow: hidden; 20 | transform-origin: 50% 100%; 21 | transform: perspective(300px) rotateX(25deg); 22 | } 23 | 24 | #perspective img { 25 | width: 100vw; 26 | display: block; 27 | margin: 0 auto; 28 | } 29 | 30 | #perspective-content 31 | { 32 | position: absolute; 33 | top: 100%; 34 | -webkit-animation: scroll 8s linear 1s infinite; 35 | animation: scroll 8s linear 1s infinite; 36 | } 37 | 38 | /* animation */ 39 | @-webkit-keyframes scroll { 40 | 0% { transform: translateY(0); } 41 | 100% { transform: translateY(-800px); } 42 | } 43 | 44 | @keyframes scroll { 45 | 0% { transform: translateY(0); } 46 | 100% { transform: translateY(-800px); } 47 | } 48 | -------------------------------------------------------------------------------- /src/theme/variables.scss: -------------------------------------------------------------------------------- 1 | // Ionic Variables and Theming. For more info, please see: 2 | // http://ionicframework.com/docs/v2/theming/ 3 | $font-path: "../assets/fonts"; 4 | 5 | @import "ionic.globals"; 6 | 7 | 8 | // Shared Variables 9 | // -------------------------------------------------- 10 | // To customize the look and feel of this app, you can override 11 | // the Sass variables found in Ionic's source scss files. 12 | // To view all the possible Ionic variables, see: 13 | // http://ionicframework.com/docs/v2/theming/overriding-ionic-variables/ 14 | 15 | 16 | 17 | 18 | // Named Color Variables 19 | // -------------------------------------------------- 20 | // Named colors makes it easy to reuse colors on various components. 21 | // It's highly recommended to change the default colors 22 | // to match your app's branding. Ionic uses a Sass map of 23 | // colors so you can add, rename and remove colors as needed. 24 | // The "primary" color is the only required color in the map. 25 | 26 | $colors: ( 27 | primary: #488aff, 28 | secondary: #32db64, 29 | danger: #f53d3d, 30 | light: #f4f4f4, 31 | dark: #222 32 | ); 33 | 34 | $darkside-red : #f53d3d; 35 | $darkside-black : #212121; 36 | $lightside-blue : #488aff; 37 | 38 | 39 | $menu-width: 220px; 40 | 41 | // App iOS Variables 42 | // -------------------------------------------------- 43 | // iOS only Sass variables can go here 44 | 45 | 46 | 47 | 48 | // App Material Design Variables 49 | // -------------------------------------------------- 50 | // Material Design only Sass variables can go here 51 | 52 | 53 | 54 | 55 | // App Windows Variables 56 | // -------------------------------------------------- 57 | // Windows only Sass variables can go here 58 | 59 | 60 | 61 | 62 | // App Theme 63 | // -------------------------------------------------- 64 | // Ionic apps can have different themes applied, which can 65 | // then be future customized. This import comes last 66 | // so that the above variables are used and Ionic's 67 | // default are overridden. 68 | 69 | @import "ionic.theme.default"; 70 | 71 | // Ionicons 72 | // -------------------------------------------------- 73 | // The premium icon font for Ionic. For more info, please see: 74 | // http://ionicframework.com/docs/v2/ionicons/ 75 | 76 | @import "ionicons"; 77 | 78 | // These fonts were generated on https://icomoon.io/app/ 79 | @font-face { 80 | font-family: 'icomoon'; 81 | src: url('../assets/fonts/icomoon.eot?q976bf'); 82 | src: url('../assets/fonts/icomoon.eot?q976bf#iefix') format('embedded-opentype'), 83 | url('../assets/fonts/icomoon.ttf?q976bf') format('truetype'), 84 | url('../assets/fonts/icomoon.woff?q976bf') format('woff'), 85 | url('../assets/fonts/icomoon.svg?q976bf#icomoon') format('svg'); 86 | font-weight: normal; 87 | font-style: normal; 88 | } 89 | 90 | [class^="icon-"], [class*=" icon-"] { 91 | /* use !important to prevent issues with browser extensions that change fonts */ 92 | font-family: 'icomoon' !important; 93 | speak: none; 94 | font-style: normal; 95 | font-weight: normal; 96 | font-variant: normal; 97 | text-transform: none; 98 | line-height: 1; 99 | /* Better Font Rendering =========== */ 100 | -webkit-font-smoothing: antialiased; 101 | -moz-osx-font-smoothing: grayscale; 102 | } 103 | 104 | @mixin makeIcon($arg, $val) { 105 | .sw-#{$arg}:before , 106 | .ion-ios-sw-#{$arg}:before , 107 | .ion-ios-sw-#{$arg}-outline:before , 108 | .ion-md-sw-#{$arg}:before , 109 | .ion-md-sw-#{$arg}-outline:before { 110 | content: $val; 111 | font-size: 26px; 112 | } 113 | } 114 | 115 | @include makeIcon(logo, '\e900'); 116 | @include makeIcon(death-star, '\e901'); 117 | @include makeIcon(darth-vader, '\e902'); 118 | @include makeIcon(c3p0, '\e903'); 119 | 120 | // Fonts 121 | // -------------------------------------------------- 122 | 123 | @import "roboto"; 124 | @import "noto-sans"; 125 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "declaration": false, 5 | "emitDecoratorMetadata": true, 6 | "experimentalDecorators": true, 7 | "lib": [ 8 | "dom", 9 | "es2015" 10 | ], 11 | "module": "es2015", 12 | "moduleResolution": "node", 13 | "sourceMap": true, 14 | "target": "es5" 15 | }, 16 | "include": [ 17 | "src/**/*.ts" 18 | ], 19 | "exclude": [ 20 | "node_modules" 21 | ], 22 | "compileOnSave": false, 23 | "atom": { 24 | "rewriteTsconfig": false 25 | } 26 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-duplicate-variable": true, 4 | "no-unused-variable": [ 5 | true 6 | ] 7 | }, 8 | "rulesDirectory": [ 9 | "node_modules/tslint-eslint-rules/dist/rules" 10 | ] 11 | } 12 | --------------------------------------------------------------------------------