├── .firebaserc ├── .gitignore ├── .metadata ├── README.md ├── android ├── app │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── flutter_instagram_clone │ │ │ │ └── MainActivity.kt │ │ └── 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 │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle ├── assets ├── fonts │ └── Billabong.ttf └── images │ └── user_placeholder.jpg ├── firebase.json ├── functions ├── .eslintrc.json ├── .gitignore ├── index.js ├── package-lock.json └── package.json ├── ios ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ ├── Release.xcconfig │ └── flutter_export_environment.sh ├── Podfile ├── Podfile.lock ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── Runner │ ├── AppDelegate.swift │ ├── 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 │ └── Runner-Bridging-Header.h ├── lib ├── main.dart ├── models │ ├── activity_model.dart │ ├── comment_model.dart │ ├── post_model.dart │ ├── user_data.dart │ └── user_model.dart ├── screens │ ├── activity_screen.dart │ ├── comments_screen.dart │ ├── create_post_screen.dart │ ├── edit_profile_screen.dart │ ├── feed_screen.dart │ ├── home_screen.dart │ ├── login_screen.dart │ ├── profile_screen.dart │ ├── search_screen.dart │ └── signup_screen.dart ├── services │ ├── auth_service.dart │ ├── database_service.dart │ └── storage_service.dart ├── utilities │ └── constants.dart └── widgets │ └── post_view.dart ├── pubspec.lock ├── pubspec.yaml └── test └── widget_test.dart /.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "flutter-instagram-clone-596b4" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | android/app/google-services.json 2 | ios/Runner/GoogleService-Info.plist 3 | 4 | # Miscellaneous 5 | *.class 6 | *.log 7 | *.pyc 8 | *.swp 9 | .DS_Store 10 | .atom/ 11 | .buildlog/ 12 | .history 13 | .svn/ 14 | 15 | # IntelliJ related 16 | *.iml 17 | *.ipr 18 | *.iws 19 | .idea/ 20 | 21 | # The .vscode folder contains launch configuration and tasks you configure in 22 | # VS Code which you may wish to be included in version control, so this line 23 | # is commented out by default. 24 | #.vscode/ 25 | 26 | # Flutter/Dart/Pub related 27 | **/doc/api/ 28 | .dart_tool/ 29 | .flutter-plugins 30 | .packages 31 | .pub-cache/ 32 | .pub/ 33 | /build/ 34 | 35 | # Android related 36 | **/android/**/gradle-wrapper.jar 37 | **/android/.gradle 38 | **/android/captures/ 39 | **/android/gradlew 40 | **/android/gradlew.bat 41 | **/android/local.properties 42 | **/android/**/GeneratedPluginRegistrant.java 43 | 44 | # iOS/XCode related 45 | **/ios/**/*.mode1v3 46 | **/ios/**/*.mode2v3 47 | **/ios/**/*.moved-aside 48 | **/ios/**/*.pbxuser 49 | **/ios/**/*.perspectivev3 50 | **/ios/**/*sync/ 51 | **/ios/**/.sconsign.dblite 52 | **/ios/**/.tags* 53 | **/ios/**/.vagrant/ 54 | **/ios/**/DerivedData/ 55 | **/ios/**/Icon? 56 | **/ios/**/Pods/ 57 | **/ios/**/.symlinks/ 58 | **/ios/**/profile 59 | **/ios/**/xcuserdata 60 | **/ios/.generated/ 61 | **/ios/Flutter/App.framework 62 | **/ios/Flutter/Flutter.framework 63 | **/ios/Flutter/Generated.xcconfig 64 | **/ios/Flutter/app.flx 65 | **/ios/Flutter/app.zip 66 | **/ios/Flutter/flutter_assets/ 67 | **/ios/ServiceDefinitions.json 68 | **/ios/Runner/GeneratedPluginRegistrant.* 69 | 70 | # Exceptions to above rules. 71 | !**/ios/**/default.mode1v3 72 | !**/ios/**/default.mode2v3 73 | !**/ios/**/default.pbxuser 74 | !**/ios/**/default.perspectivev3 75 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 76 | -------------------------------------------------------------------------------- /.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: 20e59316b8b8474554b38493b8ca888794b0234a 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [Flutter Firebase Instagram Tutorial](https://marcus-ng.com/p/flutter-firebase-instagram-tutorial) 2 | -------------------------------------------------------------------------------- /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 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion 28 30 | 31 | sourceSets { 32 | main.java.srcDirs += 'src/main/kotlin' 33 | } 34 | 35 | lintOptions { 36 | disable 'InvalidPackage' 37 | } 38 | 39 | defaultConfig { 40 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 41 | applicationId "com.example.flutterInstagramClone" 42 | minSdkVersion 16 43 | targetSdkVersion 28 44 | versionCode flutterVersionCode.toInteger() 45 | versionName flutterVersionName 46 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 47 | multiDexEnabled true 48 | } 49 | 50 | buildTypes { 51 | release { 52 | // TODO: Add your own signing config for the release build. 53 | // Signing with the debug keys for now, so `flutter run --release` works. 54 | signingConfig signingConfigs.debug 55 | } 56 | } 57 | } 58 | 59 | flutter { 60 | source '../..' 61 | } 62 | 63 | dependencies { 64 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 65 | implementation 'com.google.firebase:firebase-analytics:17.2.0' 66 | implementation 'androidx.multidex:multidex:2.0.0' 67 | testImplementation 'junit:junit:4.12' 68 | androidTestImplementation 'com.androidx.support.test:runner:1.1.0' 69 | androidTestImplementation 'com.androidx.support.test.espresso:espresso-core:3.1.0' 70 | } 71 | 72 | apply plugin: 'com.google.gms.google-services' -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 9 | 13 | 20 | 24 | 27 | 28 | 29 | 30 | 31 | 32 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/example/flutter_instagram_clone/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.flutter_instagram_clone 2 | 3 | import android.os.Bundle 4 | 5 | import io.flutter.app.FlutterActivity 6 | import io.flutter.plugins.GeneratedPluginRegistrant 7 | 8 | class MainActivity: FlutterActivity() { 9 | override fun onCreate(savedInstanceState: Bundle?) { 10 | super.onCreate(savedInstanceState) 11 | GeneratedPluginRegistrant.registerWith(this) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /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/MarcusNg/flutter_instagram_clone/c15b9e5ddc9c0ec66d7f3b84c0d4e9440f5abdac/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcusNg/flutter_instagram_clone/c15b9e5ddc9c0ec66d7f3b84c0d4e9440f5abdac/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcusNg/flutter_instagram_clone/c15b9e5ddc9c0ec66d7f3b84c0d4e9440f5abdac/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcusNg/flutter_instagram_clone/c15b9e5ddc9c0ec66d7f3b84c0d4e9440f5abdac/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcusNg/flutter_instagram_clone/c15b9e5ddc9c0ec66d7f3b84c0d4e9440f5abdac/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.21' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.3.1' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | classpath 'com.google.gms:google-services:4.3.2' 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | google() 18 | jcenter() 19 | } 20 | } 21 | 22 | rootProject.buildDir = '../build' 23 | subprojects { 24 | project.buildDir = "${rootProject.buildDir}/${project.name}" 25 | } 26 | subprojects { 27 | project.evaluationDependsOn(':app') 28 | } 29 | 30 | task clean(type: Delete) { 31 | delete rootProject.buildDir 32 | } 33 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | android.enableR8=true 5 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 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-4.10.2-all.zip 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /assets/fonts/Billabong.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcusNg/flutter_instagram_clone/c15b9e5ddc9c0ec66d7f3b84c0d4e9440f5abdac/assets/fonts/Billabong.ttf -------------------------------------------------------------------------------- /assets/images/user_placeholder.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcusNg/flutter_instagram_clone/c15b9e5ddc9c0ec66d7f3b84c0d4e9440f5abdac/assets/images/user_placeholder.jpg -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "functions": { 3 | "predeploy": [ 4 | "npm --prefix \"$RESOURCE_DIR\" run lint" 5 | ] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /functions/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parserOptions": { 3 | // Required for certain syntax usages 4 | "ecmaVersion": 2017 5 | }, 6 | "plugins": [ 7 | "promise" 8 | ], 9 | "extends": "eslint:recommended", 10 | "rules": { 11 | // Removed rule "disallow the use of console" from recommended eslint rules 12 | "no-console": "off", 13 | 14 | // Removed rule "disallow multiple spaces in regular expressions" from recommended eslint rules 15 | "no-regex-spaces": "off", 16 | 17 | // Removed rule "disallow the use of debugger" from recommended eslint rules 18 | "no-debugger": "off", 19 | 20 | // Removed rule "disallow unused variables" from recommended eslint rules 21 | "no-unused-vars": "off", 22 | 23 | // Removed rule "disallow mixed spaces and tabs for indentation" from recommended eslint rules 24 | "no-mixed-spaces-and-tabs": "off", 25 | 26 | // Removed rule "disallow the use of undeclared variables unless mentioned in /*global */ comments" from recommended eslint rules 27 | "no-undef": "off", 28 | 29 | // Warn against template literal placeholder syntax in regular strings 30 | "no-template-curly-in-string": 1, 31 | 32 | // Warn if return statements do not either always or never specify values 33 | "consistent-return": 1, 34 | 35 | // Warn if no return statements in callbacks of array methods 36 | "array-callback-return": 1, 37 | 38 | // Require the use of === and !== 39 | "eqeqeq": 2, 40 | 41 | // Disallow the use of alert, confirm, and prompt 42 | "no-alert": 2, 43 | 44 | // Disallow the use of arguments.caller or arguments.callee 45 | "no-caller": 2, 46 | 47 | // Disallow null comparisons without type-checking operators 48 | "no-eq-null": 2, 49 | 50 | // Disallow the use of eval() 51 | "no-eval": 2, 52 | 53 | // Warn against extending native types 54 | "no-extend-native": 1, 55 | 56 | // Warn against unnecessary calls to .bind() 57 | "no-extra-bind": 1, 58 | 59 | // Warn against unnecessary labels 60 | "no-extra-label": 1, 61 | 62 | // Disallow leading or trailing decimal points in numeric literals 63 | "no-floating-decimal": 2, 64 | 65 | // Warn against shorthand type conversions 66 | "no-implicit-coercion": 1, 67 | 68 | // Warn against function declarations and expressions inside loop statements 69 | "no-loop-func": 1, 70 | 71 | // Disallow new operators with the Function object 72 | "no-new-func": 2, 73 | 74 | // Warn against new operators with the String, Number, and Boolean objects 75 | "no-new-wrappers": 1, 76 | 77 | // Disallow throwing literals as exceptions 78 | "no-throw-literal": 2, 79 | 80 | // Require using Error objects as Promise rejection reasons 81 | "prefer-promise-reject-errors": 2, 82 | 83 | // Enforce “for” loop update clause moving the counter in the right direction 84 | "for-direction": 2, 85 | 86 | // Enforce return statements in getters 87 | "getter-return": 2, 88 | 89 | // Disallow await inside of loops 90 | "no-await-in-loop": 2, 91 | 92 | // Disallow comparing against -0 93 | "no-compare-neg-zero": 2, 94 | 95 | // Warn against catch clause parameters from shadowing variables in the outer scope 96 | "no-catch-shadow": 1, 97 | 98 | // Disallow identifiers from shadowing restricted names 99 | "no-shadow-restricted-names": 2, 100 | 101 | // Enforce return statements in callbacks of array methods 102 | "callback-return": 2, 103 | 104 | // Require error handling in callbacks 105 | "handle-callback-err": 2, 106 | 107 | // Warn against string concatenation with __dirname and __filename 108 | "no-path-concat": 1, 109 | 110 | // Prefer using arrow functions for callbacks 111 | "prefer-arrow-callback": 1, 112 | 113 | // Return inside each then() to create readable and reusable Promise chains. 114 | // Forces developers to return console logs and http calls in promises. 115 | "promise/always-return": 2, 116 | 117 | //Enforces the use of catch() on un-returned promises 118 | "promise/catch-or-return": 2, 119 | 120 | // Warn against nested then() or catch() statements 121 | "promise/no-nesting": 1 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /functions/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ -------------------------------------------------------------------------------- /functions/index.js: -------------------------------------------------------------------------------- 1 | const functions = require('firebase-functions'); 2 | const admin = require('firebase-admin'); 3 | admin.initializeApp(); 4 | 5 | exports.onFollowUser = functions.firestore 6 | .document('/followers/{userId}/userFollowers/{followerId}') 7 | .onCreate(async (snapshot, context) => { 8 | console.log(snapshot.data()); 9 | const userId = context.params.userId; 10 | const followerId = context.params.followerId; 11 | const followedUserPostsRef = admin 12 | .firestore() 13 | .collection('posts') 14 | .doc(userId) 15 | .collection('userPosts'); 16 | const userFeedRef = admin 17 | .firestore() 18 | .collection('feeds') 19 | .doc(followerId) 20 | .collection('userFeed'); 21 | const followedUserPostsSnapshot = await followedUserPostsRef.get(); 22 | followedUserPostsSnapshot.forEach(doc => { 23 | if (doc.exists) { 24 | userFeedRef.doc(doc.id).set(doc.data()); 25 | } 26 | }); 27 | }); 28 | 29 | exports.onUnfollowUser = functions.firestore 30 | .document('/followers/{userId}/userFollowers/{followerId}') 31 | .onDelete(async (snapshot, context) => { 32 | const userId = context.params.userId; 33 | const followerId = context.params.followerId; 34 | const userFeedRef = admin 35 | .firestore() 36 | .collection('feeds') 37 | .doc(followerId) 38 | .collection('userFeed') 39 | .where('authorId', '==', userId); 40 | const userPostsSnapshot = await userFeedRef.get(); 41 | userPostsSnapshot.forEach(doc => { 42 | if (doc.exists) { 43 | doc.ref.delete(); 44 | } 45 | }); 46 | }); 47 | 48 | exports.onUploadPost = functions.firestore 49 | .document('/posts/{userId}/userPosts/{postId}') 50 | .onCreate(async (snapshot, context) => { 51 | console.log(snapshot.data()); 52 | const userId = context.params.userId; 53 | const postId = context.params.postId; 54 | const userFollowersRef = admin 55 | .firestore() 56 | .collection('followers') 57 | .doc(userId) 58 | .collection('userFollowers'); 59 | const userFollowersSnapshot = await userFollowersRef.get(); 60 | userFollowersSnapshot.forEach(doc => { 61 | admin 62 | .firestore() 63 | .collection('feeds') 64 | .doc(doc.id) 65 | .collection('userFeed') 66 | .doc(postId) 67 | .set(snapshot.data()); 68 | }); 69 | }); 70 | 71 | exports.onUpdatePost = functions.firestore 72 | .document('/posts/{userId}/userPosts/{postId}') 73 | .onUpdate(async (snapshot, context) => { 74 | const userId = context.params.userId; 75 | const postId = context.params.postId; 76 | const newPostData = snapshot.after.data(); 77 | console.log(newPostData); 78 | const userFollowersRef = admin 79 | .firestore() 80 | .collection('followers') 81 | .doc(userId) 82 | .collection('userFollowers'); 83 | const userFollowersSnapshot = await userFollowersRef.get(); 84 | userFollowersSnapshot.forEach(async userDoc => { 85 | const postRef = admin 86 | .firestore() 87 | .collection('feeds') 88 | .doc(userDoc.id) 89 | .collection('userFeed'); 90 | const postDoc = await postRef.doc(postId).get(); 91 | if (postDoc.exists) { 92 | postDoc.ref.update(newPostData); 93 | } 94 | }); 95 | }); 96 | -------------------------------------------------------------------------------- /functions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "functions", 3 | "description": "Cloud Functions for Firebase", 4 | "scripts": { 5 | "lint": "eslint .", 6 | "serve": "firebase serve --only functions", 7 | "shell": "firebase functions:shell", 8 | "start": "npm run shell", 9 | "deploy": "firebase deploy --only functions", 10 | "logs": "firebase functions:log" 11 | }, 12 | "engines": { 13 | "node": "8" 14 | }, 15 | "dependencies": { 16 | "firebase-admin": "^8.6.0", 17 | "firebase-functions": "^3.3.0" 18 | }, 19 | "devDependencies": { 20 | "eslint": "^5.12.0", 21 | "eslint-plugin-promise": "^4.0.1", 22 | "firebase-functions-test": "^0.1.6" 23 | }, 24 | "private": true 25 | } 26 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 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 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /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/Flutter/flutter_export_environment.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # This is a generated file; do not edit or check into version control. 3 | export "FLUTTER_ROOT=/Users/marcusng/Downloads/flutter" 4 | export "FLUTTER_APPLICATION_PATH=/Users/marcusng/Development/YouTube/Tutorials/flutter_instagram_clone" 5 | export "FLUTTER_TARGET=/Users/marcusng/Development/YouTube/Tutorials/flutter_instagram_clone/lib/main.dart" 6 | export "FLUTTER_BUILD_DIR=build" 7 | export "SYMROOT=${SOURCE_ROOT}/../build/ios" 8 | export "FLUTTER_FRAMEWORK_DIR=/Users/marcusng/Downloads/flutter/bin/cache/artifacts/engine/ios" 9 | export "FLUTTER_BUILD_NAME=1.0.0" 10 | export "FLUTTER_BUILD_NUMBER=1" 11 | export "TRACK_WIDGET_CREATION=true" 12 | -------------------------------------------------------------------------------- /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 parse_KV_file(file, separator='=') 14 | file_abs_path = File.expand_path(file) 15 | if !File.exists? file_abs_path 16 | return []; 17 | end 18 | generated_key_values = {} 19 | skip_line_start_symbols = ["#", "/"] 20 | File.foreach(file_abs_path) do |line| 21 | next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } 22 | plugin = line.split(pattern=separator) 23 | if plugin.length == 2 24 | podname = plugin[0].strip() 25 | path = plugin[1].strip() 26 | podpath = File.expand_path("#{path}", file_abs_path) 27 | generated_key_values[podname] = podpath 28 | else 29 | puts "Invalid plugin specification: #{line}" 30 | end 31 | end 32 | generated_key_values 33 | end 34 | 35 | target 'Runner' do 36 | use_frameworks! 37 | use_modular_headers! 38 | 39 | # Flutter Pod 40 | 41 | copied_flutter_dir = File.join(__dir__, 'Flutter') 42 | copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework') 43 | copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec') 44 | unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path) 45 | # Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet. 46 | # That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration. 47 | # CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist. 48 | 49 | generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig') 50 | unless File.exist?(generated_xcode_build_settings_path) 51 | raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first" 52 | end 53 | generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path) 54 | cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR']; 55 | 56 | unless File.exist?(copied_framework_path) 57 | FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir) 58 | end 59 | unless File.exist?(copied_podspec_path) 60 | FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir) 61 | end 62 | end 63 | 64 | # Keep pod path relative so it can be checked into Podfile.lock. 65 | pod 'Flutter', :path => 'Flutter' 66 | 67 | # Plugin Pods 68 | 69 | # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock 70 | # referring to absolute paths on developers' machines. 71 | system('rm -rf .symlinks') 72 | system('mkdir -p .symlinks/plugins') 73 | plugin_pods = parse_KV_file('../.flutter-plugins') 74 | plugin_pods.each do |name, path| 75 | symlink = File.join('.symlinks', 'plugins', name) 76 | File.symlink(path, symlink) 77 | pod name, :path => File.join(symlink, 'ios') 78 | end 79 | end 80 | 81 | # Prevent Cocoapods from embedding a second Flutter framework and causing an error with the new Xcode build system. 82 | install! 'cocoapods', :disable_input_output_paths => true 83 | 84 | post_install do |installer| 85 | installer.pods_project.targets.each do |target| 86 | target.build_configurations.each do |config| 87 | config.build_settings['ENABLE_BITCODE'] = 'NO' 88 | end 89 | end 90 | end 91 | -------------------------------------------------------------------------------- /ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - abseil/algorithm (0.20190808): 3 | - abseil/algorithm/algorithm (= 0.20190808) 4 | - abseil/algorithm/container (= 0.20190808) 5 | - abseil/algorithm/algorithm (0.20190808) 6 | - abseil/algorithm/container (0.20190808): 7 | - abseil/algorithm/algorithm 8 | - abseil/base/core_headers 9 | - abseil/meta/type_traits 10 | - abseil/base (0.20190808): 11 | - abseil/base/atomic_hook (= 0.20190808) 12 | - abseil/base/base (= 0.20190808) 13 | - abseil/base/base_internal (= 0.20190808) 14 | - abseil/base/bits (= 0.20190808) 15 | - abseil/base/config (= 0.20190808) 16 | - abseil/base/core_headers (= 0.20190808) 17 | - abseil/base/dynamic_annotations (= 0.20190808) 18 | - abseil/base/endian (= 0.20190808) 19 | - abseil/base/log_severity (= 0.20190808) 20 | - abseil/base/malloc_internal (= 0.20190808) 21 | - abseil/base/pretty_function (= 0.20190808) 22 | - abseil/base/spinlock_wait (= 0.20190808) 23 | - abseil/base/throw_delegate (= 0.20190808) 24 | - abseil/base/atomic_hook (0.20190808) 25 | - abseil/base/base (0.20190808): 26 | - abseil/base/atomic_hook 27 | - abseil/base/base_internal 28 | - abseil/base/config 29 | - abseil/base/core_headers 30 | - abseil/base/dynamic_annotations 31 | - abseil/base/log_severity 32 | - abseil/base/spinlock_wait 33 | - abseil/meta/type_traits 34 | - abseil/base/base_internal (0.20190808): 35 | - abseil/meta/type_traits 36 | - abseil/base/bits (0.20190808): 37 | - abseil/base/core_headers 38 | - abseil/base/config (0.20190808) 39 | - abseil/base/core_headers (0.20190808): 40 | - abseil/base/config 41 | - abseil/base/dynamic_annotations (0.20190808) 42 | - abseil/base/endian (0.20190808): 43 | - abseil/base/config 44 | - abseil/base/core_headers 45 | - abseil/base/log_severity (0.20190808): 46 | - abseil/base/core_headers 47 | - abseil/base/malloc_internal (0.20190808): 48 | - abseil/base/base 49 | - abseil/base/config 50 | - abseil/base/core_headers 51 | - abseil/base/dynamic_annotations 52 | - abseil/base/spinlock_wait 53 | - abseil/base/pretty_function (0.20190808) 54 | - abseil/base/spinlock_wait (0.20190808): 55 | - abseil/base/core_headers 56 | - abseil/base/throw_delegate (0.20190808): 57 | - abseil/base/base 58 | - abseil/base/config 59 | - abseil/memory (0.20190808): 60 | - abseil/memory/memory (= 0.20190808) 61 | - abseil/memory/memory (0.20190808): 62 | - abseil/base/core_headers 63 | - abseil/meta/type_traits 64 | - abseil/meta (0.20190808): 65 | - abseil/meta/type_traits (= 0.20190808) 66 | - abseil/meta/type_traits (0.20190808): 67 | - abseil/base/config 68 | - abseil/numeric/int128 (0.20190808): 69 | - abseil/base/config 70 | - abseil/base/core_headers 71 | - abseil/strings/internal (0.20190808): 72 | - abseil/base/core_headers 73 | - abseil/base/endian 74 | - abseil/meta/type_traits 75 | - abseil/strings/strings (0.20190808): 76 | - abseil/base/base 77 | - abseil/base/bits 78 | - abseil/base/config 79 | - abseil/base/core_headers 80 | - abseil/base/endian 81 | - abseil/base/throw_delegate 82 | - abseil/memory/memory 83 | - abseil/meta/type_traits 84 | - abseil/numeric/int128 85 | - abseil/strings/internal 86 | - abseil/time (0.20190808): 87 | - abseil/time/internal (= 0.20190808) 88 | - abseil/time/time (= 0.20190808) 89 | - abseil/time/internal (0.20190808): 90 | - abseil/time/internal/cctz (= 0.20190808) 91 | - abseil/time/internal/cctz (0.20190808): 92 | - abseil/time/internal/cctz/civil_time (= 0.20190808) 93 | - abseil/time/internal/cctz/includes (= 0.20190808) 94 | - abseil/time/internal/cctz/time_zone (= 0.20190808) 95 | - abseil/time/internal/cctz/civil_time (0.20190808) 96 | - abseil/time/internal/cctz/includes (0.20190808) 97 | - abseil/time/internal/cctz/time_zone (0.20190808): 98 | - abseil/time/internal/cctz/civil_time 99 | - abseil/time/time (0.20190808): 100 | - abseil/base/base 101 | - abseil/base/core_headers 102 | - abseil/numeric/int128 103 | - abseil/strings/strings 104 | - abseil/time/internal/cctz/civil_time 105 | - abseil/time/internal/cctz/time_zone 106 | - abseil/types (0.20190808): 107 | - abseil/types/any (= 0.20190808) 108 | - abseil/types/bad_any_cast (= 0.20190808) 109 | - abseil/types/bad_any_cast_impl (= 0.20190808) 110 | - abseil/types/bad_optional_access (= 0.20190808) 111 | - abseil/types/bad_variant_access (= 0.20190808) 112 | - abseil/types/compare (= 0.20190808) 113 | - abseil/types/optional (= 0.20190808) 114 | - abseil/types/span (= 0.20190808) 115 | - abseil/types/variant (= 0.20190808) 116 | - abseil/types/any (0.20190808): 117 | - abseil/base/config 118 | - abseil/base/core_headers 119 | - abseil/meta/type_traits 120 | - abseil/types/bad_any_cast 121 | - abseil/utility/utility 122 | - abseil/types/bad_any_cast (0.20190808): 123 | - abseil/base/config 124 | - abseil/types/bad_any_cast_impl 125 | - abseil/types/bad_any_cast_impl (0.20190808): 126 | - abseil/base/base 127 | - abseil/base/config 128 | - abseil/types/bad_optional_access (0.20190808): 129 | - abseil/base/base 130 | - abseil/base/config 131 | - abseil/types/bad_variant_access (0.20190808): 132 | - abseil/base/base 133 | - abseil/base/config 134 | - abseil/types/compare (0.20190808): 135 | - abseil/base/core_headers 136 | - abseil/meta/type_traits 137 | - abseil/types/optional (0.20190808): 138 | - abseil/base/base_internal 139 | - abseil/base/config 140 | - abseil/base/core_headers 141 | - abseil/memory/memory 142 | - abseil/meta/type_traits 143 | - abseil/types/bad_optional_access 144 | - abseil/utility/utility 145 | - abseil/types/span (0.20190808): 146 | - abseil/algorithm/algorithm 147 | - abseil/base/core_headers 148 | - abseil/base/throw_delegate 149 | - abseil/meta/type_traits 150 | - abseil/types/variant (0.20190808): 151 | - abseil/base/base_internal 152 | - abseil/base/config 153 | - abseil/base/core_headers 154 | - abseil/meta/type_traits 155 | - abseil/types/bad_variant_access 156 | - abseil/utility/utility 157 | - abseil/utility/utility (0.20190808): 158 | - abseil/base/base_internal 159 | - abseil/base/config 160 | - abseil/meta/type_traits 161 | - BoringSSL-GRPC (0.0.3): 162 | - BoringSSL-GRPC/Implementation (= 0.0.3) 163 | - BoringSSL-GRPC/Interface (= 0.0.3) 164 | - BoringSSL-GRPC/Implementation (0.0.3): 165 | - BoringSSL-GRPC/Interface (= 0.0.3) 166 | - BoringSSL-GRPC/Interface (0.0.3) 167 | - cloud_firestore (0.0.1): 168 | - Firebase/Core 169 | - Firebase/Firestore (~> 6.0) 170 | - Flutter 171 | - Firebase/Analytics (6.13.0): 172 | - Firebase/Core 173 | - Firebase/Auth (6.13.0): 174 | - Firebase/CoreOnly 175 | - FirebaseAuth (~> 6.4.0) 176 | - Firebase/Core (6.13.0): 177 | - Firebase/CoreOnly 178 | - FirebaseAnalytics (= 6.1.6) 179 | - Firebase/CoreOnly (6.13.0): 180 | - FirebaseCore (= 6.4.0) 181 | - Firebase/Firestore (6.13.0): 182 | - Firebase/CoreOnly 183 | - FirebaseFirestore (~> 1.8.0) 184 | - Firebase/Storage (6.13.0): 185 | - Firebase/CoreOnly 186 | - FirebaseStorage (~> 3.4.2) 187 | - firebase_analytics (0.0.1): 188 | - Firebase/Analytics (~> 6.0) 189 | - Firebase/Core 190 | - Flutter 191 | - firebase_auth (0.0.1): 192 | - Firebase/Auth (~> 6.0) 193 | - Firebase/Core 194 | - Flutter 195 | - firebase_core (0.0.1): 196 | - Firebase/Core 197 | - Flutter 198 | - firebase_core_web (0.1.0): 199 | - Flutter 200 | - firebase_storage (0.0.1): 201 | - Firebase/Storage 202 | - Flutter 203 | - FirebaseAnalytics (6.1.6): 204 | - FirebaseCore (~> 6.4) 205 | - FirebaseInstanceID (~> 4.2) 206 | - GoogleAppMeasurement (= 6.1.6) 207 | - GoogleUtilities/AppDelegateSwizzler (~> 6.0) 208 | - GoogleUtilities/MethodSwizzler (~> 6.0) 209 | - GoogleUtilities/Network (~> 6.0) 210 | - "GoogleUtilities/NSData+zlib (~> 6.0)" 211 | - nanopb (= 0.3.9011) 212 | - FirebaseAuth (6.4.0): 213 | - FirebaseAuthInterop (~> 1.0) 214 | - FirebaseCore (~> 6.2) 215 | - GoogleUtilities/AppDelegateSwizzler (~> 6.2) 216 | - GoogleUtilities/Environment (~> 6.2) 217 | - GTMSessionFetcher/Core (~> 1.1) 218 | - FirebaseAuthInterop (1.0.0) 219 | - FirebaseCore (6.4.0): 220 | - FirebaseCoreDiagnostics (~> 1.0) 221 | - FirebaseCoreDiagnosticsInterop (~> 1.0) 222 | - GoogleUtilities/Environment (~> 6.2) 223 | - GoogleUtilities/Logger (~> 6.2) 224 | - FirebaseCoreDiagnostics (1.1.2): 225 | - FirebaseCoreDiagnosticsInterop (~> 1.0) 226 | - GoogleDataTransportCCTSupport (~> 1.0) 227 | - GoogleUtilities/Environment (~> 6.2) 228 | - GoogleUtilities/Logger (~> 6.2) 229 | - nanopb (~> 0.3.901) 230 | - FirebaseCoreDiagnosticsInterop (1.1.0) 231 | - FirebaseFirestore (1.8.1): 232 | - abseil/algorithm (= 0.20190808) 233 | - abseil/base (= 0.20190808) 234 | - abseil/memory (= 0.20190808) 235 | - abseil/meta (= 0.20190808) 236 | - abseil/strings/strings (= 0.20190808) 237 | - abseil/time (= 0.20190808) 238 | - abseil/types (= 0.20190808) 239 | - FirebaseAuthInterop (~> 1.0) 240 | - FirebaseCore (~> 6.2) 241 | - "gRPC-C++ (= 0.0.9)" 242 | - leveldb-library (~> 1.22) 243 | - nanopb (~> 0.3.901) 244 | - FirebaseInstanceID (4.2.7): 245 | - FirebaseCore (~> 6.0) 246 | - GoogleUtilities/Environment (~> 6.0) 247 | - GoogleUtilities/UserDefaults (~> 6.0) 248 | - FirebaseStorage (3.4.2): 249 | - FirebaseAuthInterop (~> 1.0) 250 | - FirebaseCore (~> 6.0) 251 | - GTMSessionFetcher/Core (~> 1.1) 252 | - Flutter (1.0.0) 253 | - flutter_image_compress (0.0.1): 254 | - Flutter 255 | - Mantle 256 | - SDWebImageWebPCoder 257 | - flutter_plugin_android_lifecycle (0.0.1): 258 | - Flutter 259 | - FMDB (2.7.5): 260 | - FMDB/standard (= 2.7.5) 261 | - FMDB/standard (2.7.5) 262 | - GoogleAppMeasurement (6.1.6): 263 | - GoogleUtilities/AppDelegateSwizzler (~> 6.0) 264 | - GoogleUtilities/MethodSwizzler (~> 6.0) 265 | - GoogleUtilities/Network (~> 6.0) 266 | - "GoogleUtilities/NSData+zlib (~> 6.0)" 267 | - nanopb (= 0.3.9011) 268 | - GoogleDataTransport (3.2.0) 269 | - GoogleDataTransportCCTSupport (1.2.2): 270 | - GoogleDataTransport (~> 3.2) 271 | - nanopb (~> 0.3.901) 272 | - GoogleUtilities/AppDelegateSwizzler (6.3.2): 273 | - GoogleUtilities/Environment 274 | - GoogleUtilities/Logger 275 | - GoogleUtilities/Network 276 | - GoogleUtilities/Environment (6.3.2) 277 | - GoogleUtilities/Logger (6.3.2): 278 | - GoogleUtilities/Environment 279 | - GoogleUtilities/MethodSwizzler (6.3.2): 280 | - GoogleUtilities/Logger 281 | - GoogleUtilities/Network (6.3.2): 282 | - GoogleUtilities/Logger 283 | - "GoogleUtilities/NSData+zlib" 284 | - GoogleUtilities/Reachability 285 | - "GoogleUtilities/NSData+zlib (6.3.2)" 286 | - GoogleUtilities/Reachability (6.3.2): 287 | - GoogleUtilities/Logger 288 | - GoogleUtilities/UserDefaults (6.3.2): 289 | - GoogleUtilities/Logger 290 | - "gRPC-C++ (0.0.9)": 291 | - "gRPC-C++/Implementation (= 0.0.9)" 292 | - "gRPC-C++/Interface (= 0.0.9)" 293 | - "gRPC-C++/Implementation (0.0.9)": 294 | - "gRPC-C++/Interface (= 0.0.9)" 295 | - gRPC-Core (= 1.21.0) 296 | - nanopb (~> 0.3) 297 | - "gRPC-C++/Interface (0.0.9)" 298 | - gRPC-Core (1.21.0): 299 | - gRPC-Core/Implementation (= 1.21.0) 300 | - gRPC-Core/Interface (= 1.21.0) 301 | - gRPC-Core/Implementation (1.21.0): 302 | - BoringSSL-GRPC (= 0.0.3) 303 | - gRPC-Core/Interface (= 1.21.0) 304 | - nanopb (~> 0.3) 305 | - gRPC-Core/Interface (1.21.0) 306 | - GTMSessionFetcher/Core (1.3.0) 307 | - image_cropper (0.0.2): 308 | - Flutter 309 | - TOCropViewController (~> 2.5.2) 310 | - image_picker (0.0.1): 311 | - Flutter 312 | - leveldb-library (1.22) 313 | - libwebp (1.1.0): 314 | - libwebp/demux (= 1.1.0) 315 | - libwebp/mux (= 1.1.0) 316 | - libwebp/webp (= 1.1.0) 317 | - libwebp/demux (1.1.0): 318 | - libwebp/webp 319 | - libwebp/mux (1.1.0): 320 | - libwebp/demux 321 | - libwebp/webp (1.1.0) 322 | - Mantle (2.1.0): 323 | - Mantle/extobjc (= 2.1.0) 324 | - Mantle/extobjc (2.1.0) 325 | - nanopb (0.3.9011): 326 | - nanopb/decode (= 0.3.9011) 327 | - nanopb/encode (= 0.3.9011) 328 | - nanopb/decode (0.3.9011) 329 | - nanopb/encode (0.3.9011) 330 | - path_provider (0.0.1): 331 | - Flutter 332 | - path_provider_macos (0.0.1): 333 | - Flutter 334 | - SDWebImage/Core (5.8.0) 335 | - SDWebImageWebPCoder (0.6.1): 336 | - libwebp (~> 1.0) 337 | - SDWebImage/Core (~> 5.7) 338 | - sqflite (0.0.1): 339 | - Flutter 340 | - FMDB (~> 2.7.2) 341 | - TOCropViewController (2.5.2) 342 | 343 | DEPENDENCIES: 344 | - cloud_firestore (from `.symlinks/plugins/cloud_firestore/ios`) 345 | - firebase_analytics (from `.symlinks/plugins/firebase_analytics/ios`) 346 | - firebase_auth (from `.symlinks/plugins/firebase_auth/ios`) 347 | - firebase_core (from `.symlinks/plugins/firebase_core/ios`) 348 | - firebase_core_web (from `.symlinks/plugins/firebase_core_web/ios`) 349 | - firebase_storage (from `.symlinks/plugins/firebase_storage/ios`) 350 | - Flutter (from `Flutter`) 351 | - flutter_image_compress (from `.symlinks/plugins/flutter_image_compress/ios`) 352 | - flutter_plugin_android_lifecycle (from `.symlinks/plugins/flutter_plugin_android_lifecycle/ios`) 353 | - image_cropper (from `.symlinks/plugins/image_cropper/ios`) 354 | - image_picker (from `.symlinks/plugins/image_picker/ios`) 355 | - path_provider (from `.symlinks/plugins/path_provider/ios`) 356 | - path_provider_macos (from `.symlinks/plugins/path_provider_macos/ios`) 357 | - sqflite (from `.symlinks/plugins/sqflite/ios`) 358 | 359 | SPEC REPOS: 360 | https://github.com/CocoaPods/Specs.git: 361 | - abseil 362 | - BoringSSL-GRPC 363 | - Firebase 364 | - FirebaseAnalytics 365 | - FirebaseAuth 366 | - FirebaseAuthInterop 367 | - FirebaseCore 368 | - FirebaseCoreDiagnostics 369 | - FirebaseCoreDiagnosticsInterop 370 | - FirebaseFirestore 371 | - FirebaseInstanceID 372 | - FirebaseStorage 373 | - FMDB 374 | - GoogleAppMeasurement 375 | - GoogleDataTransport 376 | - GoogleDataTransportCCTSupport 377 | - GoogleUtilities 378 | - "gRPC-C++" 379 | - gRPC-Core 380 | - GTMSessionFetcher 381 | - leveldb-library 382 | - Mantle 383 | - nanopb 384 | - TOCropViewController 385 | trunk: 386 | - libwebp 387 | - SDWebImage 388 | - SDWebImageWebPCoder 389 | 390 | EXTERNAL SOURCES: 391 | cloud_firestore: 392 | :path: ".symlinks/plugins/cloud_firestore/ios" 393 | firebase_analytics: 394 | :path: ".symlinks/plugins/firebase_analytics/ios" 395 | firebase_auth: 396 | :path: ".symlinks/plugins/firebase_auth/ios" 397 | firebase_core: 398 | :path: ".symlinks/plugins/firebase_core/ios" 399 | firebase_core_web: 400 | :path: ".symlinks/plugins/firebase_core_web/ios" 401 | firebase_storage: 402 | :path: ".symlinks/plugins/firebase_storage/ios" 403 | Flutter: 404 | :path: Flutter 405 | flutter_image_compress: 406 | :path: ".symlinks/plugins/flutter_image_compress/ios" 407 | flutter_plugin_android_lifecycle: 408 | :path: ".symlinks/plugins/flutter_plugin_android_lifecycle/ios" 409 | image_cropper: 410 | :path: ".symlinks/plugins/image_cropper/ios" 411 | image_picker: 412 | :path: ".symlinks/plugins/image_picker/ios" 413 | path_provider: 414 | :path: ".symlinks/plugins/path_provider/ios" 415 | path_provider_macos: 416 | :path: ".symlinks/plugins/path_provider_macos/ios" 417 | sqflite: 418 | :path: ".symlinks/plugins/sqflite/ios" 419 | 420 | SPEC CHECKSUMS: 421 | abseil: 18063d773f5366ff8736a050fe035a28f635fd27 422 | BoringSSL-GRPC: db8764df3204ccea016e1c8dd15d9a9ad63ff318 423 | cloud_firestore: 10fd4a09ebffc33efd64708106e2e7de701fe9ae 424 | Firebase: 458d109512200d1aca2e1b9b6cf7d68a869a4a46 425 | firebase_analytics: dacdcfc524d722fff13dcff942f0dfa47e6be567 426 | firebase_auth: d99b993c1405096e66c58211b1cd956c23eed1c5 427 | firebase_core: 0d8be0e0d14c4902953aeb5ac5d7316d1fe4b978 428 | firebase_core_web: d501d8b946b60c8af265428ce483b0fff5ad52d1 429 | firebase_storage: 5e931af5cdef32331676c659bdd1ebcaba9dc78a 430 | FirebaseAnalytics: 45f36d9c429fc91d206283900ab75390cd05ee8a 431 | FirebaseAuth: 7d0f84873926f6648bbd1391a318dfb1a26b5e4f 432 | FirebaseAuthInterop: 0ffa57668be100582bb7643d4fcb7615496c41fc 433 | FirebaseCore: 307ea2508df730c5865334e41965bd9ea344b0e5 434 | FirebaseCoreDiagnostics: 511f4f3ed7d440bb69127e8b97c2bc8befae639e 435 | FirebaseCoreDiagnosticsInterop: e9b1b023157e3a2fc6418b5cb601e79b9af7b3a0 436 | FirebaseFirestore: 2e92e977280d63ecbf3fd58bdbfaf9145abb880f 437 | FirebaseInstanceID: ebd2ea79ee38db0cb5f5167b17a0d387e1cc7b6e 438 | FirebaseStorage: 046abe9ac4e2a1a0e47d72f536490ffa50896632 439 | Flutter: 0e3d915762c693b495b44d77113d4970485de6ec 440 | flutter_image_compress: 082f8daaf6c1b0c9fe798251c750ef0ecd98d7ae 441 | flutter_plugin_android_lifecycle: dc0b544e129eebb77a6bfb1239d4d1c673a60a35 442 | FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a 443 | GoogleAppMeasurement: dfe55efa543e899d906309eaaac6ca26d249862f 444 | GoogleDataTransport: 8e9b210c97d55fbff306cc5468ff91b9cb32dcf5 445 | GoogleDataTransportCCTSupport: ef79a4728b864946a8aafdbab770d5294faf3b5f 446 | GoogleUtilities: 547a86735c6f0ee30ad17e94df4fc21f616b71cb 447 | "gRPC-C++": 9dfe7b44821e7b3e44aacad2af29d2c21f7cde83 448 | gRPC-Core: c9aef9a261a1247e881b18059b84d597293c9947 449 | GTMSessionFetcher: 43b8b64263023d4f32caa0b40f4c8bfa3c5f36d8 450 | image_cropper: 3c16d7651730ffe85897f5a1c4e2547e6b54989a 451 | image_picker: 66aa71bc96850a90590a35d4c4a2907b0d823109 452 | leveldb-library: 55d93ee664b4007aac644a782d11da33fba316f7 453 | libwebp: 946cb3063cea9236285f7e9a8505d806d30e07f3 454 | Mantle: 2fa750afa478cd625a94230fbf1c13462f29395b 455 | nanopb: 18003b5e52dab79db540fe93fe9579f399bd1ccd 456 | path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c 457 | path_provider_macos: f760a3c5b04357c380e2fddb6f9db6f3015897e0 458 | SDWebImage: 84000f962cbfa70c07f19d2234cbfcf5d779b5dc 459 | SDWebImageWebPCoder: d0dac55073088d24b2ac1b191a71a8f8d0adac21 460 | sqflite: 4001a31ff81d210346b500c55b17f4d6c7589dd0 461 | TOCropViewController: e9da34f484aedd4e5d5a8ab230ba217cfe16c729 462 | 463 | PODFILE CHECKSUM: 1b66dae606f75376c5f2135a8290850eeb09ae83 464 | 465 | COCOAPODS: 1.9.1 466 | -------------------------------------------------------------------------------- /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 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 12 | 5FBE55EB23349021000F9E35 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5FBE55EA23349021000F9E35 /* GoogleService-Info.plist */; }; 13 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 14 | 78793A16DF1F1A82170B3DE9 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 865E1BE3AA309E4275C1DB71 /* Pods_Runner.framework */; }; 15 | 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; }; 16 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 17 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 18 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXCopyFilesBuildPhase section */ 22 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 23 | isa = PBXCopyFilesBuildPhase; 24 | buildActionMask = 2147483647; 25 | dstPath = ""; 26 | dstSubfolderSpec = 10; 27 | files = ( 28 | ); 29 | name = "Embed Frameworks"; 30 | runOnlyForDeploymentPostprocessing = 0; 31 | }; 32 | /* End PBXCopyFilesBuildPhase section */ 33 | 34 | /* Begin PBXFileReference section */ 35 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 36 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 37 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 38 | 3E039AA066500859DF7905B8 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 39 | 5FBE55EA23349021000F9E35 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; 40 | 643161AA6B9F36D1527B62B8 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 41 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 42 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 43 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 44 | 865E1BE3AA309E4275C1DB71 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 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 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 49 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 50 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 51 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 52 | C1E78B0CE9FB62F5B43A8522 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 53 | /* End PBXFileReference section */ 54 | 55 | /* Begin PBXFrameworksBuildPhase section */ 56 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 57 | isa = PBXFrameworksBuildPhase; 58 | buildActionMask = 2147483647; 59 | files = ( 60 | 78793A16DF1F1A82170B3DE9 /* Pods_Runner.framework in Frameworks */, 61 | ); 62 | runOnlyForDeploymentPostprocessing = 0; 63 | }; 64 | /* End PBXFrameworksBuildPhase section */ 65 | 66 | /* Begin PBXGroup section */ 67 | 3D844193235670F462BED8D2 /* Frameworks */ = { 68 | isa = PBXGroup; 69 | children = ( 70 | 865E1BE3AA309E4275C1DB71 /* Pods_Runner.framework */, 71 | ); 72 | name = Frameworks; 73 | sourceTree = ""; 74 | }; 75 | 68E51E5CEF045D3B87612DC0 /* Pods */ = { 76 | isa = PBXGroup; 77 | children = ( 78 | 643161AA6B9F36D1527B62B8 /* Pods-Runner.debug.xcconfig */, 79 | 3E039AA066500859DF7905B8 /* Pods-Runner.release.xcconfig */, 80 | C1E78B0CE9FB62F5B43A8522 /* Pods-Runner.profile.xcconfig */, 81 | ); 82 | path = Pods; 83 | sourceTree = ""; 84 | }; 85 | 9740EEB11CF90186004384FC /* Flutter */ = { 86 | isa = PBXGroup; 87 | children = ( 88 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 89 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 90 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 91 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 92 | ); 93 | name = Flutter; 94 | sourceTree = ""; 95 | }; 96 | 97C146E51CF9000F007C117D = { 97 | isa = PBXGroup; 98 | children = ( 99 | 9740EEB11CF90186004384FC /* Flutter */, 100 | 97C146F01CF9000F007C117D /* Runner */, 101 | 97C146EF1CF9000F007C117D /* Products */, 102 | 68E51E5CEF045D3B87612DC0 /* Pods */, 103 | 3D844193235670F462BED8D2 /* Frameworks */, 104 | ); 105 | sourceTree = ""; 106 | }; 107 | 97C146EF1CF9000F007C117D /* Products */ = { 108 | isa = PBXGroup; 109 | children = ( 110 | 97C146EE1CF9000F007C117D /* Runner.app */, 111 | ); 112 | name = Products; 113 | sourceTree = ""; 114 | }; 115 | 97C146F01CF9000F007C117D /* Runner */ = { 116 | isa = PBXGroup; 117 | children = ( 118 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 119 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 120 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 121 | 97C147021CF9000F007C117D /* Info.plist */, 122 | 5FBE55EA23349021000F9E35 /* GoogleService-Info.plist */, 123 | 97C146F11CF9000F007C117D /* Supporting Files */, 124 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 125 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 126 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 127 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 128 | ); 129 | path = Runner; 130 | sourceTree = ""; 131 | }; 132 | 97C146F11CF9000F007C117D /* Supporting Files */ = { 133 | isa = PBXGroup; 134 | children = ( 135 | ); 136 | name = "Supporting Files"; 137 | sourceTree = ""; 138 | }; 139 | /* End PBXGroup section */ 140 | 141 | /* Begin PBXNativeTarget section */ 142 | 97C146ED1CF9000F007C117D /* Runner */ = { 143 | isa = PBXNativeTarget; 144 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 145 | buildPhases = ( 146 | E95A4F4A575A15A18A85F306 /* [CP] Check Pods Manifest.lock */, 147 | 9740EEB61CF901F6004384FC /* Run Script */, 148 | 97C146EA1CF9000F007C117D /* Sources */, 149 | 97C146EB1CF9000F007C117D /* Frameworks */, 150 | 97C146EC1CF9000F007C117D /* Resources */, 151 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 152 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 153 | 890BBE6022408E9DA3C0CCC9 /* [CP] Embed Pods Frameworks */, 154 | ); 155 | buildRules = ( 156 | ); 157 | dependencies = ( 158 | ); 159 | name = Runner; 160 | productName = Runner; 161 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 162 | productType = "com.apple.product-type.application"; 163 | }; 164 | /* End PBXNativeTarget section */ 165 | 166 | /* Begin PBXProject section */ 167 | 97C146E61CF9000F007C117D /* Project object */ = { 168 | isa = PBXProject; 169 | attributes = { 170 | LastUpgradeCheck = 1020; 171 | ORGANIZATIONNAME = "The Chromium Authors"; 172 | TargetAttributes = { 173 | 97C146ED1CF9000F007C117D = { 174 | CreatedOnToolsVersion = 7.3.1; 175 | LastSwiftMigration = 0910; 176 | }; 177 | }; 178 | }; 179 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 180 | compatibilityVersion = "Xcode 3.2"; 181 | developmentRegion = en; 182 | hasScannedForEncodings = 0; 183 | knownRegions = ( 184 | en, 185 | Base, 186 | ); 187 | mainGroup = 97C146E51CF9000F007C117D; 188 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 189 | projectDirPath = ""; 190 | projectRoot = ""; 191 | targets = ( 192 | 97C146ED1CF9000F007C117D /* Runner */, 193 | ); 194 | }; 195 | /* End PBXProject section */ 196 | 197 | /* Begin PBXResourcesBuildPhase section */ 198 | 97C146EC1CF9000F007C117D /* Resources */ = { 199 | isa = PBXResourcesBuildPhase; 200 | buildActionMask = 2147483647; 201 | files = ( 202 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 203 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 204 | 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */, 205 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 206 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 207 | 5FBE55EB23349021000F9E35 /* GoogleService-Info.plist in Resources */, 208 | ); 209 | runOnlyForDeploymentPostprocessing = 0; 210 | }; 211 | /* End PBXResourcesBuildPhase section */ 212 | 213 | /* Begin PBXShellScriptBuildPhase section */ 214 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 215 | isa = PBXShellScriptBuildPhase; 216 | buildActionMask = 2147483647; 217 | files = ( 218 | ); 219 | inputPaths = ( 220 | ); 221 | name = "Thin Binary"; 222 | outputPaths = ( 223 | ); 224 | runOnlyForDeploymentPostprocessing = 0; 225 | shellPath = /bin/sh; 226 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; 227 | }; 228 | 890BBE6022408E9DA3C0CCC9 /* [CP] Embed Pods Frameworks */ = { 229 | isa = PBXShellScriptBuildPhase; 230 | buildActionMask = 2147483647; 231 | files = ( 232 | ); 233 | inputPaths = ( 234 | ); 235 | name = "[CP] Embed Pods Frameworks"; 236 | outputPaths = ( 237 | ); 238 | runOnlyForDeploymentPostprocessing = 0; 239 | shellPath = /bin/sh; 240 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; 241 | showEnvVarsInLog = 0; 242 | }; 243 | 9740EEB61CF901F6004384FC /* Run Script */ = { 244 | isa = PBXShellScriptBuildPhase; 245 | buildActionMask = 2147483647; 246 | files = ( 247 | ); 248 | inputPaths = ( 249 | ); 250 | name = "Run Script"; 251 | outputPaths = ( 252 | ); 253 | runOnlyForDeploymentPostprocessing = 0; 254 | shellPath = /bin/sh; 255 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 256 | }; 257 | E95A4F4A575A15A18A85F306 /* [CP] Check Pods Manifest.lock */ = { 258 | isa = PBXShellScriptBuildPhase; 259 | buildActionMask = 2147483647; 260 | files = ( 261 | ); 262 | inputFileListPaths = ( 263 | ); 264 | inputPaths = ( 265 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 266 | "${PODS_ROOT}/Manifest.lock", 267 | ); 268 | name = "[CP] Check Pods Manifest.lock"; 269 | outputFileListPaths = ( 270 | ); 271 | outputPaths = ( 272 | "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", 273 | ); 274 | runOnlyForDeploymentPostprocessing = 0; 275 | shellPath = /bin/sh; 276 | 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"; 277 | showEnvVarsInLog = 0; 278 | }; 279 | /* End PBXShellScriptBuildPhase section */ 280 | 281 | /* Begin PBXSourcesBuildPhase section */ 282 | 97C146EA1CF9000F007C117D /* Sources */ = { 283 | isa = PBXSourcesBuildPhase; 284 | buildActionMask = 2147483647; 285 | files = ( 286 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 287 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 288 | ); 289 | runOnlyForDeploymentPostprocessing = 0; 290 | }; 291 | /* End PBXSourcesBuildPhase section */ 292 | 293 | /* Begin PBXVariantGroup section */ 294 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 295 | isa = PBXVariantGroup; 296 | children = ( 297 | 97C146FB1CF9000F007C117D /* Base */, 298 | ); 299 | name = Main.storyboard; 300 | sourceTree = ""; 301 | }; 302 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 303 | isa = PBXVariantGroup; 304 | children = ( 305 | 97C147001CF9000F007C117D /* Base */, 306 | ); 307 | name = LaunchScreen.storyboard; 308 | sourceTree = ""; 309 | }; 310 | /* End PBXVariantGroup section */ 311 | 312 | /* Begin XCBuildConfiguration section */ 313 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 314 | isa = XCBuildConfiguration; 315 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 316 | buildSettings = { 317 | ALWAYS_SEARCH_USER_PATHS = NO; 318 | CLANG_ANALYZER_NONNULL = YES; 319 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 320 | CLANG_CXX_LIBRARY = "libc++"; 321 | CLANG_ENABLE_MODULES = YES; 322 | CLANG_ENABLE_OBJC_ARC = YES; 323 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 324 | CLANG_WARN_BOOL_CONVERSION = YES; 325 | CLANG_WARN_COMMA = YES; 326 | CLANG_WARN_CONSTANT_CONVERSION = YES; 327 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 328 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 329 | CLANG_WARN_EMPTY_BODY = YES; 330 | CLANG_WARN_ENUM_CONVERSION = YES; 331 | CLANG_WARN_INFINITE_RECURSION = YES; 332 | CLANG_WARN_INT_CONVERSION = YES; 333 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 334 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 335 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 336 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 337 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 338 | CLANG_WARN_STRICT_PROTOTYPES = YES; 339 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 340 | CLANG_WARN_UNREACHABLE_CODE = YES; 341 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 342 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 343 | COPY_PHASE_STRIP = NO; 344 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 345 | ENABLE_NS_ASSERTIONS = NO; 346 | ENABLE_STRICT_OBJC_MSGSEND = YES; 347 | GCC_C_LANGUAGE_STANDARD = gnu99; 348 | GCC_NO_COMMON_BLOCKS = YES; 349 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 350 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 351 | GCC_WARN_UNDECLARED_SELECTOR = YES; 352 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 353 | GCC_WARN_UNUSED_FUNCTION = YES; 354 | GCC_WARN_UNUSED_VARIABLE = YES; 355 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 356 | MTL_ENABLE_DEBUG_INFO = NO; 357 | SDKROOT = iphoneos; 358 | TARGETED_DEVICE_FAMILY = "1,2"; 359 | VALIDATE_PRODUCT = YES; 360 | }; 361 | name = Profile; 362 | }; 363 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 364 | isa = XCBuildConfiguration; 365 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 366 | buildSettings = { 367 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 368 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 369 | DEVELOPMENT_TEAM = ""; 370 | ENABLE_BITCODE = NO; 371 | FRAMEWORK_SEARCH_PATHS = ( 372 | "$(inherited)", 373 | "$(PROJECT_DIR)/Flutter", 374 | ); 375 | INFOPLIST_FILE = Runner/Info.plist; 376 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 377 | LIBRARY_SEARCH_PATHS = ( 378 | "$(inherited)", 379 | "$(PROJECT_DIR)/Flutter", 380 | ); 381 | PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterInstagramClone; 382 | PRODUCT_NAME = "$(TARGET_NAME)"; 383 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 384 | SWIFT_VERSION = 4.0; 385 | VERSIONING_SYSTEM = "apple-generic"; 386 | }; 387 | name = Profile; 388 | }; 389 | 97C147031CF9000F007C117D /* Debug */ = { 390 | isa = XCBuildConfiguration; 391 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 392 | buildSettings = { 393 | ALWAYS_SEARCH_USER_PATHS = NO; 394 | CLANG_ANALYZER_NONNULL = YES; 395 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 396 | CLANG_CXX_LIBRARY = "libc++"; 397 | CLANG_ENABLE_MODULES = YES; 398 | CLANG_ENABLE_OBJC_ARC = YES; 399 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 400 | CLANG_WARN_BOOL_CONVERSION = YES; 401 | CLANG_WARN_COMMA = YES; 402 | CLANG_WARN_CONSTANT_CONVERSION = YES; 403 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 404 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 405 | CLANG_WARN_EMPTY_BODY = YES; 406 | CLANG_WARN_ENUM_CONVERSION = YES; 407 | CLANG_WARN_INFINITE_RECURSION = YES; 408 | CLANG_WARN_INT_CONVERSION = YES; 409 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 410 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = 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; 421 | ENABLE_STRICT_OBJC_MSGSEND = YES; 422 | ENABLE_TESTABILITY = YES; 423 | GCC_C_LANGUAGE_STANDARD = gnu99; 424 | GCC_DYNAMIC_NO_PIC = NO; 425 | GCC_NO_COMMON_BLOCKS = YES; 426 | GCC_OPTIMIZATION_LEVEL = 0; 427 | GCC_PREPROCESSOR_DEFINITIONS = ( 428 | "DEBUG=1", 429 | "$(inherited)", 430 | ); 431 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 432 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 433 | GCC_WARN_UNDECLARED_SELECTOR = YES; 434 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 435 | GCC_WARN_UNUSED_FUNCTION = YES; 436 | GCC_WARN_UNUSED_VARIABLE = YES; 437 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 438 | MTL_ENABLE_DEBUG_INFO = YES; 439 | ONLY_ACTIVE_ARCH = YES; 440 | SDKROOT = iphoneos; 441 | TARGETED_DEVICE_FAMILY = "1,2"; 442 | }; 443 | name = Debug; 444 | }; 445 | 97C147041CF9000F007C117D /* Release */ = { 446 | isa = XCBuildConfiguration; 447 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 448 | buildSettings = { 449 | ALWAYS_SEARCH_USER_PATHS = NO; 450 | CLANG_ANALYZER_NONNULL = YES; 451 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 452 | CLANG_CXX_LIBRARY = "libc++"; 453 | CLANG_ENABLE_MODULES = YES; 454 | CLANG_ENABLE_OBJC_ARC = YES; 455 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 456 | CLANG_WARN_BOOL_CONVERSION = YES; 457 | CLANG_WARN_COMMA = YES; 458 | CLANG_WARN_CONSTANT_CONVERSION = YES; 459 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 460 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 461 | CLANG_WARN_EMPTY_BODY = YES; 462 | CLANG_WARN_ENUM_CONVERSION = YES; 463 | CLANG_WARN_INFINITE_RECURSION = YES; 464 | CLANG_WARN_INT_CONVERSION = YES; 465 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 466 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 467 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 468 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 469 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 470 | CLANG_WARN_STRICT_PROTOTYPES = YES; 471 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 472 | CLANG_WARN_UNREACHABLE_CODE = YES; 473 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 474 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 475 | COPY_PHASE_STRIP = NO; 476 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 477 | ENABLE_NS_ASSERTIONS = NO; 478 | ENABLE_STRICT_OBJC_MSGSEND = YES; 479 | GCC_C_LANGUAGE_STANDARD = gnu99; 480 | GCC_NO_COMMON_BLOCKS = YES; 481 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 482 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 483 | GCC_WARN_UNDECLARED_SELECTOR = YES; 484 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 485 | GCC_WARN_UNUSED_FUNCTION = YES; 486 | GCC_WARN_UNUSED_VARIABLE = YES; 487 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 488 | MTL_ENABLE_DEBUG_INFO = NO; 489 | SDKROOT = iphoneos; 490 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 491 | TARGETED_DEVICE_FAMILY = "1,2"; 492 | VALIDATE_PRODUCT = YES; 493 | }; 494 | name = Release; 495 | }; 496 | 97C147061CF9000F007C117D /* Debug */ = { 497 | isa = XCBuildConfiguration; 498 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 499 | buildSettings = { 500 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 501 | CLANG_ENABLE_MODULES = YES; 502 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 503 | DEVELOPMENT_TEAM = ""; 504 | ENABLE_BITCODE = NO; 505 | FRAMEWORK_SEARCH_PATHS = ( 506 | "$(inherited)", 507 | "$(PROJECT_DIR)/Flutter", 508 | ); 509 | INFOPLIST_FILE = Runner/Info.plist; 510 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 511 | LIBRARY_SEARCH_PATHS = ( 512 | "$(inherited)", 513 | "$(PROJECT_DIR)/Flutter", 514 | ); 515 | PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterInstagramClone; 516 | PRODUCT_NAME = "$(TARGET_NAME)"; 517 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 518 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 519 | SWIFT_SWIFT3_OBJC_INFERENCE = On; 520 | SWIFT_VERSION = 4.0; 521 | VERSIONING_SYSTEM = "apple-generic"; 522 | }; 523 | name = Debug; 524 | }; 525 | 97C147071CF9000F007C117D /* Release */ = { 526 | isa = XCBuildConfiguration; 527 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 528 | buildSettings = { 529 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 530 | CLANG_ENABLE_MODULES = YES; 531 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 532 | DEVELOPMENT_TEAM = ""; 533 | ENABLE_BITCODE = NO; 534 | FRAMEWORK_SEARCH_PATHS = ( 535 | "$(inherited)", 536 | "$(PROJECT_DIR)/Flutter", 537 | ); 538 | INFOPLIST_FILE = Runner/Info.plist; 539 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 540 | LIBRARY_SEARCH_PATHS = ( 541 | "$(inherited)", 542 | "$(PROJECT_DIR)/Flutter", 543 | ); 544 | PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterInstagramClone; 545 | PRODUCT_NAME = "$(TARGET_NAME)"; 546 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 547 | SWIFT_SWIFT3_OBJC_INFERENCE = On; 548 | SWIFT_VERSION = 4.0; 549 | VERSIONING_SYSTEM = "apple-generic"; 550 | }; 551 | name = Release; 552 | }; 553 | /* End XCBuildConfiguration section */ 554 | 555 | /* Begin XCConfigurationList section */ 556 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 557 | isa = XCConfigurationList; 558 | buildConfigurations = ( 559 | 97C147031CF9000F007C117D /* Debug */, 560 | 97C147041CF9000F007C117D /* Release */, 561 | 249021D3217E4FDB00AE95B9 /* Profile */, 562 | ); 563 | defaultConfigurationIsVisible = 0; 564 | defaultConfigurationName = Release; 565 | }; 566 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 567 | isa = XCConfigurationList; 568 | buildConfigurations = ( 569 | 97C147061CF9000F007C117D /* Debug */, 570 | 97C147071CF9000F007C117D /* Release */, 571 | 249021D4217E4FDB00AE95B9 /* Profile */, 572 | ); 573 | defaultConfigurationIsVisible = 0; 574 | defaultConfigurationName = Release; 575 | }; 576 | /* End XCConfigurationList section */ 577 | }; 578 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 579 | } 580 | -------------------------------------------------------------------------------- /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 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /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/MarcusNg/flutter_instagram_clone/c15b9e5ddc9c0ec66d7f3b84c0d4e9440f5abdac/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/MarcusNg/flutter_instagram_clone/c15b9e5ddc9c0ec66d7f3b84c0d4e9440f5abdac/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/MarcusNg/flutter_instagram_clone/c15b9e5ddc9c0ec66d7f3b84c0d4e9440f5abdac/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/MarcusNg/flutter_instagram_clone/c15b9e5ddc9c0ec66d7f3b84c0d4e9440f5abdac/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/MarcusNg/flutter_instagram_clone/c15b9e5ddc9c0ec66d7f3b84c0d4e9440f5abdac/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/MarcusNg/flutter_instagram_clone/c15b9e5ddc9c0ec66d7f3b84c0d4e9440f5abdac/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/MarcusNg/flutter_instagram_clone/c15b9e5ddc9c0ec66d7f3b84c0d4e9440f5abdac/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/MarcusNg/flutter_instagram_clone/c15b9e5ddc9c0ec66d7f3b84c0d4e9440f5abdac/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/MarcusNg/flutter_instagram_clone/c15b9e5ddc9c0ec66d7f3b84c0d4e9440f5abdac/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/MarcusNg/flutter_instagram_clone/c15b9e5ddc9c0ec66d7f3b84c0d4e9440f5abdac/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/MarcusNg/flutter_instagram_clone/c15b9e5ddc9c0ec66d7f3b84c0d4e9440f5abdac/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/MarcusNg/flutter_instagram_clone/c15b9e5ddc9c0ec66d7f3b84c0d4e9440f5abdac/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/MarcusNg/flutter_instagram_clone/c15b9e5ddc9c0ec66d7f3b84c0d4e9440f5abdac/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/MarcusNg/flutter_instagram_clone/c15b9e5ddc9c0ec66d7f3b84c0d4e9440f5abdac/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/MarcusNg/flutter_instagram_clone/c15b9e5ddc9c0ec66d7f3b84c0d4e9440f5abdac/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/MarcusNg/flutter_instagram_clone/c15b9e5ddc9c0ec66d7f3b84c0d4e9440f5abdac/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcusNg/flutter_instagram_clone/c15b9e5ddc9c0ec66d7f3b84c0d4e9440f5abdac/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcusNg/flutter_instagram_clone/c15b9e5ddc9c0ec66d7f3b84c0d4e9440f5abdac/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 | -------------------------------------------------------------------------------- /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 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | flutter_instagram_clone 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | NSPhotoLibraryUsageDescription 45 | Post and share photos 46 | NSCameraUsageDescription 47 | Post and share photos 48 | 49 | 50 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:firebase_auth/firebase_auth.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_instagram_clone/models/user_data.dart'; 4 | import 'package:flutter_instagram_clone/screens/feed_screen.dart'; 5 | import 'package:flutter_instagram_clone/screens/home_screen.dart'; 6 | import 'package:flutter_instagram_clone/screens/login_screen.dart'; 7 | import 'package:flutter_instagram_clone/screens/signup_screen.dart'; 8 | import 'package:provider/provider.dart'; 9 | 10 | void main() => runApp(MyApp()); 11 | 12 | class MyApp extends StatelessWidget { 13 | Widget _getScreenId() { 14 | return StreamBuilder( 15 | stream: FirebaseAuth.instance.onAuthStateChanged, 16 | builder: (BuildContext context, snapshot) { 17 | if (snapshot.hasData) { 18 | Provider.of(context).currentUserId = snapshot.data.uid; 19 | return HomeScreen(); 20 | } else { 21 | return LoginScreen(); 22 | } 23 | }, 24 | ); 25 | } 26 | 27 | // This widget is the root of your application. 28 | @override 29 | Widget build(BuildContext context) { 30 | return ChangeNotifierProvider( 31 | create: (context) => UserData(), 32 | child: MaterialApp( 33 | title: 'Instagram Clone', 34 | debugShowCheckedModeBanner: false, 35 | theme: ThemeData( 36 | primaryIconTheme: Theme.of(context).primaryIconTheme.copyWith( 37 | color: Colors.black, 38 | ), 39 | ), 40 | home: _getScreenId(), 41 | routes: { 42 | LoginScreen.id: (context) => LoginScreen(), 43 | SignupScreen.id: (context) => SignupScreen(), 44 | FeedScreen.id: (context) => FeedScreen(), 45 | }, 46 | ), 47 | ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lib/models/activity_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | 3 | class Activity { 4 | final String id; 5 | final String fromUserId; 6 | final String postId; 7 | final String postImageUrl; 8 | final String comment; 9 | final Timestamp timestamp; 10 | 11 | Activity({ 12 | this.id, 13 | this.fromUserId, 14 | this.postId, 15 | this.postImageUrl, 16 | this.comment, 17 | this.timestamp, 18 | }); 19 | 20 | factory Activity.fromDoc(DocumentSnapshot doc) { 21 | return Activity( 22 | id: doc.documentID, 23 | fromUserId: doc['fromUserId'], 24 | postId: doc['postId'], 25 | postImageUrl: doc['postImageUrl'], 26 | comment: doc['comment'], 27 | timestamp: doc['timestamp'], 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/models/comment_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | 3 | class Comment { 4 | final String id; 5 | final String content; 6 | final String authorId; 7 | final Timestamp timestamp; 8 | 9 | Comment({ 10 | this.id, 11 | this.content, 12 | this.authorId, 13 | this.timestamp, 14 | }); 15 | 16 | factory Comment.fromDoc(DocumentSnapshot doc) { 17 | return Comment( 18 | id: doc.documentID, 19 | content: doc['content'], 20 | authorId: doc['authorId'], 21 | timestamp: doc['timestamp'], 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/models/post_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | 3 | class Post { 4 | final String id; 5 | final String imageUrl; 6 | final String caption; 7 | final int likeCount; 8 | final String authorId; 9 | final Timestamp timestamp; 10 | 11 | Post({ 12 | this.id, 13 | this.imageUrl, 14 | this.caption, 15 | this.likeCount, 16 | this.authorId, 17 | this.timestamp, 18 | }); 19 | 20 | factory Post.fromDoc(DocumentSnapshot doc) { 21 | return Post( 22 | id: doc.documentID, 23 | imageUrl: doc['imageUrl'], 24 | caption: doc['caption'], 25 | likeCount: doc['likeCount'], 26 | authorId: doc['authorId'], 27 | timestamp: doc['timestamp'], 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/models/user_data.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | 3 | class UserData extends ChangeNotifier { 4 | 5 | String currentUserId; 6 | 7 | } 8 | -------------------------------------------------------------------------------- /lib/models/user_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | 3 | class User { 4 | final String id; 5 | final String name; 6 | final String profileImageUrl; 7 | final String email; 8 | final String bio; 9 | 10 | User({ 11 | this.id, 12 | this.name, 13 | this.profileImageUrl, 14 | this.email, 15 | this.bio, 16 | }); 17 | 18 | factory User.fromDoc(DocumentSnapshot doc) { 19 | return User( 20 | id: doc.documentID, 21 | name: doc['name'], 22 | profileImageUrl: doc['profileImageUrl'], 23 | email: doc['email'], 24 | bio: doc['bio'] ?? '', 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/screens/activity_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:cached_network_image/cached_network_image.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_instagram_clone/models/activity_model.dart'; 4 | import 'package:flutter_instagram_clone/models/post_model.dart'; 5 | import 'package:flutter_instagram_clone/models/user_data.dart'; 6 | import 'package:flutter_instagram_clone/models/user_model.dart'; 7 | import 'package:flutter_instagram_clone/screens/comments_screen.dart'; 8 | import 'package:flutter_instagram_clone/services/database_service.dart'; 9 | import 'package:intl/intl.dart'; 10 | import 'package:provider/provider.dart'; 11 | 12 | class ActivityScreen extends StatefulWidget { 13 | final String currentUserId; 14 | 15 | ActivityScreen({this.currentUserId}); 16 | 17 | @override 18 | _ActivityScreenState createState() => _ActivityScreenState(); 19 | } 20 | 21 | class _ActivityScreenState extends State { 22 | List _activities = []; 23 | 24 | @override 25 | void initState() { 26 | super.initState(); 27 | _setupActivities(); 28 | } 29 | 30 | _setupActivities() async { 31 | List activities = 32 | await DatabaseService.getActivities(widget.currentUserId); 33 | if (mounted) { 34 | setState(() { 35 | _activities = activities; 36 | }); 37 | } 38 | } 39 | 40 | _buildActivity(Activity activity) { 41 | return FutureBuilder( 42 | future: DatabaseService.getUserWithId(activity.fromUserId), 43 | builder: (BuildContext context, AsyncSnapshot snapshot) { 44 | if (!snapshot.hasData) { 45 | return SizedBox.shrink(); 46 | } 47 | User user = snapshot.data; 48 | return ListTile( 49 | leading: CircleAvatar( 50 | radius: 20.0, 51 | backgroundColor: Colors.grey, 52 | backgroundImage: user.profileImageUrl.isEmpty 53 | ? AssetImage('assets/images/user_placeholder.jpg') 54 | : CachedNetworkImageProvider(user.profileImageUrl), 55 | ), 56 | title: activity.comment != null 57 | ? Text('${user.name} commented: "${activity.comment}"') 58 | : Text('${user.name} liked your post'), 59 | subtitle: Text( 60 | DateFormat.yMd().add_jm().format(activity.timestamp.toDate()), 61 | ), 62 | trailing: CachedNetworkImage( 63 | imageUrl: activity.postImageUrl, 64 | height: 40.0, 65 | width: 40.0, 66 | fit: BoxFit.cover, 67 | ), 68 | onTap: () async { 69 | String currentUserId = Provider.of(context).currentUserId; 70 | Post post = await DatabaseService.getUserPost( 71 | currentUserId, 72 | activity.postId, 73 | ); 74 | Navigator.push( 75 | context, 76 | MaterialPageRoute( 77 | builder: (_) => CommentsScreen( 78 | post: post, 79 | likeCount: post.likeCount, 80 | ), 81 | ), 82 | ); 83 | }, 84 | ); 85 | }, 86 | ); 87 | } 88 | 89 | @override 90 | Widget build(BuildContext context) { 91 | return Scaffold( 92 | appBar: AppBar( 93 | backgroundColor: Colors.white, 94 | title: Text( 95 | 'Instagram', 96 | style: TextStyle( 97 | color: Colors.black, 98 | fontFamily: 'Billabong', 99 | fontSize: 35.0, 100 | ), 101 | ), 102 | ), 103 | body: RefreshIndicator( 104 | onRefresh: () => _setupActivities(), 105 | child: ListView.builder( 106 | itemCount: _activities.length, 107 | itemBuilder: (BuildContext context, int index) { 108 | Activity activity = _activities[index]; 109 | return _buildActivity(activity); 110 | }, 111 | ), 112 | ), 113 | ); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /lib/screens/comments_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:cached_network_image/cached_network_image.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_instagram_clone/models/comment_model.dart'; 4 | import 'package:flutter_instagram_clone/models/post_model.dart'; 5 | import 'package:flutter_instagram_clone/models/user_data.dart'; 6 | import 'package:flutter_instagram_clone/models/user_model.dart'; 7 | import 'package:flutter_instagram_clone/services/database_service.dart'; 8 | import 'package:flutter_instagram_clone/utilities/constants.dart'; 9 | import 'package:intl/intl.dart'; 10 | import 'package:provider/provider.dart'; 11 | 12 | class CommentsScreen extends StatefulWidget { 13 | final Post post; 14 | final int likeCount; 15 | 16 | CommentsScreen({this.post, this.likeCount}); 17 | 18 | @override 19 | _CommentsScreenState createState() => _CommentsScreenState(); 20 | } 21 | 22 | class _CommentsScreenState extends State { 23 | final TextEditingController _commentController = TextEditingController(); 24 | bool _isCommenting = false; 25 | 26 | _buildComment(Comment comment) { 27 | return FutureBuilder( 28 | future: DatabaseService.getUserWithId(comment.authorId), 29 | builder: (BuildContext context, AsyncSnapshot snapshot) { 30 | if (!snapshot.hasData) { 31 | return SizedBox.shrink(); 32 | } 33 | User author = snapshot.data; 34 | return ListTile( 35 | leading: CircleAvatar( 36 | radius: 25.0, 37 | backgroundColor: Colors.grey, 38 | backgroundImage: author.profileImageUrl.isEmpty 39 | ? AssetImage('assets/images/user_placeholder.jpg') 40 | : CachedNetworkImageProvider(author.profileImageUrl), 41 | ), 42 | title: Text(author.name), 43 | subtitle: Column( 44 | crossAxisAlignment: CrossAxisAlignment.start, 45 | children: [ 46 | Text(comment.content), 47 | SizedBox(height: 6.0), 48 | Text( 49 | DateFormat.yMd().add_jm().format(comment.timestamp.toDate()), 50 | ), 51 | ], 52 | ), 53 | ); 54 | }, 55 | ); 56 | } 57 | 58 | _buildCommentTF() { 59 | final currentUserId = Provider.of(context).currentUserId; 60 | return IconTheme( 61 | data: IconThemeData( 62 | color: _isCommenting 63 | ? Theme.of(context).accentColor 64 | : Theme.of(context).disabledColor, 65 | ), 66 | child: Container( 67 | margin: EdgeInsets.symmetric(horizontal: 8.0), 68 | child: Row( 69 | crossAxisAlignment: CrossAxisAlignment.center, 70 | children: [ 71 | SizedBox(width: 10.0), 72 | Expanded( 73 | child: TextField( 74 | controller: _commentController, 75 | textCapitalization: TextCapitalization.sentences, 76 | onChanged: (comment) { 77 | setState(() { 78 | _isCommenting = comment.length > 0; 79 | }); 80 | }, 81 | decoration: 82 | InputDecoration.collapsed(hintText: 'Write a comment...'), 83 | ), 84 | ), 85 | Container( 86 | margin: EdgeInsets.symmetric(horizontal: 4.0), 87 | child: IconButton( 88 | icon: Icon(Icons.send), 89 | onPressed: () { 90 | if (_isCommenting) { 91 | DatabaseService.commentOnPost( 92 | currentUserId: currentUserId, 93 | post: widget.post, 94 | comment: _commentController.text, 95 | ); 96 | _commentController.clear(); 97 | setState(() { 98 | _isCommenting = false; 99 | }); 100 | } 101 | }, 102 | ), 103 | ), 104 | ], 105 | ), 106 | ), 107 | ); 108 | } 109 | 110 | @override 111 | Widget build(BuildContext context) { 112 | return Scaffold( 113 | appBar: AppBar( 114 | backgroundColor: Colors.white, 115 | title: Text( 116 | 'Comments', 117 | style: TextStyle(color: Colors.black), 118 | ), 119 | ), 120 | body: Column( 121 | children: [ 122 | Padding( 123 | padding: EdgeInsets.all(12.0), 124 | child: Text( 125 | '${widget.likeCount} likes', 126 | style: TextStyle( 127 | fontSize: 20.0, 128 | fontWeight: FontWeight.w600, 129 | ), 130 | ), 131 | ), 132 | StreamBuilder( 133 | stream: commentsRef 134 | .document(widget.post.id) 135 | .collection('postComments') 136 | .orderBy('timestamp', descending: true) 137 | .snapshots(), 138 | builder: (BuildContext context, AsyncSnapshot snapshot) { 139 | if (!snapshot.hasData) { 140 | return Center( 141 | child: CircularProgressIndicator(), 142 | ); 143 | } 144 | return Expanded( 145 | child: ListView.builder( 146 | itemCount: snapshot.data.documents.length, 147 | itemBuilder: (BuildContext context, int index) { 148 | Comment comment = 149 | Comment.fromDoc(snapshot.data.documents[index]); 150 | return _buildComment(comment); 151 | }, 152 | ), 153 | ); 154 | }, 155 | ), 156 | Divider(height: 1.0), 157 | _buildCommentTF(), 158 | ], 159 | ), 160 | ); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /lib/screens/create_post_screen.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:cloud_firestore/cloud_firestore.dart'; 4 | import 'package:flutter/cupertino.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:flutter_instagram_clone/models/post_model.dart'; 7 | import 'package:flutter_instagram_clone/models/user_data.dart'; 8 | import 'package:flutter_instagram_clone/services/database_service.dart'; 9 | import 'package:flutter_instagram_clone/services/storage_service.dart'; 10 | import 'package:image_cropper/image_cropper.dart'; 11 | import 'package:image_picker/image_picker.dart'; 12 | import 'package:provider/provider.dart'; 13 | 14 | class CreatePostScreen extends StatefulWidget { 15 | @override 16 | _CreatePostScreenState createState() => _CreatePostScreenState(); 17 | } 18 | 19 | class _CreatePostScreenState extends State { 20 | File _image; 21 | TextEditingController _captionController = TextEditingController(); 22 | String _caption = ''; 23 | bool _isLoading = false; 24 | 25 | _showSelectImageDialog() { 26 | return Platform.isIOS ? _iosBottomSheet() : _androidDialog(); 27 | } 28 | 29 | _iosBottomSheet() { 30 | showCupertinoModalPopup( 31 | context: context, 32 | builder: (BuildContext context) { 33 | return CupertinoActionSheet( 34 | title: Text('Add Photo'), 35 | actions: [ 36 | CupertinoActionSheetAction( 37 | child: Text('Take Photo'), 38 | onPressed: () => _handleImage(ImageSource.camera), 39 | ), 40 | CupertinoActionSheetAction( 41 | child: Text('Choose From Gallery'), 42 | onPressed: () => _handleImage(ImageSource.gallery), 43 | ), 44 | ], 45 | cancelButton: CupertinoActionSheetAction( 46 | child: Text('Cancel'), 47 | onPressed: () => Navigator.pop(context), 48 | ), 49 | ); 50 | }, 51 | ); 52 | } 53 | 54 | _androidDialog() { 55 | showDialog( 56 | context: context, 57 | builder: (BuildContext context) { 58 | return SimpleDialog( 59 | title: Text('Add Photo'), 60 | children: [ 61 | SimpleDialogOption( 62 | child: Text('Take Photo'), 63 | onPressed: () => _handleImage(ImageSource.camera), 64 | ), 65 | SimpleDialogOption( 66 | child: Text('Choose From Gallery'), 67 | onPressed: () => _handleImage(ImageSource.gallery), 68 | ), 69 | SimpleDialogOption( 70 | child: Text( 71 | 'Cancel', 72 | style: TextStyle( 73 | color: Colors.redAccent, 74 | ), 75 | ), 76 | onPressed: () => Navigator.pop(context), 77 | ), 78 | ], 79 | ); 80 | }, 81 | ); 82 | } 83 | 84 | _handleImage(ImageSource source) async { 85 | Navigator.pop(context); 86 | File imageFile = await ImagePicker.pickImage(source: source); 87 | if (imageFile != null) { 88 | imageFile = await _cropImage(imageFile); 89 | setState(() { 90 | _image = imageFile; 91 | }); 92 | } 93 | } 94 | 95 | _cropImage(File imageFile) async { 96 | File croppedImage = await ImageCropper.cropImage( 97 | sourcePath: imageFile.path, 98 | aspectRatio: CropAspectRatio(ratioX: 1.0, ratioY: 1.0), 99 | ); 100 | return croppedImage; 101 | } 102 | 103 | _submit() async { 104 | if (!_isLoading && _image != null && _caption.isNotEmpty) { 105 | setState(() { 106 | _isLoading = true; 107 | }); 108 | 109 | // Create post 110 | String imageUrl = await StorageService.uploadPost(_image); 111 | Post post = Post( 112 | imageUrl: imageUrl, 113 | caption: _caption, 114 | likeCount: 0, 115 | authorId: Provider.of(context).currentUserId, 116 | timestamp: Timestamp.fromDate(DateTime.now()), 117 | ); 118 | DatabaseService.createPost(post); 119 | 120 | // Reset data 121 | _captionController.clear(); 122 | 123 | setState(() { 124 | _caption = ''; 125 | _image = null; 126 | _isLoading = false; 127 | }); 128 | } 129 | } 130 | 131 | @override 132 | Widget build(BuildContext context) { 133 | final height = MediaQuery.of(context).size.height; 134 | final width = MediaQuery.of(context).size.width; 135 | return Scaffold( 136 | appBar: AppBar( 137 | backgroundColor: Colors.white, 138 | title: Text( 139 | 'Create Post', 140 | style: TextStyle( 141 | color: Colors.black, 142 | ), 143 | ), 144 | actions: [ 145 | IconButton( 146 | icon: Icon(Icons.add), 147 | onPressed: _submit, 148 | ), 149 | ], 150 | ), 151 | body: GestureDetector( 152 | onTap: () => FocusScope.of(context).unfocus(), 153 | child: SingleChildScrollView( 154 | child: Container( 155 | height: height, 156 | child: Column( 157 | children: [ 158 | _isLoading 159 | ? Padding( 160 | padding: EdgeInsets.only(bottom: 10.0), 161 | child: LinearProgressIndicator( 162 | backgroundColor: Colors.blue[200], 163 | valueColor: AlwaysStoppedAnimation(Colors.blue), 164 | ), 165 | ) 166 | : SizedBox.shrink(), 167 | GestureDetector( 168 | onTap: _showSelectImageDialog, 169 | child: Container( 170 | height: width, 171 | width: width, 172 | color: Colors.grey[300], 173 | child: _image == null 174 | ? Icon( 175 | Icons.add_a_photo, 176 | color: Colors.white70, 177 | size: 150.0, 178 | ) 179 | : Image( 180 | image: FileImage(_image), 181 | fit: BoxFit.cover, 182 | ), 183 | ), 184 | ), 185 | SizedBox(height: 20.0), 186 | Padding( 187 | padding: EdgeInsets.symmetric(horizontal: 30.0), 188 | child: TextField( 189 | controller: _captionController, 190 | style: TextStyle(fontSize: 18.0), 191 | decoration: InputDecoration( 192 | labelText: 'Caption', 193 | ), 194 | onChanged: (input) => _caption = input, 195 | ), 196 | ), 197 | ], 198 | ), 199 | ), 200 | ), 201 | ), 202 | ); 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /lib/screens/edit_profile_screen.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:cached_network_image/cached_network_image.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_instagram_clone/models/user_model.dart'; 6 | import 'package:flutter_instagram_clone/services/database_service.dart'; 7 | import 'package:flutter_instagram_clone/services/storage_service.dart'; 8 | import 'package:image_picker/image_picker.dart'; 9 | 10 | class EditProfileScreen extends StatefulWidget { 11 | final User user; 12 | final Function updateUser; 13 | 14 | EditProfileScreen({this.user, this.updateUser}); 15 | 16 | @override 17 | _EditProfileScreenState createState() => _EditProfileScreenState(); 18 | } 19 | 20 | class _EditProfileScreenState extends State { 21 | final _formKey = GlobalKey(); 22 | File _profileImage; 23 | String _name = ''; 24 | String _bio = ''; 25 | bool _isLoading = false; 26 | 27 | @override 28 | void initState() { 29 | super.initState(); 30 | _name = widget.user.name; 31 | _bio = widget.user.bio; 32 | } 33 | 34 | _handleImageFromGallery() async { 35 | File imageFile = await ImagePicker.pickImage(source: ImageSource.gallery); 36 | if (imageFile != null) { 37 | setState(() { 38 | _profileImage = imageFile; 39 | }); 40 | } 41 | } 42 | 43 | _displayProfileImage() { 44 | // No new profile image 45 | if (_profileImage == null) { 46 | // No existing profile image 47 | if (widget.user.profileImageUrl.isEmpty) { 48 | // Display placeholder 49 | return AssetImage('assets/images/user_placeholder.jpg'); 50 | } else { 51 | // User profile image exists 52 | return CachedNetworkImageProvider(widget.user.profileImageUrl); 53 | } 54 | } else { 55 | // New profile image 56 | return FileImage(_profileImage); 57 | } 58 | } 59 | 60 | _submit() async { 61 | if (_formKey.currentState.validate() && !_isLoading) { 62 | _formKey.currentState.save(); 63 | 64 | setState(() { 65 | _isLoading = true; 66 | }); 67 | 68 | // Update user in database 69 | String _profileImageUrl = ''; 70 | 71 | if (_profileImage == null) { 72 | _profileImageUrl = widget.user.profileImageUrl; 73 | } else { 74 | _profileImageUrl = await StorageService.uploadUserProfileImage( 75 | widget.user.profileImageUrl, 76 | _profileImage, 77 | ); 78 | } 79 | 80 | User user = User( 81 | id: widget.user.id, 82 | name: _name, 83 | profileImageUrl: _profileImageUrl, 84 | bio: _bio, 85 | ); 86 | // Database update 87 | DatabaseService.updateUser(user); 88 | 89 | widget.updateUser(user); 90 | 91 | Navigator.pop(context); 92 | } 93 | } 94 | 95 | @override 96 | Widget build(BuildContext context) { 97 | return Scaffold( 98 | backgroundColor: Colors.white, 99 | appBar: AppBar( 100 | backgroundColor: Colors.white, 101 | title: Text( 102 | 'Edit Profile', 103 | style: TextStyle(color: Colors.black), 104 | ), 105 | ), 106 | body: GestureDetector( 107 | onTap: () => FocusScope.of(context).unfocus(), 108 | child: ListView( 109 | children: [ 110 | _isLoading 111 | ? LinearProgressIndicator( 112 | backgroundColor: Colors.blue[200], 113 | valueColor: AlwaysStoppedAnimation(Colors.blue), 114 | ) 115 | : SizedBox.shrink(), 116 | Padding( 117 | padding: EdgeInsets.all(30.0), 118 | child: Form( 119 | key: _formKey, 120 | child: Column( 121 | children: [ 122 | CircleAvatar( 123 | radius: 60.0, 124 | backgroundColor: Colors.grey, 125 | backgroundImage: _displayProfileImage(), 126 | ), 127 | FlatButton( 128 | onPressed: _handleImageFromGallery, 129 | child: Text( 130 | 'Change Profile Image', 131 | style: TextStyle( 132 | color: Theme.of(context).accentColor, 133 | fontSize: 16.0), 134 | ), 135 | ), 136 | TextFormField( 137 | initialValue: _name, 138 | style: TextStyle(fontSize: 18.0), 139 | decoration: InputDecoration( 140 | icon: Icon( 141 | Icons.person, 142 | size: 30.0, 143 | ), 144 | labelText: 'Name', 145 | ), 146 | validator: (input) => input.trim().length < 1 147 | ? 'Please enter a valid name' 148 | : null, 149 | onSaved: (input) => _name = input, 150 | ), 151 | TextFormField( 152 | initialValue: _bio, 153 | style: TextStyle(fontSize: 18.0), 154 | decoration: InputDecoration( 155 | icon: Icon( 156 | Icons.book, 157 | size: 30.0, 158 | ), 159 | labelText: 'Bio', 160 | ), 161 | validator: (input) => input.trim().length > 150 162 | ? 'Please enter a bio less than 150 characters' 163 | : null, 164 | onSaved: (input) => _bio = input, 165 | ), 166 | Container( 167 | margin: EdgeInsets.all(40.0), 168 | height: 40.0, 169 | width: 250.0, 170 | child: FlatButton( 171 | onPressed: _submit, 172 | color: Colors.blue, 173 | textColor: Colors.white, 174 | child: Text( 175 | 'Save Profile', 176 | style: TextStyle(fontSize: 18.0), 177 | ), 178 | ), 179 | ), 180 | ], 181 | ), 182 | ), 183 | ), 184 | ], 185 | ), 186 | ), 187 | ); 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /lib/screens/feed_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_instagram_clone/models/post_model.dart'; 3 | import 'package:flutter_instagram_clone/models/user_model.dart'; 4 | import 'package:flutter_instagram_clone/services/database_service.dart'; 5 | import 'package:flutter_instagram_clone/widgets/post_view.dart'; 6 | 7 | class FeedScreen extends StatefulWidget { 8 | static final String id = 'feed_screen'; 9 | final String currentUserId; 10 | 11 | FeedScreen({this.currentUserId}); 12 | 13 | @override 14 | _FeedScreenState createState() => _FeedScreenState(); 15 | } 16 | 17 | class _FeedScreenState extends State { 18 | /// SETUP FEED is unnecessary because we switched it from a FutureBuilder to a StreamBuilder 19 | 20 | // List _posts = []; 21 | 22 | // @override 23 | // void initState() { 24 | // super.initState(); 25 | // _setupFeed(); 26 | // } 27 | 28 | // _setupFeed() async { 29 | // List posts = await DatabaseService.getFeedPosts(widget.currentUserId); 30 | // setState(() { 31 | // _posts = posts; 32 | // }); 33 | // } 34 | 35 | @override 36 | Widget build(BuildContext context) { 37 | return Scaffold( 38 | appBar: AppBar( 39 | backgroundColor: Colors.white, 40 | title: Text( 41 | 'Instagram', 42 | style: TextStyle( 43 | color: Colors.black, 44 | fontFamily: 'Billabong', 45 | fontSize: 35.0, 46 | ), 47 | ), 48 | ), 49 | body: StreamBuilder( 50 | stream: DatabaseService.getFeedPosts(widget.currentUserId), 51 | builder: (BuildContext context, AsyncSnapshot snapshot) { 52 | if (!snapshot.hasData) { 53 | return SizedBox.shrink(); 54 | } 55 | final List posts = snapshot.data; 56 | return ListView.builder( 57 | itemCount: posts.length, 58 | itemBuilder: (BuildContext context, int index) { 59 | Post post = posts[index]; 60 | return FutureBuilder( 61 | future: DatabaseService.getUserWithId(post.authorId), 62 | builder: (BuildContext context, AsyncSnapshot snapshot) { 63 | if (!snapshot.hasData) { 64 | return SizedBox.shrink(); 65 | } 66 | User author = snapshot.data; 67 | return PostView( 68 | currentUserId: widget.currentUserId, 69 | post: post, 70 | author: author, 71 | ); 72 | }, 73 | ); 74 | }, 75 | ); 76 | }, 77 | ), 78 | ); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /lib/screens/home_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_instagram_clone/models/user_data.dart'; 4 | import 'package:flutter_instagram_clone/screens/activity_screen.dart'; 5 | import 'package:flutter_instagram_clone/screens/create_post_screen.dart'; 6 | import 'package:flutter_instagram_clone/screens/feed_screen.dart'; 7 | import 'package:flutter_instagram_clone/screens/profile_screen.dart'; 8 | import 'package:flutter_instagram_clone/screens/search_screen.dart'; 9 | import 'package:provider/provider.dart'; 10 | 11 | class HomeScreen extends StatefulWidget { 12 | @override 13 | _HomeScreenState createState() => _HomeScreenState(); 14 | } 15 | 16 | class _HomeScreenState extends State { 17 | int _currentTab = 0; 18 | PageController _pageController; 19 | 20 | @override 21 | void initState() { 22 | super.initState(); 23 | _pageController = PageController(); 24 | } 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | final String currentUserId = Provider.of(context).currentUserId; 29 | return Scaffold( 30 | body: PageView( 31 | controller: _pageController, 32 | children: [ 33 | FeedScreen(currentUserId: currentUserId), 34 | SearchScreen(), 35 | CreatePostScreen(), 36 | ActivityScreen(currentUserId: currentUserId), 37 | ProfileScreen( 38 | currentUserId: currentUserId, 39 | userId: currentUserId, 40 | ), 41 | ], 42 | onPageChanged: (int index) { 43 | setState(() { 44 | _currentTab = index; 45 | }); 46 | }, 47 | ), 48 | bottomNavigationBar: CupertinoTabBar( 49 | currentIndex: _currentTab, 50 | onTap: (int index) { 51 | setState(() { 52 | _currentTab = index; 53 | }); 54 | _pageController.animateToPage( 55 | index, 56 | duration: Duration(milliseconds: 200), 57 | curve: Curves.easeIn, 58 | ); 59 | }, 60 | activeColor: Colors.black, 61 | items: [ 62 | BottomNavigationBarItem( 63 | icon: Icon( 64 | Icons.home, 65 | size: 32.0, 66 | ), 67 | ), 68 | BottomNavigationBarItem( 69 | icon: Icon( 70 | Icons.search, 71 | size: 32.0, 72 | ), 73 | ), 74 | BottomNavigationBarItem( 75 | icon: Icon( 76 | Icons.photo_camera, 77 | size: 32.0, 78 | ), 79 | ), 80 | BottomNavigationBarItem( 81 | icon: Icon( 82 | Icons.notifications, 83 | size: 32.0, 84 | ), 85 | ), 86 | BottomNavigationBarItem( 87 | icon: Icon( 88 | Icons.account_circle, 89 | size: 32.0, 90 | ), 91 | ), 92 | ], 93 | ), 94 | ); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /lib/screens/login_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_instagram_clone/screens/signup_screen.dart'; 3 | import 'package:flutter_instagram_clone/services/auth_service.dart'; 4 | 5 | class LoginScreen extends StatefulWidget { 6 | static final String id = 'login_screen'; 7 | 8 | @override 9 | _LoginScreenState createState() => _LoginScreenState(); 10 | } 11 | 12 | class _LoginScreenState extends State { 13 | final _formKey = GlobalKey(); 14 | String _email, _password; 15 | 16 | _submit() { 17 | if (_formKey.currentState.validate()) { 18 | _formKey.currentState.save(); 19 | // Logging in the user w/ Firebase 20 | AuthService.login(_email, _password); 21 | } 22 | } 23 | 24 | @override 25 | Widget build(BuildContext context) { 26 | return Scaffold( 27 | body: SingleChildScrollView( 28 | child: Container( 29 | height: MediaQuery.of(context).size.height, 30 | child: Column( 31 | mainAxisAlignment: MainAxisAlignment.center, 32 | crossAxisAlignment: CrossAxisAlignment.center, 33 | children: [ 34 | Text( 35 | 'Instagram', 36 | style: TextStyle( 37 | fontFamily: 'Billabong', 38 | fontSize: 50.0, 39 | ), 40 | ), 41 | Form( 42 | key: _formKey, 43 | child: Column( 44 | mainAxisSize: MainAxisSize.min, 45 | children: [ 46 | Padding( 47 | padding: EdgeInsets.symmetric( 48 | horizontal: 30.0, 49 | vertical: 10.0, 50 | ), 51 | child: TextFormField( 52 | decoration: InputDecoration(labelText: 'Email'), 53 | validator: (input) => !input.contains('@') 54 | ? 'Please enter a valid email' 55 | : null, 56 | onSaved: (input) => _email = input, 57 | ), 58 | ), 59 | Padding( 60 | padding: EdgeInsets.symmetric( 61 | horizontal: 30.0, 62 | vertical: 10.0, 63 | ), 64 | child: TextFormField( 65 | decoration: InputDecoration(labelText: 'Password'), 66 | validator: (input) => input.length < 6 67 | ? 'Must be at least 6 characters' 68 | : null, 69 | onSaved: (input) => _password = input, 70 | obscureText: true, 71 | ), 72 | ), 73 | SizedBox(height: 20.0), 74 | Container( 75 | width: 250.0, 76 | child: FlatButton( 77 | onPressed: _submit, 78 | color: Colors.blue, 79 | padding: EdgeInsets.all(10.0), 80 | child: Text( 81 | 'Login', 82 | style: TextStyle( 83 | color: Colors.white, 84 | fontSize: 18.0, 85 | ), 86 | ), 87 | ), 88 | ), 89 | SizedBox(height: 20.0), 90 | Container( 91 | width: 250.0, 92 | child: FlatButton( 93 | onPressed: () => 94 | Navigator.pushNamed(context, SignupScreen.id), 95 | color: Colors.blue, 96 | padding: EdgeInsets.all(10.0), 97 | child: Text( 98 | 'Go to Signup', 99 | style: TextStyle( 100 | color: Colors.white, 101 | fontSize: 18.0, 102 | ), 103 | ), 104 | ), 105 | ), 106 | ], 107 | ), 108 | ), 109 | ], 110 | ), 111 | ), 112 | ), 113 | ); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /lib/screens/profile_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:cached_network_image/cached_network_image.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_instagram_clone/models/post_model.dart'; 4 | import 'package:flutter_instagram_clone/models/user_data.dart'; 5 | import 'package:flutter_instagram_clone/models/user_model.dart'; 6 | import 'package:flutter_instagram_clone/screens/edit_profile_screen.dart'; 7 | import 'package:flutter_instagram_clone/services/auth_service.dart'; 8 | import 'package:flutter_instagram_clone/services/database_service.dart'; 9 | import 'package:flutter_instagram_clone/utilities/constants.dart'; 10 | import 'package:flutter_instagram_clone/widgets/post_view.dart'; 11 | import 'package:provider/provider.dart'; 12 | 13 | import 'comments_screen.dart'; 14 | 15 | class ProfileScreen extends StatefulWidget { 16 | final String currentUserId; 17 | final String userId; 18 | 19 | ProfileScreen({this.currentUserId, this.userId}); 20 | 21 | @override 22 | _ProfileScreenState createState() => _ProfileScreenState(); 23 | } 24 | 25 | class _ProfileScreenState extends State { 26 | bool _isFollowing = false; 27 | int _followerCount = 0; 28 | int _followingCount = 0; 29 | List _posts = []; 30 | int _displayPosts = 0; // 0 - grid, 1 - column 31 | User _profileUser; 32 | 33 | @override 34 | void initState() { 35 | super.initState(); 36 | _setupIsFollowing(); 37 | _setupFollowers(); 38 | _setupFollowing(); 39 | _setupPosts(); 40 | _setupProfileUser(); 41 | } 42 | 43 | _setupIsFollowing() async { 44 | bool isFollowingUser = await DatabaseService.isFollowingUser( 45 | currentUserId: widget.currentUserId, 46 | userId: widget.userId, 47 | ); 48 | setState(() { 49 | _isFollowing = isFollowingUser; 50 | }); 51 | } 52 | 53 | _setupFollowers() async { 54 | int userFollowerCount = await DatabaseService.numFollowers(widget.userId); 55 | setState(() { 56 | _followerCount = userFollowerCount; 57 | }); 58 | } 59 | 60 | _setupFollowing() async { 61 | int userFollowingCount = await DatabaseService.numFollowing(widget.userId); 62 | setState(() { 63 | _followingCount = userFollowingCount; 64 | }); 65 | } 66 | 67 | _setupPosts() async { 68 | List posts = await DatabaseService.getUserPosts(widget.userId); 69 | setState(() { 70 | _posts = posts; 71 | }); 72 | } 73 | 74 | _setupProfileUser() async { 75 | User profileUser = await DatabaseService.getUserWithId(widget.userId); 76 | setState(() { 77 | _profileUser = profileUser; 78 | }); 79 | } 80 | 81 | _followOrUnfollow() { 82 | if (_isFollowing) { 83 | _unfollowUser(); 84 | } else { 85 | _followUser(); 86 | } 87 | } 88 | 89 | _unfollowUser() { 90 | DatabaseService.unfollowUser( 91 | currentUserId: widget.currentUserId, 92 | userId: widget.userId, 93 | ); 94 | setState(() { 95 | _isFollowing = false; 96 | _followerCount--; 97 | }); 98 | } 99 | 100 | _followUser() { 101 | DatabaseService.followUser( 102 | currentUserId: widget.currentUserId, 103 | userId: widget.userId, 104 | ); 105 | setState(() { 106 | _isFollowing = true; 107 | _followerCount++; 108 | }); 109 | } 110 | 111 | _displayButton(User user) { 112 | return user.id == Provider.of(context).currentUserId 113 | ? Container( 114 | width: 200.0, 115 | child: FlatButton( 116 | onPressed: () => Navigator.push( 117 | context, 118 | MaterialPageRoute( 119 | builder: (_) => EditProfileScreen( 120 | user: user, 121 | updateUser: (User updateUser) { 122 | // Trigger state rebuild after editing profile 123 | User updatedUser = User( 124 | id: updateUser.id, 125 | name: updateUser.name, 126 | email: user.email, 127 | profileImageUrl: updateUser.profileImageUrl, 128 | bio: updateUser.bio, 129 | ); 130 | setState(() => _profileUser = updatedUser); 131 | }, 132 | ), 133 | ), 134 | ), 135 | color: Colors.blue, 136 | textColor: Colors.white, 137 | child: Text( 138 | 'Edit Profile', 139 | style: TextStyle(fontSize: 18.0), 140 | ), 141 | ), 142 | ) 143 | : Container( 144 | width: 200.0, 145 | child: FlatButton( 146 | onPressed: _followOrUnfollow, 147 | color: _isFollowing ? Colors.grey[200] : Colors.blue, 148 | textColor: _isFollowing ? Colors.black : Colors.white, 149 | child: Text( 150 | _isFollowing ? 'Unfollow' : 'Follow', 151 | style: TextStyle(fontSize: 18.0), 152 | ), 153 | ), 154 | ); 155 | } 156 | 157 | _buildProfileInfo(User user) { 158 | return Column( 159 | children: [ 160 | Padding( 161 | padding: EdgeInsets.fromLTRB(30.0, 30.0, 30.0, 0.0), 162 | child: Row( 163 | children: [ 164 | CircleAvatar( 165 | radius: 50.0, 166 | backgroundColor: Colors.grey, 167 | backgroundImage: user.profileImageUrl.isEmpty 168 | ? AssetImage('assets/images/user_placeholder.jpg') 169 | : CachedNetworkImageProvider(user.profileImageUrl), 170 | ), 171 | Expanded( 172 | child: Column( 173 | children: [ 174 | Row( 175 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 176 | children: [ 177 | Column( 178 | children: [ 179 | Text( 180 | _posts.length.toString(), 181 | style: TextStyle( 182 | fontSize: 18.0, 183 | fontWeight: FontWeight.w600, 184 | ), 185 | ), 186 | Text( 187 | 'posts', 188 | style: TextStyle(color: Colors.black54), 189 | ), 190 | ], 191 | ), 192 | Column( 193 | children: [ 194 | Text( 195 | _followerCount.toString(), 196 | style: TextStyle( 197 | fontSize: 18.0, 198 | fontWeight: FontWeight.w600, 199 | ), 200 | ), 201 | Text( 202 | 'followers', 203 | style: TextStyle(color: Colors.black54), 204 | ), 205 | ], 206 | ), 207 | Column( 208 | children: [ 209 | Text( 210 | _followingCount.toString(), 211 | style: TextStyle( 212 | fontSize: 18.0, 213 | fontWeight: FontWeight.w600, 214 | ), 215 | ), 216 | Text( 217 | 'following', 218 | style: TextStyle(color: Colors.black54), 219 | ), 220 | ], 221 | ), 222 | ], 223 | ), 224 | _displayButton(user), 225 | ], 226 | ), 227 | ) 228 | ], 229 | ), 230 | ), 231 | Padding( 232 | padding: EdgeInsets.symmetric(horizontal: 30.0, vertical: 10.0), 233 | child: Column( 234 | crossAxisAlignment: CrossAxisAlignment.start, 235 | children: [ 236 | Text( 237 | user.name, 238 | style: TextStyle( 239 | fontSize: 18.0, 240 | fontWeight: FontWeight.bold, 241 | ), 242 | ), 243 | SizedBox(height: 5.0), 244 | Container( 245 | height: 80.0, 246 | child: Text( 247 | user.bio, 248 | style: TextStyle(fontSize: 15.0), 249 | ), 250 | ), 251 | Divider(), 252 | ], 253 | ), 254 | ), 255 | ], 256 | ); 257 | } 258 | 259 | _buildToggleButtons() { 260 | return Row( 261 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 262 | children: [ 263 | IconButton( 264 | icon: Icon(Icons.grid_on), 265 | iconSize: 30.0, 266 | color: _displayPosts == 0 267 | ? Theme.of(context).primaryColor 268 | : Colors.grey[300], 269 | onPressed: () => setState(() { 270 | _displayPosts = 0; 271 | }), 272 | ), 273 | IconButton( 274 | icon: Icon(Icons.list), 275 | iconSize: 30.0, 276 | color: _displayPosts == 1 277 | ? Theme.of(context).primaryColor 278 | : Colors.grey[300], 279 | onPressed: () => setState(() { 280 | _displayPosts = 1; 281 | }), 282 | ), 283 | ], 284 | ); 285 | } 286 | 287 | _buildTilePost(Post post) { 288 | return GridTile( 289 | child: GestureDetector( 290 | onTap: () => Navigator.push( 291 | context, 292 | MaterialPageRoute( 293 | builder: (_) => CommentsScreen( 294 | post: post, 295 | likeCount: post.likeCount, 296 | ), 297 | ), 298 | ), 299 | child: Image( 300 | image: CachedNetworkImageProvider(post.imageUrl), 301 | fit: BoxFit.cover, 302 | ), 303 | ), 304 | ); 305 | } 306 | 307 | _buildDisplayPosts() { 308 | if (_displayPosts == 0) { 309 | // Grid 310 | List tiles = []; 311 | _posts.forEach( 312 | (post) => tiles.add(_buildTilePost(post)), 313 | ); 314 | return GridView.count( 315 | crossAxisCount: 3, 316 | childAspectRatio: 1.0, 317 | mainAxisSpacing: 2.0, 318 | crossAxisSpacing: 2.0, 319 | shrinkWrap: true, 320 | physics: NeverScrollableScrollPhysics(), 321 | children: tiles, 322 | ); 323 | } else { 324 | // Column 325 | List postViews = []; 326 | _posts.forEach((post) { 327 | postViews.add( 328 | PostView( 329 | currentUserId: widget.currentUserId, 330 | post: post, 331 | author: _profileUser, 332 | ), 333 | ); 334 | }); 335 | return Column(children: postViews); 336 | } 337 | } 338 | 339 | @override 340 | Widget build(BuildContext context) { 341 | return Scaffold( 342 | backgroundColor: Colors.white, 343 | appBar: AppBar( 344 | backgroundColor: Colors.white, 345 | title: Text( 346 | 'Instagram', 347 | style: TextStyle( 348 | color: Colors.black, 349 | fontFamily: 'Billabong', 350 | fontSize: 35.0, 351 | ), 352 | ), 353 | actions: [ 354 | IconButton( 355 | icon: Icon(Icons.exit_to_app), 356 | onPressed: AuthService.logout, 357 | ), 358 | ], 359 | ), 360 | body: FutureBuilder( 361 | future: usersRef.document(widget.userId).get(), 362 | builder: (BuildContext context, AsyncSnapshot snapshot) { 363 | if (!snapshot.hasData) { 364 | return Center( 365 | child: CircularProgressIndicator(), 366 | ); 367 | } 368 | User user = User.fromDoc(snapshot.data); 369 | return ListView( 370 | children: [ 371 | _buildProfileInfo(user), 372 | _buildToggleButtons(), 373 | Divider(), 374 | _buildDisplayPosts(), 375 | ], 376 | ); 377 | }, 378 | ), 379 | ); 380 | } 381 | } 382 | -------------------------------------------------------------------------------- /lib/screens/search_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:cached_network_image/cached_network_image.dart'; 2 | import 'package:cloud_firestore/cloud_firestore.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_instagram_clone/models/user_data.dart'; 5 | import 'package:flutter_instagram_clone/models/user_model.dart'; 6 | import 'package:flutter_instagram_clone/screens/profile_screen.dart'; 7 | import 'package:flutter_instagram_clone/services/database_service.dart'; 8 | import 'package:provider/provider.dart'; 9 | 10 | class SearchScreen extends StatefulWidget { 11 | @override 12 | _SearchScreenState createState() => _SearchScreenState(); 13 | } 14 | 15 | class _SearchScreenState extends State { 16 | TextEditingController _searchController = TextEditingController(); 17 | Future _users; 18 | 19 | _buildUserTile(User user) { 20 | return ListTile( 21 | leading: CircleAvatar( 22 | radius: 20.0, 23 | backgroundImage: user.profileImageUrl.isEmpty 24 | ? AssetImage('assets/images/user_placeholder.jpg') 25 | : CachedNetworkImageProvider(user.profileImageUrl), 26 | ), 27 | title: Text(user.name), 28 | onTap: () => Navigator.push( 29 | context, 30 | MaterialPageRoute( 31 | builder: (_) => ProfileScreen( 32 | currentUserId: Provider.of(context).currentUserId, 33 | userId: user.id, 34 | ), 35 | ), 36 | ), 37 | ); 38 | } 39 | 40 | _clearSearch() { 41 | WidgetsBinding.instance 42 | .addPostFrameCallback((_) => _searchController.clear()); 43 | setState(() { 44 | _users = null; 45 | }); 46 | } 47 | 48 | @override 49 | Widget build(BuildContext context) { 50 | return Scaffold( 51 | appBar: AppBar( 52 | backgroundColor: Colors.white, 53 | title: TextField( 54 | controller: _searchController, 55 | decoration: InputDecoration( 56 | contentPadding: EdgeInsets.symmetric(vertical: 15.0), 57 | border: InputBorder.none, 58 | hintText: 'Search', 59 | prefixIcon: Icon( 60 | Icons.search, 61 | size: 30.0, 62 | ), 63 | suffixIcon: IconButton( 64 | icon: Icon( 65 | Icons.clear, 66 | ), 67 | onPressed: _clearSearch, 68 | ), 69 | filled: true, 70 | ), 71 | onSubmitted: (input) { 72 | if (input.isNotEmpty) { 73 | setState(() { 74 | _users = DatabaseService.searchUsers(input); 75 | }); 76 | } 77 | }, 78 | ), 79 | ), 80 | body: _users == null 81 | ? Center( 82 | child: Text('Search for a user'), 83 | ) 84 | : FutureBuilder( 85 | future: _users, 86 | builder: (context, snapshot) { 87 | if (!snapshot.hasData) { 88 | return Center( 89 | child: CircularProgressIndicator(), 90 | ); 91 | } 92 | if (snapshot.data.documents.length == 0) { 93 | return Center( 94 | child: Text('No users found! Please try again.'), 95 | ); 96 | } 97 | return ListView.builder( 98 | itemCount: snapshot.data.documents.length, 99 | itemBuilder: (BuildContext context, int index) { 100 | User user = User.fromDoc(snapshot.data.documents[index]); 101 | return _buildUserTile(user); 102 | }, 103 | ); 104 | }, 105 | ), 106 | ); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /lib/screens/signup_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_instagram_clone/services/auth_service.dart'; 3 | 4 | class SignupScreen extends StatefulWidget { 5 | static final String id = 'signup_screen'; 6 | 7 | @override 8 | _SignupScreenState createState() => _SignupScreenState(); 9 | } 10 | 11 | class _SignupScreenState extends State { 12 | final _formKey = GlobalKey(); 13 | String _name, _email, _password; 14 | 15 | _submit() { 16 | if (_formKey.currentState.validate()) { 17 | _formKey.currentState.save(); 18 | // Logging in the user w/ Firebase 19 | AuthService.signUpUser(context, _name, _email, _password); 20 | } 21 | } 22 | 23 | @override 24 | Widget build(BuildContext context) { 25 | return Scaffold( 26 | body: SingleChildScrollView( 27 | child: Container( 28 | height: MediaQuery.of(context).size.height, 29 | child: Column( 30 | mainAxisAlignment: MainAxisAlignment.center, 31 | crossAxisAlignment: CrossAxisAlignment.center, 32 | children: [ 33 | Text( 34 | 'Instagram', 35 | style: TextStyle( 36 | fontFamily: 'Billabong', 37 | fontSize: 50.0, 38 | ), 39 | ), 40 | Form( 41 | key: _formKey, 42 | child: Column( 43 | mainAxisSize: MainAxisSize.min, 44 | children: [ 45 | Padding( 46 | padding: EdgeInsets.symmetric( 47 | horizontal: 30.0, 48 | vertical: 10.0, 49 | ), 50 | child: TextFormField( 51 | decoration: InputDecoration(labelText: 'Name'), 52 | validator: (input) => input.trim().isEmpty 53 | ? 'Please enter a valid name' 54 | : null, 55 | onSaved: (input) => _name = input, 56 | ), 57 | ), 58 | Padding( 59 | padding: EdgeInsets.symmetric( 60 | horizontal: 30.0, 61 | vertical: 10.0, 62 | ), 63 | child: TextFormField( 64 | decoration: InputDecoration(labelText: 'Email'), 65 | validator: (input) => !input.contains('@') 66 | ? 'Please enter a valid email' 67 | : null, 68 | onSaved: (input) => _email = input, 69 | ), 70 | ), 71 | Padding( 72 | padding: EdgeInsets.symmetric( 73 | horizontal: 30.0, 74 | vertical: 10.0, 75 | ), 76 | child: TextFormField( 77 | decoration: InputDecoration(labelText: 'Password'), 78 | validator: (input) => input.length < 6 79 | ? 'Must be at least 6 characters' 80 | : null, 81 | onSaved: (input) => _password = input, 82 | obscureText: true, 83 | ), 84 | ), 85 | SizedBox(height: 20.0), 86 | Container( 87 | width: 250.0, 88 | child: FlatButton( 89 | onPressed: _submit, 90 | color: Colors.blue, 91 | padding: EdgeInsets.all(10.0), 92 | child: Text( 93 | 'Sign Up', 94 | style: TextStyle( 95 | color: Colors.white, 96 | fontSize: 18.0, 97 | ), 98 | ), 99 | ), 100 | ), 101 | SizedBox(height: 20.0), 102 | Container( 103 | width: 250.0, 104 | child: FlatButton( 105 | onPressed: () => Navigator.pop(context), 106 | color: Colors.blue, 107 | padding: EdgeInsets.all(10.0), 108 | child: Text( 109 | 'Back to Login', 110 | style: TextStyle( 111 | color: Colors.white, 112 | fontSize: 18.0, 113 | ), 114 | ), 115 | ), 116 | ), 117 | ], 118 | ), 119 | ), 120 | ], 121 | ), 122 | ), 123 | ), 124 | ); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /lib/services/auth_service.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:firebase_auth/firebase_auth.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_instagram_clone/models/user_data.dart'; 5 | import 'package:provider/provider.dart'; 6 | 7 | class AuthService { 8 | static final _auth = FirebaseAuth.instance; 9 | static final _firestore = Firestore.instance; 10 | 11 | static void signUpUser( 12 | BuildContext context, String name, String email, String password) async { 13 | try { 14 | AuthResult authResult = await _auth.createUserWithEmailAndPassword( 15 | email: email, 16 | password: password, 17 | ); 18 | FirebaseUser signedInUser = authResult.user; 19 | if (signedInUser != null) { 20 | _firestore.collection('/users').document(signedInUser.uid).setData({ 21 | 'name': name, 22 | 'email': email, 23 | 'profileImageUrl': '', 24 | }); 25 | Provider.of(context).currentUserId = signedInUser.uid; 26 | Navigator.pop(context); 27 | } 28 | } catch (e) { 29 | print(e); 30 | } 31 | } 32 | 33 | static void logout() { 34 | _auth.signOut(); 35 | } 36 | 37 | static void login(String email, String password) async { 38 | try { 39 | await _auth.signInWithEmailAndPassword(email: email, password: password); 40 | } catch (e) { 41 | print(e); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /lib/services/database_service.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:flutter_instagram_clone/models/activity_model.dart'; 3 | import 'package:flutter_instagram_clone/models/post_model.dart'; 4 | import 'package:flutter_instagram_clone/models/user_model.dart'; 5 | import 'package:flutter_instagram_clone/utilities/constants.dart'; 6 | 7 | class DatabaseService { 8 | static void updateUser(User user) { 9 | usersRef.document(user.id).updateData({ 10 | 'name': user.name, 11 | 'profileImageUrl': user.profileImageUrl, 12 | 'bio': user.bio, 13 | }); 14 | } 15 | 16 | static Future searchUsers(String name) { 17 | Future users = 18 | usersRef.where('name', isGreaterThanOrEqualTo: name).getDocuments(); 19 | return users; 20 | } 21 | 22 | static void createPost(Post post) { 23 | postsRef.document(post.authorId).collection('userPosts').add({ 24 | 'imageUrl': post.imageUrl, 25 | 'caption': post.caption, 26 | 'likeCount': post.likeCount, 27 | 'authorId': post.authorId, 28 | 'timestamp': post.timestamp, 29 | }); 30 | } 31 | 32 | static void followUser({String currentUserId, String userId}) { 33 | // Add user to current user's following collection 34 | followingRef 35 | .document(currentUserId) 36 | .collection('userFollowing') 37 | .document(userId) 38 | .setData({}); 39 | // Add current user to user's followers collection 40 | followersRef 41 | .document(userId) 42 | .collection('userFollowers') 43 | .document(currentUserId) 44 | .setData({}); 45 | } 46 | 47 | static void unfollowUser({String currentUserId, String userId}) { 48 | // Remove user from current user's following collection 49 | followingRef 50 | .document(currentUserId) 51 | .collection('userFollowing') 52 | .document(userId) 53 | .get() 54 | .then((doc) { 55 | if (doc.exists) { 56 | doc.reference.delete(); 57 | } 58 | }); 59 | // Remove current user from user's followers collection 60 | followersRef 61 | .document(userId) 62 | .collection('userFollowers') 63 | .document(currentUserId) 64 | .get() 65 | .then((doc) { 66 | if (doc.exists) { 67 | doc.reference.delete(); 68 | } 69 | }); 70 | } 71 | 72 | static Future isFollowingUser( 73 | {String currentUserId, String userId}) async { 74 | DocumentSnapshot followingDoc = await followersRef 75 | .document(userId) 76 | .collection('userFollowers') 77 | .document(currentUserId) 78 | .get(); 79 | return followingDoc.exists; 80 | } 81 | 82 | static Future numFollowing(String userId) async { 83 | QuerySnapshot followingSnapshot = await followingRef 84 | .document(userId) 85 | .collection('userFollowing') 86 | .getDocuments(); 87 | return followingSnapshot.documents.length; 88 | } 89 | 90 | static Future numFollowers(String userId) async { 91 | QuerySnapshot followersSnapshot = await followersRef 92 | .document(userId) 93 | .collection('userFollowers') 94 | .getDocuments(); 95 | return followersSnapshot.documents.length; 96 | } 97 | 98 | static Stream> getFeedPosts(String userId) { 99 | return feedsRef 100 | .document(userId) 101 | .collection('userFeed') 102 | .orderBy('timestamp', descending: true) 103 | .snapshots() 104 | .map((snapshot) => 105 | snapshot.documents.map((doc) => Post.fromDoc(doc)).toList()); 106 | } 107 | 108 | static Future> getUserPosts(String userId) async { 109 | QuerySnapshot userPostsSnapshot = await postsRef 110 | .document(userId) 111 | .collection('userPosts') 112 | .orderBy('timestamp', descending: true) 113 | .getDocuments(); 114 | List posts = 115 | userPostsSnapshot.documents.map((doc) => Post.fromDoc(doc)).toList(); 116 | return posts; 117 | } 118 | 119 | static Future getUserWithId(String userId) async { 120 | DocumentSnapshot userDocSnapshot = await usersRef.document(userId).get(); 121 | if (userDocSnapshot.exists) { 122 | return User.fromDoc(userDocSnapshot); 123 | } 124 | return User(); 125 | } 126 | 127 | static void likePost({String currentUserId, Post post}) { 128 | DocumentReference postRef = postsRef 129 | .document(post.authorId) 130 | .collection('userPosts') 131 | .document(post.id); 132 | postRef.get().then((doc) { 133 | int likeCount = doc.data['likeCount']; 134 | postRef.updateData({'likeCount': likeCount + 1}); 135 | likesRef 136 | .document(post.id) 137 | .collection('postLikes') 138 | .document(currentUserId) 139 | .setData({}); 140 | addActivityItem(currentUserId: currentUserId, post: post, comment: null); 141 | }); 142 | } 143 | 144 | static void unlikePost({String currentUserId, Post post}) { 145 | DocumentReference postRef = postsRef 146 | .document(post.authorId) 147 | .collection('userPosts') 148 | .document(post.id); 149 | postRef.get().then((doc) { 150 | int likeCount = doc.data['likeCount']; 151 | postRef.updateData({'likeCount': likeCount - 1}); 152 | likesRef 153 | .document(post.id) 154 | .collection('postLikes') 155 | .document(currentUserId) 156 | .get() 157 | .then((doc) { 158 | if (doc.exists) { 159 | doc.reference.delete(); 160 | } 161 | }); 162 | }); 163 | } 164 | 165 | static Future didLikePost({String currentUserId, Post post}) async { 166 | DocumentSnapshot userDoc = await likesRef 167 | .document(post.id) 168 | .collection('postLikes') 169 | .document(currentUserId) 170 | .get(); 171 | return userDoc.exists; 172 | } 173 | 174 | static void commentOnPost({String currentUserId, Post post, String comment}) { 175 | commentsRef.document(post.id).collection('postComments').add({ 176 | 'content': comment, 177 | 'authorId': currentUserId, 178 | 'timestamp': Timestamp.fromDate(DateTime.now()), 179 | }); 180 | addActivityItem(currentUserId: currentUserId, post: post, comment: comment); 181 | } 182 | 183 | static void addActivityItem( 184 | {String currentUserId, Post post, String comment}) { 185 | if (currentUserId != post.authorId) { 186 | activitiesRef.document(post.authorId).collection('userActivities').add({ 187 | 'fromUserId': currentUserId, 188 | 'postId': post.id, 189 | 'postImageUrl': post.imageUrl, 190 | 'comment': comment, 191 | 'timestamp': Timestamp.fromDate(DateTime.now()), 192 | }); 193 | } 194 | } 195 | 196 | static Future> getActivities(String userId) async { 197 | QuerySnapshot userActivitiesSnapshot = await activitiesRef 198 | .document(userId) 199 | .collection('userActivities') 200 | .orderBy('timestamp', descending: true) 201 | .getDocuments(); 202 | List activity = userActivitiesSnapshot.documents 203 | .map((doc) => Activity.fromDoc(doc)) 204 | .toList(); 205 | return activity; 206 | } 207 | 208 | static Future getUserPost(String userId, String postId) async { 209 | DocumentSnapshot postDocSnapshot = await postsRef 210 | .document(userId) 211 | .collection('userPosts') 212 | .document(postId) 213 | .get(); 214 | return Post.fromDoc(postDocSnapshot); 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /lib/services/storage_service.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:firebase_storage/firebase_storage.dart'; 4 | import 'package:flutter_image_compress/flutter_image_compress.dart'; 5 | import 'package:flutter_instagram_clone/utilities/constants.dart'; 6 | import 'package:path_provider/path_provider.dart'; 7 | import 'package:uuid/uuid.dart'; 8 | 9 | class StorageService { 10 | static Future uploadUserProfileImage( 11 | String url, File imageFile) async { 12 | String photoId = Uuid().v4(); 13 | File image = await compressImage(photoId, imageFile); 14 | 15 | if (url.isNotEmpty) { 16 | // Updating user profile image 17 | RegExp exp = RegExp(r'userProfile_(.*).jpg'); 18 | photoId = exp.firstMatch(url)[1]; 19 | } 20 | 21 | StorageUploadTask uploadTask = storageRef 22 | .child('images/users/userProfile_$photoId.jpg') 23 | .putFile(image); 24 | StorageTaskSnapshot storageSnap = await uploadTask.onComplete; 25 | String downloadUrl = await storageSnap.ref.getDownloadURL(); 26 | return downloadUrl; 27 | } 28 | 29 | static Future compressImage(String photoId, File image) async { 30 | final tempDir = await getTemporaryDirectory(); 31 | final path = tempDir.path; 32 | File compressedImageFile = await FlutterImageCompress.compressAndGetFile( 33 | image.absolute.path, 34 | '$path/img_$photoId.jpg', 35 | quality: 70, 36 | ); 37 | return compressedImageFile; 38 | } 39 | 40 | static Future uploadPost(File imageFile) async { 41 | String photoId = Uuid().v4(); 42 | File image = await compressImage(photoId, imageFile); 43 | StorageUploadTask uploadTask = storageRef 44 | .child('images/posts/post_$photoId.jpg') 45 | .putFile(image); 46 | StorageTaskSnapshot storageSnap = await uploadTask.onComplete; 47 | String downloadUrl = await storageSnap.ref.getDownloadURL(); 48 | return downloadUrl; 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /lib/utilities/constants.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:firebase_storage/firebase_storage.dart'; 3 | 4 | final _firestore = Firestore.instance; 5 | final storageRef = FirebaseStorage.instance.ref(); 6 | final usersRef = _firestore.collection('users'); 7 | final postsRef = _firestore.collection('posts'); 8 | final followersRef = _firestore.collection('followers'); 9 | final followingRef = _firestore.collection('following'); 10 | final feedsRef = _firestore.collection('feeds'); 11 | final likesRef = _firestore.collection('likes'); 12 | final commentsRef = _firestore.collection('comments'); 13 | final activitiesRef = _firestore.collection('activities'); -------------------------------------------------------------------------------- /lib/widgets/post_view.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:animator/animator.dart'; 4 | import 'package:cached_network_image/cached_network_image.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:flutter_instagram_clone/models/post_model.dart'; 7 | import 'package:flutter_instagram_clone/models/user_model.dart'; 8 | import 'package:flutter_instagram_clone/screens/comments_screen.dart'; 9 | import 'package:flutter_instagram_clone/screens/profile_screen.dart'; 10 | import 'package:flutter_instagram_clone/services/database_service.dart'; 11 | 12 | class PostView extends StatefulWidget { 13 | final String currentUserId; 14 | final Post post; 15 | final User author; 16 | 17 | PostView({this.currentUserId, this.post, this.author}); 18 | 19 | @override 20 | _PostViewState createState() => _PostViewState(); 21 | } 22 | 23 | class _PostViewState extends State { 24 | int _likeCount = 0; 25 | bool _isLiked = false; 26 | bool _heartAnim = false; 27 | 28 | @override 29 | void initState() { 30 | super.initState(); 31 | _likeCount = widget.post.likeCount; 32 | _initPostLiked(); 33 | } 34 | 35 | @override 36 | void didUpdateWidget(PostView oldWidget) { 37 | super.didUpdateWidget(oldWidget); 38 | if (oldWidget.post.likeCount != widget.post.likeCount) { 39 | _likeCount = widget.post.likeCount; 40 | } 41 | } 42 | 43 | _initPostLiked() async { 44 | bool isLiked = await DatabaseService.didLikePost( 45 | currentUserId: widget.currentUserId, 46 | post: widget.post, 47 | ); 48 | if (mounted) { 49 | setState(() { 50 | _isLiked = isLiked; 51 | }); 52 | } 53 | } 54 | 55 | _likePost() { 56 | if (_isLiked) { 57 | // Unlike Post 58 | DatabaseService.unlikePost( 59 | currentUserId: widget.currentUserId, post: widget.post); 60 | setState(() { 61 | _isLiked = false; 62 | _likeCount = _likeCount - 1; 63 | }); 64 | } else { 65 | // Like Post 66 | DatabaseService.likePost( 67 | currentUserId: widget.currentUserId, post: widget.post); 68 | setState(() { 69 | _heartAnim = true; 70 | _isLiked = true; 71 | _likeCount = _likeCount + 1; 72 | }); 73 | Timer(Duration(milliseconds: 350), () { 74 | setState(() { 75 | _heartAnim = false; 76 | }); 77 | }); 78 | } 79 | } 80 | 81 | @override 82 | Widget build(BuildContext context) { 83 | return Column( 84 | children: [ 85 | GestureDetector( 86 | onTap: () => Navigator.push( 87 | context, 88 | MaterialPageRoute( 89 | builder: (_) => ProfileScreen( 90 | currentUserId: widget.currentUserId, 91 | userId: widget.post.authorId, 92 | ), 93 | ), 94 | ), 95 | child: Container( 96 | padding: EdgeInsets.symmetric( 97 | horizontal: 16.0, 98 | vertical: 10.0, 99 | ), 100 | child: Row( 101 | children: [ 102 | CircleAvatar( 103 | radius: 25.0, 104 | backgroundColor: Colors.grey, 105 | backgroundImage: widget.author.profileImageUrl.isEmpty 106 | ? AssetImage('assets/images/user_placeholder.jpg') 107 | : CachedNetworkImageProvider( 108 | widget.author.profileImageUrl), 109 | ), 110 | SizedBox(width: 8.0), 111 | Text( 112 | widget.author.name, 113 | style: TextStyle( 114 | fontSize: 18.0, 115 | fontWeight: FontWeight.w600, 116 | ), 117 | ) 118 | ], 119 | ), 120 | ), 121 | ), 122 | GestureDetector( 123 | onDoubleTap: _likePost, 124 | child: Stack( 125 | alignment: Alignment.center, 126 | children: [ 127 | Container( 128 | height: MediaQuery.of(context).size.width, 129 | decoration: BoxDecoration( 130 | image: DecorationImage( 131 | image: CachedNetworkImageProvider(widget.post.imageUrl), 132 | fit: BoxFit.cover, 133 | ), 134 | ), 135 | ), 136 | _heartAnim 137 | ? Animator( 138 | duration: Duration(milliseconds: 300), 139 | tween: Tween(begin: 0.5, end: 1.4), 140 | curve: Curves.elasticOut, 141 | builder: (anim) => Transform.scale( 142 | scale: anim.value, 143 | child: Icon( 144 | Icons.favorite, 145 | size: 100.0, 146 | color: Colors.red[400], 147 | ), 148 | ), 149 | ) 150 | : SizedBox.shrink(), 151 | ], 152 | ), 153 | ), 154 | Padding( 155 | padding: EdgeInsets.symmetric(horizontal: 8.0), 156 | child: Column( 157 | crossAxisAlignment: CrossAxisAlignment.start, 158 | children: [ 159 | Row( 160 | children: [ 161 | IconButton( 162 | icon: _isLiked 163 | ? Icon( 164 | Icons.favorite, 165 | color: Colors.red, 166 | ) 167 | : Icon(Icons.favorite_border), 168 | iconSize: 30.0, 169 | onPressed: _likePost, 170 | ), 171 | IconButton( 172 | icon: Icon(Icons.comment), 173 | iconSize: 30.0, 174 | onPressed: () => Navigator.push( 175 | context, 176 | MaterialPageRoute( 177 | builder: (_) => CommentsScreen( 178 | post: widget.post, 179 | likeCount: _likeCount, 180 | ), 181 | ), 182 | ), 183 | ), 184 | ], 185 | ), 186 | Padding( 187 | padding: EdgeInsets.symmetric(horizontal: 12.0), 188 | child: Text( 189 | '${_likeCount.toString()} likes', 190 | style: TextStyle( 191 | fontSize: 16.0, 192 | fontWeight: FontWeight.bold, 193 | ), 194 | ), 195 | ), 196 | SizedBox(height: 4.0), 197 | Row( 198 | children: [ 199 | Container( 200 | margin: EdgeInsets.only( 201 | left: 12.0, 202 | right: 6.0, 203 | ), 204 | child: Text( 205 | widget.author.name, 206 | style: TextStyle( 207 | fontSize: 16.0, 208 | fontWeight: FontWeight.bold, 209 | ), 210 | ), 211 | ), 212 | Expanded( 213 | child: Text( 214 | widget.post.caption, 215 | style: TextStyle( 216 | fontSize: 16.0, 217 | ), 218 | overflow: TextOverflow.ellipsis, 219 | ), 220 | ), 221 | ], 222 | ), 223 | SizedBox(height: 12.0), 224 | ], 225 | ), 226 | ), 227 | ], 228 | ); 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | animator: 5 | dependency: "direct main" 6 | description: 7 | name: animator 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "1.0.0+5" 11 | archive: 12 | dependency: transitive 13 | description: 14 | name: archive 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "2.0.13" 18 | args: 19 | dependency: transitive 20 | description: 21 | name: args 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "1.6.0" 25 | async: 26 | dependency: transitive 27 | description: 28 | name: async 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "2.4.1" 32 | boolean_selector: 33 | dependency: transitive 34 | description: 35 | name: boolean_selector 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "2.0.0" 39 | cached_network_image: 40 | dependency: "direct main" 41 | description: 42 | name: cached_network_image 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "2.2.0+1" 46 | charcode: 47 | dependency: transitive 48 | description: 49 | name: charcode 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "1.1.3" 53 | clock: 54 | dependency: transitive 55 | description: 56 | name: clock 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "1.0.1" 60 | cloud_firestore: 61 | dependency: "direct main" 62 | description: 63 | name: cloud_firestore 64 | url: "https://pub.dartlang.org" 65 | source: hosted 66 | version: "0.12.11" 67 | collection: 68 | dependency: transitive 69 | description: 70 | name: collection 71 | url: "https://pub.dartlang.org" 72 | source: hosted 73 | version: "1.14.12" 74 | convert: 75 | dependency: transitive 76 | description: 77 | name: convert 78 | url: "https://pub.dartlang.org" 79 | source: hosted 80 | version: "2.1.1" 81 | crypto: 82 | dependency: transitive 83 | description: 84 | name: crypto 85 | url: "https://pub.dartlang.org" 86 | source: hosted 87 | version: "2.1.4" 88 | cupertino_icons: 89 | dependency: "direct main" 90 | description: 91 | name: cupertino_icons 92 | url: "https://pub.dartlang.org" 93 | source: hosted 94 | version: "0.1.3" 95 | file: 96 | dependency: transitive 97 | description: 98 | name: file 99 | url: "https://pub.dartlang.org" 100 | source: hosted 101 | version: "5.1.0" 102 | firebase: 103 | dependency: transitive 104 | description: 105 | name: firebase 106 | url: "https://pub.dartlang.org" 107 | source: hosted 108 | version: "7.3.0" 109 | firebase_analytics: 110 | dependency: "direct main" 111 | description: 112 | name: firebase_analytics 113 | url: "https://pub.dartlang.org" 114 | source: hosted 115 | version: "5.0.11" 116 | firebase_auth: 117 | dependency: "direct main" 118 | description: 119 | name: firebase_auth 120 | url: "https://pub.dartlang.org" 121 | source: hosted 122 | version: "0.14.0+9" 123 | firebase_core: 124 | dependency: "direct main" 125 | description: 126 | name: firebase_core 127 | url: "https://pub.dartlang.org" 128 | source: hosted 129 | version: "0.4.4+3" 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: "1.0.4" 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: "0.1.1+2" 144 | firebase_storage: 145 | dependency: "direct main" 146 | description: 147 | name: firebase_storage 148 | url: "https://pub.dartlang.org" 149 | source: hosted 150 | version: "3.1.5" 151 | flutter: 152 | dependency: "direct main" 153 | description: flutter 154 | source: sdk 155 | version: "0.0.0" 156 | flutter_cache_manager: 157 | dependency: transitive 158 | description: 159 | name: flutter_cache_manager 160 | url: "https://pub.dartlang.org" 161 | source: hosted 162 | version: "1.2.2" 163 | flutter_image_compress: 164 | dependency: "direct main" 165 | description: 166 | name: flutter_image_compress 167 | url: "https://pub.dartlang.org" 168 | source: hosted 169 | version: "0.6.7" 170 | flutter_plugin_android_lifecycle: 171 | dependency: transitive 172 | description: 173 | name: flutter_plugin_android_lifecycle 174 | url: "https://pub.dartlang.org" 175 | source: hosted 176 | version: "1.0.7" 177 | flutter_test: 178 | dependency: "direct dev" 179 | description: flutter 180 | source: sdk 181 | version: "0.0.0" 182 | flutter_web_plugins: 183 | dependency: transitive 184 | description: flutter 185 | source: sdk 186 | version: "0.0.0" 187 | http: 188 | dependency: transitive 189 | description: 190 | name: http 191 | url: "https://pub.dartlang.org" 192 | source: hosted 193 | version: "0.12.1" 194 | http_parser: 195 | dependency: transitive 196 | description: 197 | name: http_parser 198 | url: "https://pub.dartlang.org" 199 | source: hosted 200 | version: "3.1.4" 201 | image: 202 | dependency: transitive 203 | description: 204 | name: image 205 | url: "https://pub.dartlang.org" 206 | source: hosted 207 | version: "2.1.12" 208 | image_cropper: 209 | dependency: "direct main" 210 | description: 211 | name: image_cropper 212 | url: "https://pub.dartlang.org" 213 | source: hosted 214 | version: "1.2.2" 215 | image_picker: 216 | dependency: "direct main" 217 | description: 218 | name: image_picker 219 | url: "https://pub.dartlang.org" 220 | source: hosted 221 | version: "0.6.6+1" 222 | image_picker_platform_interface: 223 | dependency: transitive 224 | description: 225 | name: image_picker_platform_interface 226 | url: "https://pub.dartlang.org" 227 | source: hosted 228 | version: "1.0.0" 229 | intl: 230 | dependency: "direct main" 231 | description: 232 | name: intl 233 | url: "https://pub.dartlang.org" 234 | source: hosted 235 | version: "0.16.1" 236 | js: 237 | dependency: transitive 238 | description: 239 | name: js 240 | url: "https://pub.dartlang.org" 241 | source: hosted 242 | version: "0.6.1+1" 243 | matcher: 244 | dependency: transitive 245 | description: 246 | name: matcher 247 | url: "https://pub.dartlang.org" 248 | source: hosted 249 | version: "0.12.6" 250 | meta: 251 | dependency: transitive 252 | description: 253 | name: meta 254 | url: "https://pub.dartlang.org" 255 | source: hosted 256 | version: "1.1.8" 257 | path: 258 | dependency: transitive 259 | description: 260 | name: path 261 | url: "https://pub.dartlang.org" 262 | source: hosted 263 | version: "1.6.4" 264 | path_provider: 265 | dependency: "direct main" 266 | description: 267 | name: path_provider 268 | url: "https://pub.dartlang.org" 269 | source: hosted 270 | version: "1.6.8" 271 | path_provider_macos: 272 | dependency: transitive 273 | description: 274 | name: path_provider_macos 275 | url: "https://pub.dartlang.org" 276 | source: hosted 277 | version: "0.0.4+2" 278 | path_provider_platform_interface: 279 | dependency: transitive 280 | description: 281 | name: path_provider_platform_interface 282 | url: "https://pub.dartlang.org" 283 | source: hosted 284 | version: "1.0.2" 285 | pedantic: 286 | dependency: transitive 287 | description: 288 | name: pedantic 289 | url: "https://pub.dartlang.org" 290 | source: hosted 291 | version: "1.9.0" 292 | petitparser: 293 | dependency: transitive 294 | description: 295 | name: petitparser 296 | url: "https://pub.dartlang.org" 297 | source: hosted 298 | version: "2.4.0" 299 | platform: 300 | dependency: transitive 301 | description: 302 | name: platform 303 | url: "https://pub.dartlang.org" 304 | source: hosted 305 | version: "2.2.1" 306 | plugin_platform_interface: 307 | dependency: transitive 308 | description: 309 | name: plugin_platform_interface 310 | url: "https://pub.dartlang.org" 311 | source: hosted 312 | version: "1.0.2" 313 | provider: 314 | dependency: "direct main" 315 | description: 316 | name: provider 317 | url: "https://pub.dartlang.org" 318 | source: hosted 319 | version: "3.2.0" 320 | quiver: 321 | dependency: transitive 322 | description: 323 | name: quiver 324 | url: "https://pub.dartlang.org" 325 | source: hosted 326 | version: "2.1.3" 327 | rxdart: 328 | dependency: transitive 329 | description: 330 | name: rxdart 331 | url: "https://pub.dartlang.org" 332 | source: hosted 333 | version: "0.24.0" 334 | sky_engine: 335 | dependency: transitive 336 | description: flutter 337 | source: sdk 338 | version: "0.0.99" 339 | source_span: 340 | dependency: transitive 341 | description: 342 | name: source_span 343 | url: "https://pub.dartlang.org" 344 | source: hosted 345 | version: "1.7.0" 346 | sqflite: 347 | dependency: transitive 348 | description: 349 | name: sqflite 350 | url: "https://pub.dartlang.org" 351 | source: hosted 352 | version: "1.3.0+1" 353 | sqflite_common: 354 | dependency: transitive 355 | description: 356 | name: sqflite_common 357 | url: "https://pub.dartlang.org" 358 | source: hosted 359 | version: "1.0.1" 360 | stack_trace: 361 | dependency: transitive 362 | description: 363 | name: stack_trace 364 | url: "https://pub.dartlang.org" 365 | source: hosted 366 | version: "1.9.3" 367 | states_rebuilder: 368 | dependency: transitive 369 | description: 370 | name: states_rebuilder 371 | url: "https://pub.dartlang.org" 372 | source: hosted 373 | version: "1.15.0" 374 | stream_channel: 375 | dependency: transitive 376 | description: 377 | name: stream_channel 378 | url: "https://pub.dartlang.org" 379 | source: hosted 380 | version: "2.0.0" 381 | string_scanner: 382 | dependency: transitive 383 | description: 384 | name: string_scanner 385 | url: "https://pub.dartlang.org" 386 | source: hosted 387 | version: "1.0.5" 388 | synchronized: 389 | dependency: transitive 390 | description: 391 | name: synchronized 392 | url: "https://pub.dartlang.org" 393 | source: hosted 394 | version: "2.2.0" 395 | term_glyph: 396 | dependency: transitive 397 | description: 398 | name: term_glyph 399 | url: "https://pub.dartlang.org" 400 | source: hosted 401 | version: "1.1.0" 402 | test_api: 403 | dependency: transitive 404 | description: 405 | name: test_api 406 | url: "https://pub.dartlang.org" 407 | source: hosted 408 | version: "0.2.15" 409 | typed_data: 410 | dependency: transitive 411 | description: 412 | name: typed_data 413 | url: "https://pub.dartlang.org" 414 | source: hosted 415 | version: "1.1.6" 416 | uuid: 417 | dependency: "direct main" 418 | description: 419 | name: uuid 420 | url: "https://pub.dartlang.org" 421 | source: hosted 422 | version: "2.0.4" 423 | vector_math: 424 | dependency: transitive 425 | description: 426 | name: vector_math 427 | url: "https://pub.dartlang.org" 428 | source: hosted 429 | version: "2.0.8" 430 | xml: 431 | dependency: transitive 432 | description: 433 | name: xml 434 | url: "https://pub.dartlang.org" 435 | source: hosted 436 | version: "3.6.1" 437 | sdks: 438 | dart: ">=2.7.0 <3.0.0" 439 | flutter: ">=1.12.13+hotfix.5 <2.0.0" 440 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_instagram_clone 2 | description: A new Flutter project. 3 | 4 | # The following defines the version and build number for your application. 5 | # A version number is three numbers separated by dots, like 1.2.43 6 | # followed by an optional build number separated by a +. 7 | # Both the version and the builder number may be overridden in flutter 8 | # build by specifying --build-name and --build-number, respectively. 9 | # In Android, build-name is used as versionName while build-number used as versionCode. 10 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 11 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 12 | # Read more about iOS versioning at 13 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 14 | version: 1.0.0+1 15 | 16 | environment: 17 | sdk: ">=2.1.0 <3.0.0" 18 | 19 | dependencies: 20 | flutter: 21 | sdk: flutter 22 | 23 | # The following adds the Cupertino Icons font to your application. 24 | # Use with the CupertinoIcons class for iOS style icons. 25 | cupertino_icons: ^0.1.2 26 | firebase_core: ^0.4.0+9 27 | firebase_analytics: ^5.0.2 28 | firebase_auth: ^0.14.0+5 29 | cloud_firestore: ^0.12.9+4 30 | image_picker: ^0.6.1+4 31 | cached_network_image: ^2.0.0-rc 32 | firebase_storage: ^3.0.6 33 | flutter_image_compress: ^0.6.3 34 | path_provider: ^1.2.0 35 | uuid: ^2.0.2 36 | provider: ^3.2.0 37 | image_cropper: ^1.1.0 38 | animator: ^1.0.0+3 39 | intl: ^0.16.0 40 | 41 | dev_dependencies: 42 | flutter_test: 43 | sdk: flutter 44 | 45 | 46 | # For information on the generic Dart part of this file, see the 47 | # following page: https://dart.dev/tools/pub/pubspec 48 | 49 | # The following section is specific to Flutter. 50 | flutter: 51 | 52 | # The following line ensures that the Material Icons font is 53 | # included with your application, so that you can use the icons in 54 | # the material Icons class. 55 | uses-material-design: true 56 | 57 | # To add assets to your application, add an assets section, like this: 58 | assets: 59 | - assets/images/ 60 | # - images/a_dot_ham.jpeg 61 | 62 | # An image asset can refer to one or more resolution-specific "variants", see 63 | # https://flutter.dev/assets-and-images/#resolution-aware. 64 | 65 | # For details regarding adding assets from package dependencies, see 66 | # https://flutter.dev/assets-and-images/#from-packages 67 | 68 | # To add custom fonts to your application, add a fonts section here, 69 | # in this "flutter" section. Each entry in this list should have a 70 | # "family" key with the font family name, and a "fonts" key with a 71 | # list giving the asset and other descriptors for the font. For 72 | # example: 73 | fonts: 74 | - family: Billabong 75 | fonts: 76 | - asset: assets/fonts/Billabong.ttf 77 | # - asset: fonts/Schyler-Italic.ttf 78 | # style: italic 79 | # - family: Trajan Pro 80 | # fonts: 81 | # - asset: fonts/TrajanPro.ttf 82 | # - asset: fonts/TrajanPro_Bold.ttf 83 | # weight: 700 84 | # 85 | # For details regarding fonts from package dependencies, 86 | # see https://flutter.dev/custom-fonts/#from-packages 87 | -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:flutter_instagram_clone/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(MyApp()); 17 | 18 | // Verify that our counter starts at 0. 19 | expect(find.text('0'), findsOneWidget); 20 | expect(find.text('1'), findsNothing); 21 | 22 | // Tap the '+' icon and trigger a frame. 23 | await tester.tap(find.byIcon(Icons.add)); 24 | await tester.pump(); 25 | 26 | // Verify that our counter has incremented. 27 | expect(find.text('0'), findsNothing); 28 | expect(find.text('1'), findsOneWidget); 29 | }); 30 | } 31 | --------------------------------------------------------------------------------