├── .gitignore
├── .metadata
├── CHANGELOG.md
├── LICENSE
├── README.md
├── android.iml
├── android
├── .gitignore
├── app
│ ├── build.gradle
│ └── src
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ └── me
│ │ │ └── danics
│ │ │ └── firestoretest
│ │ │ └── MainActivity.java
│ │ └── res
│ │ ├── drawable
│ │ └── launch_background.xml
│ │ ├── mipmap-hdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-mdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxxhdpi
│ │ └── ic_launcher.png
│ │ └── values
│ │ └── styles.xml
├── build.gradle
├── gradle.properties
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
├── app-release.apk
├── assets
├── flare
│ └── Like.flr
├── fonts
│ └── Billabong.ttf
└── images
│ └── google_signin_button.png
├── firestore_test.iml
├── firestore_test_android.iml
├── functions
├── lib
│ ├── getFeed.js
│ ├── getFeed.js.map
│ ├── index.js
│ ├── index.js.map
│ ├── notificationHandler.js
│ └── notificationHandler.js.map
├── package-lock.json
├── package.json
├── src
│ ├── getFeed.ts
│ ├── index.ts
│ └── notificationHandler.ts
├── tsconfig.json
└── tslint.json
├── ios
├── .gitignore
├── Flutter
│ ├── AppFrameworkInfo.plist
│ ├── Debug.xcconfig
│ └── Release.xcconfig
├── Podfile
├── Runner.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ └── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── Runner.xcscheme
├── Runner.xcworkspace
│ └── contents.xcworkspacedata
└── Runner
│ ├── AppDelegate.h
│ ├── AppDelegate.m
│ ├── Assets.xcassets
│ ├── AppIcon.appiconset
│ │ ├── Contents.json
│ │ ├── Icon-App-1024x1024@1x.png
│ │ ├── Icon-App-20x20@1x.png
│ │ ├── Icon-App-20x20@2x.png
│ │ ├── Icon-App-20x20@3x.png
│ │ ├── Icon-App-29x29@1x.png
│ │ ├── Icon-App-29x29@2x.png
│ │ ├── Icon-App-29x29@3x.png
│ │ ├── Icon-App-40x40@1x.png
│ │ ├── Icon-App-40x40@2x.png
│ │ ├── Icon-App-40x40@3x.png
│ │ ├── Icon-App-60x60@2x.png
│ │ ├── Icon-App-60x60@3x.png
│ │ ├── Icon-App-76x76@1x.png
│ │ ├── Icon-App-76x76@2x.png
│ │ └── Icon-App-83.5x83.5@2x.png
│ └── LaunchImage.imageset
│ │ ├── Contents.json
│ │ ├── LaunchImage.png
│ │ ├── LaunchImage@2x.png
│ │ ├── LaunchImage@3x.png
│ │ └── README.md
│ ├── Base.lproj
│ ├── LaunchScreen.storyboard
│ └── Main.storyboard
│ ├── Info.plist
│ └── main.m
├── lib
├── activity_feed.dart
├── comment_screen.dart
├── create_account.dart
├── edit_profile_page.dart
├── feed.dart
├── image_post.dart
├── location.dart
├── main.dart
├── models
│ └── user.dart
├── profile_page.dart
├── search_page.dart
└── upload_page.dart
├── pubspec.lock
└── pubspec.yaml
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .atom/
3 | .idea
4 | .vscode/
5 | .packages
6 | .pub/
7 | build/
8 | ios/.generated/
9 | packages
10 | .flutter-plugins
11 | functions/node_modules/
12 |
--------------------------------------------------------------------------------
/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: 3ea4d06340a97a1e9d7cae97567c64e0569dcaa2
8 | channel: beta
9 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 | All notable changes to this project will be documented in this file.
3 |
4 | ## [1.2.3] - 2020-01-04
5 | ### Changes
6 | - Add animation to Heart when liking a post
7 | - add user's post to their own feed
8 | - add logout button to edit profile page
9 | - tabs now keep state after switching pages, improves overall ux
10 | - Code clean up
11 | - Dependency bumps
12 | - Fixed bugs
13 | - remove width constraint on follow button
14 | - feed now loads after username selection
15 | - fix duplicate username screens on first google signin
16 | - fixed error on open + removed useless code
17 | - Set maxWidth and maxHeight on pickImage()
18 | - added missing keys
19 |
20 |
21 | ## [1.2.2] - 2018-11-07
22 | ### Changes
23 | - New - Location suggestions on upload page
24 |
25 |
26 | ## [1.2.1] - 2018-11-04
27 | ### Changes
28 | - Changed `BottomNavigationBar` to `CupertinoTabBar` to remove empty space underneath icons
29 | - Added highlighting for the active page on the `CupertinoTabBar`
30 |
31 |
32 | ## [1.2.0] - 2018-10-26
33 | ### Changes
34 | - Added push notifications with Cloud Functions (Android)
35 | - Get notifications when someone likes or comments on a post and follows you
36 | - Notifications are for android as iOS notifications need an Apple developer account.
37 | - Cloud Functions Changes
38 | - Separated functions to individual files
39 | - npm dependencies updated
40 |
41 |
42 | ## [1.1.0] - 2018-10-22
43 | ### Changes
44 | - Updated to most recent dependencies
45 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International
2 | Public License
3 |
4 | By exercising the Licensed Rights (defined below), You accept and agree
5 | to be bound by the terms and conditions of this Creative Commons
6 | Attribution-NonCommercial-ShareAlike 4.0 International Public License
7 | ("Public License"). To the extent this Public License may be
8 | interpreted as a contract, You are granted the Licensed Rights in
9 | consideration of Your acceptance of these terms and conditions, and the
10 | Licensor grants You such rights in consideration of benefits the
11 | Licensor receives from making the Licensed Material available under
12 | these terms and conditions.
13 |
14 |
15 | Section 1 -- Definitions.
16 |
17 | a. Adapted Material means material subject to Copyright and Similar
18 | Rights that is derived from or based upon the Licensed Material
19 | and in which the Licensed Material is translated, altered,
20 | arranged, transformed, or otherwise modified in a manner requiring
21 | permission under the Copyright and Similar Rights held by the
22 | Licensor. For purposes of this Public License, where the Licensed
23 | Material is a musical work, performance, or sound recording,
24 | Adapted Material is always produced where the Licensed Material is
25 | synched in timed relation with a moving image.
26 |
27 | b. Adapter's License means the license You apply to Your Copyright
28 | and Similar Rights in Your contributions to Adapted Material in
29 | accordance with the terms and conditions of this Public License.
30 |
31 | c. BY-NC-SA Compatible License means a license listed at
32 | creativecommons.org/compatiblelicenses, approved by Creative
33 | Commons as essentially the equivalent of this Public License.
34 |
35 | d. Copyright and Similar Rights means copyright and/or similar rights
36 | closely related to copyright including, without limitation,
37 | performance, broadcast, sound recording, and Sui Generis Database
38 | Rights, without regard to how the rights are labeled or
39 | categorized. For purposes of this Public License, the rights
40 | specified in Section 2(b)(1)-(2) are not Copyright and Similar
41 | Rights.
42 |
43 | e. Effective Technological Measures means those measures that, in the
44 | absence of proper authority, may not be circumvented under laws
45 | fulfilling obligations under Article 11 of the WIPO Copyright
46 | Treaty adopted on December 20, 1996, and/or similar international
47 | agreements.
48 |
49 | f. Exceptions and Limitations means fair use, fair dealing, and/or
50 | any other exception or limitation to Copyright and Similar Rights
51 | that applies to Your use of the Licensed Material.
52 |
53 | g. License Elements means the license attributes listed in the name
54 | of a Creative Commons Public License. The License Elements of this
55 | Public License are Attribution, NonCommercial, and ShareAlike.
56 |
57 | h. Licensed Material means the artistic or literary work, database,
58 | or other material to which the Licensor applied this Public
59 | License.
60 |
61 | i. Licensed Rights means the rights granted to You subject to the
62 | terms and conditions of this Public License, which are limited to
63 | all Copyright and Similar Rights that apply to Your use of the
64 | Licensed Material and that the Licensor has authority to license.
65 |
66 | j. Licensor means the individual(s) or entity(ies) granting rights
67 | under this Public License.
68 |
69 | k. NonCommercial means not primarily intended for or directed towards
70 | commercial advantage or monetary compensation. For purposes of
71 | this Public License, the exchange of the Licensed Material for
72 | other material subject to Copyright and Similar Rights by digital
73 | file-sharing or similar means is NonCommercial provided there is
74 | no payment of monetary compensation in connection with the
75 | exchange.
76 |
77 | l. Share means to provide material to the public by any means or
78 | process that requires permission under the Licensed Rights, such
79 | as reproduction, public display, public performance, distribution,
80 | dissemination, communication, or importation, and to make material
81 | available to the public including in ways that members of the
82 | public may access the material from a place and at a time
83 | individually chosen by them.
84 |
85 | m. Sui Generis Database Rights means rights other than copyright
86 | resulting from Directive 96/9/EC of the European Parliament and of
87 | the Council of 11 March 1996 on the legal protection of databases,
88 | as amended and/or succeeded, as well as other essentially
89 | equivalent rights anywhere in the world.
90 |
91 | n. You means the individual or entity exercising the Licensed Rights
92 | under this Public License. Your has a corresponding meaning.
93 |
94 |
95 | Section 2 -- Scope.
96 |
97 | a. License grant.
98 |
99 | 1. Subject to the terms and conditions of this Public License,
100 | the Licensor hereby grants You a worldwide, royalty-free,
101 | non-sublicensable, non-exclusive, irrevocable license to
102 | exercise the Licensed Rights in the Licensed Material to:
103 |
104 | a. reproduce and Share the Licensed Material, in whole or
105 | in part, for NonCommercial purposes only; and
106 |
107 | b. produce, reproduce, and Share Adapted Material for
108 | NonCommercial purposes only.
109 |
110 | 2. Exceptions and Limitations. For the avoidance of doubt, where
111 | Exceptions and Limitations apply to Your use, this Public
112 | License does not apply, and You do not need to comply with
113 | its terms and conditions.
114 |
115 | 3. Term. The term of this Public License is specified in Section
116 | 6(a).
117 |
118 | 4. Media and formats; technical modifications allowed. The
119 | Licensor authorizes You to exercise the Licensed Rights in
120 | all media and formats whether now known or hereafter created,
121 | and to make technical modifications necessary to do so. The
122 | Licensor waives and/or agrees not to assert any right or
123 | authority to forbid You from making technical modifications
124 | necessary to exercise the Licensed Rights, including
125 | technical modifications necessary to circumvent Effective
126 | Technological Measures. For purposes of this Public License,
127 | simply making modifications authorized by this Section 2(a)
128 | (4) never produces Adapted Material.
129 |
130 | 5. Downstream recipients.
131 |
132 | a. Offer from the Licensor -- Licensed Material. Every
133 | recipient of the Licensed Material automatically
134 | receives an offer from the Licensor to exercise the
135 | Licensed Rights under the terms and conditions of this
136 | Public License.
137 |
138 | b. Additional offer from the Licensor -- Adapted Material.
139 | Every recipient of Adapted Material from You
140 | automatically receives an offer from the Licensor to
141 | exercise the Licensed Rights in the Adapted Material
142 | under the conditions of the Adapter's License You apply.
143 |
144 | c. No downstream restrictions. You may not offer or impose
145 | any additional or different terms or conditions on, or
146 | apply any Effective Technological Measures to, the
147 | Licensed Material if doing so restricts exercise of the
148 | Licensed Rights by any recipient of the Licensed
149 | Material.
150 |
151 | 6. No endorsement. Nothing in this Public License constitutes or
152 | may be construed as permission to assert or imply that You
153 | are, or that Your use of the Licensed Material is, connected
154 | with, or sponsored, endorsed, or granted official status by,
155 | the Licensor or others designated to receive attribution as
156 | provided in Section 3(a)(1)(A)(i).
157 |
158 | b. Other rights.
159 |
160 | 1. Moral rights, such as the right of integrity, are not
161 | licensed under this Public License, nor are publicity,
162 | privacy, and/or other similar personality rights; however, to
163 | the extent possible, the Licensor waives and/or agrees not to
164 | assert any such rights held by the Licensor to the limited
165 | extent necessary to allow You to exercise the Licensed
166 | Rights, but not otherwise.
167 |
168 | 2. Patent and trademark rights are not licensed under this
169 | Public License.
170 |
171 | 3. To the extent possible, the Licensor waives any right to
172 | collect royalties from You for the exercise of the Licensed
173 | Rights, whether directly or through a collecting society
174 | under any voluntary or waivable statutory or compulsory
175 | licensing scheme. In all other cases the Licensor expressly
176 | reserves any right to collect such royalties, including when
177 | the Licensed Material is used other than for NonCommercial
178 | purposes.
179 |
180 |
181 | Section 3 -- License Conditions.
182 |
183 | Your exercise of the Licensed Rights is expressly made subject to the
184 | following conditions.
185 |
186 | a. Attribution.
187 |
188 | 1. If You Share the Licensed Material (including in modified
189 | form), You must:
190 |
191 | a. retain the following if it is supplied by the Licensor
192 | with the Licensed Material:
193 |
194 | i. identification of the creator(s) of the Licensed
195 | Material and any others designated to receive
196 | attribution, in any reasonable manner requested by
197 | the Licensor (including by pseudonym if
198 | designated);
199 |
200 | ii. a copyright notice;
201 |
202 | iii. a notice that refers to this Public License;
203 |
204 | iv. a notice that refers to the disclaimer of
205 | warranties;
206 |
207 | v. a URI or hyperlink to the Licensed Material to the
208 | extent reasonably practicable;
209 |
210 | b. indicate if You modified the Licensed Material and
211 | retain an indication of any previous modifications; and
212 |
213 | c. indicate the Licensed Material is licensed under this
214 | Public License, and include the text of, or the URI or
215 | hyperlink to, this Public License.
216 |
217 | 2. You may satisfy the conditions in Section 3(a)(1) in any
218 | reasonable manner based on the medium, means, and context in
219 | which You Share the Licensed Material. For example, it may be
220 | reasonable to satisfy the conditions by providing a URI or
221 | hyperlink to a resource that includes the required
222 | information.
223 | 3. If requested by the Licensor, You must remove any of the
224 | information required by Section 3(a)(1)(A) to the extent
225 | reasonably practicable.
226 |
227 | b. ShareAlike.
228 |
229 | In addition to the conditions in Section 3(a), if You Share
230 | Adapted Material You produce, the following conditions also apply.
231 |
232 | 1. The Adapter's License You apply must be a Creative Commons
233 | license with the same License Elements, this version or
234 | later, or a BY-NC-SA Compatible License.
235 |
236 | 2. You must include the text of, or the URI or hyperlink to, the
237 | Adapter's License You apply. You may satisfy this condition
238 | in any reasonable manner based on the medium, means, and
239 | context in which You Share Adapted Material.
240 |
241 | 3. You may not offer or impose any additional or different terms
242 | or conditions on, or apply any Effective Technological
243 | Measures to, Adapted Material that restrict exercise of the
244 | rights granted under the Adapter's License You apply.
245 |
246 |
247 | Section 4 -- Sui Generis Database Rights.
248 |
249 | Where the Licensed Rights include Sui Generis Database Rights that
250 | apply to Your use of the Licensed Material:
251 |
252 | a. for the avoidance of doubt, Section 2(a)(1) grants You the right
253 | to extract, reuse, reproduce, and Share all or a substantial
254 | portion of the contents of the database for NonCommercial purposes
255 | only;
256 |
257 | b. if You include all or a substantial portion of the database
258 | contents in a database in which You have Sui Generis Database
259 | Rights, then the database in which You have Sui Generis Database
260 | Rights (but not its individual contents) is Adapted Material,
261 | including for purposes of Section 3(b); and
262 |
263 | c. You must comply with the conditions in Section 3(a) if You Share
264 | all or a substantial portion of the contents of the database.
265 |
266 | For the avoidance of doubt, this Section 4 supplements and does not
267 | replace Your obligations under this Public License where the Licensed
268 | Rights include other Copyright and Similar Rights.
269 |
270 |
271 | Section 5 -- Disclaimer of Warranties and Limitation of Liability.
272 |
273 | a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
274 | EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
275 | AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
276 | ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
277 | IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
278 | WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
279 | PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
280 | ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
281 | KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
282 | ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
283 |
284 | b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
285 | TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
286 | NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
287 | INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
288 | COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
289 | USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
290 | ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
291 | DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
292 | IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
293 |
294 | c. The disclaimer of warranties and limitation of liability provided
295 | above shall be interpreted in a manner that, to the extent
296 | possible, most closely approximates an absolute disclaimer and
297 | waiver of all liability.
298 |
299 |
300 | Section 6 -- Term and Termination.
301 |
302 | a. This Public License applies for the term of the Copyright and
303 | Similar Rights licensed here. However, if You fail to comply with
304 | this Public License, then Your rights under this Public License
305 | terminate automatically.
306 |
307 | b. Where Your right to use the Licensed Material has terminated under
308 | Section 6(a), it reinstates:
309 |
310 | 1. automatically as of the date the violation is cured, provided
311 | it is cured within 30 days of Your discovery of the
312 | violation; or
313 |
314 | 2. upon express reinstatement by the Licensor.
315 |
316 | For the avoidance of doubt, this Section 6(b) does not affect any
317 | right the Licensor may have to seek remedies for Your violations
318 | of this Public License.
319 |
320 | c. For the avoidance of doubt, the Licensor may also offer the
321 | Licensed Material under separate terms or conditions or stop
322 | distributing the Licensed Material at any time; however, doing so
323 | will not terminate this Public License.
324 |
325 | d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
326 | License.
327 |
328 |
329 | Section 7 -- Other Terms and Conditions.
330 |
331 | a. The Licensor shall not be bound by any additional or different
332 | terms or conditions communicated by You unless expressly agreed.
333 |
334 | b. Any arrangements, understandings, or agreements regarding the
335 | Licensed Material not stated herein are separate from and
336 | independent of the terms and conditions of this Public License.
337 |
338 |
339 | Section 8 -- Interpretation.
340 |
341 | a. For the avoidance of doubt, this Public License does not, and
342 | shall not be interpreted to, reduce, limit, restrict, or impose
343 | conditions on any use of the Licensed Material that could lawfully
344 | be made without permission under this Public License.
345 |
346 | b. To the extent possible, if any provision of this Public License is
347 | deemed unenforceable, it shall be automatically reformed to the
348 | minimum extent necessary to make it enforceable. If the provision
349 | cannot be reformed, it shall be severed from this Public License
350 | without affecting the enforceability of the remaining terms and
351 | conditions.
352 |
353 | c. No term or condition of this Public License will be waived and no
354 | failure to comply consented to unless expressly agreed to by the
355 | Licensor.
356 |
357 | d. Nothing in this Public License constitutes or may be interpreted
358 | as a limitation upon, or waiver of, any privileges and immunities
359 | that apply to the Licensor or You, including from the legal
360 | processes of any jurisdiction or authority.
361 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Fluttergram
2 | A working Instagram clone written in Flutter using Firebase / Firestore
3 |
4 | ## Code Quality Disclaimer
5 | I built Fluttergram as a way to learn Flutter when it was still in Beta V1. At this time, Flutter was in its really early stages, and best practices were not yet established. As such, this repo **does not** reflect the current Flutter best practices and is not at a high code quality standard. That said, it still has merit in being a complete example of what can be done in Flutter.
6 |
7 | # Demo
8 | [Download the release APK to try out Fluttergram](https://github.com/mdanics/fluttergram/raw/master/app-release.apk)
9 |
10 | I update Fluttergram with new features and bugs fixes, but the apk may be behind master. Take a look at the [changelog](/CHANGELOG.md) to see the most recent additions to the apk.
11 |
12 | ## Features
13 |
14 | * Custom photo feed based on who you follow (using firebase cloud functions)
15 | * Post photo posts from camera or gallery
16 | * Like posts
17 | * Comment on posts
18 | * View all comments on a post
19 | * Search for users
20 | * Profile Pages
21 | * Follow / Unfollow Users
22 | * Change image view from grid layout to feed layout
23 | * Add your own bio
24 | * Activity Feed showing recent likes / comments of your posts + new followers
25 |
26 | ## Screenshots
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | ## Dependencies
37 |
38 | * [Flutter](https://flutter.dev/)
39 | * [Firestore](https://github.com/flutter/plugins/tree/master/packages/cloud_firestore)
40 | * [Image Picker](https://github.com/flutter/plugins/tree/master/packages/image_picker)
41 | * [Google Sign In](https://github.com/flutter/plugins/tree/master/packages/google_sign_in)
42 | * [Firebase Auth](https://github.com/flutter/plugins/tree/master/packages/firebase_auth)
43 | * [UUID](https://github.com/Daegalus/dart-uuid)
44 | * [Dart Image](https://github.com/brendan-duncan/image)
45 | * [Path Provider](https://github.com/flutter/plugins/tree/master/packages/path_provider)
46 | * [Font Awesome](https://github.com/brianegan/font_awesome_flutter)
47 | * [Dart HTTP](https://github.com/dart-lang/http)
48 | * [Dart Async](https://github.com/dart-lang/async)
49 | * [Flutter Shared Preferences]()
50 | * [Cached Network Image](https://github.com/renefloor/flutter_cached_network_image)
51 |
52 | ## Getting started
53 |
54 | #### 1. [Setup Flutter](https://flutter.dev/docs/get-started/install)
55 |
56 | #### 2. Clone the repo
57 |
58 | ```sh
59 | $ git clone https://github.com/mdanics/fluttergram.git
60 | $ cd fluttergram/
61 | ```
62 |
63 | #### 3. Setup the firebase app
64 |
65 | 1. You'll need to create a Firebase instance. Follow the instructions at https://console.firebase.google.com.
66 | 2. Once your Firebase instance is created, you'll need to enable Google authentication.
67 |
68 | * Go to the Firebase Console for your new instance.
69 | * Click "Authentication" in the left-hand menu
70 | * Click the "sign-in method" tab
71 | * Click "Google" and enable it
72 |
73 | 3. Create Cloud Functions (to make the Feed work)
74 | * Create a new firebase project with `firebase init`
75 | * Copy this project's `functions/lib/index.js` to your firebase project's `functions/index.js`
76 | * Push the function `getFeed` with `firebase deploy --only functions` In the output, you'll see the getFeed URL, copy that.
77 | * Replace the url in the `_getFeed` function in `feed.dart` with your cloud function url from the previous step.
78 |
79 | _**If this does not work** and you get the error `Error: Error parsing triggers: Cannot find module './notificationHandler'` Try following [these steps](https://github.com/mdanics/fluttergram/issues/25#issuecomment-434031430). If you are still unable to get it to work please open a new issue._
80 |
81 | _You may need to create the neccessary index by running `firebase functions:log` and then clicking the link_
82 |
83 | _**If you are getting no errors, but an empty feed** You must post photos or follow users with posts as the getFeed function only returns your own posts & posts from people you follow._
84 |
85 | 4. Enable the Firebase Database
86 | * Go to the Firebase Console
87 | * Click "Database" in the left-hand menu
88 | * Click the Cloudstore "Create Database" button
89 | * Select "Start in test mode" and "Enable"
90 |
91 | 5. (skip if not running on Android)
92 |
93 | * Create an app within your Firebase instance for Android, with package name com.yourcompany.news
94 | * Run the following command to get your SHA-1 key:
95 |
96 | ```
97 | keytool -exportcert -list -v \
98 | -alias androiddebugkey -keystore ~/.android/debug.keystore
99 | ```
100 |
101 | * In the Firebase console, in the settings of your Android app, add your SHA-1 key by clicking "Add Fingerprint".
102 | * Follow instructions to download google-services.json
103 | * place `google-services.json` into `/android/app/`.
104 |
105 | 6. (skip if not running on iOS)
106 |
107 | * Create an app within your Firebase instance for iOS, with your app package name
108 | * Follow instructions to download GoogleService-Info.plist
109 | * Open XCode, right click the Runner folder, select the "Add Files to 'Runner'" menu, and select the GoogleService-Info.plist file to add it to /ios/Runner in XCode
110 | * Open /ios/Runner/Info.plist in a text editor. Locate the CFBundleURLSchemes key. The second item in the array value of this key is specific to the Firebase instance. Replace it with the value for REVERSED_CLIENT_ID from GoogleService-Info.plist
111 |
112 | Double check install instructions for both
113 | - Google Auth Plugin
114 | - https://pub.dartlang.org/packages/firebase_auth
115 | - Firestore Plugin
116 | - https://pub.dartlang.org/packages/cloud_firestore
117 |
118 | # What's Next?
119 | - [x] Notificaitons for likes, comments, follows, etc
120 | - [X] Animations (heart when liking image) [(#77)](https://github.com/mdanics/fluttergram/pull/77)
121 | - [ ] Improve Caching of Profiles, Images, Etc.
122 | - [ ] Better post creation, add filters to your image
123 | - [ ] Custom Camera Implementation
124 | - [ ] Firebase Security Rules
125 | - [ ] Delete Posts
126 | - [ ] Direct Messaging
127 | - [ ] Stories
128 | - [ ] Clean up code
129 |
--------------------------------------------------------------------------------
/android.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/android/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | *.class
3 | .gradle
4 | /local.properties
5 | /.idea/workspace.xml
6 | /.idea/libraries
7 | .DS_Store
8 | /build
9 | /captures
10 | GeneratedPluginRegistrant.java
11 | /app/google-services.json
12 |
--------------------------------------------------------------------------------
/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | def localProperties = new Properties()
2 | def localPropertiesFile = rootProject.file('local.properties')
3 | if (localPropertiesFile.exists()) {
4 | localPropertiesFile.withReader('UTF-8') { reader ->
5 | localProperties.load(reader)
6 | }
7 | }
8 |
9 | def flutterRoot = localProperties.getProperty('flutter.sdk')
10 | if (flutterRoot == null) {
11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
12 | }
13 |
14 | apply plugin: 'com.android.application'
15 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
16 |
17 | android {
18 | compileSdkVersion 30
19 |
20 | lintOptions {
21 | disable 'InvalidPackage'
22 | }
23 |
24 | defaultConfig {
25 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
26 | applicationId "me.danics.firestoretest"
27 | minSdkVersion 21
28 | targetSdkVersion 27
29 | versionCode 1
30 | versionName "1.2.3"
31 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
32 | multiDexEnabled true
33 | }
34 |
35 | buildTypes {
36 | release {
37 | // TODO: Add your own signing config for the release build.
38 | // Signing with the debug keys for now, so `flutter run --release` works.
39 | signingConfig signingConfigs.debug
40 | }
41 | }
42 | }
43 |
44 | flutter {
45 | source '../..'
46 | }
47 |
48 | dependencies {
49 | testImplementation 'junit:junit:4.12'
50 | androidTestImplementation 'androidx.test:runner:1.1.0-alpha4'
51 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0-alpha4'
52 | implementation 'com.google.firebase:firebase-core:16.0.4'
53 |
54 | }
55 | apply plugin: 'com.google.gms.google-services'
56 |
--------------------------------------------------------------------------------
/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
8 |
9 |
10 |
11 |
12 |
13 |
18 |
21 |
28 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
46 |
47 |
48 |
52 |
53 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/android/app/src/main/java/me/danics/firestoretest/MainActivity.java:
--------------------------------------------------------------------------------
1 | package me.danics.firestoretest;
2 |
3 | import io.flutter.embedding.android.FlutterActivity;
4 |
5 | public class MainActivity extends FlutterActivity {
6 | }
7 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdanics/fluttergram/d372cfd8e34994af5f774a25b3d4ae147d3965aa/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdanics/fluttergram/d372cfd8e34994af5f774a25b3d4ae147d3965aa/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdanics/fluttergram/d372cfd8e34994af5f774a25b3d4ae147d3965aa/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdanics/fluttergram/d372cfd8e34994af5f774a25b3d4ae147d3965aa/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdanics/fluttergram/d372cfd8e34994af5f774a25b3d4ae147d3965aa/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | google()
4 | jcenter()
5 | }
6 |
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:3.3.2'
9 | classpath 'com.google.gms:google-services:4.3.0'
10 | }
11 | }
12 |
13 | allprojects {
14 | repositories {
15 | google()
16 | jcenter()
17 | maven {
18 | url "https://maven.google.com" // Google's Maven repository
19 | }
20 | }
21 | }
22 |
23 | rootProject.buildDir = '../build'
24 | subprojects {
25 | project.buildDir = "${rootProject.buildDir}/${project.name}"
26 | }
27 | subprojects {
28 | project.evaluationDependsOn(':app')
29 | }
30 |
31 | task clean(type: Delete) {
32 | delete rootProject.buildDir
33 | }
34 |
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | android.enableJetifier=true
2 | android.useAndroidX=true
3 | org.gradle.jvmargs=-Xmx1536M
4 | android.enableR8=true
5 |
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdanics/fluttergram/d372cfd8e34994af5f774a25b3d4ae147d3965aa/android/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Apr 02 01:36:58 EDT 2019
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
7 |
--------------------------------------------------------------------------------
/android/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/android/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/android/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
4 |
5 | def plugins = new Properties()
6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
7 | if (pluginsFile.exists()) {
8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
9 | }
10 |
11 | plugins.each { name, path ->
12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
13 | include ":$name"
14 | project(":$name").projectDir = pluginDirectory
15 | }
16 |
--------------------------------------------------------------------------------
/app-release.apk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdanics/fluttergram/d372cfd8e34994af5f774a25b3d4ae147d3965aa/app-release.apk
--------------------------------------------------------------------------------
/assets/fonts/Billabong.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdanics/fluttergram/d372cfd8e34994af5f774a25b3d4ae147d3965aa/assets/fonts/Billabong.ttf
--------------------------------------------------------------------------------
/assets/images/google_signin_button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdanics/fluttergram/d372cfd8e34994af5f774a25b3d4ae147d3965aa/assets/images/google_signin_button.png
--------------------------------------------------------------------------------
/firestore_test.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
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 |
--------------------------------------------------------------------------------
/firestore_test_android.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/functions/lib/getFeed.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 admin = require("firebase-admin");
12 | exports.getFeedModule = function (req, res) {
13 | const uid = String(req.query.uid);
14 | function compileFeedPost() {
15 | return __awaiter(this, void 0, void 0, function* () {
16 | const following = yield getFollowing(uid, res);
17 | let listOfPosts = yield getAllPosts(following, uid, res);
18 | listOfPosts = [].concat.apply([], listOfPosts); // flattens list
19 | res.send(listOfPosts);
20 | });
21 | }
22 | compileFeedPost().then().catch();
23 | };
24 | function getAllPosts(following, uid, res) {
25 | return __awaiter(this, void 0, void 0, function* () {
26 | const listOfPosts = [];
27 | for (const user in following) {
28 | listOfPosts.push(yield getUserPosts(following[user], res));
29 | }
30 | // add the current user's posts to the feed so that your own posts appear in your feed
31 | listOfPosts.push(yield getUserPosts(uid, res));
32 | return listOfPosts;
33 | });
34 | }
35 | function getUserPosts(userId, res) {
36 | const posts = admin.firestore().collection("insta_posts").where("ownerId", "==", userId).orderBy("timestamp");
37 | return posts.get()
38 | .then(function (querySnapshot) {
39 | const listOfPosts = [];
40 | querySnapshot.forEach(function (doc) {
41 | listOfPosts.push(doc.data());
42 | });
43 | return listOfPosts;
44 | });
45 | }
46 | function getFollowing(uid, res) {
47 | const doc = admin.firestore().doc(`insta_users/${uid}`);
48 | return doc.get().then(snapshot => {
49 | const followings = snapshot.data().following;
50 | const following_list = [];
51 | for (const following in followings) {
52 | if (followings[following] === true) {
53 | following_list.push(following);
54 | }
55 | }
56 | return following_list;
57 | }).catch(error => {
58 | res.status(500).send(error);
59 | });
60 | }
61 | //# sourceMappingURL=getFeed.js.map
--------------------------------------------------------------------------------
/functions/lib/getFeed.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"getFeed.js","sourceRoot":"","sources":["../src/getFeed.ts"],"names":[],"mappings":";;;;;;;;;;AACA,wCAAwC;AAG3B,QAAA,aAAa,GAAG,UAAS,GAAG,EAAE,GAAG;IAC1C,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAElC;;YACE,MAAM,SAAS,GAAG,MAAM,YAAY,CAAC,GAAG,EAAE,GAAG,CAAQ,CAAC;YAEtD,IAAI,WAAW,GAAG,MAAM,WAAW,CAAC,SAAS,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;YAEzD,WAAW,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,EAAE,WAAW,CAAC,CAAC,CAAC,gBAAgB;YAEhE,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QACxB,CAAC;KAAA;IAED,eAAe,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,CAAC;AACrC,CAAC,CAAA;AAED,qBAA2B,SAAS,EAAE,GAAG,EAAE,GAAG;;QAC1C,MAAM,WAAW,GAAG,EAAE,CAAC;QAEvB,KAAK,MAAM,IAAI,IAAI,SAAS,EAAC;YACzB,WAAW,CAAC,IAAI,CAAE,MAAM,YAAY,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;SAC/D;QAED,sFAAsF;QACtF,WAAW,CAAC,IAAI,CAAE,MAAM,YAAY,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;QAEhD,OAAO,WAAW,CAAC;IACvB,CAAC;CAAA;AAED,sBAAsB,MAAM,EAAE,GAAG;IAC7B,MAAM,KAAK,GAAG,KAAK,CAAC,SAAS,EAAE,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC,KAAK,CAAC,SAAS,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC,CAAA;IAE7G,OAAO,KAAK,CAAC,GAAG,EAAE;SACjB,IAAI,CAAC,UAAS,aAAa;QACxB,MAAM,WAAW,GAAG,EAAE,CAAC;QAEvB,aAAa,CAAC,OAAO,CAAC,UAAS,GAAG;YAC9B,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;QACjC,CAAC,CAAC,CAAC;QAEH,OAAO,WAAW,CAAC;IACvB,CAAC,CAAC,CAAA;AACN,CAAC;AAGD,sBAAsB,GAAG,EAAE,GAAG;IAC1B,MAAM,GAAG,GAAG,KAAK,CAAC,SAAS,EAAE,CAAC,GAAG,CAAC,eAAe,GAAG,EAAE,CAAC,CAAA;IACvD,OAAO,GAAG,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE;QAC/B,MAAM,UAAU,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC,SAAS,CAAC;QAE7C,MAAM,cAAc,GAAG,EAAE,CAAC;QAE1B,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE;YAClC,IAAI,UAAU,CAAC,SAAS,CAAC,KAAK,IAAI,EAAC;gBACjC,cAAc,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;aAChC;SACF;QACD,OAAO,cAAc,CAAC;IAC1B,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE;QACb,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IAC7B,CAAC,CAAC,CAAA;AACN,CAAC"}
--------------------------------------------------------------------------------
/functions/lib/index.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 | const notificationHandler_1 = require("./lib/notificationHandler");
14 | const getFeed_1 = require("./lib/getFeed");
15 | admin.initializeApp();
16 | exports.notificationHandler = functions.firestore.document("/insta_a_feed/{userId}/items/{activityFeedItem}")
17 | .onCreate((snapshot, context) => __awaiter(this, void 0, void 0, function* () {
18 | yield notificationHandler_1.notificationHandlerModule(snapshot, context);
19 | }));
20 | exports.getFeed = functions.https.onRequest((req, res) => {
21 | getFeed_1.getFeedModule(req, res);
22 | });
23 | //# sourceMappingURL=index.js.map
--------------------------------------------------------------------------------
/functions/lib/index.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;AAAA,gDAAgD;AAChD,wCAAwC;AACxC,+DAAiE;AACjE,uCAAyC;AACzC,KAAK,CAAC,aAAa,EAAE,CAAC;AAET,QAAA,mBAAmB,GAAG,SAAS,CAAC,SAAS,CAAC,QAAQ,CAAC,iDAAiD,CAAC;KAC7G,QAAQ,CAAC,CAAO,QAAQ,EAAE,OAAO,EAAE,EAAE;IACnC,MAAM,+CAAyB,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;AACtD,CAAC,CAAA,CAAC,CAAC;AAGM,QAAA,OAAO,GAAG,SAAS,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;IAC5D,uBAAa,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;AAC1B,CAAC,CAAC,CAAA"}
--------------------------------------------------------------------------------
/functions/lib/notificationHandler.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 admin = require("firebase-admin");
12 | exports.notificationHandlerModule = function (snapshot, context) {
13 | return __awaiter(this, void 0, void 0, function* () {
14 | console.log(snapshot.data());
15 | const ownerDoc = admin.firestore().doc("insta_users/" + context.params.userId);
16 | const ownerData = yield ownerDoc.get();
17 | const androidNotificationToken = ownerData.data()["androidNotificationToken"];
18 | if (androidNotificationToken) {
19 | sendNotification(androidNotificationToken, snapshot.data());
20 | }
21 | else {
22 | console.log("No token for User, not sending a notification");
23 | }
24 | return 0;
25 | });
26 | };
27 | function sendNotification(androidNotificationToken, activityItem) {
28 | let title;
29 | let body = "";
30 | if (activityItem['type'] === "comment") {
31 | title = "New Comment";
32 | body = activityItem['username'] + " commented on your post: " + activityItem["commentData"];
33 | }
34 | else if (activityItem["type"] === "like") {
35 | title = activityItem['username'] + " liked your post";
36 | }
37 | else if (activityItem["type"] === "follow") {
38 | title = activityItem['username'] + " started following you";
39 | }
40 | const message = {
41 | notification: {
42 | title: title,
43 | body: body
44 | },
45 | token: androidNotificationToken
46 | };
47 | admin.messaging().send(message)
48 | .then((response) => {
49 | // Response is a message ID string.
50 | console.log('Successfully sent message:', response);
51 | })
52 | .catch((error) => {
53 | console.log('Error sending message:', error);
54 | });
55 | }
56 | //# sourceMappingURL=notificationHandler.js.map
--------------------------------------------------------------------------------
/functions/lib/notificationHandler.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"notificationHandler.js","sourceRoot":"","sources":["../src/notificationHandler.ts"],"names":[],"mappings":";;;;;;;;;;AAAA,wCAAwC;AAG3B,QAAA,yBAAyB,GAAG,UAAgB,QAAQ,EAAE,OAAO;;QACpE,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAA;QAE5B,MAAM,QAAQ,GAAG,KAAK,CAAC,SAAS,EAAE,CAAC,GAAG,CAAC,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;QAC9E,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC,GAAG,EAAE,CAAA;QAItC,MAAM,wBAAwB,GAAG,SAAS,CAAC,IAAI,EAAE,CAAC,0BAA0B,CAAC,CAAC;QAC9E,IAAI,wBAAwB,EAAE;YAC3B,gBAAgB,CAAC,wBAAwB,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;SAG9D;aAAM;YACL,OAAO,CAAC,GAAG,CAAC,+CAA+C,CAAC,CAAC;SAC9D;QAED,OAAO,CAAC,CAAC;IACX,CAAC;CAAA,CAAC;AAEN,0BAA0B,wBAAgC,EAAE,YAA4C;IAEpG,IAAI,KAAa,CAAC;IAClB,IAAI,IAAI,GAAW,EAAE,CAAC;IAEtB,IAAI,YAAY,CAAC,MAAM,CAAC,KAAK,SAAS,EAAE;QACtC,KAAK,GAAG,aAAa,CAAA;QACrB,IAAI,GAAG,YAAY,CAAC,UAAU,CAAC,GAAG,2BAA2B,GAAG,YAAY,CAAC,aAAa,CAAC,CAAA;KAC5F;SAAM,IAAI,YAAY,CAAC,MAAM,CAAC,KAAK,MAAM,EAAE;QAC1C,KAAK,GAAG,YAAY,CAAC,UAAU,CAAC,GAAG,kBAAkB,CAAA;KACtD;SAAM,IAAI,YAAY,CAAC,MAAM,CAAC,KAAK,QAAQ,EAAC;QAC3C,KAAK,GAAG,YAAY,CAAC,UAAU,CAAC,GAAG,wBAAwB,CAAA;KAC5D;IAED,MAAM,OAAO,GAAG;QACd,YAAY,EAAE;YACZ,KAAK,EAAE,KAAK;YACZ,IAAI,EAAE,IAAI;SACX;QACD,KAAK,EAAE,wBAAwB;KAChC,CAAA;IAED,KAAK,CAAC,SAAS,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC;SAC9B,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE;QACjB,mCAAmC;QACnC,OAAO,CAAC,GAAG,CAAC,4BAA4B,EAAE,QAAQ,CAAC,CAAC;IACtD,CAAC,CAAC;SACD,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;QACf,OAAO,CAAC,GAAG,CAAC,wBAAwB,EAAE,KAAK,CAAC,CAAC;IAC/C,CAAC,CAAC,CAAC;AACL,CAAC"}
--------------------------------------------------------------------------------
/functions/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "functions",
3 | "scripts": {
4 | "lint": "tslint --project tsconfig.json",
5 | "build": "tsc --skipLibCheck ",
6 | "serve": "npm run build && firebase serve --only functions",
7 | "shell": "npm run build && firebase functions:shell",
8 | "start": "npm run shell",
9 | "deploy": "firebase deploy --only functions",
10 | "logs": "firebase functions:log"
11 | },
12 | "main": "lib/index.js",
13 | "dependencies": {
14 | "firebase-admin": "^6.1.0",
15 | "firebase-functions": "^2.1.0"
16 | },
17 | "devDependencies": {
18 | "tslint": "^5.8.0",
19 | "typescript": "^2.5.3"
20 | },
21 | "private": true,
22 | "engines": {
23 | "node": "8"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/functions/src/getFeed.ts:
--------------------------------------------------------------------------------
1 | import * as functions from 'firebase-functions';
2 | import * as admin from 'firebase-admin';
3 |
4 |
5 | export const getFeedModule = function(req, res) {
6 | const uid = String(req.query.uid);
7 |
8 | async function compileFeedPost() {
9 | const following = await getFollowing(uid, res) as any;
10 |
11 | let listOfPosts = await getAllPosts(following, uid, res);
12 |
13 | listOfPosts = [].concat.apply([], listOfPosts); // flattens list
14 |
15 | res.send(listOfPosts);
16 | }
17 |
18 | compileFeedPost().then().catch();
19 | }
20 |
21 | async function getAllPosts(following, uid, res) {
22 | const listOfPosts = [];
23 |
24 | for (const user in following){
25 | listOfPosts.push( await getUserPosts(following[user], res));
26 | }
27 |
28 | // add the current user's posts to the feed so that your own posts appear in your feed
29 | listOfPosts.push( await getUserPosts(uid, res));
30 |
31 | return listOfPosts;
32 | }
33 |
34 | function getUserPosts(userId, res){
35 | const posts = admin.firestore().collection("insta_posts").where("ownerId", "==", userId).orderBy("timestamp")
36 |
37 | return posts.get()
38 | .then(function(querySnapshot) {
39 | const listOfPosts = [];
40 |
41 | querySnapshot.forEach(function(doc) {
42 | listOfPosts.push(doc.data());
43 | });
44 |
45 | return listOfPosts;
46 | })
47 | }
48 |
49 |
50 | function getFollowing(uid, res){
51 | const doc = admin.firestore().doc(`insta_users/${uid}`)
52 | return doc.get().then(snapshot => {
53 | const followings = snapshot.data().following;
54 |
55 | const following_list = [];
56 |
57 | for (const following in followings) {
58 | if (followings[following] === true){
59 | following_list.push(following);
60 | }
61 | }
62 | return following_list;
63 | }).catch(error => {
64 | res.status(500).send(error)
65 | })
66 | }
67 |
--------------------------------------------------------------------------------
/functions/src/index.ts:
--------------------------------------------------------------------------------
1 | import * as functions from 'firebase-functions';
2 | import * as admin from 'firebase-admin';
3 | import { notificationHandlerModule } from "./notificationHandler"
4 | import { getFeedModule } from "./getFeed"
5 | admin.initializeApp();
6 |
7 | export const notificationHandler = functions.firestore.document("/insta_a_feed/{userId}/items/{activityFeedItem}")
8 | .onCreate(async (snapshot, context) => {
9 | await notificationHandlerModule(snapshot, context);
10 | });
11 |
12 |
13 | export const getFeed = functions.https.onRequest((req, res) => {
14 | getFeedModule(req, res);
15 | })
--------------------------------------------------------------------------------
/functions/src/notificationHandler.ts:
--------------------------------------------------------------------------------
1 | import * as admin from 'firebase-admin';
2 | import { DocumentSnapshot } from 'firebase-functions/lib/providers/firestore';
3 |
4 | export const notificationHandlerModule = async function (snapshot, context) {
5 | console.log(snapshot.data())
6 |
7 | const ownerDoc = admin.firestore().doc("insta_users/" + context.params.userId)
8 | const ownerData = await ownerDoc.get()
9 |
10 |
11 |
12 | const androidNotificationToken = ownerData.data()["androidNotificationToken"];
13 | if (androidNotificationToken) {
14 | sendNotification(androidNotificationToken, snapshot.data());
15 |
16 |
17 | } else {
18 | console.log("No token for User, not sending a notification");
19 | }
20 |
21 | return 0;
22 | };
23 |
24 | function sendNotification(androidNotificationToken: string, activityItem: FirebaseFirestore.DocumentData) {
25 |
26 | let title: string;
27 | let body: string = "";
28 |
29 | if (activityItem['type'] === "comment") {
30 | title = "New Comment"
31 | body = activityItem['username'] + " commented on your post: " + activityItem["commentData"]
32 | } else if (activityItem["type"] === "like") {
33 | title = activityItem['username'] + " liked your post"
34 | } else if (activityItem["type"] === "follow"){
35 | title = activityItem['username'] + " started following you"
36 | }
37 |
38 | const message = {
39 | notification: {
40 | title: title,
41 | body: body
42 | },
43 | token: androidNotificationToken
44 | }
45 |
46 | admin.messaging().send(message)
47 | .then((response) => {
48 | // Response is a message ID string.
49 | console.log('Successfully sent message:', response);
50 | })
51 | .catch((error) => {
52 | console.log('Error sending message:', error);
53 | });
54 | }
55 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | // Disallow duplicate imports in the same file.
75 | "no-duplicate-imports": true,
76 |
77 |
78 | // -- Strong Warnings --
79 | // These rules should almost never be needed, but may be included due to legacy code.
80 | // They are left as a warning to avoid frustration with blocked deploys when the developer
81 | // understand the warning and wants to deploy anyway.
82 |
83 | // Warn when an empty interface is defined. These are generally not useful.
84 | "no-empty-interface": {"severity": "warning"},
85 |
86 | // Warn when an import will have side effects.
87 | "no-import-side-effect": {"severity": "warning"},
88 |
89 | // Warn when variables are defined with var. Var has subtle meaning that can lead to bugs. Strongly prefer const for
90 | // most values and let for values that will change.
91 | "no-var-keyword": {"severity": "warning"},
92 |
93 | // Prefer === and !== over == and !=. The latter operators support overloads that are often accidental.
94 | "triple-equals": {"severity": "warning"},
95 |
96 | // Warn when using deprecated APIs.
97 | "deprecation": {"severity": "warning"},
98 |
99 | // -- Light Warnigns --
100 | // These rules are intended to help developers use better style. Simpler code has fewer bugs. These would be "info"
101 | // if TSLint supported such a level.
102 |
103 | // prefer for( ... of ... ) to an index loop when the index is only used to fetch an object from an array.
104 | // (Even better: check out utils like .map if transforming an array!)
105 | "prefer-for-of": {"severity": "warning"},
106 |
107 | // Warns if function overloads could be unified into a single function with optional or rest parameters.
108 | "unified-signatures": {"severity": "warning"},
109 |
110 | // Warns if code has an import or variable that is unused.
111 | "no-unused-variable": {"severity": "warning"},
112 |
113 | // Prefer const for values that will not change. This better documents code.
114 | "prefer-const": {"severity": "warning"},
115 |
116 | // Multi-line object liiterals and function calls should have a trailing comma. This helps avoid merge conflicts.
117 | "trailing-comma": {"severity": "warning"}
118 | },
119 |
120 | "defaultSeverity": "error"
121 | }
122 |
--------------------------------------------------------------------------------
/ios/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | .vagrant/
3 | .sconsign.dblite
4 | .svn/
5 |
6 | .DS_Store
7 | *.swp
8 | profile
9 |
10 | DerivedData/
11 | build/
12 | GeneratedPluginRegistrant.h
13 | GeneratedPluginRegistrant.m
14 |
15 | *.pbxuser
16 | *.mode1v3
17 | *.mode2v3
18 | *.perspectivev3
19 |
20 | !default.pbxuser
21 | !default.mode1v3
22 | !default.mode2v3
23 | !default.perspectivev3
24 |
25 | xcuserdata
26 |
27 | *.moved-aside
28 |
29 | *.pyc
30 | *sync/
31 | Icon?
32 | .tags*
33 |
34 | /Flutter/app.flx
35 | /Flutter/app.zip
36 | /Flutter/flutter_assets/
37 | /Flutter/App.framework
38 | /Flutter/Flutter.framework
39 | /Flutter/Generated.xcconfig
40 | /ServiceDefinitions.json
41 |
42 | Pods/
43 |
--------------------------------------------------------------------------------
/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | App
9 | CFBundleIdentifier
10 | io.flutter.flutter.app
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | App
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1.0
23 | UIRequiredDeviceCapabilities
24 |
25 | arm64
26 |
27 | MinimumOSVersion
28 | 8.0
29 |
30 |
31 |
--------------------------------------------------------------------------------
/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/ios/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment this line to define a global platform for your project
2 | # platform :ios, '9.0'
3 |
4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true'
6 |
7 | project 'Runner', {
8 | 'Debug' => :debug,
9 | 'Profile' => :release,
10 | 'Release' => :release,
11 | }
12 |
13 | def flutter_root
14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
15 | unless File.exist?(generated_xcode_build_settings_path)
16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
17 | end
18 |
19 | File.foreach(generated_xcode_build_settings_path) do |line|
20 | matches = line.match(/FLUTTER_ROOT\=(.*)/)
21 | return matches[1].strip if matches
22 | end
23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
24 | end
25 |
26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
27 |
28 | flutter_ios_podfile_setup
29 |
30 | target 'Runner' do
31 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
32 | end
33 |
34 | post_install do |installer|
35 | installer.pods_project.targets.each do |target|
36 | flutter_additional_ios_build_settings(target)
37 | end
38 | end
39 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
11 | 2FEA05412183A21600873DEA /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 2FEA05402183A21600873DEA /* GoogleService-Info.plist */; };
12 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
13 | 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; };
14 | 9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB31CF90195004384FC /* Generated.xcconfig */; };
15 | 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; };
16 | 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; };
17 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
18 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
19 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
20 | F40AB5A65EEA901E84DF62F2 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A48A54A8F3D8BA3C696BF983 /* libPods-Runner.a */; };
21 | /* End PBXBuildFile section */
22 |
23 | /* Begin PBXCopyFilesBuildPhase section */
24 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = {
25 | isa = PBXCopyFilesBuildPhase;
26 | buildActionMask = 2147483647;
27 | dstPath = "";
28 | dstSubfolderSpec = 10;
29 | files = (
30 | );
31 | name = "Embed Frameworks";
32 | runOnlyForDeploymentPostprocessing = 0;
33 | };
34 | /* End PBXCopyFilesBuildPhase section */
35 |
36 | /* Begin PBXFileReference section */
37 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; };
38 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; };
39 | 172F131D775F5297C2841A96 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; };
40 | 2FEA05402183A21600873DEA /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; };
41 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
42 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; };
43 | 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; };
44 | 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; };
45 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; };
46 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; };
47 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
48 | 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; };
49 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
50 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
51 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
52 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
53 | A47AA0D76093A7A7D14D1F88 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; };
54 | A48A54A8F3D8BA3C696BF983 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; };
55 | /* End PBXFileReference section */
56 |
57 | /* Begin PBXFrameworksBuildPhase section */
58 | 97C146EB1CF9000F007C117D /* Frameworks */ = {
59 | isa = PBXFrameworksBuildPhase;
60 | buildActionMask = 2147483647;
61 | files = (
62 | F40AB5A65EEA901E84DF62F2 /* libPods-Runner.a in Frameworks */,
63 | );
64 | runOnlyForDeploymentPostprocessing = 0;
65 | };
66 | /* End PBXFrameworksBuildPhase section */
67 |
68 | /* Begin PBXGroup section */
69 | 7CB42A7162B3851135A6C52A /* Pods */ = {
70 | isa = PBXGroup;
71 | children = (
72 | A47AA0D76093A7A7D14D1F88 /* Pods-Runner.debug.xcconfig */,
73 | 172F131D775F5297C2841A96 /* Pods-Runner.release.xcconfig */,
74 | );
75 | name = Pods;
76 | sourceTree = "";
77 | };
78 | 9740EEB11CF90186004384FC /* Flutter */ = {
79 | isa = PBXGroup;
80 | children = (
81 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
82 | 9740EEB21CF90195004384FC /* Debug.xcconfig */,
83 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
84 | 9740EEB31CF90195004384FC /* Generated.xcconfig */,
85 | );
86 | name = Flutter;
87 | sourceTree = "";
88 | };
89 | 97C146E51CF9000F007C117D = {
90 | isa = PBXGroup;
91 | children = (
92 | 2FEA05402183A21600873DEA /* GoogleService-Info.plist */,
93 | 9740EEB11CF90186004384FC /* Flutter */,
94 | 97C146F01CF9000F007C117D /* Runner */,
95 | 97C146EF1CF9000F007C117D /* Products */,
96 | 7CB42A7162B3851135A6C52A /* Pods */,
97 | B60D479C97BD6EB628333B42 /* Frameworks */,
98 | );
99 | sourceTree = "";
100 | };
101 | 97C146EF1CF9000F007C117D /* Products */ = {
102 | isa = PBXGroup;
103 | children = (
104 | 97C146EE1CF9000F007C117D /* Runner.app */,
105 | );
106 | name = Products;
107 | sourceTree = "";
108 | };
109 | 97C146F01CF9000F007C117D /* Runner */ = {
110 | isa = PBXGroup;
111 | children = (
112 | 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */,
113 | 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */,
114 | 97C146FA1CF9000F007C117D /* Main.storyboard */,
115 | 97C146FD1CF9000F007C117D /* Assets.xcassets */,
116 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
117 | 97C147021CF9000F007C117D /* Info.plist */,
118 | 97C146F11CF9000F007C117D /* Supporting Files */,
119 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
120 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
121 | );
122 | path = Runner;
123 | sourceTree = "";
124 | };
125 | 97C146F11CF9000F007C117D /* Supporting Files */ = {
126 | isa = PBXGroup;
127 | children = (
128 | 97C146F21CF9000F007C117D /* main.m */,
129 | );
130 | name = "Supporting Files";
131 | sourceTree = "";
132 | };
133 | B60D479C97BD6EB628333B42 /* Frameworks */ = {
134 | isa = PBXGroup;
135 | children = (
136 | A48A54A8F3D8BA3C696BF983 /* libPods-Runner.a */,
137 | );
138 | name = Frameworks;
139 | sourceTree = "";
140 | };
141 | /* End PBXGroup section */
142 |
143 | /* Begin PBXNativeTarget section */
144 | 97C146ED1CF9000F007C117D /* Runner */ = {
145 | isa = PBXNativeTarget;
146 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
147 | buildPhases = (
148 | 0AB6CFB15BD839216C341DA3 /* [CP] Check Pods Manifest.lock */,
149 | 9740EEB61CF901F6004384FC /* Run Script */,
150 | 97C146EA1CF9000F007C117D /* Sources */,
151 | 97C146EB1CF9000F007C117D /* Frameworks */,
152 | 97C146EC1CF9000F007C117D /* Resources */,
153 | 9705A1C41CF9048500538489 /* Embed Frameworks */,
154 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
155 | D2C1B3B5A3B05D71F659340E /* [CP] Embed Pods Frameworks */,
156 | 11EDB517A9D814A7C2BF386E /* [CP] Copy Pods Resources */,
157 | );
158 | buildRules = (
159 | );
160 | dependencies = (
161 | );
162 | name = Runner;
163 | productName = Runner;
164 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
165 | productType = "com.apple.product-type.application";
166 | };
167 | /* End PBXNativeTarget section */
168 |
169 | /* Begin PBXProject section */
170 | 97C146E61CF9000F007C117D /* Project object */ = {
171 | isa = PBXProject;
172 | attributes = {
173 | LastUpgradeCheck = 0910;
174 | ORGANIZATIONNAME = "The Chromium Authors";
175 | TargetAttributes = {
176 | 97C146ED1CF9000F007C117D = {
177 | CreatedOnToolsVersion = 7.3.1;
178 | DevelopmentTeam = XFKDZYYPTU;
179 | ProvisioningStyle = Automatic;
180 | };
181 | };
182 | };
183 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
184 | compatibilityVersion = "Xcode 3.2";
185 | developmentRegion = English;
186 | hasScannedForEncodings = 0;
187 | knownRegions = (
188 | English,
189 | en,
190 | Base,
191 | );
192 | mainGroup = 97C146E51CF9000F007C117D;
193 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
194 | projectDirPath = "";
195 | projectRoot = "";
196 | targets = (
197 | 97C146ED1CF9000F007C117D /* Runner */,
198 | );
199 | };
200 | /* End PBXProject section */
201 |
202 | /* Begin PBXResourcesBuildPhase section */
203 | 97C146EC1CF9000F007C117D /* Resources */ = {
204 | isa = PBXResourcesBuildPhase;
205 | buildActionMask = 2147483647;
206 | files = (
207 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
208 | 2FEA05412183A21600873DEA /* GoogleService-Info.plist in Resources */,
209 | 9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */,
210 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
211 | 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */,
212 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
213 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
214 | );
215 | runOnlyForDeploymentPostprocessing = 0;
216 | };
217 | /* End PBXResourcesBuildPhase section */
218 |
219 | /* Begin PBXShellScriptBuildPhase section */
220 | 0AB6CFB15BD839216C341DA3 /* [CP] Check Pods Manifest.lock */ = {
221 | isa = PBXShellScriptBuildPhase;
222 | buildActionMask = 2147483647;
223 | files = (
224 | );
225 | inputPaths = (
226 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
227 | "${PODS_ROOT}/Manifest.lock",
228 | );
229 | name = "[CP] Check Pods Manifest.lock";
230 | outputPaths = (
231 | "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
232 | );
233 | runOnlyForDeploymentPostprocessing = 0;
234 | shellPath = /bin/sh;
235 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
236 | showEnvVarsInLog = 0;
237 | };
238 | 11EDB517A9D814A7C2BF386E /* [CP] Copy Pods Resources */ = {
239 | isa = PBXShellScriptBuildPhase;
240 | buildActionMask = 2147483647;
241 | files = (
242 | );
243 | inputPaths = (
244 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh",
245 | "${PODS_ROOT}/GoogleSignIn/Resources/GoogleSignIn.bundle",
246 | "${PODS_CONFIGURATION_BUILD_DIR}/gRPC-C++/gRPCCertificates-Cpp.bundle",
247 | );
248 | name = "[CP] Copy Pods Resources";
249 | outputPaths = (
250 | "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleSignIn.bundle",
251 | "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/gRPCCertificates-Cpp.bundle",
252 | );
253 | runOnlyForDeploymentPostprocessing = 0;
254 | shellPath = /bin/sh;
255 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
256 | showEnvVarsInLog = 0;
257 | };
258 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
259 | isa = PBXShellScriptBuildPhase;
260 | buildActionMask = 2147483647;
261 | files = (
262 | );
263 | inputPaths = (
264 | );
265 | name = "Thin Binary";
266 | outputPaths = (
267 | );
268 | runOnlyForDeploymentPostprocessing = 0;
269 | shellPath = /bin/sh;
270 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
271 | };
272 | 9740EEB61CF901F6004384FC /* Run Script */ = {
273 | isa = PBXShellScriptBuildPhase;
274 | buildActionMask = 2147483647;
275 | files = (
276 | );
277 | inputPaths = (
278 | );
279 | name = "Run Script";
280 | outputPaths = (
281 | );
282 | runOnlyForDeploymentPostprocessing = 0;
283 | shellPath = /bin/sh;
284 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
285 | };
286 | D2C1B3B5A3B05D71F659340E /* [CP] Embed Pods Frameworks */ = {
287 | isa = PBXShellScriptBuildPhase;
288 | buildActionMask = 2147483647;
289 | files = (
290 | );
291 | inputPaths = (
292 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh",
293 | "${PODS_ROOT}/../Flutter/Flutter.framework",
294 | );
295 | name = "[CP] Embed Pods Frameworks";
296 | outputPaths = (
297 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework",
298 | );
299 | runOnlyForDeploymentPostprocessing = 0;
300 | shellPath = /bin/sh;
301 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
302 | showEnvVarsInLog = 0;
303 | };
304 | /* End PBXShellScriptBuildPhase section */
305 |
306 | /* Begin PBXSourcesBuildPhase section */
307 | 97C146EA1CF9000F007C117D /* Sources */ = {
308 | isa = PBXSourcesBuildPhase;
309 | buildActionMask = 2147483647;
310 | files = (
311 | 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */,
312 | 97C146F31CF9000F007C117D /* main.m in Sources */,
313 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
314 | );
315 | runOnlyForDeploymentPostprocessing = 0;
316 | };
317 | /* End PBXSourcesBuildPhase section */
318 |
319 | /* Begin PBXVariantGroup section */
320 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = {
321 | isa = PBXVariantGroup;
322 | children = (
323 | 97C146FB1CF9000F007C117D /* Base */,
324 | );
325 | name = Main.storyboard;
326 | sourceTree = "";
327 | };
328 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
329 | isa = PBXVariantGroup;
330 | children = (
331 | 97C147001CF9000F007C117D /* Base */,
332 | );
333 | name = LaunchScreen.storyboard;
334 | sourceTree = "";
335 | };
336 | /* End PBXVariantGroup section */
337 |
338 | /* Begin XCBuildConfiguration section */
339 | 97C147031CF9000F007C117D /* Debug */ = {
340 | isa = XCBuildConfiguration;
341 | buildSettings = {
342 | ALWAYS_SEARCH_USER_PATHS = NO;
343 | CLANG_ANALYZER_NONNULL = YES;
344 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
345 | CLANG_CXX_LIBRARY = "libc++";
346 | CLANG_ENABLE_MODULES = YES;
347 | CLANG_ENABLE_OBJC_ARC = YES;
348 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
349 | CLANG_WARN_BOOL_CONVERSION = YES;
350 | CLANG_WARN_COMMA = YES;
351 | CLANG_WARN_CONSTANT_CONVERSION = YES;
352 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
353 | CLANG_WARN_EMPTY_BODY = YES;
354 | CLANG_WARN_ENUM_CONVERSION = YES;
355 | CLANG_WARN_INFINITE_RECURSION = YES;
356 | CLANG_WARN_INT_CONVERSION = YES;
357 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
358 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
359 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
360 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
361 | CLANG_WARN_STRICT_PROTOTYPES = YES;
362 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
363 | CLANG_WARN_UNREACHABLE_CODE = YES;
364 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
365 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
366 | COPY_PHASE_STRIP = NO;
367 | DEBUG_INFORMATION_FORMAT = dwarf;
368 | ENABLE_STRICT_OBJC_MSGSEND = YES;
369 | ENABLE_TESTABILITY = YES;
370 | GCC_C_LANGUAGE_STANDARD = gnu99;
371 | GCC_DYNAMIC_NO_PIC = NO;
372 | GCC_NO_COMMON_BLOCKS = YES;
373 | GCC_OPTIMIZATION_LEVEL = 0;
374 | GCC_PREPROCESSOR_DEFINITIONS = (
375 | "DEBUG=1",
376 | "$(inherited)",
377 | );
378 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
379 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
380 | GCC_WARN_UNDECLARED_SELECTOR = YES;
381 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
382 | GCC_WARN_UNUSED_FUNCTION = YES;
383 | GCC_WARN_UNUSED_VARIABLE = YES;
384 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
385 | MTL_ENABLE_DEBUG_INFO = YES;
386 | ONLY_ACTIVE_ARCH = YES;
387 | SDKROOT = iphoneos;
388 | TARGETED_DEVICE_FAMILY = "1,2";
389 | };
390 | name = Debug;
391 | };
392 | 97C147041CF9000F007C117D /* Release */ = {
393 | isa = XCBuildConfiguration;
394 | buildSettings = {
395 | ALWAYS_SEARCH_USER_PATHS = NO;
396 | CLANG_ANALYZER_NONNULL = YES;
397 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
398 | CLANG_CXX_LIBRARY = "libc++";
399 | CLANG_ENABLE_MODULES = YES;
400 | CLANG_ENABLE_OBJC_ARC = YES;
401 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
402 | CLANG_WARN_BOOL_CONVERSION = YES;
403 | CLANG_WARN_COMMA = YES;
404 | CLANG_WARN_CONSTANT_CONVERSION = YES;
405 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
406 | CLANG_WARN_EMPTY_BODY = YES;
407 | CLANG_WARN_ENUM_CONVERSION = YES;
408 | CLANG_WARN_INFINITE_RECURSION = YES;
409 | CLANG_WARN_INT_CONVERSION = YES;
410 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
411 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
412 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
413 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
414 | CLANG_WARN_STRICT_PROTOTYPES = YES;
415 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
416 | CLANG_WARN_UNREACHABLE_CODE = YES;
417 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
418 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
419 | COPY_PHASE_STRIP = NO;
420 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
421 | ENABLE_NS_ASSERTIONS = NO;
422 | ENABLE_STRICT_OBJC_MSGSEND = YES;
423 | GCC_C_LANGUAGE_STANDARD = gnu99;
424 | GCC_NO_COMMON_BLOCKS = YES;
425 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
426 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
427 | GCC_WARN_UNDECLARED_SELECTOR = YES;
428 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
429 | GCC_WARN_UNUSED_FUNCTION = YES;
430 | GCC_WARN_UNUSED_VARIABLE = YES;
431 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
432 | MTL_ENABLE_DEBUG_INFO = NO;
433 | SDKROOT = iphoneos;
434 | TARGETED_DEVICE_FAMILY = "1,2";
435 | VALIDATE_PRODUCT = YES;
436 | };
437 | name = Release;
438 | };
439 | 97C147061CF9000F007C117D /* Debug */ = {
440 | isa = XCBuildConfiguration;
441 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
442 | buildSettings = {
443 | ARCHS = arm64;
444 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
445 | CODE_SIGN_IDENTITY = "iPhone Developer";
446 | CODE_SIGN_STYLE = Automatic;
447 | DEVELOPMENT_TEAM = XFKDZYYPTU;
448 | ENABLE_BITCODE = NO;
449 | FRAMEWORK_SEARCH_PATHS = (
450 | "$(inherited)",
451 | "$(PROJECT_DIR)/Flutter",
452 | );
453 | INFOPLIST_FILE = Runner/Info.plist;
454 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
455 | LIBRARY_SEARCH_PATHS = (
456 | "$(inherited)",
457 | "$(PROJECT_DIR)/Flutter",
458 | );
459 | PRODUCT_BUNDLE_IDENTIFIER = me.danics.fluttergram;
460 | PRODUCT_NAME = "$(TARGET_NAME)";
461 | PROVISIONING_PROFILE = "";
462 | PROVISIONING_PROFILE_SPECIFIER = "";
463 | };
464 | name = Debug;
465 | };
466 | 97C147071CF9000F007C117D /* Release */ = {
467 | isa = XCBuildConfiguration;
468 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
469 | buildSettings = {
470 | ARCHS = arm64;
471 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
472 | CODE_SIGN_IDENTITY = "iPhone Developer";
473 | CODE_SIGN_STYLE = Automatic;
474 | DEVELOPMENT_TEAM = XFKDZYYPTU;
475 | ENABLE_BITCODE = NO;
476 | FRAMEWORK_SEARCH_PATHS = (
477 | "$(inherited)",
478 | "$(PROJECT_DIR)/Flutter",
479 | );
480 | INFOPLIST_FILE = Runner/Info.plist;
481 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
482 | LIBRARY_SEARCH_PATHS = (
483 | "$(inherited)",
484 | "$(PROJECT_DIR)/Flutter",
485 | );
486 | PRODUCT_BUNDLE_IDENTIFIER = me.danics.fluttergram;
487 | PRODUCT_NAME = "$(TARGET_NAME)";
488 | PROVISIONING_PROFILE_SPECIFIER = "";
489 | };
490 | name = Release;
491 | };
492 | /* End XCBuildConfiguration section */
493 |
494 | /* Begin XCConfigurationList section */
495 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
496 | isa = XCConfigurationList;
497 | buildConfigurations = (
498 | 97C147031CF9000F007C117D /* Debug */,
499 | 97C147041CF9000F007C117D /* Release */,
500 | );
501 | defaultConfigurationIsVisible = 0;
502 | defaultConfigurationName = Release;
503 | };
504 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
505 | isa = XCConfigurationList;
506 | buildConfigurations = (
507 | 97C147061CF9000F007C117D /* Debug */,
508 | 97C147071CF9000F007C117D /* Release */,
509 | );
510 | defaultConfigurationIsVisible = 0;
511 | defaultConfigurationName = Release;
512 | };
513 | /* End XCConfigurationList section */
514 | };
515 | rootObject = 97C146E61CF9000F007C117D /* Project object */;
516 | }
517 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
33 |
34 |
40 |
41 |
42 |
43 |
44 |
45 |
56 |
58 |
64 |
65 |
66 |
67 |
68 |
69 |
75 |
77 |
83 |
84 |
85 |
86 |
88 |
89 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/ios/Runner/AppDelegate.h:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 |
4 | @interface AppDelegate : FlutterAppDelegate
5 |
6 | @end
7 |
--------------------------------------------------------------------------------
/ios/Runner/AppDelegate.m:
--------------------------------------------------------------------------------
1 | #include "AppDelegate.h"
2 | #include "GeneratedPluginRegistrant.h"
3 |
4 | @implementation AppDelegate
5 |
6 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
7 | [GeneratedPluginRegistrant registerWithRegistry:self];
8 | // Override point for customization after application launch.
9 | return [super application:application didFinishLaunchingWithOptions:launchOptions];
10 | }
11 |
12 | @end
13 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "20x20",
5 | "idiom" : "iphone",
6 | "filename" : "Icon-App-20x20@2x.png",
7 | "scale" : "2x"
8 | },
9 | {
10 | "size" : "20x20",
11 | "idiom" : "iphone",
12 | "filename" : "Icon-App-20x20@3x.png",
13 | "scale" : "3x"
14 | },
15 | {
16 | "size" : "29x29",
17 | "idiom" : "iphone",
18 | "filename" : "Icon-App-29x29@1x.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "29x29",
23 | "idiom" : "iphone",
24 | "filename" : "Icon-App-29x29@2x.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "29x29",
29 | "idiom" : "iphone",
30 | "filename" : "Icon-App-29x29@3x.png",
31 | "scale" : "3x"
32 | },
33 | {
34 | "size" : "40x40",
35 | "idiom" : "iphone",
36 | "filename" : "Icon-App-40x40@2x.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "40x40",
41 | "idiom" : "iphone",
42 | "filename" : "Icon-App-40x40@3x.png",
43 | "scale" : "3x"
44 | },
45 | {
46 | "size" : "60x60",
47 | "idiom" : "iphone",
48 | "filename" : "Icon-App-60x60@2x.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "60x60",
53 | "idiom" : "iphone",
54 | "filename" : "Icon-App-60x60@3x.png",
55 | "scale" : "3x"
56 | },
57 | {
58 | "size" : "20x20",
59 | "idiom" : "ipad",
60 | "filename" : "Icon-App-20x20@1x.png",
61 | "scale" : "1x"
62 | },
63 | {
64 | "size" : "20x20",
65 | "idiom" : "ipad",
66 | "filename" : "Icon-App-20x20@2x.png",
67 | "scale" : "2x"
68 | },
69 | {
70 | "size" : "29x29",
71 | "idiom" : "ipad",
72 | "filename" : "Icon-App-29x29@1x.png",
73 | "scale" : "1x"
74 | },
75 | {
76 | "size" : "29x29",
77 | "idiom" : "ipad",
78 | "filename" : "Icon-App-29x29@2x.png",
79 | "scale" : "2x"
80 | },
81 | {
82 | "size" : "40x40",
83 | "idiom" : "ipad",
84 | "filename" : "Icon-App-40x40@1x.png",
85 | "scale" : "1x"
86 | },
87 | {
88 | "size" : "40x40",
89 | "idiom" : "ipad",
90 | "filename" : "Icon-App-40x40@2x.png",
91 | "scale" : "2x"
92 | },
93 | {
94 | "size" : "76x76",
95 | "idiom" : "ipad",
96 | "filename" : "Icon-App-76x76@1x.png",
97 | "scale" : "1x"
98 | },
99 | {
100 | "size" : "76x76",
101 | "idiom" : "ipad",
102 | "filename" : "Icon-App-76x76@2x.png",
103 | "scale" : "2x"
104 | },
105 | {
106 | "size" : "83.5x83.5",
107 | "idiom" : "ipad",
108 | "filename" : "Icon-App-83.5x83.5@2x.png",
109 | "scale" : "2x"
110 | },
111 | {
112 | "size" : "1024x1024",
113 | "idiom" : "ios-marketing",
114 | "filename" : "Icon-App-1024x1024@1x.png",
115 | "scale" : "1x"
116 | }
117 | ],
118 | "info" : {
119 | "version" : 1,
120 | "author" : "xcode"
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdanics/fluttergram/d372cfd8e34994af5f774a25b3d4ae147d3965aa/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdanics/fluttergram/d372cfd8e34994af5f774a25b3d4ae147d3965aa/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdanics/fluttergram/d372cfd8e34994af5f774a25b3d4ae147d3965aa/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdanics/fluttergram/d372cfd8e34994af5f774a25b3d4ae147d3965aa/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdanics/fluttergram/d372cfd8e34994af5f774a25b3d4ae147d3965aa/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdanics/fluttergram/d372cfd8e34994af5f774a25b3d4ae147d3965aa/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdanics/fluttergram/d372cfd8e34994af5f774a25b3d4ae147d3965aa/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdanics/fluttergram/d372cfd8e34994af5f774a25b3d4ae147d3965aa/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdanics/fluttergram/d372cfd8e34994af5f774a25b3d4ae147d3965aa/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdanics/fluttergram/d372cfd8e34994af5f774a25b3d4ae147d3965aa/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdanics/fluttergram/d372cfd8e34994af5f774a25b3d4ae147d3965aa/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdanics/fluttergram/d372cfd8e34994af5f774a25b3d4ae147d3965aa/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdanics/fluttergram/d372cfd8e34994af5f774a25b3d4ae147d3965aa/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdanics/fluttergram/d372cfd8e34994af5f774a25b3d4ae147d3965aa/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdanics/fluttergram/d372cfd8e34994af5f774a25b3d4ae147d3965aa/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "LaunchImage.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "LaunchImage@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "LaunchImage@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdanics/fluttergram/d372cfd8e34994af5f774a25b3d4ae147d3965aa/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdanics/fluttergram/d372cfd8e34994af5f774a25b3d4ae147d3965aa/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mdanics/fluttergram/d372cfd8e34994af5f774a25b3d4ae147d3965aa/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md:
--------------------------------------------------------------------------------
1 | # Launch Screen Assets
2 |
3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory.
4 |
5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/ios/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleDisplayName
8 | Fluttergram
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | firestore_test
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleSignature
22 | ????
23 | CFBundleURLTypes
24 |
25 |
26 | CFBundleTypeRole
27 | Editor
28 | CFBundleURLSchemes
29 |
30 | com.googleusercontent.apps.7019780124-8d9k97bo7jd6gn2fdm8pahj260br014j
31 | com.googleusercontent.apps.7019780124-lrego1vac3jl8a6egvo8efe56o8v2po0
32 |
33 |
34 |
35 | CFBundleVersion
36 | 1
37 | LSRequiresIPhoneOS
38 |
39 | NSCameraUsageDescription
40 | Used to allow the user to take a photo to upload as a post
41 | NSPhotoLibraryUsageDescription
42 | Used to allow the user to select a photo to upload as a post
43 | UILaunchStoryboardName
44 | LaunchScreen
45 | UIMainStoryboardFile
46 | Main
47 | UIRequiredDeviceCapabilities
48 |
49 | arm64
50 |
51 | UISupportedInterfaceOrientations
52 |
53 | UIInterfaceOrientationPortrait
54 | UIInterfaceOrientationLandscapeLeft
55 | UIInterfaceOrientationLandscapeRight
56 |
57 | UISupportedInterfaceOrientations~ipad
58 |
59 | UIInterfaceOrientationPortrait
60 | UIInterfaceOrientationPortraitUpsideDown
61 | UIInterfaceOrientationLandscapeLeft
62 | UIInterfaceOrientationLandscapeRight
63 |
64 | NSLocationAlwaysUsageDescription
65 | Your location required to post
66 | NSLocationWhenInUseUsageDescription
67 | Your location required to post
68 | UIViewControllerBasedStatusBarAppearance
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/ios/Runner/main.m:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 | #import "AppDelegate.h"
4 |
5 | int main(int argc, char * argv[]) {
6 | @autoreleasepool {
7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/lib/activity_feed.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:cloud_firestore/cloud_firestore.dart';
3 | import 'image_post.dart'; //needed to open image when clicked
4 | import 'profile_page.dart'; // to open the profile page when username clicked
5 | import 'main.dart'; //needed for currentuser id
6 |
7 | class ActivityFeedPage extends StatefulWidget {
8 | @override
9 | _ActivityFeedPageState createState() => _ActivityFeedPageState();
10 | }
11 |
12 | class _ActivityFeedPageState extends State with AutomaticKeepAliveClientMixin {
13 | @override
14 | Widget build(BuildContext context) {
15 | super.build(context); // reloads state when opened again
16 |
17 | return Scaffold(
18 | appBar: AppBar(
19 | title: Text(
20 | "Activity Feed",
21 | style: TextStyle(color: Colors.black),
22 | ),
23 | backgroundColor: Colors.white,
24 | ),
25 | body: buildActivityFeed(),
26 | );
27 | }
28 |
29 | buildActivityFeed() {
30 | return Container(
31 | child: FutureBuilder(
32 | future: getFeed(),
33 | builder: (context, snapshot) {
34 | if (!snapshot.hasData)
35 | return Container(
36 | alignment: FractionalOffset.center,
37 | padding: const EdgeInsets.only(top: 10.0),
38 | child: CircularProgressIndicator());
39 | else {
40 | return ListView(children: snapshot.data);
41 | }
42 | }),
43 | );
44 | }
45 |
46 | getFeed() async {
47 | List items = [];
48 | var snap = await FirebaseFirestore.instance
49 | .collection('insta_a_feed')
50 | .doc(currentUserModel.id)
51 | .collection("items")
52 | .orderBy("timestamp")
53 | .get();
54 |
55 | for (var doc in snap.docs) {
56 | items.add(ActivityFeedItem.fromDocument(doc));
57 | }
58 | return items;
59 | }
60 |
61 | // ensures state is kept when switching pages
62 | @override
63 | bool get wantKeepAlive => true;
64 |
65 | }
66 |
67 | class ActivityFeedItem extends StatelessWidget {
68 | final String username;
69 | final String userId;
70 | final String
71 | type; // types include liked photo, follow user, comment on photo
72 | final String mediaUrl;
73 | final String mediaId;
74 | final String userProfileImg;
75 | final String commentData;
76 |
77 | ActivityFeedItem(
78 | {this.username,
79 | this.userId,
80 | this.type,
81 | this.mediaUrl,
82 | this.mediaId,
83 | this.userProfileImg,
84 | this.commentData});
85 |
86 | factory ActivityFeedItem.fromDocument(DocumentSnapshot document) {
87 | var data = document.data();
88 | return ActivityFeedItem(
89 | username: data['username'],
90 | userId: data['userId'],
91 | type: data['type'],
92 | mediaUrl: data['mediaUrl'],
93 | mediaId: data['postId'],
94 | userProfileImg: data['userProfileImg'],
95 | commentData: data["commentData"],
96 | );
97 | }
98 |
99 | Widget mediaPreview = Container();
100 | String actionText;
101 |
102 | void configureItem(BuildContext context) {
103 | if (type == "like" || type == "comment") {
104 | mediaPreview = GestureDetector(
105 | onTap: () {
106 | openImage(context, mediaId);
107 | },
108 | child: Container(
109 | height: 45.0,
110 | width: 45.0,
111 | child: AspectRatio(
112 | aspectRatio: 487 / 451,
113 | child: Container(
114 | decoration: BoxDecoration(
115 | image: DecorationImage(
116 | fit: BoxFit.fill,
117 | alignment: FractionalOffset.topCenter,
118 | image: NetworkImage(mediaUrl),
119 | )),
120 | ),
121 | ),
122 | ),
123 | );
124 | }
125 |
126 | if (type == "like") {
127 | actionText = " liked your post.";
128 | } else if (type == "follow") {
129 | actionText = " starting following you.";
130 | } else if (type == "comment") {
131 | actionText = " commented: $commentData";
132 | } else {
133 | actionText = "Error - invalid activityFeed type: $type";
134 | }
135 | }
136 |
137 | @override
138 | Widget build(BuildContext context) {
139 | configureItem(context);
140 | return Row(
141 | mainAxisSize: MainAxisSize.max,
142 | children: [
143 | Padding(
144 | padding: const EdgeInsets.only(left: 20.0, right: 15.0),
145 | child: CircleAvatar(
146 | radius: 23.0,
147 | backgroundImage: NetworkImage(userProfileImg),
148 | ),
149 | ),
150 | Expanded(
151 | child: Row(
152 | mainAxisSize: MainAxisSize.min,
153 | children: [
154 | GestureDetector(
155 | child: Text(
156 | username,
157 | style: TextStyle(fontWeight: FontWeight.bold),
158 | ),
159 | onTap: () {
160 | openProfile(context, userId);
161 | },
162 | ),
163 | Flexible(
164 | child: Container(
165 | child: Text(
166 | actionText,
167 | overflow: TextOverflow.ellipsis,
168 | ),
169 | ),
170 | )
171 | ],
172 | ),
173 | ),
174 | Container(
175 | child: Align(
176 | child: Padding(
177 | child: mediaPreview,
178 | padding: EdgeInsets.all(15.0),
179 | ),
180 | alignment: AlignmentDirectional.bottomEnd))
181 | ],
182 | );
183 | }
184 | }
185 |
186 | openImage(BuildContext context, String imageId) {
187 | print("the image id is $imageId");
188 | Navigator.of(context)
189 | .push(MaterialPageRoute(builder: (BuildContext context) {
190 | return Center(
191 | child: Scaffold(
192 | appBar: AppBar(
193 | title: Text('Photo',
194 | style: TextStyle(
195 | color: Colors.black, fontWeight: FontWeight.bold)),
196 | backgroundColor: Colors.white,
197 | ),
198 | body: ListView(
199 | children: [
200 | Container(
201 | child: ImagePostFromId(id: imageId),
202 | ),
203 | ],
204 | )),
205 | );
206 | }));
207 | }
208 |
--------------------------------------------------------------------------------
/lib/comment_screen.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:cloud_firestore/cloud_firestore.dart';
3 | import "dart:async";
4 | import "main.dart"; //for current user
5 |
6 | class CommentScreen extends StatefulWidget {
7 | final String postId;
8 | final String postOwner;
9 | final String postMediaUrl;
10 |
11 | const CommentScreen({this.postId, this.postOwner, this.postMediaUrl});
12 | @override
13 | _CommentScreenState createState() => _CommentScreenState(
14 | postId: this.postId,
15 | postOwner: this.postOwner,
16 | postMediaUrl: this.postMediaUrl);
17 | }
18 |
19 | class _CommentScreenState extends State {
20 | final String postId;
21 | final String postOwner;
22 | final String postMediaUrl;
23 |
24 | bool didFetchComments = false;
25 | List fetchedComments = [];
26 |
27 | final TextEditingController _commentController = TextEditingController();
28 |
29 | _CommentScreenState({this.postId, this.postOwner, this.postMediaUrl});
30 |
31 | @override
32 | Widget build(BuildContext context) {
33 | return Scaffold(
34 | appBar: AppBar(
35 | title: Text(
36 | "Comments",
37 | style: TextStyle(color: Colors.black),
38 | ),
39 | backgroundColor: Colors.white,
40 | ),
41 | body: buildPage(),
42 | );
43 | }
44 |
45 | Widget buildPage() {
46 | return Column(
47 | children: [
48 | Expanded(
49 | child:
50 | buildComments(),
51 | ),
52 | Divider(),
53 | ListTile(
54 | title: TextFormField(
55 | controller: _commentController,
56 | decoration: InputDecoration(labelText: 'Write a comment...'),
57 | onFieldSubmitted: addComment,
58 | ),
59 | trailing: OutlineButton(onPressed: (){addComment(_commentController.text);}, borderSide: BorderSide.none, child: Text("Post"),),
60 | ),
61 | ],
62 | );
63 | }
64 |
65 |
66 | Widget buildComments() {
67 | if (this.didFetchComments == false){
68 | return FutureBuilder>(
69 | future: getComments(),
70 | builder: (context, snapshot) {
71 | if (!snapshot.hasData)
72 | return Container(
73 | alignment: FractionalOffset.center,
74 | child: CircularProgressIndicator());
75 |
76 | this.didFetchComments = true;
77 | this.fetchedComments = snapshot.data;
78 | return ListView(
79 | children: snapshot.data,
80 | );
81 | });
82 | } else {
83 | // for optimistic updating
84 | return ListView(
85 | children: this.fetchedComments
86 | );
87 | }
88 | }
89 |
90 | Future> getComments() async {
91 | List comments = [];
92 |
93 | QuerySnapshot data = await FirebaseFirestore.instance
94 | .collection("insta_comments")
95 | .doc(postId)
96 | .collection("comments")
97 | .get();
98 | data.docs.forEach((DocumentSnapshot doc) {
99 | comments.add(Comment.fromDocument(doc));
100 | });
101 | return comments;
102 | }
103 |
104 | addComment(String comment) {
105 | _commentController.clear();
106 | FirebaseFirestore.instance
107 | .collection("insta_comments")
108 | .doc(postId)
109 | .collection("comments")
110 | .add({
111 | "username": currentUserModel.username,
112 | "comment": comment,
113 | "timestamp": Timestamp.now(),
114 | "avatarUrl": currentUserModel.photoUrl,
115 | "userId": currentUserModel.id
116 | });
117 |
118 | //adds to postOwner's activity feed
119 | FirebaseFirestore.instance
120 | .collection("insta_a_feed")
121 | .doc(postOwner)
122 | .collection("items")
123 | .add({
124 | "username": currentUserModel.username,
125 | "userId": currentUserModel.id,
126 | "type": "comment",
127 | "userProfileImg": currentUserModel.photoUrl,
128 | "commentData": comment,
129 | "timestamp": Timestamp.now(),
130 | "postId": postId,
131 | "mediaUrl": postMediaUrl,
132 | });
133 |
134 | // add comment to the current listview for an optimistic update
135 | setState(() {
136 | fetchedComments = List.from(fetchedComments)..add(Comment(
137 | username: currentUserModel.username,
138 | comment: comment,
139 | timestamp: Timestamp.now(),
140 | avatarUrl: currentUserModel.photoUrl,
141 | userId: currentUserModel.id
142 | ));
143 | });
144 | }
145 | }
146 |
147 | class Comment extends StatelessWidget {
148 | final String username;
149 | final String userId;
150 | final String avatarUrl;
151 | final String comment;
152 | final Timestamp timestamp;
153 |
154 | Comment(
155 | {this.username,
156 | this.userId,
157 | this.avatarUrl,
158 | this.comment,
159 | this.timestamp});
160 |
161 | factory Comment.fromDocument(DocumentSnapshot document) {
162 | var data = document.data();
163 | return Comment(
164 | username: data['username'],
165 | userId: data['userId'],
166 | comment: data["comment"],
167 | timestamp: data["timestamp"],
168 | avatarUrl: data["avatarUrl"],
169 | );
170 | }
171 |
172 | @override
173 | Widget build(BuildContext context) {
174 | return Column(
175 | children: [
176 | ListTile(
177 | title: Text(comment),
178 | leading: CircleAvatar(
179 | backgroundImage: NetworkImage(avatarUrl),
180 | ),
181 | ),
182 | Divider(),
183 | ],
184 | );
185 | }
186 | }
187 |
--------------------------------------------------------------------------------
/lib/create_account.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class CreateAccount extends StatefulWidget {
4 | @override
5 | _CreateAccountState createState() => _CreateAccountState();
6 | }
7 |
8 | class _CreateAccountState extends State {
9 | final name = TextEditingController();
10 |
11 |
12 | @override
13 | void dispose() {
14 | // Clean up the controller when the Widget is removed from the Widget tree
15 | name.dispose();
16 | super.dispose();
17 | }
18 |
19 | @override
20 | Widget build(BuildContext context2) {
21 | return Column(children: [
22 | Padding(
23 | padding: const EdgeInsets.only(top: 25.0),
24 | child: Center(
25 | child: Text(
26 | "Create a username",
27 | style: TextStyle(fontSize: 25.0),
28 | ),
29 | ),
30 | ),
31 | Padding(
32 | padding: const EdgeInsets.all(16.0),
33 | child: Container(
34 | decoration: BoxDecoration(
35 | border: Border.all(width: 1.0, color: Colors.black26),
36 | borderRadius: BorderRadius.circular(7.0)),
37 | child: TextField(
38 | controller: name,
39 | decoration: InputDecoration(
40 | border: InputBorder.none,
41 | contentPadding: const EdgeInsets.all(10.0),
42 | labelText: "Username",
43 | labelStyle: TextStyle(fontSize: 15.0)),
44 | ),
45 | ),
46 | ),
47 | GestureDetector(
48 | onTap: () {
49 | if (name.text == null || name.text.length == 0){
50 | return;
51 | }
52 | Navigator.pop(context, name.text);
53 | },
54 |
55 |
56 | child: Container(
57 | width: 350.0,
58 | height: 50.0,
59 | child: Center(
60 | child: Text(
61 | "Next",
62 | style: TextStyle(
63 | color: Colors.white, fontSize: 15.0, fontWeight: FontWeight.bold),
64 | )),
65 | decoration: BoxDecoration(
66 | color: Colors.blue, borderRadius: BorderRadius.circular(7.0)),
67 | ))
68 | ]);
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/lib/edit_profile_page.dart:
--------------------------------------------------------------------------------
1 | import "package:flutter/material.dart";
2 | import 'package:cloud_firestore/cloud_firestore.dart';
3 | import 'main.dart'; //for currentuser & google signin instance
4 | import 'models/user.dart';
5 | import 'package:shared_preferences/shared_preferences.dart';
6 |
7 | class EditProfilePage extends StatelessWidget {
8 | final TextEditingController nameController = TextEditingController();
9 | final TextEditingController bioController = TextEditingController();
10 |
11 | changeProfilePhoto(BuildContext parentContext) {
12 | return showDialog(
13 | context: parentContext,
14 | builder: (BuildContext context) {
15 | return AlertDialog(
16 | title: Text('Change Photo'),
17 | content: SingleChildScrollView(
18 | child: ListBody(
19 | children: [
20 | Text(
21 | 'Changing your profile photo has not been implemented yet'),
22 | ],
23 | ),
24 | ),
25 | );
26 | },
27 | );
28 | }
29 |
30 | applyChanges() {
31 | FirebaseFirestore.instance
32 | .collection('insta_users')
33 | .doc(currentUserModel.id)
34 | .update({
35 | "displayName": nameController.text,
36 | "bio": bioController.text,
37 | });
38 | }
39 |
40 | Widget buildTextField({String name, TextEditingController controller}) {
41 | return Column(
42 | crossAxisAlignment: CrossAxisAlignment.start,
43 | children: [
44 | Padding(
45 | padding: const EdgeInsets.only(top: 12.0),
46 | child: Text(
47 | name,
48 | style: TextStyle(color: Colors.grey),
49 | ),
50 | ),
51 | TextField(
52 | controller: controller,
53 | decoration: InputDecoration(
54 | hintText: name,
55 | ),
56 | ),
57 | ],
58 | );
59 | }
60 |
61 | @override
62 | Widget build(BuildContext context) {
63 | return FutureBuilder(
64 | future: FirebaseFirestore.instance
65 | .collection('insta_users')
66 | .doc(currentUserModel.id)
67 | .get(),
68 | builder: (context, snapshot) {
69 | if (!snapshot.hasData)
70 | return Container(
71 | alignment: FractionalOffset.center,
72 | child: CircularProgressIndicator());
73 |
74 | User user = User.fromDocument(snapshot.data);
75 |
76 | nameController.text = user.displayName;
77 | bioController.text = user.bio;
78 |
79 | return Column(
80 | children: [
81 | Padding(
82 | padding: const EdgeInsets.only(top: 16.0, bottom: 8.0),
83 | child: CircleAvatar(
84 | backgroundImage: NetworkImage(currentUserModel.photoUrl),
85 | radius: 50.0,
86 | ),
87 | ),
88 | FlatButton(
89 | onPressed: () {
90 | changeProfilePhoto(context);
91 | },
92 | child: Text(
93 | "Change Photo",
94 | style: const TextStyle(
95 | color: Colors.blue,
96 | fontSize: 20.0,
97 | fontWeight: FontWeight.bold),
98 | )),
99 | Padding(
100 | padding: const EdgeInsets.all(16.0),
101 | child: Column(
102 | children: [
103 | buildTextField(name: "Name", controller: nameController),
104 | buildTextField(name: "Bio", controller: bioController),
105 | ],
106 | ),
107 | ),
108 | Padding(
109 | padding: const EdgeInsets.all(16.0),
110 | child: MaterialButton(
111 | onPressed: () => {_logout(context)},
112 | child: Text("Logout")
113 |
114 | )
115 | )
116 | ],
117 | );
118 | });
119 | }
120 |
121 | void _logout(BuildContext context) async {
122 | print("logout");
123 | await auth.signOut();
124 | await googleSignIn.signOut();
125 |
126 | SharedPreferences prefs = await SharedPreferences.getInstance();
127 | await prefs.clear();
128 |
129 | currentUserModel = null;
130 |
131 | Navigator.pop(context);
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/lib/feed.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'image_post.dart';
3 | import 'dart:async';
4 | import 'main.dart';
5 | import 'dart:io';
6 | import 'dart:convert';
7 | import 'package:shared_preferences/shared_preferences.dart';
8 |
9 | class Feed extends StatefulWidget {
10 | _Feed createState() => _Feed();
11 | }
12 |
13 | class _Feed extends State with AutomaticKeepAliveClientMixin {
14 | List feedData;
15 |
16 | @override
17 | void initState() {
18 | super.initState();
19 | this._loadFeed();
20 | }
21 |
22 | buildFeed() {
23 | if (feedData != null) {
24 | return ListView(
25 | children: feedData,
26 | );
27 | } else {
28 | return Container(
29 | alignment: FractionalOffset.center,
30 | child: CircularProgressIndicator());
31 | }
32 | }
33 |
34 | @override
35 | Widget build(BuildContext context) {
36 | super.build(context); // reloads state when opened again
37 |
38 | return Scaffold(
39 | appBar: AppBar(
40 | title: const Text('Fluttergram',
41 | style: const TextStyle(
42 | fontFamily: "Billabong", color: Colors.black, fontSize: 35.0)),
43 | centerTitle: true,
44 | backgroundColor: Colors.white,
45 | ),
46 | body: RefreshIndicator(
47 | onRefresh: _refresh,
48 | child: buildFeed(),
49 | ),
50 | );
51 | }
52 |
53 | Future _refresh() async {
54 | await _getFeed();
55 |
56 | setState(() {});
57 |
58 | return;
59 | }
60 |
61 | _loadFeed() async {
62 | SharedPreferences prefs = await SharedPreferences.getInstance();
63 | String json = prefs.getString("feed");
64 |
65 | if (json != null) {
66 | List> data =
67 | jsonDecode(json).cast>();
68 | List listOfPosts = _generateFeed(data);
69 | setState(() {
70 | feedData = listOfPosts;
71 | });
72 | } else {
73 | _getFeed();
74 | }
75 | }
76 |
77 | _getFeed() async {
78 | print("Staring getFeed");
79 |
80 | SharedPreferences prefs = await SharedPreferences.getInstance();
81 |
82 | String userId = googleSignIn.currentUser.id.toString();
83 | var url =
84 | 'https://us-central1-mp-rps.cloudfunctions.net/getFeed?uid=' + userId;
85 | var httpClient = HttpClient();
86 |
87 | List listOfPosts;
88 | String result;
89 | try {
90 | var request = await httpClient.getUrl(Uri.parse(url));
91 | var response = await request.close();
92 | if (response.statusCode == HttpStatus.ok) {
93 | String json = await response.transform(utf8.decoder).join();
94 | prefs.setString("feed", json);
95 | List> data =
96 | jsonDecode(json).cast>();
97 | listOfPosts = _generateFeed(data);
98 | result = "Success in http request for feed";
99 | } else {
100 | result =
101 | 'Error getting a feed: Http status ${response.statusCode} | userId $userId';
102 | }
103 | } catch (exception) {
104 | result = 'Failed invoking the getFeed function. Exception: $exception';
105 | }
106 | print(result);
107 |
108 | setState(() {
109 | feedData = listOfPosts;
110 | });
111 | }
112 |
113 | List _generateFeed(List> feedData) {
114 | List listOfPosts = [];
115 |
116 | for (var postData in feedData) {
117 | listOfPosts.add(ImagePost.fromJSON(postData));
118 | }
119 |
120 | return listOfPosts;
121 | }
122 |
123 | // ensures state is kept when switching pages
124 | @override
125 | bool get wantKeepAlive => true;
126 | }
127 |
--------------------------------------------------------------------------------
/lib/image_post.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:cloud_firestore/cloud_firestore.dart';
3 | import 'package:font_awesome_flutter/font_awesome_flutter.dart';
4 | import 'main.dart';
5 | import 'dart:async';
6 | import 'profile_page.dart';
7 | import 'package:cached_network_image/cached_network_image.dart';
8 | import 'comment_screen.dart';
9 | import 'package:flare_flutter/flare_actor.dart';
10 |
11 | class ImagePost extends StatefulWidget {
12 | const ImagePost(
13 | {this.mediaUrl,
14 | this.username,
15 | this.location,
16 | this.description,
17 | this.likes,
18 | this.postId,
19 | this.ownerId});
20 |
21 | factory ImagePost.fromDocument(DocumentSnapshot document) {
22 | return ImagePost(
23 | username: document['username'],
24 | location: document['location'],
25 | mediaUrl: document['mediaUrl'],
26 | likes: document['likes'],
27 | description: document['description'],
28 | postId: document.id,
29 | ownerId: document['ownerId'],
30 | );
31 | }
32 |
33 | factory ImagePost.fromJSON(Map data) {
34 | return ImagePost(
35 | username: data['username'],
36 | location: data['location'],
37 | mediaUrl: data['mediaUrl'],
38 | likes: data['likes'],
39 | description: data['description'],
40 | ownerId: data['ownerId'],
41 | postId: data['postId'],
42 | );
43 | }
44 |
45 | int getLikeCount(var likes) {
46 | if (likes == null) {
47 | return 0;
48 | }
49 | // issue is below
50 | var vals = likes.values;
51 | int count = 0;
52 | for (var val in vals) {
53 | if (val == true) {
54 | count = count + 1;
55 | }
56 | }
57 |
58 | return count;
59 | }
60 |
61 | final String mediaUrl;
62 | final String username;
63 | final String location;
64 | final String description;
65 | final likes;
66 | final String postId;
67 | final String ownerId;
68 |
69 | _ImagePost createState() => _ImagePost(
70 | mediaUrl: this.mediaUrl,
71 | username: this.username,
72 | location: this.location,
73 | description: this.description,
74 | likes: this.likes,
75 | likeCount: getLikeCount(this.likes),
76 | ownerId: this.ownerId,
77 | postId: this.postId,
78 | );
79 | }
80 |
81 | class _ImagePost extends State {
82 | final String mediaUrl;
83 | final String username;
84 | final String location;
85 | final String description;
86 | Map likes;
87 | int likeCount;
88 | final String postId;
89 | bool liked;
90 | final String ownerId;
91 |
92 | bool showHeart = false;
93 |
94 | TextStyle boldStyle = TextStyle(
95 | color: Colors.black,
96 | fontWeight: FontWeight.bold,
97 | );
98 |
99 | var reference = FirebaseFirestore.instance.collection('insta_posts');
100 |
101 | _ImagePost(
102 | {this.mediaUrl,
103 | this.username,
104 | this.location,
105 | this.description,
106 | this.likes,
107 | this.postId,
108 | this.likeCount,
109 | this.ownerId});
110 |
111 | GestureDetector buildLikeIcon() {
112 | Color color;
113 | IconData icon;
114 |
115 | if (liked) {
116 | color = Colors.pink;
117 | icon = FontAwesomeIcons.solidHeart;
118 | } else {
119 | icon = FontAwesomeIcons.heart;
120 | }
121 |
122 | return GestureDetector(
123 | child: Icon(
124 | icon,
125 | size: 25.0,
126 | color: color,
127 | ),
128 | onTap: () {
129 | _likePost(postId);
130 | });
131 | }
132 |
133 | GestureDetector buildLikeableImage() {
134 | return GestureDetector(
135 | onDoubleTap: () => _likePost(postId),
136 | child: Stack(
137 | alignment: Alignment.center,
138 | children: [
139 | CachedNetworkImage(
140 | imageUrl: mediaUrl,
141 | fit: BoxFit.fitWidth,
142 | placeholder: (context, url) => loadingPlaceHolder,
143 | errorWidget: (context, url, error) => Icon(Icons.error),
144 | ),
145 | showHeart
146 | ? Positioned(
147 | child: Container(
148 | width: 100,
149 | height: 100,
150 | child: Opacity(
151 | opacity: 0.85,
152 | child: FlareActor("assets/flare/Like.flr",
153 | animation: "Like",
154 | )),
155 | ),
156 | )
157 | : Container()
158 | ],
159 | ),
160 | );
161 | }
162 |
163 | buildPostHeader({String ownerId}) {
164 | if (ownerId == null) {
165 | return Text("owner error");
166 | }
167 |
168 | return FutureBuilder(
169 | future: FirebaseFirestore.instance
170 | .collection('insta_users')
171 | .doc(ownerId)
172 | .get(),
173 | builder: (context, snapshot) {
174 |
175 | if (snapshot.data != null) {
176 | return ListTile(
177 | leading: CircleAvatar(
178 | backgroundImage: CachedNetworkImageProvider(snapshot.data.data()['photoUrl']),
179 | backgroundColor: Colors.grey,
180 | ),
181 | title: GestureDetector(
182 | child: Text(snapshot.data.data()['username'], style: boldStyle),
183 | onTap: () {
184 | openProfile(context, ownerId);
185 | },
186 | ),
187 | subtitle: Text(this.location),
188 | trailing: const Icon(Icons.more_vert),
189 | );
190 | }
191 |
192 | // snapshot data is null here
193 | return Container();
194 | });
195 | }
196 |
197 | Container loadingPlaceHolder = Container(
198 | height: 400.0,
199 | child: Center(child: CircularProgressIndicator()),
200 | );
201 |
202 | @override
203 | Widget build(BuildContext context) {
204 | liked = (likes[googleSignIn.currentUser.id.toString()] == true);
205 |
206 | return Column(
207 | mainAxisSize: MainAxisSize.min,
208 | children: [
209 | buildPostHeader(ownerId: ownerId),
210 | buildLikeableImage(),
211 | Row(
212 | mainAxisAlignment: MainAxisAlignment.start,
213 | children: [
214 | Padding(padding: const EdgeInsets.only(left: 20.0, top: 40.0)),
215 | buildLikeIcon(),
216 | Padding(padding: const EdgeInsets.only(right: 20.0)),
217 | GestureDetector(
218 | child: const Icon(
219 | FontAwesomeIcons.comment,
220 | size: 25.0,
221 | ),
222 | onTap: () {
223 | goToComments(
224 | context: context,
225 | postId: postId,
226 | ownerId: ownerId,
227 | mediaUrl: mediaUrl);
228 | }),
229 | ],
230 | ),
231 | Row(
232 | children: [
233 | Container(
234 | margin: const EdgeInsets.only(left: 20.0),
235 | child: Text(
236 | "$likeCount likes",
237 | style: boldStyle,
238 | ),
239 | )
240 | ],
241 | ),
242 | Row(
243 | crossAxisAlignment: CrossAxisAlignment.start,
244 | children: [
245 | Container(
246 | margin: const EdgeInsets.only(left: 20.0),
247 | child: Text(
248 | "$username ",
249 | style: boldStyle,
250 | )),
251 | Expanded(child: Text(description)),
252 | ],
253 | )
254 | ],
255 | );
256 | }
257 |
258 | void _likePost(String postId2) {
259 | var userId = googleSignIn.currentUser.id;
260 | bool _liked = likes[userId] == true;
261 |
262 | if (_liked) {
263 | print('removing like');
264 | reference.doc(postId).update({
265 | 'likes.$userId': false
266 | //firestore plugin doesnt support deleting, so it must be nulled / falsed
267 | });
268 |
269 | setState(() {
270 | likeCount = likeCount - 1;
271 | liked = false;
272 | likes[userId] = false;
273 | });
274 |
275 | removeActivityFeedItem();
276 | }
277 |
278 | if (!_liked) {
279 | print('liking');
280 | reference.doc(postId).update({'likes.$userId': true});
281 |
282 | addActivityFeedItem();
283 |
284 | setState(() {
285 | likeCount = likeCount + 1;
286 | liked = true;
287 | likes[userId] = true;
288 | showHeart = true;
289 | });
290 | Timer(const Duration(milliseconds: 2000), () {
291 | setState(() {
292 | showHeart = false;
293 | });
294 | });
295 | }
296 | }
297 |
298 | void addActivityFeedItem() {
299 | FirebaseFirestore.instance
300 | .collection("insta_a_feed")
301 | .doc(ownerId)
302 | .collection("items")
303 | .doc(postId)
304 | .set({
305 | "username": currentUserModel.username,
306 | "userId": currentUserModel.id,
307 | "type": "like",
308 | "userProfileImg": currentUserModel.photoUrl,
309 | "mediaUrl": mediaUrl,
310 | "timestamp": DateTime.now(),
311 | "postId": postId,
312 | });
313 | }
314 |
315 | void removeActivityFeedItem() {
316 | FirebaseFirestore.instance
317 | .collection("insta_a_feed")
318 | .doc(ownerId)
319 | .collection("items")
320 | .doc(postId)
321 | .delete();
322 | }
323 | }
324 |
325 | class ImagePostFromId extends StatelessWidget {
326 | final String id;
327 |
328 | const ImagePostFromId({this.id});
329 |
330 | getImagePost() async {
331 | var document =
332 | await FirebaseFirestore.instance.collection('insta_posts').doc(id).get();
333 | return ImagePost.fromDocument(document);
334 | }
335 |
336 | @override
337 | Widget build(BuildContext context) {
338 | return FutureBuilder(
339 | future: getImagePost(),
340 | builder: (context, snapshot) {
341 | if (!snapshot.hasData)
342 | return Container(
343 | alignment: FractionalOffset.center,
344 | padding: const EdgeInsets.only(top: 10.0),
345 | child: CircularProgressIndicator());
346 | return snapshot.data;
347 | });
348 | }
349 | }
350 |
351 | void goToComments(
352 | {BuildContext context, String postId, String ownerId, String mediaUrl}) {
353 | Navigator.of(context)
354 | .push(MaterialPageRoute(builder: (BuildContext context) {
355 | return CommentScreen(
356 | postId: postId,
357 | postOwner: ownerId,
358 | postMediaUrl: mediaUrl,
359 | );
360 | }));
361 | }
362 |
--------------------------------------------------------------------------------
/lib/location.dart:
--------------------------------------------------------------------------------
1 | /* File to get location of user
2 | * used dependencies - location => to get location coordinates of user,
3 | * - geoLocation => To get Address from the location coordinates
4 | */
5 | import 'package:flutter/services.dart';
6 | import 'package:geocoder/geocoder.dart';
7 | import 'package:location/location.dart';
8 |
9 | getUserLocation() async {
10 | LocationData currentLocation;
11 | String error;
12 | Location location = Location();
13 | try {
14 | currentLocation = await location.getLocation();
15 | } on PlatformException catch (e) {
16 | if (e.code == 'PERMISSION_DENIED') {
17 | error = 'please grant permission';
18 | print(error);
19 | }
20 | if (e.code == 'PERMISSION_DENIED_NEVER_ASK') {
21 | error = 'permission denied- please enable it from app settings';
22 | print(error);
23 | }
24 | currentLocation = null;
25 | }
26 | final coordinates = Coordinates(
27 | currentLocation.latitude, currentLocation.longitude);
28 | var addresses =
29 | await Geocoder.local.findAddressesFromCoordinates(coordinates);
30 | var first = addresses.first;
31 | return first;
32 | }
33 |
--------------------------------------------------------------------------------
/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter/cupertino.dart';
3 | import 'feed.dart';
4 | import 'upload_page.dart';
5 | import 'dart:async';
6 | import 'package:firebase_core/firebase_core.dart';
7 | import 'package:google_sign_in/google_sign_in.dart';
8 | import 'package:firebase_auth/firebase_auth.dart' as FBA;
9 | import 'package:cloud_firestore/cloud_firestore.dart';
10 | import 'profile_page.dart';
11 | import 'search_page.dart';
12 | import 'activity_feed.dart';
13 | import 'create_account.dart';
14 | import 'package:firebase_messaging/firebase_messaging.dart';
15 | import 'dart:io' show Platform;
16 | import 'models/user.dart';
17 |
18 | final auth = FBA.FirebaseAuth.instance;
19 | final googleSignIn = GoogleSignIn();
20 | final ref = FirebaseFirestore.instance.collection('insta_users');
21 | final FirebaseMessaging _firebaseMessaging = FirebaseMessaging.instance;
22 |
23 |
24 | User currentUserModel;
25 |
26 | Future main() async {
27 |
28 | WidgetsFlutterBinding.ensureInitialized(); // after upgrading flutter this is now necessary
29 |
30 | runApp(Fluttergram());
31 | }
32 |
33 | Future _ensureLoggedIn(BuildContext context) async {
34 | GoogleSignInAccount user = googleSignIn.currentUser;
35 | if (user == null) {
36 | user = await googleSignIn.signInSilently();
37 | }
38 | if (user == null) {
39 | await googleSignIn.signIn();
40 | await tryCreateUserRecord(context);
41 | }
42 |
43 | if (auth.currentUser == null) {
44 |
45 | final GoogleSignInAccount googleUser = await googleSignIn.signIn();
46 | final GoogleSignInAuthentication googleAuth = await googleUser
47 | .authentication;
48 |
49 | final FBA.GoogleAuthCredential credential = FBA.GoogleAuthProvider.credential(
50 | accessToken: googleAuth.accessToken,
51 | idToken: googleAuth.idToken,
52 | );
53 |
54 | await auth.signInWithCredential(credential);
55 | }
56 | }
57 |
58 | Future _silentLogin(BuildContext context) async {
59 | GoogleSignInAccount user = googleSignIn.currentUser;
60 |
61 | if (user == null) {
62 | user = await googleSignIn.signInSilently();
63 | await tryCreateUserRecord(context);
64 | }
65 |
66 | if (await auth.currentUser == null && user != null) {
67 | final GoogleSignInAccount googleUser = await googleSignIn.signIn();
68 | final GoogleSignInAuthentication googleAuth = await googleUser
69 | .authentication;
70 |
71 |
72 | final FBA.GoogleAuthCredential credential = FBA.GoogleAuthProvider.credential(
73 | accessToken: googleAuth.accessToken,
74 | idToken: googleAuth.idToken,
75 | );
76 |
77 | await auth.signInWithCredential(credential);
78 | }
79 |
80 |
81 | }
82 |
83 | Future _setUpNotifications() async {
84 | if (Platform.isAndroid) {
85 |
86 | _firebaseMessaging.getToken().then((token) {
87 | print("Firebase Messaging Token: " + token);
88 |
89 | FirebaseFirestore.instance
90 | .collection("insta_users")
91 | .doc(currentUserModel.id)
92 | .update({"androidNotificationToken": token});
93 | });
94 | }
95 | }
96 |
97 | Future tryCreateUserRecord(BuildContext context) async {
98 | GoogleSignInAccount user = googleSignIn.currentUser;
99 | if (user == null) {
100 | return null;
101 | }
102 | DocumentSnapshot userRecord = await ref.doc(user.id).get();
103 | if (userRecord.data() == null) {
104 | // no user record exists, time to create
105 |
106 | String userName = await Navigator.push(
107 | context,
108 | MaterialPageRoute(
109 | builder: (context) => Center(
110 | child: Scaffold(
111 | appBar: AppBar(
112 | leading: Container(),
113 | title: Text('Fill out missing data',
114 | style: TextStyle(
115 | color: Colors.black,
116 | fontWeight: FontWeight.bold)),
117 | backgroundColor: Colors.white,
118 | ),
119 | body: ListView(
120 | children: [
121 | Container(
122 | child: CreateAccount(),
123 | ),
124 | ],
125 | )),
126 | )),
127 | );
128 |
129 | if (userName != null || userName.length != 0) {
130 | ref.doc(user.id).set({
131 | "id": user.id,
132 | "username": userName,
133 | "photoUrl": user.photoUrl,
134 | "email": user.email,
135 | "displayName": user.displayName,
136 | "bio": "",
137 | "followers": {},
138 | "following": {},
139 | });
140 | }
141 | userRecord = await ref.doc(user.id).get();
142 | }
143 |
144 | currentUserModel = User.fromDocument(userRecord);
145 | return null;
146 | }
147 |
148 | class Fluttergram extends StatelessWidget {
149 | // This widget is the root of your application.
150 | @override
151 | Widget build(BuildContext context) {
152 | return MaterialApp(
153 | title: 'Fluttergram',
154 | theme: ThemeData(
155 | // This is the theme of your application.
156 | //
157 | // Try running your application with "flutter run". You'll see the
158 | // application has a blue toolbar. Then, without quitting the app, try
159 | // changing the primarySwatch below to Colors.green and then invoke
160 | // "hot reload" (press "r" in the console where you ran "flutter run",
161 | // or press Run > Flutter Hot Reload in IntelliJ). Notice that the
162 | // counter didn't reset back to zero; the application is not restarted.
163 | primarySwatch: Colors.blue,
164 | buttonColor: Colors.pink,
165 | primaryIconTheme: IconThemeData(color: Colors.black)),
166 | home: HomePage(title: 'Fluttergram'),
167 | );
168 | }
169 | }
170 |
171 | class HomePage extends StatefulWidget {
172 | HomePage({Key key, this.title}) : super(key: key);
173 | final String title;
174 |
175 | @override
176 | _HomePageState createState() => _HomePageState();
177 | }
178 |
179 | PageController pageController;
180 |
181 | class _HomePageState extends State {
182 | int _page = 0;
183 | bool triedSilentLogin = false;
184 | bool setupNotifications = false;
185 | bool firebaseInitialized = false;
186 |
187 | Scaffold buildLoginPage() {
188 | return Scaffold(
189 | body: Center(
190 | child: Padding(
191 | padding: const EdgeInsets.only(top: 240.0),
192 | child: Column(
193 | children: [
194 | Text(
195 | 'Fluttergram',
196 | style: TextStyle(
197 | fontSize: 60.0,
198 | fontFamily: "Billabong",
199 | color: Colors.black),
200 | ),
201 | Padding(padding: const EdgeInsets.only(bottom: 100.0)),
202 | GestureDetector(
203 | onTap: login,
204 | child: Image.asset(
205 | "assets/images/google_signin_button.png",
206 | width: 225.0,
207 | ),
208 | )
209 | ],
210 | ),
211 | ),
212 | ),
213 | );
214 | }
215 |
216 | @override
217 | Widget build(BuildContext context) {
218 | if (triedSilentLogin == false) {
219 | silentLogin(context);
220 | }
221 |
222 | if (setupNotifications == false && currentUserModel != null) {
223 | setUpNotifications();
224 | }
225 |
226 | if (!firebaseInitialized) return CircularProgressIndicator();
227 |
228 | auth.authStateChanges().listen((event) {
229 | if (event == null) {
230 | silentLogin(context);
231 | }
232 | });
233 |
234 | return (googleSignIn.currentUser == null || currentUserModel == null)
235 | ? buildLoginPage()
236 | : Scaffold(
237 | body: PageView(
238 | children: [
239 | Container(
240 | color: Colors.white,
241 | child: Feed(),
242 | ),
243 | Container(color: Colors.white, child: SearchPage()),
244 | Container(
245 | color: Colors.white,
246 | child: Uploader(),
247 | ),
248 | Container(
249 | color: Colors.white, child: ActivityFeedPage()),
250 | Container(
251 | color: Colors.white,
252 | child: ProfilePage(
253 | userId: googleSignIn.currentUser.id,
254 | )),
255 | ],
256 | controller: pageController,
257 | physics: NeverScrollableScrollPhysics(),
258 | onPageChanged: onPageChanged,
259 | ),
260 | bottomNavigationBar: CupertinoTabBar(
261 | backgroundColor: Colors.white,
262 | items: [
263 | BottomNavigationBarItem(
264 | icon: Icon(Icons.home,
265 | color: (_page == 0) ? Colors.black : Colors.grey),
266 | title: Container(height: 0.0),
267 | backgroundColor: Colors.white),
268 | BottomNavigationBarItem(
269 | icon: Icon(Icons.search,
270 | color: (_page == 1) ? Colors.black : Colors.grey),
271 | title: Container(height: 0.0),
272 | backgroundColor: Colors.white),
273 | BottomNavigationBarItem(
274 | icon: Icon(Icons.add_circle,
275 | color: (_page == 2) ? Colors.black : Colors.grey),
276 | title: Container(height: 0.0),
277 | backgroundColor: Colors.white),
278 | BottomNavigationBarItem(
279 | icon: Icon(Icons.star,
280 | color: (_page == 3) ? Colors.black : Colors.grey),
281 | title: Container(height: 0.0),
282 | backgroundColor: Colors.white),
283 | BottomNavigationBarItem(
284 | icon: Icon(Icons.person,
285 | color: (_page == 4) ? Colors.black : Colors.grey),
286 | title: Container(height: 0.0),
287 | backgroundColor: Colors.white),
288 | ],
289 | onTap: navigationTapped,
290 | currentIndex: _page,
291 | ),
292 | );
293 | }
294 |
295 | void login() async {
296 | await _ensureLoggedIn(context);
297 | setState(() {
298 | triedSilentLogin = true;
299 | });
300 | }
301 |
302 | void setUpNotifications() {
303 | _setUpNotifications();
304 | setState(() {
305 | setupNotifications = true;
306 | });
307 | }
308 |
309 | void silentLogin(BuildContext context) async {
310 | await _silentLogin(context);
311 | setState(() {
312 | triedSilentLogin = true;
313 | });
314 | }
315 |
316 | void navigationTapped(int page) {
317 | //Animating Page
318 | pageController.jumpToPage(page);
319 | }
320 |
321 | void onPageChanged(int page) {
322 | setState(() {
323 | this._page = page;
324 | });
325 | }
326 |
327 | @override
328 | void initState() {
329 | super.initState();
330 | Firebase.initializeApp().then((_) {
331 | setState(() {
332 | firebaseInitialized= true;
333 | });
334 | });
335 | pageController = PageController();
336 | }
337 |
338 | @override
339 | void dispose() {
340 | super.dispose();
341 | pageController.dispose();
342 | }
343 | }
344 |
--------------------------------------------------------------------------------
/lib/models/user.dart:
--------------------------------------------------------------------------------
1 | import 'package:cloud_firestore/cloud_firestore.dart';
2 |
3 | class User {
4 | final String email;
5 | final String id;
6 | final String photoUrl;
7 | final String username;
8 | final String displayName;
9 | final String bio;
10 | final Map followers;
11 | final Map following;
12 |
13 | const User(
14 | {this.username,
15 | this.id,
16 | this.photoUrl,
17 | this.email,
18 | this.displayName,
19 | this.bio,
20 | this.followers,
21 | this.following});
22 |
23 | factory User.fromDocument(DocumentSnapshot document) {
24 | return User(
25 | email: document['email'],
26 | username: document['username'],
27 | photoUrl: document['photoUrl'],
28 | id: document.id,
29 | displayName: document['displayName'],
30 | bio: document['bio'],
31 | followers: document['followers'],
32 | following: document['following'],
33 | );
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/lib/profile_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:cloud_firestore/cloud_firestore.dart';
3 | import 'main.dart';
4 | import 'image_post.dart';
5 | import 'dart:async';
6 | import 'edit_profile_page.dart';
7 | import 'models/user.dart';
8 |
9 | class ProfilePage extends StatefulWidget {
10 | const ProfilePage({this.userId});
11 |
12 | final String userId;
13 |
14 | _ProfilePage createState() => _ProfilePage(this.userId);
15 | }
16 |
17 | class _ProfilePage extends State
18 | with AutomaticKeepAliveClientMixin {
19 | final String profileId;
20 | String currentUserId = googleSignIn.currentUser.id;
21 | String view = "grid"; // default view
22 | bool isFollowing = false;
23 | bool followButtonClicked = false;
24 | int postCount = 0;
25 | int followerCount = 0;
26 | int followingCount = 0;
27 | _ProfilePage(this.profileId);
28 |
29 | editProfile() {
30 | EditProfilePage editPage = EditProfilePage();
31 |
32 | Navigator.of(context)
33 | .push(MaterialPageRoute(builder: (BuildContext context) {
34 | return Center(
35 | child: Scaffold(
36 | appBar: AppBar(
37 | leading: IconButton(
38 | icon: Icon(Icons.close),
39 | onPressed: () {
40 | Navigator.maybePop(context);
41 | },
42 | ),
43 | title: Text('Edit Profile',
44 | style: TextStyle(
45 | color: Colors.black, fontWeight: FontWeight.bold)),
46 | backgroundColor: Colors.white,
47 | actions: [
48 | IconButton(
49 | icon: Icon(
50 | Icons.check,
51 | color: Colors.blueAccent,
52 | ),
53 | onPressed: () {
54 | editPage.applyChanges();
55 | Navigator.maybePop(context);
56 | })
57 | ],
58 | ),
59 | body: ListView(
60 | children: [
61 | Container(
62 | child: editPage,
63 | ),
64 | ],
65 | )),
66 | );
67 | }));
68 | }
69 |
70 | followUser() {
71 | print('following user');
72 | setState(() {
73 | this.isFollowing = true;
74 | followButtonClicked = true;
75 | });
76 |
77 | FirebaseFirestore.instance.doc("insta_users/$profileId").update({
78 | 'followers.$currentUserId': true
79 | //firestore plugin doesnt support deleting, so it must be nulled / falsed
80 | });
81 |
82 | FirebaseFirestore.instance.doc("insta_users/$currentUserId").update({
83 | 'following.$profileId': true
84 | //firestore plugin doesnt support deleting, so it must be nulled / falsed
85 | });
86 |
87 | //updates activity feed
88 | FirebaseFirestore.instance
89 | .collection("insta_a_feed")
90 | .doc(profileId)
91 | .collection("items")
92 | .doc(currentUserId)
93 | .set({
94 | "ownerId": profileId,
95 | "username": currentUserModel.username,
96 | "userId": currentUserId,
97 | "type": "follow",
98 | "userProfileImg": currentUserModel.photoUrl,
99 | "timestamp": DateTime.now()
100 | });
101 | }
102 |
103 | unfollowUser() {
104 | setState(() {
105 | isFollowing = false;
106 | followButtonClicked = true;
107 | });
108 |
109 | FirebaseFirestore.instance.doc("insta_users/$profileId").update({
110 | 'followers.$currentUserId': false
111 | //firestore plugin doesnt support deleting, so it must be nulled / falsed
112 | });
113 |
114 | FirebaseFirestore.instance.doc("insta_users/$currentUserId").update({
115 | 'following.$profileId': false
116 | //firestore plugin doesnt support deleting, so it must be nulled / falsed
117 | });
118 |
119 | FirebaseFirestore.instance
120 | .collection("insta_a_feed")
121 | .doc(profileId)
122 | .collection("items")
123 | .doc(currentUserId)
124 | .delete();
125 | }
126 |
127 | @override
128 | Widget build(BuildContext context) {
129 | super.build(context); // reloads state when opened again
130 |
131 | Column buildStatColumn(String label, int number) {
132 | return Column(
133 | mainAxisSize: MainAxisSize.min,
134 | mainAxisAlignment: MainAxisAlignment.center,
135 | children: [
136 | Text(
137 | number.toString(),
138 | style: TextStyle(fontSize: 22.0, fontWeight: FontWeight.bold),
139 | ),
140 | Container(
141 | margin: const EdgeInsets.only(top: 4.0),
142 | child: Text(
143 | label,
144 | style: TextStyle(
145 | color: Colors.grey,
146 | fontSize: 15.0,
147 | fontWeight: FontWeight.w400),
148 | ))
149 | ],
150 | );
151 | }
152 |
153 | Container buildFollowButton(
154 | {String text,
155 | Color backgroundcolor,
156 | Color textColor,
157 | Color borderColor,
158 | Function function}) {
159 | return Container(
160 | padding: EdgeInsets.only(top: 2.0),
161 | child: FlatButton(
162 | onPressed: function,
163 | child: Container(
164 | decoration: BoxDecoration(
165 | color: backgroundcolor,
166 | border: Border.all(color: borderColor),
167 | borderRadius: BorderRadius.circular(5.0)),
168 | alignment: Alignment.center,
169 | child: Text(text,
170 | style: TextStyle(
171 | color: textColor, fontWeight: FontWeight.bold)),
172 | width: 250.0,
173 | height: 27.0,
174 | )),
175 | );
176 | }
177 |
178 | Container buildProfileFollowButton(User user) {
179 | // viewing your own profile - should show edit button
180 | if (currentUserId == profileId) {
181 | return buildFollowButton(
182 | text: "Edit Profile",
183 | backgroundcolor: Colors.white,
184 | textColor: Colors.black,
185 | borderColor: Colors.grey,
186 | function: editProfile,
187 | );
188 | }
189 |
190 | // already following user - should show unfollow button
191 | if (isFollowing) {
192 | return buildFollowButton(
193 | text: "Unfollow",
194 | backgroundcolor: Colors.white,
195 | textColor: Colors.black,
196 | borderColor: Colors.grey,
197 | function: unfollowUser,
198 | );
199 | }
200 |
201 | // does not follow user - should show follow button
202 | if (!isFollowing) {
203 | return buildFollowButton(
204 | text: "Follow",
205 | backgroundcolor: Colors.blue,
206 | textColor: Colors.white,
207 | borderColor: Colors.blue,
208 | function: followUser,
209 | );
210 | }
211 |
212 | return buildFollowButton(
213 | text: "loading...",
214 | backgroundcolor: Colors.white,
215 | textColor: Colors.black,
216 | borderColor: Colors.grey);
217 | }
218 |
219 | Row buildImageViewButtonBar() {
220 | Color isActiveButtonColor(String viewName) {
221 | if (view == viewName) {
222 | return Colors.blueAccent;
223 | } else {
224 | return Colors.black26;
225 | }
226 | }
227 |
228 | return Row(
229 | mainAxisAlignment: MainAxisAlignment.spaceEvenly,
230 | children: [
231 | IconButton(
232 | icon: Icon(Icons.grid_on, color: isActiveButtonColor("grid")),
233 | onPressed: () {
234 | changeView("grid");
235 | },
236 | ),
237 | IconButton(
238 | icon: Icon(Icons.list, color: isActiveButtonColor("feed")),
239 | onPressed: () {
240 | changeView("feed");
241 | },
242 | ),
243 | ],
244 | );
245 | }
246 |
247 | Container buildUserPosts() {
248 | Future> getPosts() async {
249 | List posts = [];
250 | var snap = await FirebaseFirestore.instance
251 | .collection('insta_posts')
252 | .where('ownerId', isEqualTo: profileId)
253 | .orderBy("timestamp")
254 | .get();
255 | for (var doc in snap.docs) {
256 | posts.add(ImagePost.fromDocument(doc));
257 | }
258 | setState(() {
259 | postCount = snap.docs.length;
260 | });
261 |
262 | return posts.reversed.toList();
263 | }
264 |
265 | return Container(
266 | child: FutureBuilder>(
267 | future: getPosts(),
268 | builder: (context, snapshot) {
269 | if (!snapshot.hasData)
270 | return Container(
271 | alignment: FractionalOffset.center,
272 | padding: const EdgeInsets.only(top: 10.0),
273 | child: CircularProgressIndicator());
274 | else if (view == "grid") {
275 | // build the grid
276 | return GridView.count(
277 | crossAxisCount: 3,
278 | childAspectRatio: 1.0,
279 | // padding: const EdgeInsets.all(0.5),
280 | mainAxisSpacing: 1.5,
281 | crossAxisSpacing: 1.5,
282 | shrinkWrap: true,
283 | physics: const NeverScrollableScrollPhysics(),
284 | children: snapshot.data.map((ImagePost imagePost) {
285 | return GridTile(child: ImageTile(imagePost));
286 | }).toList());
287 | } else if (view == "feed") {
288 | return Column(
289 | children: snapshot.data.map((ImagePost imagePost) {
290 | return imagePost;
291 | }).toList());
292 | }
293 | },
294 | ));
295 | }
296 |
297 | return StreamBuilder(
298 | stream: FirebaseFirestore.instance
299 | .collection('insta_users')
300 | .doc(profileId)
301 | .snapshots(),
302 | builder: (context, snapshot) {
303 | if (!snapshot.hasData)
304 | return Container(
305 | alignment: FractionalOffset.center,
306 | child: CircularProgressIndicator());
307 |
308 | User user = User.fromDocument(snapshot.data);
309 |
310 | if (user.followers.containsKey(currentUserId) &&
311 | user.followers[currentUserId] &&
312 | followButtonClicked == false) {
313 | isFollowing = true;
314 | }
315 |
316 | return Scaffold(
317 | appBar: AppBar(
318 | title: Text(
319 | user.username,
320 | style: const TextStyle(color: Colors.black),
321 | ),
322 | backgroundColor: Colors.white,
323 | ),
324 | body: ListView(
325 | children: [
326 | Padding(
327 | padding: const EdgeInsets.all(16.0),
328 | child: Column(
329 | children: [
330 | Row(
331 | children: [
332 | CircleAvatar(
333 | radius: 40.0,
334 | backgroundColor: Colors.grey,
335 | backgroundImage: NetworkImage(user.photoUrl),
336 | ),
337 | Expanded(
338 | flex: 1,
339 | child: Column(
340 | children: [
341 | Row(
342 | mainAxisSize: MainAxisSize.max,
343 | mainAxisAlignment:
344 | MainAxisAlignment.spaceEvenly,
345 | children: [
346 | buildStatColumn("posts", postCount),
347 | buildStatColumn("followers",
348 | _countFollowings(user.followers)),
349 | buildStatColumn("following",
350 | _countFollowings(user.following)),
351 | ],
352 | ),
353 | Row(
354 | mainAxisAlignment:
355 | MainAxisAlignment.spaceEvenly,
356 | children: [
357 | buildProfileFollowButton(user)
358 | ]),
359 | ],
360 | ),
361 | )
362 | ],
363 | ),
364 | Container(
365 | alignment: Alignment.centerLeft,
366 | padding: const EdgeInsets.only(top: 15.0),
367 | child: Text(
368 | user.displayName,
369 | style: TextStyle(fontWeight: FontWeight.bold),
370 | )),
371 | Container(
372 | alignment: Alignment.centerLeft,
373 | padding: const EdgeInsets.only(top: 1.0),
374 | child: Text(user.bio),
375 | ),
376 | ],
377 | ),
378 | ),
379 | Divider(),
380 | buildImageViewButtonBar(),
381 | Divider(height: 0.0),
382 | buildUserPosts(),
383 | ],
384 | ));
385 | });
386 | }
387 |
388 | changeView(String viewName) {
389 | setState(() {
390 | view = viewName;
391 | });
392 | }
393 |
394 | int _countFollowings(Map followings) {
395 | int count = 0;
396 |
397 | void countValues(key, value) {
398 | if (value) {
399 | count += 1;
400 | }
401 | }
402 |
403 | followings.forEach(countValues);
404 |
405 | return count;
406 | }
407 |
408 | // ensures state is kept when switching pages
409 | @override
410 | bool get wantKeepAlive => true;
411 | }
412 |
413 | class ImageTile extends StatelessWidget {
414 | final ImagePost imagePost;
415 |
416 | ImageTile(this.imagePost);
417 |
418 | clickedImage(BuildContext context) {
419 | Navigator.of(context)
420 | .push(MaterialPageRoute(builder: (BuildContext context) {
421 | return Center(
422 | child: Scaffold(
423 | appBar: AppBar(
424 | title: Text('Photo',
425 | style: TextStyle(
426 | color: Colors.black, fontWeight: FontWeight.bold)),
427 | backgroundColor: Colors.white,
428 | ),
429 | body: ListView(
430 | children: [
431 | Container(
432 | child: imagePost,
433 | ),
434 | ],
435 | )),
436 | );
437 | }));
438 | }
439 |
440 | Widget build(BuildContext context) {
441 | return GestureDetector(
442 | onTap: () => clickedImage(context),
443 | child: Image.network(imagePost.mediaUrl, fit: BoxFit.cover));
444 | }
445 | }
446 |
447 | void openProfile(BuildContext context, String userId) {
448 | Navigator.of(context)
449 | .push(MaterialPageRoute(builder: (BuildContext context) {
450 | return ProfilePage(userId: userId);
451 | }));
452 | }
453 |
--------------------------------------------------------------------------------
/lib/search_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:cloud_firestore/cloud_firestore.dart';
3 | import 'dart:async';
4 | import "profile_page.dart"; // needed to import for openProfile function
5 | import 'models/user.dart';
6 |
7 | class SearchPage extends StatefulWidget {
8 | _SearchPage createState() => _SearchPage();
9 | }
10 |
11 | class _SearchPage extends State with AutomaticKeepAliveClientMixin{
12 | Future userDocs;
13 |
14 | buildSearchField() {
15 | return AppBar(
16 | backgroundColor: Colors.white,
17 | title: Form(
18 | child: TextFormField(
19 | decoration: InputDecoration(labelText: 'Search for a user...'),
20 | onFieldSubmitted: submit,
21 | ),
22 | ),
23 | );
24 | }
25 |
26 | ListView buildSearchResults(List docs) {
27 | List userSearchItems = [];
28 |
29 | docs.forEach((DocumentSnapshot doc) {
30 | User user = User.fromDocument(doc);
31 | UserSearchItem searchItem = UserSearchItem(user);
32 | userSearchItems.add(searchItem);
33 | });
34 |
35 | return ListView(
36 | children: userSearchItems,
37 | );
38 | }
39 |
40 | void submit(String searchValue) async {
41 | Future users = FirebaseFirestore.instance
42 | .collection("insta_users")
43 | .where('displayName', isGreaterThanOrEqualTo: searchValue)
44 | .get();
45 |
46 | setState(() {
47 | userDocs = users;
48 | });
49 | }
50 |
51 | Widget build(BuildContext context) {
52 | super.build(context); // reloads state when opened again
53 |
54 | return Scaffold(
55 | appBar: buildSearchField(),
56 | body: userDocs == null
57 | ? Text("")
58 | : FutureBuilder(
59 | future: userDocs,
60 | builder: (context, snapshot) {
61 | if (snapshot.hasData) {
62 | return buildSearchResults(snapshot.data.docs);
63 | } else {
64 | return Container(
65 | alignment: FractionalOffset.center,
66 | child: CircularProgressIndicator());
67 | }
68 | }),
69 | );
70 | }
71 |
72 | // ensures state is kept when switching pages
73 | @override
74 | bool get wantKeepAlive => true;
75 | }
76 |
77 | class UserSearchItem extends StatelessWidget {
78 | final User user;
79 |
80 | const UserSearchItem(this.user);
81 |
82 | @override
83 | Widget build(BuildContext context) {
84 | TextStyle boldStyle = TextStyle(
85 | color: Colors.black,
86 | fontWeight: FontWeight.bold,
87 | );
88 |
89 | return GestureDetector(
90 | child: ListTile(
91 | leading: CircleAvatar(
92 | backgroundImage: NetworkImage(user.photoUrl),
93 | backgroundColor: Colors.grey,
94 | ),
95 | title: Text(user.username, style: boldStyle),
96 | subtitle: Text(user.displayName),
97 | ),
98 | onTap: () {
99 | openProfile(context, user.id);
100 | });
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/lib/upload_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:image_picker/image_picker.dart';
3 | import 'package:firebase_storage/firebase_storage.dart';
4 | import 'package:cloud_firestore/cloud_firestore.dart';
5 | import 'package:uuid/uuid.dart';
6 | import 'dart:async';
7 | import 'main.dart';
8 | import 'dart:io';
9 | import 'location.dart';
10 | import 'package:geocoder/geocoder.dart';
11 |
12 | class Uploader extends StatefulWidget {
13 | _Uploader createState() => _Uploader();
14 | }
15 |
16 | class _Uploader extends State {
17 | File file;
18 | //Strings required to save address
19 | Address address;
20 |
21 | Map currentLocation = Map();
22 | TextEditingController descriptionController = TextEditingController();
23 | TextEditingController locationController = TextEditingController();
24 | ImagePicker imagePicker = ImagePicker();
25 |
26 | bool uploading = false;
27 |
28 | @override
29 | initState() {
30 | //variables with location assigned as 0.0
31 | currentLocation['latitude'] = 0.0;
32 | currentLocation['longitude'] = 0.0;
33 | initPlatformState(); //method to call location
34 | super.initState();
35 | }
36 |
37 | //method to get Location and save into variables
38 | initPlatformState() async {
39 | Address first = await getUserLocation();
40 | setState(() {
41 | address = first;
42 | });
43 | }
44 |
45 | Widget build(BuildContext context) {
46 | return file == null
47 | ? IconButton(
48 | icon: Icon(Icons.file_upload),
49 | onPressed: () => {_selectImage(context)})
50 | : Scaffold(
51 | resizeToAvoidBottomInset: false,
52 | appBar: AppBar(
53 | backgroundColor: Colors.white70,
54 | leading: IconButton(
55 | icon: Icon(Icons.arrow_back, color: Colors.black),
56 | onPressed: clearImage),
57 | title: const Text(
58 | 'Post to',
59 | style: const TextStyle(color: Colors.black),
60 | ),
61 | actions: [
62 | FlatButton(
63 | onPressed: postImage,
64 | child: Text(
65 | "Post",
66 | style: TextStyle(
67 | color: Colors.blueAccent,
68 | fontWeight: FontWeight.bold,
69 | fontSize: 20.0),
70 | ))
71 | ],
72 | ),
73 | body: ListView(
74 | children: [
75 | PostForm(
76 | imageFile: file,
77 | descriptionController: descriptionController,
78 | locationController: locationController,
79 | loading: uploading,
80 | ),
81 | Divider(), //scroll view where we will show location to users
82 | (address == null)
83 | ? Container()
84 | : SingleChildScrollView(
85 | scrollDirection: Axis.horizontal,
86 | padding: EdgeInsets.only(right: 5.0, left: 5.0),
87 | child: Row(
88 | children: [
89 | buildLocationButton(address.featureName),
90 | buildLocationButton(address.subLocality),
91 | buildLocationButton(address.locality),
92 | buildLocationButton(address.subAdminArea),
93 | buildLocationButton(address.adminArea),
94 | buildLocationButton(address.countryName),
95 | ],
96 | ),
97 | ),
98 | (address == null) ? Container() : Divider(),
99 | ],
100 | ));
101 | }
102 |
103 | //method to build buttons with location.
104 | buildLocationButton(String locationName) {
105 | if (locationName != null ?? locationName.isNotEmpty) {
106 | return InkWell(
107 | onTap: () {
108 | locationController.text = locationName;
109 | },
110 | child: Center(
111 | child: Container(
112 | //width: 100.0,
113 | height: 30.0,
114 | padding: EdgeInsets.only(left: 8.0, right: 8.0),
115 | margin: EdgeInsets.only(right: 3.0, left: 3.0),
116 | decoration: BoxDecoration(
117 | color: Colors.grey[200],
118 | borderRadius: BorderRadius.circular(5.0),
119 | ),
120 | child: Center(
121 | child: Text(
122 | locationName,
123 | style: TextStyle(color: Colors.grey),
124 | ),
125 | ),
126 | ),
127 | ),
128 | );
129 | } else {
130 | return Container();
131 | }
132 | }
133 |
134 | _selectImage(BuildContext parentContext) async {
135 | return showDialog(
136 | context: parentContext,
137 | barrierDismissible: false, // user must tap button!
138 |
139 | builder: (BuildContext context) {
140 | return SimpleDialog(
141 | title: const Text('Create a Post'),
142 | children: [
143 | SimpleDialogOption(
144 | child: const Text('Take a photo'),
145 | onPressed: () async {
146 | Navigator.pop(context);
147 | PickedFile imageFile =
148 | await imagePicker.getImage(source: ImageSource.camera, maxWidth: 1920, maxHeight: 1200, imageQuality: 80);
149 | setState(() {
150 | file = File(imageFile.path);
151 | });
152 | }),
153 | SimpleDialogOption(
154 | child: const Text('Choose from Gallery'),
155 | onPressed: () async {
156 | Navigator.of(context).pop();
157 | PickedFile imageFile =
158 | await imagePicker.getImage(source: ImageSource.gallery, maxWidth: 1920, maxHeight: 1200, imageQuality: 80);
159 | setState(() {
160 | file = File(imageFile.path);
161 | });
162 | }),
163 | SimpleDialogOption(
164 | child: const Text("Cancel"),
165 | onPressed: () {
166 | Navigator.pop(context);
167 | },
168 | )
169 | ],
170 | );
171 | },
172 | );
173 | }
174 |
175 | void clearImage() {
176 | setState(() {
177 | file = null;
178 | });
179 | }
180 |
181 | void postImage() {
182 | setState(() {
183 | uploading = true;
184 | });
185 | uploadImage(file).then((String data) {
186 | postToFireStore(
187 | mediaUrl: data,
188 | description: descriptionController.text,
189 | location: locationController.text);
190 | }).then((_) {
191 | setState(() {
192 | file = null;
193 | uploading = false;
194 | });
195 | });
196 | }
197 | }
198 |
199 | class PostForm extends StatelessWidget {
200 | final imageFile;
201 | final TextEditingController descriptionController;
202 | final TextEditingController locationController;
203 | final bool loading;
204 | PostForm(
205 | {this.imageFile,
206 | this.descriptionController,
207 | this.loading,
208 | this.locationController});
209 |
210 | Widget build(BuildContext context) {
211 | return Column(
212 | children: [
213 | loading
214 | ? LinearProgressIndicator()
215 | : Padding(padding: EdgeInsets.only(top: 0.0)),
216 | Divider(),
217 | Row(
218 | mainAxisAlignment: MainAxisAlignment.spaceAround,
219 | children: [
220 | CircleAvatar(
221 | backgroundImage: NetworkImage(currentUserModel.photoUrl),
222 | ),
223 | Container(
224 | width: 250.0,
225 | child: TextField(
226 | controller: descriptionController,
227 | decoration: InputDecoration(
228 | hintText: "Write a caption...", border: InputBorder.none),
229 | ),
230 | ),
231 | Container(
232 | height: 45.0,
233 | width: 45.0,
234 | child: AspectRatio(
235 | aspectRatio: 487 / 451,
236 | child: Container(
237 | decoration: BoxDecoration(
238 | image: DecorationImage(
239 | fit: BoxFit.fill,
240 | alignment: FractionalOffset.topCenter,
241 | image: FileImage(imageFile),
242 | )),
243 | ),
244 | ),
245 | ),
246 | ],
247 | ),
248 | Divider(),
249 | ListTile(
250 | leading: Icon(Icons.pin_drop),
251 | title: Container(
252 | width: 250.0,
253 | child: TextField(
254 | controller: locationController,
255 | decoration: InputDecoration(
256 | hintText: "Where was this photo taken?",
257 | border: InputBorder.none),
258 | ),
259 | ),
260 | )
261 | ],
262 | );
263 | }
264 | }
265 |
266 | Future uploadImage(var imageFile) async {
267 | var uuid = Uuid().v1();
268 | Reference ref = FirebaseStorage.instance.ref().child("post_$uuid.jpg");
269 | UploadTask uploadTask = ref.putFile(imageFile);
270 |
271 | String downloadUrl = await (await uploadTask).ref.getDownloadURL();
272 | return downloadUrl;
273 | }
274 |
275 | void postToFireStore(
276 | {String mediaUrl, String location, String description}) async {
277 | var reference = FirebaseFirestore.instance.collection('insta_posts');
278 |
279 | reference.add({
280 | "username": currentUserModel.username,
281 | "location": location,
282 | "likes": {},
283 | "mediaUrl": mediaUrl,
284 | "description": description,
285 | "ownerId": googleSignIn.currentUser.id,
286 | "timestamp": DateTime.now(),
287 | }).then((DocumentReference doc) {
288 | String docId = doc.id;
289 | reference.doc(docId).update({"postId": docId});
290 | });
291 | }
292 |
--------------------------------------------------------------------------------
/pubspec.lock:
--------------------------------------------------------------------------------
1 | # Generated by pub
2 | # See https://dart.dev/tools/pub/glossary#lockfile
3 | packages:
4 | archive:
5 | dependency: transitive
6 | description:
7 | name: archive
8 | url: "https://pub.dartlang.org"
9 | source: hosted
10 | version: "3.1.2"
11 | async:
12 | dependency: "direct main"
13 | description:
14 | name: async
15 | url: "https://pub.dartlang.org"
16 | source: hosted
17 | version: "2.5.0"
18 | cached_network_image:
19 | dependency: "direct main"
20 | description:
21 | name: cached_network_image
22 | url: "https://pub.dartlang.org"
23 | source: hosted
24 | version: "2.5.1"
25 | characters:
26 | dependency: transitive
27 | description:
28 | name: characters
29 | url: "https://pub.dartlang.org"
30 | source: hosted
31 | version: "1.1.0"
32 | charcode:
33 | dependency: transitive
34 | description:
35 | name: charcode
36 | url: "https://pub.dartlang.org"
37 | source: hosted
38 | version: "1.2.0"
39 | clock:
40 | dependency: transitive
41 | description:
42 | name: clock
43 | url: "https://pub.dartlang.org"
44 | source: hosted
45 | version: "1.1.0"
46 | cloud_firestore:
47 | dependency: "direct main"
48 | description:
49 | name: cloud_firestore
50 | url: "https://pub.dartlang.org"
51 | source: hosted
52 | version: "1.0.1"
53 | cloud_firestore_platform_interface:
54 | dependency: transitive
55 | description:
56 | name: cloud_firestore_platform_interface
57 | url: "https://pub.dartlang.org"
58 | source: hosted
59 | version: "4.0.0"
60 | cloud_firestore_web:
61 | dependency: transitive
62 | description:
63 | name: cloud_firestore_web
64 | url: "https://pub.dartlang.org"
65 | source: hosted
66 | version: "1.0.1"
67 | collection:
68 | dependency: transitive
69 | description:
70 | name: collection
71 | url: "https://pub.dartlang.org"
72 | source: hosted
73 | version: "1.15.0"
74 | crypto:
75 | dependency: transitive
76 | description:
77 | name: crypto
78 | url: "https://pub.dartlang.org"
79 | source: hosted
80 | version: "3.0.0"
81 | cupertino_icons:
82 | dependency: "direct main"
83 | description:
84 | name: cupertino_icons
85 | url: "https://pub.dartlang.org"
86 | source: hosted
87 | version: "1.0.2"
88 | ffi:
89 | dependency: transitive
90 | description:
91 | name: ffi
92 | url: "https://pub.dartlang.org"
93 | source: hosted
94 | version: "1.0.0"
95 | file:
96 | dependency: transitive
97 | description:
98 | name: file
99 | url: "https://pub.dartlang.org"
100 | source: hosted
101 | version: "6.1.0"
102 | firebase_auth:
103 | dependency: "direct main"
104 | description:
105 | name: firebase_auth
106 | url: "https://pub.dartlang.org"
107 | source: hosted
108 | version: "1.0.1"
109 | firebase_auth_platform_interface:
110 | dependency: transitive
111 | description:
112 | name: firebase_auth_platform_interface
113 | url: "https://pub.dartlang.org"
114 | source: hosted
115 | version: "4.0.0"
116 | firebase_auth_web:
117 | dependency: transitive
118 | description:
119 | name: firebase_auth_web
120 | url: "https://pub.dartlang.org"
121 | source: hosted
122 | version: "1.0.2"
123 | firebase_core:
124 | dependency: "direct main"
125 | description:
126 | name: firebase_core
127 | url: "https://pub.dartlang.org"
128 | source: hosted
129 | version: "1.0.1"
130 | firebase_core_platform_interface:
131 | dependency: transitive
132 | description:
133 | name: firebase_core_platform_interface
134 | url: "https://pub.dartlang.org"
135 | source: hosted
136 | version: "4.0.0"
137 | firebase_core_web:
138 | dependency: transitive
139 | description:
140 | name: firebase_core_web
141 | url: "https://pub.dartlang.org"
142 | source: hosted
143 | version: "1.0.1"
144 | firebase_messaging:
145 | dependency: "direct main"
146 | description:
147 | name: firebase_messaging
148 | url: "https://pub.dartlang.org"
149 | source: hosted
150 | version: "9.0.0"
151 | firebase_messaging_platform_interface:
152 | dependency: transitive
153 | description:
154 | name: firebase_messaging_platform_interface
155 | url: "https://pub.dartlang.org"
156 | source: hosted
157 | version: "2.0.0"
158 | firebase_messaging_web:
159 | dependency: transitive
160 | description:
161 | name: firebase_messaging_web
162 | url: "https://pub.dartlang.org"
163 | source: hosted
164 | version: "1.0.1"
165 | firebase_storage:
166 | dependency: "direct main"
167 | description:
168 | name: firebase_storage
169 | url: "https://pub.dartlang.org"
170 | source: hosted
171 | version: "8.0.0"
172 | firebase_storage_platform_interface:
173 | dependency: transitive
174 | description:
175 | name: firebase_storage_platform_interface
176 | url: "https://pub.dartlang.org"
177 | source: hosted
178 | version: "2.0.0"
179 | firebase_storage_web:
180 | dependency: transitive
181 | description:
182 | name: firebase_storage_web
183 | url: "https://pub.dartlang.org"
184 | source: hosted
185 | version: "1.0.1"
186 | flare_dart:
187 | dependency: transitive
188 | description:
189 | name: flare_dart
190 | url: "https://pub.dartlang.org"
191 | source: hosted
192 | version: "2.3.4"
193 | flare_flutter:
194 | dependency: "direct main"
195 | description:
196 | name: flare_flutter
197 | url: "https://pub.dartlang.org"
198 | source: hosted
199 | version: "2.0.6"
200 | flutter:
201 | dependency: "direct main"
202 | description: flutter
203 | source: sdk
204 | version: "0.0.0"
205 | flutter_blurhash:
206 | dependency: transitive
207 | description:
208 | name: flutter_blurhash
209 | url: "https://pub.dartlang.org"
210 | source: hosted
211 | version: "0.5.0"
212 | flutter_cache_manager:
213 | dependency: transitive
214 | description:
215 | name: flutter_cache_manager
216 | url: "https://pub.dartlang.org"
217 | source: hosted
218 | version: "2.1.2"
219 | flutter_plugin_android_lifecycle:
220 | dependency: transitive
221 | description:
222 | name: flutter_plugin_android_lifecycle
223 | url: "https://pub.dartlang.org"
224 | source: hosted
225 | version: "2.0.0"
226 | flutter_web_plugins:
227 | dependency: transitive
228 | description: flutter
229 | source: sdk
230 | version: "0.0.0"
231 | font_awesome_flutter:
232 | dependency: "direct main"
233 | description:
234 | name: font_awesome_flutter
235 | url: "https://pub.dartlang.org"
236 | source: hosted
237 | version: "8.12.0"
238 | geocoder:
239 | dependency: "direct main"
240 | description:
241 | name: geocoder
242 | url: "https://pub.dartlang.org"
243 | source: hosted
244 | version: "0.2.1"
245 | google_sign_in:
246 | dependency: "direct main"
247 | description:
248 | name: google_sign_in
249 | url: "https://pub.dartlang.org"
250 | source: hosted
251 | version: "5.0.0"
252 | google_sign_in_platform_interface:
253 | dependency: transitive
254 | description:
255 | name: google_sign_in_platform_interface
256 | url: "https://pub.dartlang.org"
257 | source: hosted
258 | version: "2.0.1"
259 | google_sign_in_web:
260 | dependency: transitive
261 | description:
262 | name: google_sign_in_web
263 | url: "https://pub.dartlang.org"
264 | source: hosted
265 | version: "0.10.0"
266 | http:
267 | dependency: "direct main"
268 | description:
269 | name: http
270 | url: "https://pub.dartlang.org"
271 | source: hosted
272 | version: "0.13.0"
273 | http_parser:
274 | dependency: transitive
275 | description:
276 | name: http_parser
277 | url: "https://pub.dartlang.org"
278 | source: hosted
279 | version: "4.0.0"
280 | image:
281 | dependency: "direct main"
282 | description:
283 | name: image
284 | url: "https://pub.dartlang.org"
285 | source: hosted
286 | version: "3.0.1"
287 | image_picker:
288 | dependency: "direct main"
289 | description:
290 | name: image_picker
291 | url: "https://pub.dartlang.org"
292 | source: hosted
293 | version: "0.7.2+1"
294 | image_picker_platform_interface:
295 | dependency: transitive
296 | description:
297 | name: image_picker_platform_interface
298 | url: "https://pub.dartlang.org"
299 | source: hosted
300 | version: "2.0.1"
301 | intl:
302 | dependency: transitive
303 | description:
304 | name: intl
305 | url: "https://pub.dartlang.org"
306 | source: hosted
307 | version: "0.17.0"
308 | js:
309 | dependency: transitive
310 | description:
311 | name: js
312 | url: "https://pub.dartlang.org"
313 | source: hosted
314 | version: "0.6.3"
315 | location:
316 | dependency: "direct main"
317 | description:
318 | name: location
319 | url: "https://pub.dartlang.org"
320 | source: hosted
321 | version: "4.1.1"
322 | location_platform_interface:
323 | dependency: transitive
324 | description:
325 | name: location_platform_interface
326 | url: "https://pub.dartlang.org"
327 | source: hosted
328 | version: "2.1.0"
329 | location_web:
330 | dependency: transitive
331 | description:
332 | name: location_web
333 | url: "https://pub.dartlang.org"
334 | source: hosted
335 | version: "3.0.0"
336 | matcher:
337 | dependency: transitive
338 | description:
339 | name: matcher
340 | url: "https://pub.dartlang.org"
341 | source: hosted
342 | version: "0.12.10"
343 | meta:
344 | dependency: transitive
345 | description:
346 | name: meta
347 | url: "https://pub.dartlang.org"
348 | source: hosted
349 | version: "1.3.0"
350 | octo_image:
351 | dependency: transitive
352 | description:
353 | name: octo_image
354 | url: "https://pub.dartlang.org"
355 | source: hosted
356 | version: "0.3.0"
357 | path:
358 | dependency: transitive
359 | description:
360 | name: path
361 | url: "https://pub.dartlang.org"
362 | source: hosted
363 | version: "1.8.0"
364 | path_provider:
365 | dependency: transitive
366 | description:
367 | name: path_provider
368 | url: "https://pub.dartlang.org"
369 | source: hosted
370 | version: "2.0.1"
371 | path_provider_linux:
372 | dependency: transitive
373 | description:
374 | name: path_provider_linux
375 | url: "https://pub.dartlang.org"
376 | source: hosted
377 | version: "2.0.0"
378 | path_provider_macos:
379 | dependency: transitive
380 | description:
381 | name: path_provider_macos
382 | url: "https://pub.dartlang.org"
383 | source: hosted
384 | version: "2.0.0"
385 | path_provider_platform_interface:
386 | dependency: transitive
387 | description:
388 | name: path_provider_platform_interface
389 | url: "https://pub.dartlang.org"
390 | source: hosted
391 | version: "2.0.1"
392 | path_provider_windows:
393 | dependency: transitive
394 | description:
395 | name: path_provider_windows
396 | url: "https://pub.dartlang.org"
397 | source: hosted
398 | version: "2.0.0"
399 | pedantic:
400 | dependency: transitive
401 | description:
402 | name: pedantic
403 | url: "https://pub.dartlang.org"
404 | source: hosted
405 | version: "1.11.0"
406 | petitparser:
407 | dependency: transitive
408 | description:
409 | name: petitparser
410 | url: "https://pub.dartlang.org"
411 | source: hosted
412 | version: "4.0.2"
413 | platform:
414 | dependency: transitive
415 | description:
416 | name: platform
417 | url: "https://pub.dartlang.org"
418 | source: hosted
419 | version: "3.0.0"
420 | plugin_platform_interface:
421 | dependency: transitive
422 | description:
423 | name: plugin_platform_interface
424 | url: "https://pub.dartlang.org"
425 | source: hosted
426 | version: "2.0.0"
427 | process:
428 | dependency: transitive
429 | description:
430 | name: process
431 | url: "https://pub.dartlang.org"
432 | source: hosted
433 | version: "4.1.0"
434 | quiver:
435 | dependency: transitive
436 | description:
437 | name: quiver
438 | url: "https://pub.dartlang.org"
439 | source: hosted
440 | version: "3.0.0"
441 | rxdart:
442 | dependency: transitive
443 | description:
444 | name: rxdart
445 | url: "https://pub.dartlang.org"
446 | source: hosted
447 | version: "0.25.0"
448 | shared_preferences:
449 | dependency: "direct main"
450 | description:
451 | name: shared_preferences
452 | url: "https://pub.dartlang.org"
453 | source: hosted
454 | version: "2.0.4"
455 | shared_preferences_linux:
456 | dependency: transitive
457 | description:
458 | name: shared_preferences_linux
459 | url: "https://pub.dartlang.org"
460 | source: hosted
461 | version: "2.0.0"
462 | shared_preferences_macos:
463 | dependency: transitive
464 | description:
465 | name: shared_preferences_macos
466 | url: "https://pub.dartlang.org"
467 | source: hosted
468 | version: "2.0.0"
469 | shared_preferences_platform_interface:
470 | dependency: transitive
471 | description:
472 | name: shared_preferences_platform_interface
473 | url: "https://pub.dartlang.org"
474 | source: hosted
475 | version: "2.0.0"
476 | shared_preferences_web:
477 | dependency: transitive
478 | description:
479 | name: shared_preferences_web
480 | url: "https://pub.dartlang.org"
481 | source: hosted
482 | version: "2.0.0"
483 | shared_preferences_windows:
484 | dependency: transitive
485 | description:
486 | name: shared_preferences_windows
487 | url: "https://pub.dartlang.org"
488 | source: hosted
489 | version: "2.0.0"
490 | sky_engine:
491 | dependency: transitive
492 | description: flutter
493 | source: sdk
494 | version: "0.0.99"
495 | source_span:
496 | dependency: transitive
497 | description:
498 | name: source_span
499 | url: "https://pub.dartlang.org"
500 | source: hosted
501 | version: "1.8.1"
502 | sqflite:
503 | dependency: transitive
504 | description:
505 | name: sqflite
506 | url: "https://pub.dartlang.org"
507 | source: hosted
508 | version: "2.0.0+2"
509 | sqflite_common:
510 | dependency: transitive
511 | description:
512 | name: sqflite_common
513 | url: "https://pub.dartlang.org"
514 | source: hosted
515 | version: "2.0.0+2"
516 | stack_trace:
517 | dependency: transitive
518 | description:
519 | name: stack_trace
520 | url: "https://pub.dartlang.org"
521 | source: hosted
522 | version: "1.10.0"
523 | string_scanner:
524 | dependency: transitive
525 | description:
526 | name: string_scanner
527 | url: "https://pub.dartlang.org"
528 | source: hosted
529 | version: "1.1.0"
530 | synchronized:
531 | dependency: transitive
532 | description:
533 | name: synchronized
534 | url: "https://pub.dartlang.org"
535 | source: hosted
536 | version: "3.0.0"
537 | term_glyph:
538 | dependency: transitive
539 | description:
540 | name: term_glyph
541 | url: "https://pub.dartlang.org"
542 | source: hosted
543 | version: "1.2.0"
544 | typed_data:
545 | dependency: transitive
546 | description:
547 | name: typed_data
548 | url: "https://pub.dartlang.org"
549 | source: hosted
550 | version: "1.3.0"
551 | uuid:
552 | dependency: "direct main"
553 | description:
554 | name: uuid
555 | url: "https://pub.dartlang.org"
556 | source: hosted
557 | version: "3.0.1"
558 | vector_math:
559 | dependency: transitive
560 | description:
561 | name: vector_math
562 | url: "https://pub.dartlang.org"
563 | source: hosted
564 | version: "2.1.0"
565 | win32:
566 | dependency: transitive
567 | description:
568 | name: win32
569 | url: "https://pub.dartlang.org"
570 | source: hosted
571 | version: "2.0.4"
572 | xdg_directories:
573 | dependency: transitive
574 | description:
575 | name: xdg_directories
576 | url: "https://pub.dartlang.org"
577 | source: hosted
578 | version: "0.2.0"
579 | xml:
580 | dependency: transitive
581 | description:
582 | name: xml
583 | url: "https://pub.dartlang.org"
584 | source: hosted
585 | version: "5.0.2"
586 | sdks:
587 | dart: ">=2.12.0 <3.0.0"
588 | flutter: ">=1.24.0-10"
589 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: Fluttergram
2 | description: A new Flutter application.
3 | version: 1.2.3+1
4 |
5 | environment:
6 | sdk: '>=2.10.0 <3.0.0'
7 |
8 | dependencies:
9 | flutter:
10 | sdk: flutter
11 | firebase_core: ^1.0.1
12 | cloud_firestore: ^1.0.1
13 | image_picker: ^0.7.2+1
14 | firebase_storage: ^8.0.0
15 | firebase_auth: ^1.0.1
16 | google_sign_in: ^5.0.0
17 | uuid: ^3.0.1
18 | image: ^3.0.1
19 | font_awesome_flutter: ^8.5.0
20 | async: ^2.4.0
21 | http: ^0.13.0
22 | shared_preferences: ^2.0.4
23 | cached_network_image: ^2.0.0
24 | firebase_messaging: ^9.0.0
25 | location: ^4.1.1
26 | geocoder: ^0.2.1
27 | flare_flutter: ^2.0.6
28 |
29 | # The following adds the Cupertino Icons font to your application.
30 | # Use with the CupertinoIcons class for iOS style icons.
31 | cupertino_icons: ^1.0.2
32 |
33 | #dev_dependencies:
34 | # flutter_test:
35 | # sdk: flutter
36 |
37 |
38 | # For information on the generic Dart part of this file, see the
39 | # following page: https://www.dartlang.org/tools/pub/pubspec
40 |
41 | # The following section is specific to Flutter.
42 | flutter:
43 |
44 | # The following line ensures that the Material Icons font is
45 | # included with your application, so that you can use the icons in
46 | # the material Icons class.
47 | uses-material-design: true
48 | fonts:
49 | - family: Billabong
50 | fonts:
51 | - asset: assets/fonts/Billabong.ttf
52 | assets:
53 | - assets/images/google_signin_button.png
54 | - assets/flare/Like.flr
55 | # To add assets to your application, add an assets section, like this:
56 | # assets:
57 | # - images/a_dot_burr.jpeg
58 | # - images/a_dot_ham.jpeg
59 |
60 | # An image asset can refer to one or more resolution-specific "variants", see
61 | # https://flutter.io/assets-and-images/#resolution-aware.
62 |
63 | # For details regarding adding assets from package dependencies, see
64 | # https://flutter.io/assets-and-images/#from-packages
65 |
66 | # To add custom fonts to your application, add a fonts section here,
67 | # in this "flutter" section. Each entry in this list should have a
68 | # "family" key with the font family name, and a "fonts" key with a
69 | # list giving the asset and other descriptors for the font. For
70 | # example:
71 | # fonts:
72 | # - family: Schyler
73 | # fonts:
74 | # - asset: fonts/Schyler-Regular.ttf
75 | # - asset: fonts/Schyler-Italic.ttf
76 | # style: italic
77 | # - family: Trajan Pro
78 | # fonts:
79 | # - asset: fonts/TrajanPro.ttf
80 | # - asset: fonts/TrajanPro_Bold.ttf
81 | # weight: 700
82 | #
83 | # For details regarding fonts from package dependencies,
84 | # see https://flutter.io/custom-fonts/#from-packages
85 |
--------------------------------------------------------------------------------