├── src
├── app
│ ├── app.html
│ ├── main.ts
│ ├── app.scss
│ ├── app.component.ts
│ └── app.module.ts
├── pages
│ ├── home
│ │ ├── home.scss
│ │ ├── home.html
│ │ ├── home.module.ts
│ │ └── home.ts
│ ├── login
│ │ ├── login.scss
│ │ ├── login.ts
│ │ └── login.html
│ ├── users
│ │ ├── users.scss
│ │ ├── users.module.ts
│ │ ├── users.ts
│ │ └── users.html
│ ├── user-posts
│ │ ├── user-posts.scss
│ │ ├── user-posts.html
│ │ ├── user-posts.module.ts
│ │ └── user-posts.ts
│ ├── profile-edit
│ │ ├── profile-edit.scss
│ │ ├── profile-edit.module.ts
│ │ ├── profile-edit.html
│ │ └── profile-edit.ts
│ ├── posts-create
│ │ ├── posts-create.scss
│ │ ├── posts-create.module.ts
│ │ ├── posts-create.html
│ │ └── posts-create.ts
│ ├── profile
│ │ ├── profile.scss
│ │ ├── profile.module.ts
│ │ ├── profile.ts
│ │ └── profile.html
│ └── tabs
│ │ ├── tabs.html
│ │ └── tabs.ts
├── components
│ ├── post-feed
│ │ ├── post-feed.scss
│ │ ├── post-feed.html
│ │ └── post-feed.ts
│ ├── fcm-topic
│ │ ├── fcm-topic.scss
│ │ ├── fcm-topic.html
│ │ └── fcm-topic.ts
│ ├── fcm-handler
│ │ ├── fcm-handler.scss
│ │ ├── fcm-handler.html
│ │ └── fcm-handler.ts
│ ├── user-logout
│ │ ├── user-logout.scss
│ │ ├── user-logout.html
│ │ └── user-logout.ts
│ ├── image-upload
│ │ ├── image-upload.scss
│ │ ├── image-upload.html
│ │ └── image-upload.ts
│ ├── facebook-login
│ │ ├── facebook-login.scss
│ │ ├── facebook-login.html
│ │ └── facebook-login.ts
│ ├── anonymous-login
│ │ ├── anonymous-login.scss
│ │ ├── anonymous-login.html
│ │ └── anonymous-login.ts
│ ├── user-relationship
│ │ ├── user-relationship.scss
│ │ ├── user-relationship.html
│ │ └── user-relationship.ts
│ ├── heart-button
│ │ ├── heart-button.scss
│ │ ├── heart-button.html
│ │ └── heart-button.ts
│ └── components.module.ts
├── assets
│ ├── imgs
│ │ └── logo.png
│ └── icon
│ │ └── favicon.ico
├── manifest.json
├── service-worker.js
├── providers
│ ├── remote-config
│ │ └── remote-config.ts
│ ├── analytics
│ │ └── analytics.ts
│ ├── database
│ │ └── database.ts
│ ├── fcm
│ │ └── fcm.ts
│ └── auth
│ │ └── auth.ts
├── index.html
└── theme
│ └── variables.scss
├── resources
├── icon.png.md5
├── splash.png.md5
├── icon.png
├── splash.png
├── ios
│ ├── icon
│ │ ├── icon.png
│ │ ├── icon-40.png
│ │ ├── icon-50.png
│ │ ├── icon-60.png
│ │ ├── icon-72.png
│ │ ├── icon-76.png
│ │ ├── icon@2x.png
│ │ ├── icon-1024.png
│ │ ├── icon-40@2x.png
│ │ ├── icon-40@3x.png
│ │ ├── icon-50@2x.png
│ │ ├── icon-60@2x.png
│ │ ├── icon-60@3x.png
│ │ ├── icon-72@2x.png
│ │ ├── icon-76@2x.png
│ │ ├── icon-small.png
│ │ ├── icon-83.5@2x.png
│ │ ├── icon-small@2x.png
│ │ └── icon-small@3x.png
│ └── splash
│ │ ├── Default-667h.png
│ │ ├── Default-736h.png
│ │ ├── Default~iphone.png
│ │ ├── Default@2x~iphone.png
│ │ ├── Default-Portrait~ipad.png
│ │ ├── Default-568h@2x~iphone.png
│ │ ├── Default-Landscape-736h.png
│ │ ├── Default-Landscape~ipad.png
│ │ ├── Default-Portrait@2x~ipad.png
│ │ ├── Default-Landscape@2x~ipad.png
│ │ ├── Default-Landscape@~ipadpro.png
│ │ ├── Default-Portrait@~ipadpro.png
│ │ └── Default@2x~universal~anyany.png
├── 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-port-hdpi-screen.png
│ │ ├── drawable-port-ldpi-screen.png
│ │ ├── drawable-port-mdpi-screen.png
│ │ ├── drawable-land-xhdpi-screen.png
│ │ ├── drawable-land-xxhdpi-screen.png
│ │ ├── drawable-port-xhdpi-screen.png
│ │ ├── drawable-port-xxhdpi-screen.png
│ │ ├── drawable-land-xxxhdpi-screen.png
│ │ └── drawable-port-xxxhdpi-screen.png
└── README.md
├── .firebaserc
├── ionic.config.json
├── tslint.json
├── functions
├── lib
│ ├── index.js.map
│ ├── index.js
│ ├── notifications.js.map
│ ├── notifications.js
│ ├── aggregation.js.map
│ └── aggregation.js
├── tsconfig.json
├── src
│ ├── index.ts
│ ├── notifications.ts
│ └── aggregation.ts
├── package.json
└── tslint.json
├── firebase.json
├── .editorconfig
├── tsconfig.json
├── .gitignore
├── README.md
├── package.json
└── config.xml
/src/app/app.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/pages/home/home.scss:
--------------------------------------------------------------------------------
1 | page-home {
2 |
3 | }
4 |
--------------------------------------------------------------------------------
/resources/icon.png.md5:
--------------------------------------------------------------------------------
1 | edd9e2f9d2e0649b2234c4139fb22878
--------------------------------------------------------------------------------
/resources/splash.png.md5:
--------------------------------------------------------------------------------
1 | deb265becb3b85487a856480bb38e4ab
--------------------------------------------------------------------------------
/src/pages/login/login.scss:
--------------------------------------------------------------------------------
1 | page-login {
2 |
3 | }
4 |
--------------------------------------------------------------------------------
/src/pages/users/users.scss:
--------------------------------------------------------------------------------
1 | page-users {
2 |
3 | }
4 |
--------------------------------------------------------------------------------
/src/components/post-feed/post-feed.scss:
--------------------------------------------------------------------------------
1 | post-feed {
2 | }
3 |
--------------------------------------------------------------------------------
/src/components/fcm-topic/fcm-topic.scss:
--------------------------------------------------------------------------------
1 | fcm-topic {
2 |
3 | }
4 |
--------------------------------------------------------------------------------
/src/components/fcm-handler/fcm-handler.scss:
--------------------------------------------------------------------------------
1 | fcm-handler {
2 |
3 | }
4 |
--------------------------------------------------------------------------------
/src/components/user-logout/user-logout.scss:
--------------------------------------------------------------------------------
1 | user-logout {
2 |
3 | }
4 |
--------------------------------------------------------------------------------
/src/pages/user-posts/user-posts.scss:
--------------------------------------------------------------------------------
1 | page-user-posts {
2 |
3 | }
4 |
--------------------------------------------------------------------------------
/src/components/image-upload/image-upload.scss:
--------------------------------------------------------------------------------
1 | image-upload {
2 |
3 | }
4 |
--------------------------------------------------------------------------------
/src/pages/profile-edit/profile-edit.scss:
--------------------------------------------------------------------------------
1 | page-profile-edit {
2 |
3 | }
4 |
--------------------------------------------------------------------------------
/src/components/facebook-login/facebook-login.scss:
--------------------------------------------------------------------------------
1 | facebook-login {
2 |
3 | }
4 |
--------------------------------------------------------------------------------
/src/components/anonymous-login/anonymous-login.scss:
--------------------------------------------------------------------------------
1 | anonymous-login {
2 |
3 | }
4 |
--------------------------------------------------------------------------------
/.firebaserc:
--------------------------------------------------------------------------------
1 | {
2 | "projects": {
3 | "default": "firebasics-f9f42"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/src/components/user-relationship/user-relationship.scss:
--------------------------------------------------------------------------------
1 | user-relationship {
2 |
3 | }
4 |
--------------------------------------------------------------------------------
/resources/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codediodeio/ionic-firestarter/HEAD/resources/icon.png
--------------------------------------------------------------------------------
/resources/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codediodeio/ionic-firestarter/HEAD/resources/splash.png
--------------------------------------------------------------------------------
/src/assets/imgs/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codediodeio/ionic-firestarter/HEAD/src/assets/imgs/logo.png
--------------------------------------------------------------------------------
/resources/ios/icon/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codediodeio/ionic-firestarter/HEAD/resources/ios/icon/icon.png
--------------------------------------------------------------------------------
/src/assets/icon/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codediodeio/ionic-firestarter/HEAD/src/assets/icon/favicon.ico
--------------------------------------------------------------------------------
/resources/ios/icon/icon-40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codediodeio/ionic-firestarter/HEAD/resources/ios/icon/icon-40.png
--------------------------------------------------------------------------------
/resources/ios/icon/icon-50.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codediodeio/ionic-firestarter/HEAD/resources/ios/icon/icon-50.png
--------------------------------------------------------------------------------
/resources/ios/icon/icon-60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codediodeio/ionic-firestarter/HEAD/resources/ios/icon/icon-60.png
--------------------------------------------------------------------------------
/resources/ios/icon/icon-72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codediodeio/ionic-firestarter/HEAD/resources/ios/icon/icon-72.png
--------------------------------------------------------------------------------
/resources/ios/icon/icon-76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codediodeio/ionic-firestarter/HEAD/resources/ios/icon/icon-76.png
--------------------------------------------------------------------------------
/resources/ios/icon/icon@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codediodeio/ionic-firestarter/HEAD/resources/ios/icon/icon@2x.png
--------------------------------------------------------------------------------
/src/components/user-logout/user-logout.html:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/resources/ios/icon/icon-1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codediodeio/ionic-firestarter/HEAD/resources/ios/icon/icon-1024.png
--------------------------------------------------------------------------------
/resources/ios/icon/icon-40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codediodeio/ionic-firestarter/HEAD/resources/ios/icon/icon-40@2x.png
--------------------------------------------------------------------------------
/resources/ios/icon/icon-40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codediodeio/ionic-firestarter/HEAD/resources/ios/icon/icon-40@3x.png
--------------------------------------------------------------------------------
/resources/ios/icon/icon-50@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codediodeio/ionic-firestarter/HEAD/resources/ios/icon/icon-50@2x.png
--------------------------------------------------------------------------------
/resources/ios/icon/icon-60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codediodeio/ionic-firestarter/HEAD/resources/ios/icon/icon-60@2x.png
--------------------------------------------------------------------------------
/resources/ios/icon/icon-60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codediodeio/ionic-firestarter/HEAD/resources/ios/icon/icon-60@3x.png
--------------------------------------------------------------------------------
/resources/ios/icon/icon-72@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codediodeio/ionic-firestarter/HEAD/resources/ios/icon/icon-72@2x.png
--------------------------------------------------------------------------------
/resources/ios/icon/icon-76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codediodeio/ionic-firestarter/HEAD/resources/ios/icon/icon-76@2x.png
--------------------------------------------------------------------------------
/resources/ios/icon/icon-small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codediodeio/ionic-firestarter/HEAD/resources/ios/icon/icon-small.png
--------------------------------------------------------------------------------
/resources/ios/icon/icon-83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codediodeio/ionic-firestarter/HEAD/resources/ios/icon/icon-83.5@2x.png
--------------------------------------------------------------------------------
/resources/ios/icon/icon-small@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codediodeio/ionic-firestarter/HEAD/resources/ios/icon/icon-small@2x.png
--------------------------------------------------------------------------------
/resources/ios/icon/icon-small@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codediodeio/ionic-firestarter/HEAD/resources/ios/icon/icon-small@3x.png
--------------------------------------------------------------------------------
/resources/ios/splash/Default-667h.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codediodeio/ionic-firestarter/HEAD/resources/ios/splash/Default-667h.png
--------------------------------------------------------------------------------
/resources/ios/splash/Default-736h.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codediodeio/ionic-firestarter/HEAD/resources/ios/splash/Default-736h.png
--------------------------------------------------------------------------------
/resources/ios/splash/Default~iphone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codediodeio/ionic-firestarter/HEAD/resources/ios/splash/Default~iphone.png
--------------------------------------------------------------------------------
/resources/ios/splash/Default@2x~iphone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codediodeio/ionic-firestarter/HEAD/resources/ios/splash/Default@2x~iphone.png
--------------------------------------------------------------------------------
/src/components/fcm-handler/fcm-handler.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{text}}
4 |
5 |
--------------------------------------------------------------------------------
/src/components/image-upload/image-upload.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/resources/android/icon/drawable-hdpi-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codediodeio/ionic-firestarter/HEAD/resources/android/icon/drawable-hdpi-icon.png
--------------------------------------------------------------------------------
/resources/android/icon/drawable-ldpi-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codediodeio/ionic-firestarter/HEAD/resources/android/icon/drawable-ldpi-icon.png
--------------------------------------------------------------------------------
/resources/android/icon/drawable-mdpi-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codediodeio/ionic-firestarter/HEAD/resources/android/icon/drawable-mdpi-icon.png
--------------------------------------------------------------------------------
/resources/android/icon/drawable-xhdpi-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codediodeio/ionic-firestarter/HEAD/resources/android/icon/drawable-xhdpi-icon.png
--------------------------------------------------------------------------------
/resources/ios/splash/Default-Portrait~ipad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codediodeio/ionic-firestarter/HEAD/resources/ios/splash/Default-Portrait~ipad.png
--------------------------------------------------------------------------------
/src/components/heart-button/heart-button.scss:
--------------------------------------------------------------------------------
1 | heart-button {
2 | ion-icon {
3 | color: firebrick;
4 | font-size: 2em;
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/pages/posts-create/posts-create.scss:
--------------------------------------------------------------------------------
1 | page-posts-create {
2 | input {
3 | min-height: 15vh;
4 | font-size: 1.5em;
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/resources/android/icon/drawable-xxhdpi-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codediodeio/ionic-firestarter/HEAD/resources/android/icon/drawable-xxhdpi-icon.png
--------------------------------------------------------------------------------
/resources/android/icon/drawable-xxxhdpi-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codediodeio/ionic-firestarter/HEAD/resources/android/icon/drawable-xxxhdpi-icon.png
--------------------------------------------------------------------------------
/resources/ios/splash/Default-568h@2x~iphone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codediodeio/ionic-firestarter/HEAD/resources/ios/splash/Default-568h@2x~iphone.png
--------------------------------------------------------------------------------
/resources/ios/splash/Default-Landscape-736h.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codediodeio/ionic-firestarter/HEAD/resources/ios/splash/Default-Landscape-736h.png
--------------------------------------------------------------------------------
/resources/ios/splash/Default-Landscape~ipad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codediodeio/ionic-firestarter/HEAD/resources/ios/splash/Default-Landscape~ipad.png
--------------------------------------------------------------------------------
/resources/ios/splash/Default-Portrait@2x~ipad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codediodeio/ionic-firestarter/HEAD/resources/ios/splash/Default-Portrait@2x~ipad.png
--------------------------------------------------------------------------------
/resources/ios/splash/Default-Landscape@2x~ipad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codediodeio/ionic-firestarter/HEAD/resources/ios/splash/Default-Landscape@2x~ipad.png
--------------------------------------------------------------------------------
/resources/ios/splash/Default-Landscape@~ipadpro.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codediodeio/ionic-firestarter/HEAD/resources/ios/splash/Default-Landscape@~ipadpro.png
--------------------------------------------------------------------------------
/resources/ios/splash/Default-Portrait@~ipadpro.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codediodeio/ionic-firestarter/HEAD/resources/ios/splash/Default-Portrait@~ipadpro.png
--------------------------------------------------------------------------------
/src/pages/profile/profile.scss:
--------------------------------------------------------------------------------
1 | page-profile {
2 | img {
3 | border-radius: 50%;
4 | width: 25vw;
5 | max-width: 128px;
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/resources/android/splash/drawable-land-hdpi-screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codediodeio/ionic-firestarter/HEAD/resources/android/splash/drawable-land-hdpi-screen.png
--------------------------------------------------------------------------------
/resources/android/splash/drawable-land-ldpi-screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codediodeio/ionic-firestarter/HEAD/resources/android/splash/drawable-land-ldpi-screen.png
--------------------------------------------------------------------------------
/resources/android/splash/drawable-land-mdpi-screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codediodeio/ionic-firestarter/HEAD/resources/android/splash/drawable-land-mdpi-screen.png
--------------------------------------------------------------------------------
/resources/android/splash/drawable-port-hdpi-screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codediodeio/ionic-firestarter/HEAD/resources/android/splash/drawable-port-hdpi-screen.png
--------------------------------------------------------------------------------
/resources/android/splash/drawable-port-ldpi-screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codediodeio/ionic-firestarter/HEAD/resources/android/splash/drawable-port-ldpi-screen.png
--------------------------------------------------------------------------------
/resources/android/splash/drawable-port-mdpi-screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codediodeio/ionic-firestarter/HEAD/resources/android/splash/drawable-port-mdpi-screen.png
--------------------------------------------------------------------------------
/resources/ios/splash/Default@2x~universal~anyany.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codediodeio/ionic-firestarter/HEAD/resources/ios/splash/Default@2x~universal~anyany.png
--------------------------------------------------------------------------------
/ionic.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "firebaseStarter",
3 | "app_id": "70c69565",
4 | "type": "ionic-angular",
5 | "integrations": {
6 | "cordova": {}
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/resources/android/splash/drawable-land-xhdpi-screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codediodeio/ionic-firestarter/HEAD/resources/android/splash/drawable-land-xhdpi-screen.png
--------------------------------------------------------------------------------
/resources/android/splash/drawable-land-xxhdpi-screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codediodeio/ionic-firestarter/HEAD/resources/android/splash/drawable-land-xxhdpi-screen.png
--------------------------------------------------------------------------------
/resources/android/splash/drawable-port-xhdpi-screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codediodeio/ionic-firestarter/HEAD/resources/android/splash/drawable-port-xhdpi-screen.png
--------------------------------------------------------------------------------
/resources/android/splash/drawable-port-xxhdpi-screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codediodeio/ionic-firestarter/HEAD/resources/android/splash/drawable-port-xxhdpi-screen.png
--------------------------------------------------------------------------------
/resources/android/splash/drawable-land-xxxhdpi-screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codediodeio/ionic-firestarter/HEAD/resources/android/splash/drawable-land-xxxhdpi-screen.png
--------------------------------------------------------------------------------
/resources/android/splash/drawable-port-xxxhdpi-screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codediodeio/ionic-firestarter/HEAD/resources/android/splash/drawable-port-xxxhdpi-screen.png
--------------------------------------------------------------------------------
/src/components/facebook-login/facebook-login.html:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/app/main.ts:
--------------------------------------------------------------------------------
1 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
2 |
3 | import { AppModule } from './app.module';
4 |
5 | platformBrowserDynamic().bootstrapModule(AppModule);
6 |
--------------------------------------------------------------------------------
/src/components/anonymous-login/anonymous-login.html:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/pages/home/home.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Home - Recent Posts
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/pages/user-posts/user-posts.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Posts by {{ displayName }}
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/functions/lib/index.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;AAAA,qCAAqC;AACrC,0CAA0C;AAE1C,wBAAwB;AACX,QAAA,mBAAmB,GAAG,GAAG,CAAC,oBAAoB,CAAC;AAC/C,QAAA,eAAe,GAAO,GAAG,CAAC,eAAe,CAAC;AAEvD,yBAAyB;AAEZ,QAAA,oBAAoB,GAAG,MAAM,CAAC,cAAc,CAAC"}
--------------------------------------------------------------------------------
/functions/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": ["es6"],
4 | "module": "commonjs",
5 | "noImplicitReturns": true,
6 | "outDir": "lib",
7 | "sourceMap": true,
8 | "target": "es6"
9 | },
10 | "compileOnSave": true,
11 | "include": [
12 | "src"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/src/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Ionic",
3 | "short_name": "Ionic",
4 | "start_url": "index.html",
5 | "display": "standalone",
6 | "icons": [{
7 | "src": "assets/imgs/logo.png",
8 | "sizes": "512x512",
9 | "type": "image/png"
10 | }],
11 | "background_color": "#4e8ef7",
12 | "theme_color": "#4e8ef7"
13 | }
--------------------------------------------------------------------------------
/functions/src/index.ts:
--------------------------------------------------------------------------------
1 | import * as agg from './aggregation';
2 | import * as notify from './notifications';
3 |
4 | // Aggregation Functions
5 | export const updateFollowerCount = agg.updateFollowerCounts;
6 | export const updatePostCount = agg.updatePostCount;
7 |
8 | // Notification Functions
9 |
10 | export const unicornNotifications = notify.newUnicornPost;
--------------------------------------------------------------------------------
/src/pages/tabs/tabs.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/components/heart-button/heart-button.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 | {{ heartCount }}
17 |
18 |
--------------------------------------------------------------------------------
/src/pages/profile-edit/profile-edit.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { IonicPageModule } from 'ionic-angular';
3 | import { ProfileEditPage } from './profile-edit';
4 |
5 | @NgModule({
6 | declarations: [
7 | ProfileEditPage,
8 | ],
9 | imports: [
10 | IonicPageModule.forChild(ProfileEditPage),
11 | ],
12 | })
13 | export class ProfileEditPageModule {}
14 |
--------------------------------------------------------------------------------
/firebase.json:
--------------------------------------------------------------------------------
1 | {
2 | "functions": {
3 | "predeploy": "npm --prefix functions run build",
4 | "source": "functions"
5 | },
6 | "hosting": {
7 | "public": "www",
8 | "ignore": [
9 | "firebase.json",
10 | "**/.*",
11 | "**/node_modules/**"
12 | ],
13 | "rewrites": [
14 | {
15 | "source": "**",
16 | "destination": "/index.html"
17 | }
18 | ]
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/.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
--------------------------------------------------------------------------------
/src/pages/login/login.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { NavController, NavParams } from 'ionic-angular';
3 |
4 | @Component({
5 | selector: 'page-login',
6 | templateUrl: 'login.html',
7 | })
8 | export class LoginPage {
9 |
10 | constructor(public navCtrl: NavController, public navParams: NavParams) {
11 | }
12 |
13 | ionViewDidLoad() {
14 | console.log('ionViewDidLoad LoginPage');
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/functions/lib/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | const agg = require("./aggregation");
4 | const notify = require("./notifications");
5 | // Aggregation Functions
6 | exports.updateFollowerCount = agg.updateFollowerCounts;
7 | exports.updatePostCount = agg.updatePostCount;
8 | // Notification Functions
9 | exports.unicornNotifications = notify.newUnicornPost;
10 | //# sourceMappingURL=index.js.map
--------------------------------------------------------------------------------
/src/pages/home/home.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { IonicPageModule } from 'ionic-angular';
3 | import { HomePage } from './home';
4 | import { ComponentsModule } from '../../components/components.module';
5 |
6 | @NgModule({
7 | declarations: [
8 | HomePage,
9 | ],
10 | imports: [
11 | IonicPageModule.forChild(HomePage),
12 | ComponentsModule
13 | ],
14 | })
15 | export class HomePageModule {}
16 |
--------------------------------------------------------------------------------
/resources/README.md:
--------------------------------------------------------------------------------
1 | These are Cordova resources. You can replace icon.png and splash.png and run
2 | `ionic cordova resources` to generate custom icons and splash screens for your
3 | app. See `ionic cordova resources --help` for details.
4 |
5 | Cordova reference documentation:
6 |
7 | - Icons: https://cordova.apache.org/docs/en/latest/config_ref/images.html
8 | - Splash Screens: https://cordova.apache.org/docs/en/latest/reference/cordova-plugin-splashscreen/
9 |
--------------------------------------------------------------------------------
/src/pages/users/users.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { IonicPageModule } from 'ionic-angular';
3 | import { UsersPage } from './users';
4 | import { ComponentsModule } from '../../components/components.module';
5 |
6 |
7 | @NgModule({
8 | declarations: [
9 | UsersPage,
10 | ],
11 | imports: [
12 | IonicPageModule.forChild(UsersPage),
13 | ComponentsModule
14 | ],
15 | })
16 | export class UsersPageModule {}
17 |
--------------------------------------------------------------------------------
/src/pages/profile/profile.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { IonicPageModule } from 'ionic-angular';
3 | import { ProfilePage } from './profile';
4 | import { ComponentsModule } from '../../components/components.module';
5 |
6 | @NgModule({
7 | declarations: [
8 | ProfilePage,
9 | ],
10 | imports: [
11 | IonicPageModule.forChild(ProfilePage),
12 | ComponentsModule
13 | ],
14 | })
15 | export class ProfilePageModule {}
16 |
--------------------------------------------------------------------------------
/src/pages/user-posts/user-posts.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { IonicPageModule } from 'ionic-angular';
3 | import { UserPostsPage } from './user-posts';
4 | import { ComponentsModule } from '../../components/components.module';
5 |
6 | @NgModule({
7 | declarations: [
8 | UserPostsPage,
9 | ],
10 | imports: [
11 | IonicPageModule.forChild(UserPostsPage),
12 | ComponentsModule
13 | ],
14 | })
15 | export class UserPostsPageModule {}
16 |
--------------------------------------------------------------------------------
/src/pages/posts-create/posts-create.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { IonicPageModule } from 'ionic-angular';
3 | import { PostsCreatePage } from './posts-create';
4 | import { ComponentsModule } from '../../components/components.module';
5 |
6 |
7 | @NgModule({
8 | declarations: [
9 | PostsCreatePage,
10 | ],
11 | imports: [
12 | IonicPageModule.forChild(PostsCreatePage),
13 | ComponentsModule
14 | ],
15 | })
16 | export class PostsCreatePageModule {}
17 |
--------------------------------------------------------------------------------
/src/pages/tabs/tabs.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { AuthProvider } from '../../providers/auth/auth';
3 |
4 | @Component({
5 | templateUrl: 'tabs.html'
6 | })
7 | export class TabsPage {
8 |
9 | tab1Root = 'HomePage';
10 | tab2Root = 'PostsCreatePage';
11 | tab3Root = 'UsersPage';
12 | tab4Root = 'ProfilePage';
13 |
14 | constructor(public auth: AuthProvider) {
15 |
16 | }
17 |
18 | ionViewCanEnter() {
19 | return this.auth.isLoggedIn();
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/components/post-feed/post-feed.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | {{ post.content }}
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/pages/login/login.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Ionic Firestarter
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | Welcome to Ionic Firestarter!
13 |
14 | Thanks for installing me :) I am a starter app designed for developers building mobile apps with Ionic and Firebase.
15 | Login and explore Facebook Auth, Firestore, Push Notifications, Analytics, and more!
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/components/user-relationship/user-relationship.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
14 |
15 |
16 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/src/components/fcm-topic/fcm-topic.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
16 |
17 |
18 |
19 |
20 |
21 | You must be on a native device to subscribe to topic notifications
22 |
--------------------------------------------------------------------------------
/src/components/user-logout/user-logout.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { AuthProvider } from '../../providers/auth/auth';
3 | import { NavController } from 'ionic-angular';
4 | import { LoginPage } from '../../pages/login/login';
5 |
6 | @Component({
7 | selector: 'user-logout',
8 | templateUrl: 'user-logout.html'
9 | })
10 | export class UserLogoutComponent {
11 |
12 | constructor(
13 | public auth: AuthProvider,
14 | public navCtrl: NavController
15 | ) {}
16 |
17 | async logout() {
18 | await this.auth.logout();
19 | await this.navCtrl.setRoot(LoginPage)
20 | }
21 |
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/src/pages/profile-edit/profile-edit.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Edit your Profile
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | Display Name
16 |
17 |
18 |
19 |
20 |
21 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/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 | "src/**/*.spec.ts",
22 | "src/**/__tests__/*.ts"
23 | ],
24 | "compileOnSave": false,
25 | "atom": {
26 | "rewriteTsconfig": false
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/components/facebook-login/facebook-login.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { AuthProvider } from '../../providers/auth/auth';
3 | import { NavController } from 'ionic-angular';
4 | import { TabsPage } from '../../pages/tabs/tabs';
5 |
6 | @Component({
7 | selector: 'facebook-login',
8 | templateUrl: 'facebook-login.html'
9 | })
10 |
11 | export class FacebookLoginComponent {
12 |
13 | constructor(
14 | public auth: AuthProvider,
15 | public navCtrl: NavController
16 | ) {}
17 |
18 | async login() {
19 |
20 | await this.auth.facebookLogin();
21 |
22 | await this.navCtrl.setRoot(TabsPage)
23 |
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/src/components/fcm-topic/fcm-topic.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input } from '@angular/core';
2 | import { FcmProvider } from '../../providers/fcm/fcm';
3 | import { Platform } from 'ionic-angular';
4 |
5 |
6 | @Component({
7 | selector: 'fcm-topic',
8 | templateUrl: 'fcm-topic.html'
9 | })
10 | export class FcmTopicComponent {
11 |
12 | @Input() user: any;
13 | @Input() topic: string;
14 |
15 | constructor(public fcm: FcmProvider, private platform: Platform) { }
16 |
17 | get isSubscribed() {
18 | return this.user.topics && this.user.topics[this.topic];
19 | }
20 |
21 | get isSupportedPlatform() {
22 | return this.platform.is('cordova');
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/src/components/heart-button/heart-button.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input } from '@angular/core';
2 | import { DatabaseProvider } from '../../providers/database/database';
3 |
4 |
5 | @Component({
6 | selector: 'heart-button',
7 | templateUrl: 'heart-button.html'
8 | })
9 | export class HeartButtonComponent {
10 |
11 | @Input() userId: string;
12 | @Input() post: any;
13 |
14 | constructor(public db: DatabaseProvider) {}
15 |
16 |
17 | get heartCount(): number {
18 | return this.post.hearts ? Object.keys(this.post.hearts).length : 0
19 | }
20 |
21 | get isHearted(): boolean {
22 | return !!(this.post.hearts && this.post.hearts[this.userId])
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/functions/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "functions",
3 | "scripts": {
4 | "build": "./node_modules/.bin/tslint -p tslint.json && ./node_modules/.bin/tsc",
5 | "serve": "npm run build && firebase serve --only functions",
6 | "shell": "npm run build && firebase experimental:functions:shell",
7 | "start": "npm run shell",
8 | "deploy": "firebase deploy --only functions",
9 | "logs": "firebase functions:log"
10 | },
11 | "main": "lib/index.js",
12 | "dependencies": {
13 | "firebase-admin": "~5.4.2",
14 | "firebase-functions": "^0.7.1"
15 | },
16 | "devDependencies": {
17 | "tslint": "^5.8.0",
18 | "typescript": "^2.5.3"
19 | },
20 | "private": true
21 | }
22 |
--------------------------------------------------------------------------------
/functions/lib/notifications.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"notifications.js","sourceRoot":"","sources":["../src/notifications.ts"],"names":[],"mappings":";;;;;;;;;;AAAA,gDAAgD;AAChD,wCAAwC;AAG3B,QAAA,cAAc,GAAG,SAAS,CAAC,SAAS;KAE5C,QAAQ,CAAC,gBAAgB,CAAC;KAC1B,QAAQ,CAAC,CAAM,KAAK,EAAC,EAAE;IAEpB,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;IAE/B,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IAErE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;QACb,MAAM,CAAC,IAAI,CAAC;IAChB,CAAC;IAED,uBAAuB;IACvB,MAAM,OAAO,GAAG;QACZ,YAAY,EAAE;YACV,KAAK,EAAE,yBAAyB;YAChC,IAAI,EAAE,+BAA+B;YACrC,IAAI,EAAE,uBAAuB;SAChC;KACJ,CAAA;IAED,MAAM,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC,WAAW,CAAC,UAAU,EAAE,OAAO,CAAC,CAAA;AAEjE,CAAC,CAAA,CAAC,CAAC"}
--------------------------------------------------------------------------------
/src/pages/user-posts/user-posts.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { IonicPage, NavController, NavParams } from 'ionic-angular';
3 | // import { DatabaseProvider, Post } from '../../providers/database/database';
4 |
5 | @IonicPage()
6 | @Component({
7 | selector: 'page-user-posts',
8 | templateUrl: 'user-posts.html',
9 | })
10 | export class UserPostsPage {
11 |
12 | constructor(
13 | public navCtrl: NavController,
14 | public navParams: NavParams
15 | ) {}
16 |
17 | ionViewDidLoad() {
18 | }
19 |
20 | get userId() {
21 | return this.navParams.get('userId');
22 | }
23 |
24 | get displayName() {
25 | return this.navParams.get('displayName');
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Specifies intentionally untracked files to ignore when using Git
2 | # http://git-scm.com/docs/gitignore
3 |
4 | NOTES.md
5 | src/env.ts
6 | google-services.json
7 | GoogleService-Info.plist
8 | my-release-key.jks
9 | *.apk
10 | *.p12
11 |
12 |
13 | *~
14 | *.sw[mnpcod]
15 | *.log
16 | *.tmp
17 | *.tmp.*
18 | log.txt
19 | *.sublime-project
20 | *.sublime-workspace
21 | .vscode/
22 | npm-debug.log*
23 |
24 | .idea/
25 | .sourcemaps/
26 | .sass-cache/
27 | .tmp/
28 | .versions/
29 | coverage/
30 | dist/
31 | node_modules/
32 | tmp/
33 | temp/
34 | hooks/
35 | platforms/
36 | plugins/
37 | plugins/android.json
38 | plugins/ios.json
39 | www/
40 | $RECYCLE.BIN/
41 |
42 | .DS_Store
43 | Thumbs.db
44 | UserInterfaceState.xcuserstate
45 |
--------------------------------------------------------------------------------
/src/app/app.scss:
--------------------------------------------------------------------------------
1 | // http://ionicframework.com/docs/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 |
--------------------------------------------------------------------------------
/src/components/user-relationship/user-relationship.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input } from '@angular/core';
2 | import { DatabaseProvider } from '../../providers/database/database';
3 |
4 | @Component({
5 | selector: 'user-relationship',
6 | templateUrl: 'user-relationship.html'
7 | })
8 | export class UserRelationshipComponent {
9 |
10 | @Input() currentUserId; // logged in user
11 | @Input() followId; // user to be followed/unfollowed
12 |
13 | isOwner: boolean;
14 | isFollowing: any;
15 |
16 | constructor(public db: DatabaseProvider) { }
17 |
18 | ngOnInit() {
19 | this.isOwner = this.currentUserId === this.followId;
20 |
21 | this.isFollowing = this.db.isFollowing(this.currentUserId, this.followId);
22 | }
23 |
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/functions/src/notifications.ts:
--------------------------------------------------------------------------------
1 | import * as functions from "firebase-functions";
2 | import * as admin from "firebase-admin";
3 |
4 | export const newUnicornPost = functions.firestore
5 |
6 | .document("posts/{postId}")
7 | .onCreate(async event => {
8 | const post = event.data.data();
9 |
10 | const isUnicorn = post.content.toLowerCase().indexOf("unicorn") >= 0;
11 |
12 | if (!isUnicorn) {
13 | return null;
14 | }
15 |
16 | // Notification content
17 | const payload = {
18 | notification: {
19 | title: "New Post about Unicorns",
20 | body: `Read the latest unicorn post!`,
21 | icon: "https://goo.gl/Fz9nrQ"
22 | }
23 | };
24 |
25 | return admin.messaging().sendToTopic("unicorns", payload);
26 | });
27 |
--------------------------------------------------------------------------------
/src/pages/users/users.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { IonicPage, NavController, NavParams } from 'ionic-angular';
3 | import { DatabaseProvider } from '../../providers/database/database';
4 | import { AuthProvider } from '../../providers/auth/auth';
5 |
6 |
7 | @IonicPage()
8 | @Component({
9 | selector: 'page-users',
10 | templateUrl: 'users.html',
11 | })
12 | export class UsersPage {
13 |
14 | users: any;
15 |
16 | constructor(
17 | public navCtrl: NavController,
18 | public navParams: NavParams,
19 | public auth: AuthProvider,
20 | public db: DatabaseProvider,
21 | ) {}
22 |
23 | ionViewDidLoad() {
24 | this.users = this.db.getUsers();
25 | }
26 |
27 | trackByFn(index, user) {
28 | return user.uid; // or item.id
29 | }
30 |
31 |
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/src/pages/users/users.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Users
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | {{ user.displayName }}
19 |
20 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/src/service-worker.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Check out https://googlechromelabs.github.io/sw-toolbox/ 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/vendor.js',
19 | './build/main.css',
20 | './build/polyfills.js',
21 | 'index.html',
22 | 'manifest.json'
23 | ]
24 | );
25 |
26 | // dynamically cache any other local assets
27 | self.toolbox.router.any('/*', self.toolbox.fastest);
28 |
29 | // for any other requests go to the network, cache,
30 | // and then only use that cached resource if your user goes offline
31 | self.toolbox.router.default = self.toolbox.networkFirst;
32 |
--------------------------------------------------------------------------------
/src/components/anonymous-login/anonymous-login.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { AuthProvider } from '../../providers/auth/auth';
3 | import { NavController } from 'ionic-angular';
4 | import { LoadingController } from 'ionic-angular';
5 | import { TabsPage } from '../../pages/tabs/tabs';
6 |
7 | @Component({
8 | selector: 'anonymous-login',
9 | templateUrl: 'anonymous-login.html'
10 | })
11 | export class AnonymousLoginComponent {
12 | constructor(
13 | public auth: AuthProvider,
14 | public navCtrl: NavController,
15 | public loadingCtrl: LoadingController
16 | ) {}
17 |
18 |
19 | async login() {
20 | const loader = this.loadingCtrl.create({
21 | content: "Logging in anonymously..."
22 | });
23 |
24 | loader.present()
25 |
26 | await this.auth.anonymousLogin();
27 |
28 | loader.dismiss()
29 | await this.navCtrl.setRoot(TabsPage)
30 |
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/src/components/post-feed/post-feed.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, Input } from '@angular/core';
2 | import { DatabaseProvider } from '../../providers/database/database';
3 | import { AuthProvider } from '../../providers/auth/auth';
4 | import { Observable } from 'rxjs/Observable';
5 | import { map } from 'rxjs/operators';
6 |
7 | @Component({
8 | selector: 'post-feed',
9 | templateUrl: 'post-feed.html'
10 | })
11 | export class PostFeedComponent implements OnInit {
12 |
13 | @Input() userId: string;
14 |
15 | posts: Observable;
16 |
17 | constructor(private db: DatabaseProvider, public auth: AuthProvider) { }
18 |
19 | ngOnInit() {
20 |
21 | this.posts = this.db.getRecentPosts().snapshotChanges().pipe(
22 | map(arr => arr.map(doc => {
23 | return { id: doc.payload.doc.id, ...doc.payload.doc.data() }
24 | }))
25 | )
26 | }
27 |
28 | trackByFn(index, post) {
29 | return post.id;
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/src/pages/profile-edit/profile-edit.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { IonicPage, NavController, NavParams } from 'ionic-angular';
3 | import { AuthProvider } from '../../providers/auth/auth';
4 | import { AngularFirestore } from 'angularfire2/firestore';
5 |
6 | @IonicPage()
7 | @Component({
8 | selector: 'page-profile-edit',
9 | templateUrl: 'profile-edit.html',
10 | })
11 | export class ProfileEditPage {
12 |
13 | newName: string;
14 |
15 | constructor(
16 | public navCtrl: NavController,
17 | public navParams: NavParams,
18 | public auth: AuthProvider,
19 | public afs: AngularFirestore
20 | ) {}
21 |
22 |
23 | ionViewDidLoad() {
24 | console.log('ionViewDidLoad ProfileEditPage');
25 | }
26 |
27 | updateProfile(user) {
28 | console.log(this.newName, user)
29 | const ref = this.afs.collection('users').doc(user.uid)
30 |
31 | return ref.update({ displayName: this.newName })
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/pages/posts-create/posts-create.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Create a New Post
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | Upload an image from your device and add some content to the post
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/src/pages/home/home.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { IonicPage, NavController, NavParams, Platform } from 'ionic-angular';
3 | import { AdMobFree, AdMobFreeInterstitialConfig } from '@ionic-native/admob-free';
4 |
5 | @IonicPage()
6 | @Component({
7 | selector: 'page-home',
8 | templateUrl: 'home.html',
9 | })
10 | export class HomePage {
11 |
12 | constructor(
13 | public navCtrl: NavController,
14 | public navParams: NavParams,
15 | public adMob: AdMobFree,
16 | public platform: Platform
17 | ) {
18 | }
19 |
20 | ionViewDidLoad() {
21 | console.log('ionViewDidLoad HomePage');
22 | if (this.platform.is('cordova')) {
23 | const ad: AdMobFreeInterstitialConfig = {
24 | id: 'ca-app-pub-6881663307118312/4719939210',
25 | isTesting: false,
26 | autoShow: false
27 | }
28 | this.adMob.interstitial.config(ad);
29 |
30 | // 1 in 4 chance to see an ad
31 | if (new Date().getTime() % 4 === 0) {
32 | this.adMob.interstitial.prepare()
33 | }
34 |
35 | }
36 |
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/src/components/fcm-handler/fcm-handler.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from "@angular/core";
2 | import { FcmProvider } from "../../providers/fcm/fcm";
3 | import { ToastController } from "ionic-angular";
4 | import { tap } from "rxjs/operators";
5 |
6 | @Component({
7 | selector: "fcm-handler",
8 | templateUrl: "fcm-handler.html"
9 | })
10 | export class FcmHandlerComponent implements OnInit {
11 | constructor(private fcm: FcmProvider, private toastCtrl: ToastController) {}
12 |
13 | ngOnInit() {
14 | // Get the initial token
15 | this.fcm.getToken();
16 |
17 | // Update on refresh
18 | this.fcm.monitorTokenRefresh().subscribe();
19 |
20 | // Listen to incoming messages
21 | this.fcm
22 | .listenToNotifications()
23 | .pipe(
24 | tap(msg => {
25 | // show a toast
26 | if (msg) {
27 | const toast = this.toastCtrl.create({
28 | message: msg.body,
29 | duration: 3000
30 | });
31 | toast.present();
32 | }
33 | })
34 | )
35 | .subscribe();
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/pages/profile/profile.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { IonicPage, NavController, NavParams } from 'ionic-angular';
3 | import { AuthProvider } from '../../providers/auth/auth';
4 | import { RemoteConfigProvider } from '../../providers/remote-config/remote-config';
5 |
6 |
7 | @IonicPage()
8 | @Component({
9 | selector: 'page-profile',
10 | templateUrl: 'profile.html',
11 | })
12 | export class ProfilePage {
13 |
14 | bannerText: Promise;
15 |
16 | constructor(
17 | public navCtrl: NavController,
18 | public navParams: NavParams,
19 | public auth: AuthProvider,
20 | public remoteConfig: RemoteConfigProvider
21 | ) {}
22 |
23 | ionViewCanEnter() {
24 | return this.auth.isLoggedIn();
25 | }
26 |
27 |
28 | ionViewDidLoad() {
29 | console.log('ionViewDidLoad ProfilePage');
30 | this.bannerText = this.remoteConfig.getValue('profile_banner')
31 | }
32 |
33 | openEditPage() {
34 | this.navCtrl.push('ProfileEditPage')
35 | }
36 |
37 | openPostFeed(user) {
38 | this.navCtrl.push('UserPostsPage', {
39 | userId: user.uid,
40 | name: user.displayName
41 | })
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/src/pages/posts-create/posts-create.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { IonicPage, NavController, NavParams } from 'ionic-angular';
3 | import { DatabaseProvider, Post } from '../../providers/database/database';
4 | import { AuthProvider } from '../../providers/auth/auth';
5 | import { AnalyticsProvider } from '../../providers/analytics/analytics';
6 |
7 | @IonicPage()
8 | @Component({
9 | selector: 'page-posts-create',
10 | templateUrl: 'posts-create.html',
11 | })
12 | export class PostsCreatePage {
13 |
14 | post: Partial = {};
15 |
16 | constructor(
17 | public navCtrl: NavController,
18 | public navParams: NavParams,
19 | public db: DatabaseProvider,
20 | public auth: AuthProvider,
21 | public analytics: AnalyticsProvider
22 | ) {}
23 |
24 | ionViewDidLoad() {
25 | console.log('ionViewDidLoad PostsCreatePage');
26 | }
27 |
28 | async create(user) {
29 | await this.db.createPost(user.uid, this.post as Post)
30 | await this.analytics.logEvent('create_post', {})
31 | this.post = {}
32 | await this.navCtrl.setRoot('HomePage')
33 | }
34 |
35 | updateURL(e) {
36 | this.post.img = e;
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { Platform } from 'ionic-angular';
3 | import { StatusBar } from '@ionic-native/status-bar';
4 | import { SplashScreen } from '@ionic-native/splash-screen';
5 |
6 | import { TabsPage } from '../pages/tabs/tabs';
7 | import { LoginPage } from '../pages/login/login';
8 |
9 | import { AuthProvider } from '../providers/auth/auth';
10 | import { AnalyticsProvider } from '../providers/analytics/analytics';
11 | import { RemoteConfigProvider } from '../providers/remote-config/remote-config';
12 |
13 | @Component({
14 | templateUrl: 'app.html'
15 | })
16 | export class MyApp {
17 | rootPage:any = null;
18 |
19 | constructor(
20 | platform: Platform,
21 | statusBar: StatusBar,
22 | splashScreen: SplashScreen,
23 | auth: AuthProvider,
24 | analytics: AnalyticsProvider,
25 | remoteConfig: RemoteConfigProvider
26 | ) {
27 |
28 | platform.ready().then(() => {
29 |
30 | auth.getCurrentUser()
31 | .then(user => {
32 |
33 | if (user) {
34 | this.rootPage = TabsPage
35 | } else {
36 | this.rootPage = LoginPage
37 | }
38 |
39 | statusBar.styleDefault();
40 | splashScreen.hide();
41 |
42 | remoteConfig.initialize()
43 |
44 | })
45 |
46 | });
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/pages/profile/profile.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Profile
5 |
6 |
7 |
8 |
9 |
10 |
11 | {{ bannerText | async }}
12 | Value from via Remote Config
13 |
14 |
15 |
16 |
17 |
18 | {{ user.displayName }}
19 |
20 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | {{ user.postCount || 0 }}
30 | Posts
31 |
32 |
33 | {{ user.followingCount || 0 }}
34 | Following
35 |
36 |
37 | {{ user.followerCount || 0 }}
38 | Followers
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | Get Notifications about Unicorns
50 |
51 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/functions/lib/notifications.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3 | return new (P || (P = Promise))(function (resolve, reject) {
4 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6 | function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
7 | step((generator = generator.apply(thisArg, _arguments || [])).next());
8 | });
9 | };
10 | Object.defineProperty(exports, "__esModule", { value: true });
11 | const functions = require("firebase-functions");
12 | const admin = require("firebase-admin");
13 | exports.newUnicornPost = functions.firestore
14 | .document('posts/{postId}')
15 | .onCreate((event) => __awaiter(this, void 0, void 0, function* () {
16 | const post = event.data.data();
17 | const isUnicorn = post.content.toLowerCase().indexOf("unicorn") >= 0;
18 | if (!isUnicorn) {
19 | return null;
20 | }
21 | // Notification content
22 | const payload = {
23 | notification: {
24 | title: 'New Post about Unicorns',
25 | body: `Read the latest unicorn post!`,
26 | icon: 'https://goo.gl/Fz9nrQ'
27 | }
28 | };
29 | return admin.messaging().sendToTopic("unicorns", payload);
30 | }));
31 | //# sourceMappingURL=notifications.js.map
--------------------------------------------------------------------------------
/src/providers/remote-config/remote-config.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Platform } from 'ionic-angular';
3 | import { Firebase } from '@ionic-native/firebase';
4 |
5 | @Injectable()
6 | export class RemoteConfigProvider {
7 | private defaults = {
8 | profile_banner: 'Default Hello'
9 | };
10 |
11 | constructor(public firebaseNative: Firebase, public platform: Platform) {}
12 |
13 | initialize() {
14 | if (this.platform.is('cordova')) {
15 | const win = (window as any);
16 |
17 | win.FirebasePlugin.fetch(
18 | 600,
19 | () => {
20 | console.log('fetched remote config ');
21 | win.FirebasePlugin.activateFetched(
22 | () => {
23 | console.log('activated remote config');
24 | },
25 | error => {
26 | console.error('error initializing remote config', error);
27 | }
28 | );
29 | },
30 | error => {
31 | console.error('error fetching remote config', error);
32 | }
33 | );
34 | }
35 | }
36 |
37 | async getValue(key: string) {
38 |
39 | if (this.platform.is('cordova')) {
40 | const win = window as any;
41 |
42 | const remoteVal = await new Promise((resolve, reject) => {
43 | win.FirebasePlugin.getValue(
44 | key,
45 | value => {
46 | console.log('value', value);
47 | resolve(value);
48 | },
49 | error => {
50 | console.log('error getting value' + error);
51 | reject(error);
52 | }
53 | );
54 | });
55 |
56 | return remoteVal || this.defaults[key];
57 | } else {
58 | // PWA Implementation
59 | return this.defaults[key]
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Ionic App
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/src/components/components.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 |
3 | import { IonicModule } from 'ionic-angular';
4 | import { CommonModule } from '@angular/common';
5 |
6 | import { FacebookLoginComponent } from './facebook-login/facebook-login';
7 | import { AnonymousLoginComponent } from './anonymous-login/anonymous-login';
8 | import { UserLogoutComponent } from './user-logout/user-logout';
9 | import { PostFeedComponent } from './post-feed/post-feed';
10 | import { HeartButtonComponent } from './heart-button/heart-button';
11 | import { UserRelationshipComponent } from './user-relationship/user-relationship';
12 | import { ImageUploadComponent, UploadModal } from './image-upload/image-upload';
13 | import { FcmHandlerComponent } from './fcm-handler/fcm-handler';
14 | import { FcmTopicComponent } from './fcm-topic/fcm-topic';
15 |
16 |
17 | @NgModule({
18 | declarations: [
19 | FacebookLoginComponent,
20 | FacebookLoginComponent,
21 | AnonymousLoginComponent,
22 | UserLogoutComponent,
23 | PostFeedComponent,
24 | HeartButtonComponent,
25 | UserRelationshipComponent,
26 | ImageUploadComponent,
27 | UploadModal,
28 | FcmHandlerComponent,
29 | FcmTopicComponent,
30 |
31 | ],
32 | imports: [
33 | CommonModule,
34 | IonicModule
35 | ],
36 | exports: [
37 | FacebookLoginComponent,
38 | FacebookLoginComponent,
39 | AnonymousLoginComponent,
40 | UserLogoutComponent,
41 | PostFeedComponent,
42 | HeartButtonComponent,
43 | UserRelationshipComponent,
44 | ImageUploadComponent,
45 | UploadModal,
46 | FcmHandlerComponent,
47 | FcmTopicComponent,
48 |
49 | ],
50 | entryComponents: [
51 | UploadModal
52 | ]
53 | })
54 | export class ComponentsModule { }
55 |
--------------------------------------------------------------------------------
/src/providers/analytics/analytics.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { App, Platform } from 'ionic-angular';
3 | import { Firebase } from '@ionic-native/firebase';
4 | import { AuthProvider } from '../auth/auth';
5 |
6 | @Injectable()
7 | export class AnalyticsProvider {
8 | constructor(
9 | app: App,
10 | auth: AuthProvider,
11 | private platform: Platform,
12 | private firebaseNative: Firebase,
13 | ) {
14 |
15 | if (platform.is('cordova')) {
16 | // Tracks the current page name
17 | app.viewDidEnter.subscribe(view => {
18 | firebaseNative.setScreenName(view.name);
19 | firebaseNative.logEvent('page_view', { page: view.name });
20 | });
21 | auth.user.subscribe(user => {
22 | const uid = user ? user.uid : 'guest';
23 | firebaseNative.setUserId(uid);
24 | });
25 |
26 |
27 | }
28 | }
29 |
30 | logEvent(event: string, data?: Object) {
31 | if (this.platform.is('cordova')) {
32 | console.log('analytics log...', event);
33 | return this.firebaseNative.logEvent(event, data);
34 | }
35 | }
36 |
37 | //// REMOTE CONFIG ////
38 |
39 | // getRc(){
40 | // (window).FirebasePlugin.fetch(600, result => {
41 | // // activate the fetched remote config
42 | // console.log(JSON.stringify(result)); //Always "OK"
43 | // (window).FirebasePlugin.activateFetched(
44 | // // Android seems to return error always, so we want to cath both
45 | // result => {
46 | // console.log(JSON.stringify(result)); //either true or false
47 | // (window).FirebasePlugin.getValue("value", result => {
48 | // console.log(result)
49 | // }, reason => {
50 | // console.warn(`Error ${reason}`);
51 | // });
52 | // }, reason => {
53 | // console.warn(reason);
54 | // }
55 | // )
56 | // });
57 | // }
58 |
59 |
60 |
61 | }
62 |
--------------------------------------------------------------------------------
/functions/lib/aggregation.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"aggregation.js","sourceRoot":"","sources":["../src/aggregation.ts"],"names":[],"mappings":";;;;;;;;;;AAAA,gDAAgD;AAEhD,wCAAwC;AACxC,KAAK,CAAC,aAAa,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,CAAC;AAEpC,QAAA,oBAAoB,GAAG,SAAS,CAAC,SAAS;KAElD,QAAQ,CAAC,gCAAgC,CAAC;KAC1C,OAAO,CAAC,KAAK,CAAC,EAAE;IAEb,8CAA8C;IAC9C,uEAAuE;IACvE,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,IAAI,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;QAC3C,MAAM,CAAC,IAAI,CAAC;IAChB,CAAC;IAED,kEAAkE;IAClE,MAAM,WAAW,GAAG,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IAE9C,mBAAmB;IACnB,MAAM,GAAG,GAAG,KAAK,CAAC,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAEnD,MAAM,UAAU,GAAG,GAAG,CAAC,CAAC,CAAC,CAAA;IACzB,MAAM,UAAU,GAAG,GAAG,CAAC,CAAC,CAAC,CAAA;IAEzB,mCAAmC;IACnC,MAAM,EAAE,GAAG,KAAK,CAAC,SAAS,EAAE,CAAA;IAE5B,MAAM,WAAW,GAAG,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,CAAA;IAC1D,MAAM,WAAW,GAAG,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,CAAA;IAE1D,+BAA+B;IAC/B,MAAM,CAAC,EAAE,CAAC,cAAc,CAAC,CAAM,CAAC,EAAC,EAAE;QAE/B,6BAA6B;QAC7B,MAAM,QAAQ,GAAG,MAAM,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,CAAA;QACzC,MAAM,QAAQ,GAAG,MAAM,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,CAAA;QAEzC,qBAAqB;QACrB,MAAM,cAAc,GAAG;YACnB,cAAc,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,cAAc,IAAI,CAAC,CAAC,GAAG,WAAW;SACtE,CAAA;QACD,MAAM,cAAc,GAAG;YACnB,aAAa,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,aAAa,IAAI,CAAC,CAAC,GAAG,WAAW;SACpE,CAAA;QAED,kBAAkB;QAClB,MAAM,CAAC,CAAC,GAAG,CAAC,WAAW,EAAE,cAAc,EAAE,EAAE,KAAK,EAAG,IAAI,EAAE,CAAC,CAAA;QAC1D,MAAM,CAAC,CAAC,GAAG,CAAC,WAAW,EAAE,cAAc,EAAE,EAAE,KAAK,EAAG,IAAI,EAAE,CAAC,CAAA;QAE1D,MAAM,CAAC,CAAC,CAAA;IACZ,CAAC,CAAA,CAAC,CAAA;AAEN,CAAC,CAAC,CAAA;AAGO,QAAA,eAAe,GAAG,SAAS,CAAC,SAAS;KAE7C,QAAQ,CAAC,gBAAgB,CAAC;KAC1B,QAAQ,CAAC,CAAM,KAAK,EAAC,EAAE;IAEpB,sBAAsB;IACtB,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM,CAAC;IAExC,qBAAqB;IACrB,MAAM,EAAE,GAAG,KAAK,CAAC,SAAS,EAAE,CAAA;IAC5B,MAAM,OAAO,GAAG,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;IAElD,oBAAoB;IACpB,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE,CAAA;IAGhC,sCAAsC;IACtC,MAAM,SAAS,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,SAAS,IAAI,CAAC,CAAC,GAAG,CAAC,CAAA;IAElD,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC,CAAA;AAE5C,CAAC,CAAA,CAAC,CAAA"}
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Ionic Firebase Starter
2 |
3 | [](https://goo.gl/qF8Q5r)
4 |
5 | An Ionic + Firebase starter app targeted at native deployment to iOS and Android platforms (and PWA).
6 |
7 | ## Features
8 |
9 | This app is demonstrates variety of features that can be deployed cross-platform. In most cases, you can drop a provider/component into an existing app painlessly.
10 |
11 | ### Auth
12 |
13 | - Anonymous and Facebook User Auth
14 | - Customizable User Profile
15 |
16 | ### Firestore
17 |
18 | - Basic CRUD Demo
19 | - Heart/Liking System
20 | - User Follow/Unfollow System
21 | - Automatic Data Aggregation with Cloud Functions
22 |
23 | ### Storage
24 |
25 | - Native Camera Capture
26 | - Firebase Storage Uploads
27 |
28 | ### Push Notifications
29 |
30 | - Multi-device Token Management
31 | - Topic based Notifications
32 | - Automatic Notifications with Cloud Functions
33 |
34 | ### Firebase iOS Android Platform Features
35 |
36 | - Collect Custom User Analytics
37 | - Create a Dymanic User Experience with Remote Config
38 | - Increase Conversions with Predictions
39 | - Serve Ads with Admob
40 |
41 | ## Install Steps
42 |
43 | If only targeting the web, you can skip steps 2 and 3 below.
44 |
45 | ### Ionic App
46 |
47 | 0. `git clone` this repo, cd into it, and run `npm install`
48 | 1. Add your Firebase web config to the `app.module`
49 | 2. Save `google-services.json` and `GoogleService-Info.plist` from Firebase to the project root.
50 | 3. Run `ionic cordova emulate android -l -c` or (ios) to
51 |
52 | ### Cloud Functions Deployment
53 |
54 | Cloud functions handle backend tasks, such as push notifications and data aggregation.
55 |
56 | 0. `cd functions`
57 | 1. `npm install`
58 | 2. `firebase deploy --only functions`
59 |
60 | Building a native app is hard... Watch the videos [Ionic Native + Firebase](https://projects.angularfirebase.com/p/ionic-native-with-firebase) or get in touch on Slack.
61 |
62 |
63 | ## License
64 |
65 | You must enroll in the [Ionic Native + Firebase](https://projects.angularfirebase.com/p/ionic-native-with-firebase) for an unrestricted commercial license to the source code.
66 |
67 |
68 |
--------------------------------------------------------------------------------
/functions/src/aggregation.ts:
--------------------------------------------------------------------------------
1 | import * as functions from "firebase-functions";
2 |
3 | import * as admin from "firebase-admin";
4 | admin.initializeApp(functions.config().firebase);
5 |
6 | export const updateFollowerCounts = functions.firestore
7 |
8 | .document("relationships/{relationshipId}")
9 | .onWrite(event => {
10 | // Check if previous and current data exists,
11 | // If true, it's an update and we don't want to change the follow count
12 | if (event.data.exists && event.data.previous) {
13 | return null;
14 | }
15 |
16 | // Otherwise, we want to +1 for a new doc, or -1 for a deleted doc
17 | const countChange = event.data.exists ? 1 : -1;
18 |
19 | // Get the user ids
20 | const ids = event.params.relationshipId.split("_");
21 |
22 | const followerId = ids[0];
23 | const followedId = ids[1];
24 |
25 | // Reference the document locations
26 | const db = admin.firestore();
27 |
28 | const followerRef = db.collection("users").doc(followerId);
29 | const followedRef = db.collection("users").doc(followedId);
30 |
31 | // Return a transaction promise
32 | return db.runTransaction(async t => {
33 | // Fetch the data from the DB
34 | const follower = await t.get(followerRef);
35 | const followed = await t.get(followedRef);
36 |
37 | // format the counts
38 | const followerUpdate = {
39 | followingCount: (follower.data().followingCount || 0) + countChange
40 | };
41 | const followedUpdate = {
42 | followerCount: (followed.data().followerCount || 0) + countChange
43 | };
44 |
45 | // run the updates
46 | await t.set(followerRef, followerUpdate, { merge: true });
47 | await t.set(followedRef, followedUpdate, { merge: true });
48 |
49 | return t;
50 | });
51 | });
52 |
53 | export const updatePostCount = functions.firestore
54 |
55 | .document("posts/{postId}")
56 | .onCreate(async event => {
57 | // Get the post UserID
58 | const userId = event.data.data().userId;
59 |
60 | // Reference user doc
61 | const db = admin.firestore();
62 | const userRef = db.collection("users").doc(userId);
63 |
64 | // Get the user data
65 | const user = await userRef.get();
66 |
67 | // Update the count and run the update
68 | const postCount = (user.data().postCount || 0) + 1;
69 |
70 | return userRef.update({ postCount });
71 | });
72 |
--------------------------------------------------------------------------------
/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule, ErrorHandler } from '@angular/core';
2 | import { BrowserModule } from '@angular/platform-browser';
3 | import { IonicApp, IonicModule, IonicErrorHandler } from 'ionic-angular';
4 | import { MyApp } from './app.component';
5 |
6 | import { TabsPage } from '../pages/tabs/tabs';
7 | import { LoginPage } from '../pages/login/login';
8 |
9 | import { StatusBar } from '@ionic-native/status-bar';
10 | import { SplashScreen } from '@ionic-native/splash-screen';
11 | import { AuthProvider } from '../providers/auth/auth';
12 |
13 | import { AngularFireModule } from 'angularfire2';
14 | import { AngularFirestoreModule } from 'angularfire2/firestore';
15 | import { AngularFireStorageModule } from 'angularfire2/storage';
16 | import { AngularFireAuthModule } from 'angularfire2/auth';
17 |
18 | /// DELETE this line
19 | import { firebaseConfig } from '../env';
20 |
21 | /// ADD your firebase web credentials in the object below
22 |
23 | // const firebaseConfig = {
24 | // }
25 |
26 | import { Firebase } from '@ionic-native/firebase';
27 | import { Facebook } from '@ionic-native/facebook';
28 | import { Camera } from '@ionic-native/camera';
29 | import { AdMobFree } from '@ionic-native/admob-free';
30 |
31 | import { ComponentsModule } from '../components/components.module'
32 | import { DatabaseProvider } from '../providers/database/database';
33 | import { FcmProvider } from '../providers/fcm/fcm';
34 | import { AnalyticsProvider } from '../providers/analytics/analytics';
35 | import { RemoteConfigProvider } from '../providers/remote-config/remote-config';
36 |
37 |
38 | @NgModule({
39 | declarations: [
40 | MyApp,
41 | TabsPage,
42 | LoginPage,
43 | ],
44 | imports: [
45 | BrowserModule,
46 | IonicModule.forRoot(MyApp),
47 | AngularFireModule.initializeApp(firebaseConfig),
48 | AngularFirestoreModule,
49 | AngularFireStorageModule,
50 | AngularFireAuthModule,
51 | ComponentsModule
52 | ],
53 | bootstrap: [IonicApp],
54 | entryComponents: [
55 | MyApp,
56 | TabsPage,
57 | LoginPage
58 | ],
59 | providers: [
60 | StatusBar,
61 | SplashScreen,
62 | {provide: ErrorHandler, useClass: IonicErrorHandler},
63 | AuthProvider,
64 | Firebase,
65 | Facebook,
66 | DatabaseProvider,
67 | Camera,
68 | AdMobFree,
69 | FcmProvider,
70 | AnalyticsProvider,
71 | RemoteConfigProvider
72 | ]
73 | })
74 | export class AppModule {}
75 |
--------------------------------------------------------------------------------
/src/theme/variables.scss:
--------------------------------------------------------------------------------
1 | // Ionic Variables and Theming. For more info, please see:
2 | // http://ionicframework.com/docs/theming/
3 |
4 | // Font path is used to include ionicons,
5 | // roboto, and noto sans fonts
6 | $font-path: "../assets/fonts";
7 |
8 |
9 | // The app direction is used to include
10 | // rtl styles in your app. For more info, please see:
11 | // http://ionicframework.com/docs/theming/rtl-support/
12 | $app-direction: ltr;
13 |
14 |
15 | @import "ionic.globals";
16 |
17 |
18 | // Shared Variables
19 | // --------------------------------------------------
20 | // To customize the look and feel of this app, you can override
21 | // the Sass variables found in Ionic's source scss files.
22 | // To view all the possible Ionic variables, see:
23 | // http://ionicframework.com/docs/theming/overriding-ionic-variables/
24 |
25 |
26 |
27 |
28 | // Named Color Variables
29 | // --------------------------------------------------
30 | // Named colors makes it easy to reuse colors on various components.
31 | // It's highly recommended to change the default colors
32 | // to match your app's branding. Ionic uses a Sass map of
33 | // colors so you can add, rename and remove colors as needed.
34 | // The "primary" color is the only required color in the map.
35 |
36 | $colors: (
37 | primary: #488aff,
38 | secondary: #32db64,
39 | danger: #f53d3d,
40 | light: #f4f4f4,
41 | dark: #222
42 | );
43 |
44 |
45 | // App iOS Variables
46 | // --------------------------------------------------
47 | // iOS only Sass variables can go here
48 |
49 |
50 |
51 |
52 | // App Material Design Variables
53 | // --------------------------------------------------
54 | // Material Design only Sass variables can go here
55 |
56 |
57 |
58 |
59 | // App Windows Variables
60 | // --------------------------------------------------
61 | // Windows only Sass variables can go here
62 |
63 |
64 |
65 |
66 | // App Theme
67 | // --------------------------------------------------
68 | // Ionic apps can have different themes applied, which can
69 | // then be future customized. This import comes last
70 | // so that the above variables are used and Ionic's
71 | // default are overridden.
72 |
73 | @import "ionic.theme.default";
74 |
75 |
76 | // Ionicons
77 | // --------------------------------------------------
78 | // The premium icon font for Ionic. For more info, please see:
79 | // http://ionicframework.com/docs/ionicons/
80 |
81 | @import "ionic.ionicons";
82 |
83 |
84 | // Fonts
85 | // --------------------------------------------------
86 |
87 | @import "roboto";
88 | @import "noto-sans";
89 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "firebaseStarter",
3 | "version": "0.0.1",
4 | "author": "Ionic Framework",
5 | "homepage": "http://ionicframework.com/",
6 | "private": true,
7 | "scripts": {
8 | "clean": "ionic-app-scripts clean",
9 | "build": "ionic-app-scripts build",
10 | "lint": "ionic-app-scripts lint",
11 | "ionic:build": "ionic-app-scripts build",
12 | "ionic:serve": "ionic-app-scripts serve"
13 | },
14 | "dependencies": {
15 | "@angular/common": "5.0.3",
16 | "@angular/compiler": "5.0.3",
17 | "@angular/compiler-cli": "5.0.3",
18 | "@angular/core": "5.0.3",
19 | "@angular/forms": "5.0.3",
20 | "@angular/http": "5.0.3",
21 | "@angular/platform-browser": "5.0.3",
22 | "@angular/platform-browser-dynamic": "5.0.3",
23 | "@ionic-native/admob-free": "^4.5.3",
24 | "@ionic-native/camera": "^4.5.3",
25 | "@ionic-native/core": "4.4.0",
26 | "@ionic-native/facebook": "^4.5.3",
27 | "@ionic-native/firebase": "^4.5.3",
28 | "@ionic-native/splash-screen": "4.4.0",
29 | "@ionic-native/status-bar": "4.4.0",
30 | "@ionic/pro": "1.0.20",
31 | "@ionic/storage": "2.1.3",
32 | "and": "0.0.3",
33 | "angularfire2": "^5.0.0-rc.6",
34 | "chance": "^1.0.13",
35 | "cordova-android": "7.0.0",
36 | "cordova-ios": "4.5.4",
37 | "cordova-plugin-camera": "^4.0.2",
38 | "cordova-plugin-device": "^2.0.1",
39 | "cordova-plugin-facebook4": "^1.9.1",
40 | "cordova-plugin-firebase": "git+https://github.com/arnesson/cordova-plugin-firebase.git",
41 | "cordova-plugin-ionic-keyboard": "^2.0.5",
42 | "cordova-plugin-ionic-webview": "^1.1.16",
43 | "cordova-plugin-splashscreen": "^5.0.2",
44 | "cordova-plugin-statusbar": "^2.4.1",
45 | "cordova-plugin-whitelist": "^1.3.3",
46 | "cordova-promise-polyfill": "0.0.2",
47 | "firebase": "^4.11.0",
48 | "ionic-angular": "3.9.2",
49 | "ionicons": "3.0.0",
50 | "lycwed-cordova-plugin-admob-free": "git+https://github.com/RenaudROHLINGER/lycwed-cordova-plugin-admob-free.git",
51 | "rxjs": "5.5.2",
52 | "sw-toolbox": "3.6.0",
53 | "zone.js": "0.8.18"
54 | },
55 | "devDependencies": {
56 | "@ionic/app-scripts": "3.1.8",
57 | "typescript": "2.4.2"
58 | },
59 | "description": "An Ionic project",
60 | "cordova": {
61 | "plugins": {
62 | "cordova-plugin-whitelist": {},
63 | "cordova-plugin-device": {},
64 | "cordova-plugin-splashscreen": {},
65 | "cordova-plugin-ionic-webview": {},
66 | "cordova-plugin-ionic-keyboard": {},
67 | "cordova-plugin-facebook4": {
68 | "APP_ID": "1647541315328951",
69 | "APP_NAME": "Ionic Firestarter"
70 | },
71 | "cordova-plugin-statusbar": {},
72 | "cordova-plugin-firebase": {},
73 | "lycwed-cordova-plugin-admob-free": {},
74 | "cordova-plugin-camera": {
75 | "CAMERA_USAGE_DESCRIPTION": "App would like to access the camera",
76 | "PHOTOLIBRARY_USAGE_DESCRIPTION": "App would like to access the photo library"
77 | }
78 | },
79 | "platforms": [
80 | "android",
81 | "ios"
82 | ]
83 | }
84 | }
--------------------------------------------------------------------------------
/src/providers/database/database.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import * as firebase from 'firebase/app';
3 | import {
4 | AngularFirestore,
5 | AngularFirestoreCollection
6 | } from 'angularfire2/firestore';
7 |
8 |
9 | export interface Post {
10 | userId: string;
11 | createdAt: Date;
12 | image: string;
13 | content: string;
14 | likeCount: number;
15 | [key: string]: any;
16 | }
17 |
18 | @Injectable()
19 | export class DatabaseProvider {
20 | private postsRef: AngularFirestoreCollection;
21 |
22 | constructor(private afs: AngularFirestore) {
23 | this.postsRef = this.afs.collection('posts');
24 | }
25 |
26 | getRecentPosts() {
27 | return this.afs.collection('posts', ref =>
28 | ref.orderBy('createdAt', 'desc').limit(10)
29 | );
30 | }
31 |
32 | getUserPosts(userId: string) {
33 | return this.afs.collection('posts', ref =>
34 | ref
35 | .orderBy('createdAt', 'desc')
36 | .where('userId', '==', userId)
37 | .limit(10)
38 | );
39 | }
40 |
41 | createPost(userId: string, data: Post) {
42 | const createdAt = firebase.firestore.FieldValue.serverTimestamp();
43 | const doc = { userId, createdAt, ...data };
44 | return this.postsRef.add(doc);
45 | }
46 |
47 | deletePost(id: string) {
48 | return this.postsRef.doc(id).delete();
49 | }
50 |
51 | //// HEARTS ////
52 |
53 | createHeart(userId: string, post: Post) {
54 | const hearts = post.hearts || {};
55 | hearts[userId] = true;
56 |
57 | return this.afs.doc(`posts/${post.id}`).update({ hearts });
58 | }
59 |
60 | removeHeart(userId: string, post: Post) {
61 | const hearts = post.hearts;
62 | delete post.hearts[userId];
63 |
64 | return this.afs.doc(`posts/${post.id}`).update({ hearts });
65 | }
66 |
67 | //// RELATIONSHIPS ////
68 |
69 | getUsers() {
70 | return this.afs.collection('users', ref => ref.limit(10)).valueChanges();
71 | }
72 |
73 | follow(followerId: string, followedId: string) {
74 | const docId = this.concatIds(followerId, followedId);
75 | const createdAt = firebase.firestore.FieldValue.serverTimestamp();
76 |
77 | const data = {
78 | followerId,
79 | followedId,
80 | createdAt
81 | };
82 |
83 | return this.afs
84 | .collection('relationships')
85 | .doc(docId)
86 | .set(data);
87 | }
88 |
89 | unfollow(followerId: string, followedId: string) {
90 | const docId = this.concatIds(followerId, followedId);
91 |
92 | return this.afs
93 | .collection('relationships')
94 | .doc(docId)
95 | .delete();
96 | }
97 |
98 | isFollowing(followerId: string, followedId: string) {
99 | const docId = this.concatIds(followerId, followedId);
100 |
101 | return this.afs
102 | .collection('relationships')
103 | .doc(docId)
104 | .valueChanges();
105 | }
106 |
107 | // Helper to format the docId for relationships
108 | private concatIds(a: string, b: string) {
109 | return `${a}_${b}`;
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/functions/lib/aggregation.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3 | return new (P || (P = Promise))(function (resolve, reject) {
4 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6 | function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
7 | step((generator = generator.apply(thisArg, _arguments || [])).next());
8 | });
9 | };
10 | Object.defineProperty(exports, "__esModule", { value: true });
11 | const functions = require("firebase-functions");
12 | const admin = require("firebase-admin");
13 | admin.initializeApp(functions.config().firebase);
14 | exports.updateFollowerCounts = functions.firestore
15 | .document('relationships/{relationshipId}')
16 | .onWrite(event => {
17 | // Check if previous and current data exists,
18 | // If true, it's an update and we don't want to change the follow count
19 | if (event.data.exists && event.data.previous) {
20 | return null;
21 | }
22 | // Otherwise, we want to +1 for a new doc, or -1 for a deleted doc
23 | const countChange = event.data.exists ? 1 : -1;
24 | // Get the user ids
25 | const ids = event.params.relationshipId.split('_');
26 | const followerId = ids[0];
27 | const followedId = ids[1];
28 | // Reference the document locations
29 | const db = admin.firestore();
30 | const followerRef = db.collection('users').doc(followerId);
31 | const followedRef = db.collection('users').doc(followedId);
32 | // Return a transaction promise
33 | return db.runTransaction((t) => __awaiter(this, void 0, void 0, function* () {
34 | // Fetch the data from the DB
35 | const follower = yield t.get(followerRef);
36 | const followed = yield t.get(followedRef);
37 | // format the counts
38 | const followerUpdate = {
39 | followingCount: (follower.data().followingCount || 0) + countChange
40 | };
41 | const followedUpdate = {
42 | followerCount: (followed.data().followerCount || 0) + countChange
43 | };
44 | // run the updates
45 | yield t.set(followerRef, followerUpdate, { merge: true });
46 | yield t.set(followedRef, followedUpdate, { merge: true });
47 | return t;
48 | }));
49 | });
50 | exports.updatePostCount = functions.firestore
51 | .document('posts/{postId}')
52 | .onCreate((event) => __awaiter(this, void 0, void 0, function* () {
53 | // Get the post UserID
54 | const userId = event.data.data().userId;
55 | // Reference user doc
56 | const db = admin.firestore();
57 | const userRef = db.collection('users').doc(userId);
58 | // Get the user data
59 | const user = yield userRef.get();
60 | // Update the count and run the update
61 | const postCount = (user.data().postCount || 0) + 1;
62 | return userRef.update({ postCount });
63 | }));
64 | //# sourceMappingURL=aggregation.js.map
--------------------------------------------------------------------------------
/src/providers/fcm/fcm.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from "@angular/core";
2 | import { Firebase } from "@ionic-native/firebase";
3 | import { Platform } from "ionic-angular";
4 | import { AngularFirestore } from "angularfire2/firestore";
5 | import { tap } from "rxjs/operators";
6 | import { of } from "rxjs/observable/of";
7 | import { AuthProvider } from "../auth/auth";
8 |
9 | @Injectable()
10 | export class FcmProvider {
11 | constructor(
12 | public firebaseNative: Firebase,
13 | public afs: AngularFirestore,
14 | private platform: Platform,
15 | private auth: AuthProvider
16 | ) {}
17 |
18 | async getToken() {
19 | let token;
20 |
21 | if (this.platform.is("cordova")) {
22 | const status = await this.firebaseNative.hasPermission();
23 |
24 | if (status.isEnabled) {
25 | console.log("already enabled");
26 | return;
27 | }
28 |
29 | token = await this.firebaseNative.getToken();
30 | }
31 |
32 | if (this.platform.is("ios")) {
33 | await this.firebaseNative.grantPermission();
34 | }
35 |
36 | return this.saveTokenToFirestore(token);
37 | }
38 |
39 | // Saves the token to Firestore when refreshed
40 | monitorTokenRefresh() {
41 | if (this.platform.is("cordova")) {
42 | return this.firebaseNative
43 | .onTokenRefresh()
44 | .pipe(tap(token => this.saveTokenToFirestore(token)));
45 | } else {
46 | // PWA implementation
47 | return of(null);
48 | }
49 | }
50 |
51 | private async saveTokenToFirestore(token) {
52 | if (!token) return;
53 | const devicesRef = this.afs.collection("devices");
54 |
55 | const user = await this.auth.getCurrentUser();
56 |
57 | const docData = {
58 | token,
59 | userId: user.uid
60 | };
61 |
62 | return devicesRef.doc(token).set(docData);
63 | }
64 |
65 | async subscribeTo(topic: string) {
66 | const user = await this.auth.getCurrentUser();
67 |
68 | if (this.platform.is("cordova")) {
69 | await this.firebaseNative.subscribe(topic);
70 | }
71 |
72 | const topics = { [topic]: true };
73 | return this.afs
74 | .collection("users")
75 | .doc(user.uid)
76 | .set({ topics }, { merge: true });
77 | }
78 |
79 | async unsubscribeFrom(topic: string) {
80 | const user = await this.auth.getCurrentUser();
81 |
82 | if (this.platform.is("cordova")) {
83 | await this.firebaseNative.unsubscribe(topic);
84 | }
85 |
86 | const topics = { [topic]: false };
87 | return this.afs
88 | .collection("users")
89 | .doc(user.uid)
90 | .set({ topics }, { merge: true });
91 | }
92 |
93 | // Handle incoming messages
94 | listenToNotifications() {
95 | if (this.platform.is("cordova")) {
96 | return this.firebaseNative.onNotificationOpen();
97 | } else {
98 | // PWA implementation
99 | return of(null);
100 | }
101 | }
102 |
103 | // Add this to the logout method to end notifications
104 | stopNotifications() {
105 | return this.firebaseNative.unregister();
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/src/components/image-upload/image-upload.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input, Output, EventEmitter } from "@angular/core";
2 | import { filter, tap } from "rxjs/operators";
3 |
4 | import { ModalController, NavParams, ViewController } from "ionic-angular";
5 | import { Camera, CameraOptions } from "@ionic-native/camera";
6 |
7 | import {
8 | AngularFireStorage,
9 | AngularFireUploadTask
10 | } from "angularfire2/storage";
11 |
12 | @Component({
13 | selector: "image-upload",
14 | templateUrl: "image-upload.html"
15 | })
16 | export class ImageUploadComponent {
17 | @Input() userId;
18 |
19 | @Output() uploadFinished = new EventEmitter();
20 |
21 | task: AngularFireUploadTask;
22 | image: string;
23 |
24 | constructor(
25 | private storage: AngularFireStorage,
26 | private modalCtrl: ModalController,
27 | private camera: Camera
28 | ) {}
29 |
30 | startUpload(file: string) {
31 | const path = `${this.userId}/${new Date().getTime()}`;
32 |
33 | // The main task
34 | this.image = "data:image/jpg;base64," + file;
35 | this.task = this.storage.ref(path).putString(this.image, "data_url");
36 |
37 | // Define and present the modal component
38 | let uploadModal = this.modalCtrl.create(UploadModal, { task: this.task });
39 | uploadModal.present();
40 |
41 | // Listen to the progress, when 100% dismiss the modal
42 | this.task
43 | .percentageChanges()
44 | .pipe(
45 | filter(val => val === 100),
46 | tap(complete => {
47 | uploadModal.dismiss();
48 | })
49 | )
50 | .subscribe();
51 |
52 | // Listen for the Download URL
53 | this.task
54 | .downloadURL()
55 | .pipe(tap(url => this.uploadFinished.emit(url)))
56 | .subscribe();
57 | }
58 |
59 | async captureAndUpload() {
60 | const options: CameraOptions = {
61 | quality: 70,
62 | targetWidth: 500,
63 | targetHeight: 500,
64 | destinationType: this.camera.DestinationType.DATA_URL,
65 | encodingType: this.camera.EncodingType.JPEG,
66 | mediaType: this.camera.MediaType.PICTURE,
67 | sourceType: this.camera.PictureSourceType.PHOTOLIBRARY
68 | };
69 |
70 | const base64 = await this.camera.getPicture(options);
71 |
72 | this.startUpload(base64);
73 | }
74 | }
75 |
76 | @Component({
77 | template: `
78 |
79 |
80 |
81 |
82 | Uploading to Firebase...
83 |
84 |
85 |
86 |
87 |
88 | Upload is
89 |
90 | {{ progress | async | number }}% complete
91 |
92 |
93 |
94 |
95 |
96 | `
97 | })
98 | export class UploadModal {
99 | task;
100 | progress;
101 |
102 | constructor(params: NavParams, public viewCtrl: ViewController) {
103 | this.task = params.get("task");
104 | this.progress = this.task.percentageChanges();
105 | }
106 |
107 | cancel() {
108 | this.task.cancel();
109 | this.viewCtrl.dismiss();
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/src/providers/auth/auth.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from "@angular/core";
2 | import { Platform } from "ionic-angular";
3 | import { AngularFireAuth } from "angularfire2/auth";
4 | import {
5 | AngularFirestore,
6 | AngularFirestoreDocument
7 | } from "angularfire2/firestore";
8 | import * as firebase from "firebase/app";
9 |
10 | import { Observable } from "rxjs/Observable";
11 | import { switchMap, first } from "rxjs/operators";
12 |
13 | import { Facebook } from "@ionic-native/facebook";
14 |
15 | import * as Chance from 'chance';
16 |
17 | @Injectable()
18 | export class AuthProvider {
19 | user: Observable;
20 |
21 | constructor(
22 | private afAuth: AngularFireAuth,
23 | private afs: AngularFirestore,
24 | private facebook: Facebook,
25 | private platform: Platform
26 | ) {
27 | this.user = this.afAuth.authState.pipe(
28 | switchMap(user => {
29 | if (user) {
30 | return this.afs.doc(`users/${user.uid}`).valueChanges();
31 | } else {
32 | return Observable.of(null);
33 | }
34 | })
35 | );
36 | }
37 |
38 | //// FACEBOOK ////
39 |
40 | async facebookLogin() {
41 | if (this.platform.is("cordova")) {
42 | return await this.nativeFacebookLogin();
43 | } else {
44 | return await this.webFacebookLogin();
45 | }
46 | }
47 |
48 | async nativeFacebookLogin(): Promise {
49 | try {
50 | const response = await this.facebook.login(["email", "public_profile"]);
51 | const facebookCredential = firebase.auth.FacebookAuthProvider.credential(
52 | response.authResponse.accessToken
53 | );
54 |
55 | const firebaseUser = await firebase
56 | .auth()
57 | .signInWithCredential(facebookCredential);
58 |
59 | return await this.updateUserData(firebaseUser);
60 | } catch (err) {
61 | console.log(err);
62 | }
63 | }
64 |
65 | async webFacebookLogin(): Promise {
66 | try {
67 | const provider = new firebase.auth.FacebookAuthProvider();
68 | const credential = await this.afAuth.auth.signInWithPopup(provider);
69 |
70 | return await this.updateUserData(credential.user);
71 | } catch (err) {
72 | console.log(err);
73 | }
74 | }
75 |
76 | // Save custom user data in Firestore
77 | private updateUserData(user: any) {
78 | const userRef: AngularFirestoreDocument = this.afs.doc(
79 | `users/${user.uid}`
80 | );
81 |
82 | const data = {
83 | uid: user.uid,
84 | email: user.email || null,
85 | displayName: user.displayName || new Chance().name({ prefix: true }),
86 | photoURL: user.photoURL || "https://goo.gl/7kz9qG"
87 | };
88 | return userRef.set(data, { merge: true });
89 | }
90 |
91 | //// ANONYMOUS ////
92 |
93 | async anonymousLogin(): Promise {
94 | const user = await this.afAuth.auth.signInAnonymously();
95 | await this.updateUserData(user);
96 | }
97 |
98 | //// HELPERS ////
99 |
100 | async logout(): Promise {
101 | return this.afAuth.auth.signOut();
102 | }
103 |
104 | // Current user as a Promise. Useful for one-off operations.
105 | async getCurrentUser(): Promise {
106 | return this.user.pipe(first()).toPromise();
107 | }
108 |
109 | // Current user as boolean Promise. Used in router guards
110 | async isLoggedIn(): Promise {
111 | const user = await this.getCurrentUser();
112 | return !!user;
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/functions/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | // -- Strict errors --
4 | // These lint rules are likely always a good idea.
5 |
6 | // Force function overloads to be declared together. This ensures readers understand APIs.
7 | "adjacent-overload-signatures": true,
8 |
9 | // Do not allow the subtle/obscure comma operator.
10 | "ban-comma-operator": true,
11 |
12 | // Do not allow internal modules or namespaces . These are deprecated in favor of ES6 modules.
13 | "no-namespace": true,
14 |
15 | // Do not allow parameters to be reassigned. To avoid bugs, developers should instead assign new values to new vars.
16 | "no-parameter-reassignment": true,
17 |
18 | // Force the use of ES6-style imports instead of /// imports.
19 | "no-reference": true,
20 |
21 | // Do not allow type assertions that do nothing. This is a big warning that the developer may not understand the
22 | // code currently being edited (they may be incorrectly handling a different type case that does not exist).
23 | "no-unnecessary-type-assertion": true,
24 |
25 | // Disallow nonsensical label usage.
26 | "label-position": true,
27 |
28 | // Disallows the (often typo) syntax if (var1 = var2). Replace with if (var2) { var1 = var2 }.
29 | "no-conditional-assignment": true,
30 |
31 | // Disallows constructors for primitive types (e.g. new Number('123'), though Number('123') is still allowed).
32 | "no-construct": true,
33 |
34 | // Do not allow super() to be called twice in a constructor.
35 | "no-duplicate-super": true,
36 |
37 | // Do not allow the same case to appear more than once in a switch block.
38 | "no-duplicate-switch-case": true,
39 |
40 | // Do not allow a variable to be declared more than once in the same block. Consider function parameters in this
41 | // rule.
42 | "no-duplicate-variable": [true, "check-parameters"],
43 |
44 | // Disallows a variable definition in an inner scope from shadowing a variable in an outer scope. Developers should
45 | // instead use a separate variable name.
46 | "no-shadowed-variable": true,
47 |
48 | // Empty blocks are almost never needed. Allow the one general exception: empty catch blocks.
49 | "no-empty": [true, "allow-empty-catch"],
50 |
51 | // Functions must either be handled directly (e.g. with a catch() handler) or returned to another function.
52 | // This is a major source of errors in Cloud Functions and the team strongly recommends leaving this rule on.
53 | "no-floating-promises": true,
54 |
55 | // Do not allow any imports for modules that are not in package.json. These will almost certainly fail when
56 | // deployed.
57 | "no-implicit-dependencies": true,
58 |
59 | // The 'this' keyword can only be used inside of classes.
60 | "no-invalid-this": true,
61 |
62 | // Do not allow strings to be thrown because they will not include stack traces. Throw Errors instead.
63 | "no-string-throw": true,
64 |
65 | // Disallow control flow statements, such as return, continue, break, and throw in finally blocks.
66 | "no-unsafe-finally": true,
67 |
68 | // Do not allow variables to be used before they are declared.
69 | "no-use-before-declare": true,
70 |
71 | // Expressions must always return a value. Avoids common errors like const myValue = functionReturningVoid();
72 | "no-void-expression": [true, "ignore-arrow-function-shorthand"],
73 |
74 | // Makes sure result of typeof is compared to correct string values.
75 | "typeof-compare": true,
76 |
77 | // Disallow duplicate imports in the same file.
78 | "no-duplicate-imports": true,
79 |
80 |
81 | // -- Strong Warnings --
82 | // These rules should almost never be needed, but may be included due to legacy code.
83 | // They are left as a warning to avoid frustration with blocked deploys when the developer
84 | // understand the warning and wants to deploy anyway.
85 |
86 | // Warn when an empty interface is defined. These are generally not useful.
87 | "no-empty-interface": {"severity": "warning"},
88 |
89 | // Warn when an import will have side effects.
90 | "no-import-side-effect": {"severity": "warning"},
91 |
92 | // Warn when variables are defined with var. Var has subtle meaning that can lead to bugs. Strongly prefer const for
93 | // most values and let for values that will change.
94 | "no-var-keyword": {"severity": "warning"},
95 |
96 | // Prefer === and !== over == and !=. The latter operators support overloads that are often accidental.
97 | "triple-equals": {"severity": "warning"},
98 |
99 | // Warn when using deprecated APIs.
100 | "deprecation": {"severity": "warning"},
101 |
102 | // -- Light Warnigns --
103 | // These rules are intended to help developers use better style. Simpler code has fewer bugs. These would be "info"
104 | // if TSLint supported such a level.
105 |
106 | // prefer for( ... of ... ) to an index loop when the index is only used to fetch an object from an array.
107 | // (Even better: check out utils like .map if transforming an array!)
108 | "prefer-for-of": {"severity": "warning"},
109 |
110 | // Warns if function overloads could be unified into a single function with optional or rest parameters.
111 | "unified-signatures": {"severity": "warning"},
112 |
113 | // Warns if code has an import or variable that is unused.
114 | "no-unused-variable": {"severity": "warning"},
115 |
116 | // Prefer const for values that will not change. This better documents code.
117 | "prefer-const": {"severity": "warning"},
118 |
119 | // Multi-line object liiterals and function calls should have a trailing comma. This helps avoid merge conflicts.
120 | "trailing-comma": {"severity": "warning"}
121 | },
122 |
123 | "defaultSeverity": "error"
124 | }
125 |
--------------------------------------------------------------------------------
/config.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Ionic Firestarter
4 | Ionic Firebase Starter App
5 | Jeff Delaney
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 |
100 |
101 |
102 |
103 |
--------------------------------------------------------------------------------