├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ └── feature_request.md └── workflows │ └── dart.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── LICENSE.txt ├── README.md ├── _config.yml ├── chat_sdk.iml ├── cloudFunction └── index.js ├── example └── flutter_chat.dart ├── images ├── icon_logout.png ├── icon_music.png ├── icon_nature.png └── img_not_available.jpeg ├── lib ├── Models │ └── UserModel.dart ├── chatDB.dart ├── chatData.dart ├── chatWidget.dart ├── constants.dart ├── hexColor.dart └── screens │ ├── chat.dart │ ├── dashboard_screen.dart │ ├── login_screen.dart │ └── zoomImage.dart ├── pubspec.lock └── pubspec.yaml /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: #[ankesh-kumar] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: ["https://paypal.me/ankeshkumar01"] 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/dart.yml: -------------------------------------------------------------------------------- 1 | name: Dart CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | container: 11 | image: google/dart:latest 12 | 13 | steps: 14 | - uses: actions/checkout@v1 15 | - name: Install dependencies 16 | run: pub get 17 | - name: Run tests 18 | run: pub run test 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .flutter-plugins 2 | .flutter-plugins-dependencies 3 | .packages -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## version 2.0.2 dependencies update and code improvement 2 | ## version 2.0.1 flutter web demo added 3 | ## version 2.0.0 Flutter Web Support added 4 | ## version 1.1.2 dependencies updated 5 | ## version 1.1.1 dependencies updated 6 | ## version 1.1.0 Chat with added friends only (privacy) 7 | ## version 1.0.0 dependencies updated 8 | ## version 0.1.4 user online/offline status 9 | ## version 0.1.3 widget seprated 10 | ## version 0.1.2 example added 11 | ## version 0.1.1 add screen widgets 12 | ## version 0.1.0 flutter Chat android and iOS 13 | 14 | 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Ankesh Kumar 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Ankesh Kumar 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # flutter_chat 2 | A Chat Helper for create chat application in Flutter using Firebase as backend services. 3 | 4 | 5 | # Checkout Android app Demo 6 | [](https://play.google.com/store/apps/details?id=com.smartmobilevilla.just_chat) 7 | 8 | # Checkout Web Demo 9 | [Just Chat Web Demo](http://justchat.smartmobilevilla.com) 10 | 11 | # Support Development 12 | If you found this project helpful or you learned something from the source code and want to thank me, consider buying me a cup of ☕️ 13 | 14 | [PayPal](https://paypal.me/ankeshkumar01) 15 | 16 | [](https://www.buymeacoffee.com/ankeshkumar) 17 | 18 | ## Features: 19 | 1. 1-1 chat. 20 | 2. Chat with only added friends(Privacy). New 21 | 3. Share Pic with Gallery/Camera 22 | 4. User online status 23 | 5. Flutter web supported 24 | 25 | ## Next Future Scope 26 | 27 | 1. Notification 28 | 2. Group Chat 29 | 3. User acceptance on chat request 30 | 4. share location on chat 31 | 32 | 33 | 34 | ## Screenshots: 35 | 36 | ![login screen](https://1.bp.blogspot.com/-hM837Uh65W0/Xj7adGUmwxI/AAAAAAAANjo/PoDM9bh7rZQqT37yIOu-IXAX4F-5W0NNgCLcBGAsYHQ/s640/splash_screen.jpg) 37 | ![user screen](https://1.bp.blogspot.com/-ok2AZvPw9FY/Xj7adz8i8vI/AAAAAAAANjw/TTXUBkbbBv8Ti4AvzVOVIWo5o_V6Ei63ACLcBGAsYHQ/s640/user_list.jpg) 38 | ![chat screen](https://1.bp.blogspot.com/-r2TK8wT_mV8/Xj7ade30n8I/AAAAAAAANjs/Uw6OQCBpf-Ec0Cm5XB9DIykJ5VGpDfpyACLcBGAsYHQ/s640/chat_screen.jpg) 39 | 40 | ## Getting Started 41 | * Add this to your package's pubspec.yaml file:
42 | dependencies:
[flutter_chat](https://pub.dev/packages/flutter_chat) 43 | 44 | * Add [firebase](https://firebase.google.com/) in your android and ios project. 45 | 46 | * Security Rules for Storage: 47 | 48 | rules_version = '2'; 49 | service firebase.storage { 50 | match /b/{bucket}/o { 51 | match /{allPaths=**} { 52 | allow read, write: if request.auth != null; 53 | } 54 | } 55 | } 56 | 57 | * Security Rules for Cloud fireStore: 58 | 59 | service cloud.firestore { 60 | match /databases/{database}/documents { 61 | match /{document=**} { 62 | allow read, write: if request.auth != null; 63 | } 64 | } 65 | } 66 | 67 | * You can modify the security rules as your need. 68 | 69 | * Deploy "Cloud Function" on firebase. (provided on cloudFunction folder, used for show user online/offline status). 70 | 71 | * Create a Stateful widget class and call the method in body (example can be found in Github repo),
72 | within initState():
73 | -> ChatData.init("app name",context);
74 | and in body of Widget build:
75 | -> ChatData.widgetWelcomeScreen(context) 76 | 77 | 78 | ## Flutter Web 79 | 80 | * If want to run Flutter-Chat project, on web 81 | Go to App, Firebase->Settings and then add new app, web 82 | Follow the instructions, 83 | Put the firebaseConfig script on index.html in web folder, 84 | 92 | 93 | Enjoy Fluttering 94 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /chat_sdk.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /cloudFunction/index.js: -------------------------------------------------------------------------------- 1 | const functions = require('firebase-functions'); 2 | const admin = require('firebase-admin'); 3 | admin.initializeApp(functions.config().firebase); 4 | 5 | const firestore = functions.firestore; 6 | 7 | exports.onUserStatusChange = functions.database 8 | .ref('/status/{id}') 9 | .onUpdate(event => { 10 | 11 | var db = admin.firestore(); 12 | 13 | 14 | //const usersRef = firestore.document('/users/' + event.params.userId); 15 | const usersRef = db.collection("users"); 16 | var snapShot = event.data; 17 | 18 | return event.data.ref.once('value') 19 | .then(statusSnap => snapShot.val()) 20 | .then(status => { 21 | if (status === 'offline'){ 22 | usersRef 23 | .doc(event.params.id) 24 | .set({ 25 | isOnline: false, 26 | last_active: Date.now() 27 | }, {merge: true}); 28 | } 29 | }) 30 | }); -------------------------------------------------------------------------------- /example/flutter_chat.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_chat/chatData.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_chat/chatWidget.dart'; 4 | 5 | class WelcomeScreen extends StatefulWidget { 6 | static const String id = "welcome_screen"; 7 | @override 8 | _WelcomeScreenState createState() => _WelcomeScreenState(); 9 | } 10 | 11 | class _WelcomeScreenState extends State { 12 | @override 13 | void initState() { 14 | super.initState(); 15 | ChatData.init("Just Chat",context); 16 | } 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return Scaffold( 21 | appBar: ChatWidget.getAppBar(), 22 | backgroundColor: Colors.white, 23 | body: ChatWidget.widgetWelcomeScreen(context)); 24 | } 25 | } -------------------------------------------------------------------------------- /images/icon_logout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ankesh-kumar/Flutter-chat/45b50b3a71470239c4f0e620c54e6191c969ba17/images/icon_logout.png -------------------------------------------------------------------------------- /images/icon_music.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ankesh-kumar/Flutter-chat/45b50b3a71470239c4f0e620c54e6191c969ba17/images/icon_music.png -------------------------------------------------------------------------------- /images/icon_nature.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ankesh-kumar/Flutter-chat/45b50b3a71470239c4f0e620c54e6191c969ba17/images/icon_nature.png -------------------------------------------------------------------------------- /images/img_not_available.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ankesh-kumar/Flutter-chat/45b50b3a71470239c4f0e620c54e6191c969ba17/images/img_not_available.jpeg -------------------------------------------------------------------------------- /lib/Models/UserModel.dart: -------------------------------------------------------------------------------- 1 | class UserModel { 2 | final String photoUrl; 3 | final String nickname; 4 | final String id; 5 | 6 | @override 7 | String toString() => 8 | 'UserModel{photoUrl: $photoUrl, nickname: $nickname, id: $id}'; 9 | 10 | UserModel({this.id, this.nickname, this.photoUrl}); 11 | } 12 | -------------------------------------------------------------------------------- /lib/chatDB.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:math'; 3 | 4 | import 'package:cloud_firestore/cloud_firestore.dart'; 5 | import 'package:firebase_auth/firebase_auth.dart'; 6 | import 'package:firebase_database/firebase_database.dart'; 7 | import 'package:flutter/cupertino.dart'; 8 | import 'package:google_sign_in/google_sign_in.dart'; 9 | 10 | class ChatDBFireStore { 11 | static Future isUserSignedIn() async { 12 | // GoogleSignInAccount? user = _currentUser; 13 | 14 | return false; 15 | } 16 | 17 | static String getDocName() { 18 | String dbUser = "users"; 19 | return dbUser; 20 | } 21 | 22 | void setUserLastSeen(User logInUser) async { 23 | if (logInUser != null) 24 | await FirebaseFirestore.instance 25 | .collection(getDocName()) 26 | .doc(logInUser.uid) 27 | .set({ 28 | 'lastActive': DateTime.now().millisecondsSinceEpoch, 29 | 'online': 'online' 30 | }, SetOptions(merge: true)); 31 | } 32 | 33 | void setChatLastSeen( 34 | var currentUserId, var stCollection, var chatId, bool isChat) async { 35 | //if (logInUser != null) { 36 | await FirebaseFirestore.instance.collection(stCollection).doc(chatId).set( 37 | {currentUserId: isChat ? true : DateTime.now().millisecondsSinceEpoch}, 38 | SetOptions(merge: true)); 39 | //} 40 | } 41 | 42 | static Future checkUserExists(User logInUser) async { 43 | //print('abcdearlier'); 44 | QuerySnapshot result; 45 | try { 46 | result = await FirebaseFirestore.instance 47 | .collection(getDocName()) 48 | .where('userId', isEqualTo: logInUser.uid) 49 | .get(); 50 | } catch (e) { 51 | //print('ex ' + e.toString()); 52 | } 53 | 54 | //print('abcdeafter'); 55 | final List documents = result.docs; 56 | //print('abcdeafterfinal'); 57 | if (documents.length == 0) { 58 | // Update data to server if new user 59 | await saveNewUser(logInUser); 60 | } 61 | } 62 | 63 | static saveNewUser(User logInUser) { 64 | //print('UserId ' + logInUser.uid); 65 | //print('UserID' + logInUser.displayName); 66 | //print('UserID' + logInUser.photoURL); 67 | //print('UserID' + logInUser.email); 68 | 69 | List friendList = []; 70 | 71 | FirebaseFirestore.instance.collection(getDocName()).doc(logInUser.uid).set({ 72 | 'nickname': logInUser.displayName, 73 | 'photoUrl': logInUser.photoURL, 74 | 'userId': logInUser.uid, 75 | 'email': logInUser.email, 76 | 'friends': friendList, 77 | 'createdAt': DateTime.now().millisecondsSinceEpoch.toString(), 78 | 'chattingWith': null, 79 | 'online': null 80 | }); 81 | } 82 | 83 | static streamChatData() { 84 | FirebaseFirestore.instance 85 | .collection(ChatDBFireStore.getDocName()) 86 | .snapshots(); 87 | } 88 | 89 | static Future makeUserOnline(User logInUser) async { 90 | await ChatDBFireStore().setUserLastSeen(logInUser); 91 | 92 | FirebaseDatabase.instance 93 | .reference() 94 | .child("/status/" + logInUser.uid) 95 | .onDisconnect() 96 | .set("offline"); 97 | FirebaseDatabase.instance 98 | .reference() 99 | .child("/status/" + logInUser.uid) 100 | .set("online"); 101 | } 102 | 103 | Stream streamChatDataList(var stCollection, var groupChatId) { 104 | return FirebaseFirestore.instance 105 | .collection(stCollection) 106 | .doc(groupChatId) 107 | .collection(groupChatId) 108 | .orderBy('timestamp', descending: true) 109 | .limit(50) 110 | .snapshots(); 111 | } 112 | 113 | Future> getFriendList(var userId) async { 114 | List friendList; 115 | 116 | FirebaseFirestore.instance 117 | .collection('users') 118 | .doc(userId) 119 | .get() 120 | .then((DocumentSnapshot documentSnapshot) { 121 | if (documentSnapshot.exists) { 122 | friendList = documentSnapshot.get('friends'); 123 | return friendList; 124 | } else { 125 | return null; 126 | //print('Document does not exist on the database'); 127 | } 128 | }); 129 | 130 | return friendList; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /lib/chatData.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | import 'package:firebase_auth/firebase_auth.dart'; 4 | import 'package:firebase_core/firebase_core.dart'; 5 | import 'package:flutter/cupertino.dart'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:fluttertoast/fluttertoast.dart'; 8 | import 'package:google_sign_in/google_sign_in.dart'; 9 | import 'chatDB.dart'; 10 | import 'chatWidget.dart'; 11 | import 'constants.dart'; 12 | import 'screens/dashboard_screen.dart'; 13 | import 'screens/login_screen.dart'; 14 | 15 | class ChatData { 16 | static String appName = "Just Chat "; 17 | 18 | static Future openDialog(BuildContext context) async { 19 | switch (await showDialog( 20 | context: context, 21 | builder: (BuildContext context) { 22 | return SimpleDialog( 23 | contentPadding: 24 | EdgeInsets.only(left: 0.0, right: 0.0, top: 0.0, bottom: 0.0), 25 | children: [ 26 | Container( 27 | color: themeColor, 28 | margin: EdgeInsets.all(0.0), 29 | padding: EdgeInsets.only(bottom: 10.0, top: 10.0), 30 | height: 100.0, 31 | child: Column( 32 | children: [ 33 | Container( 34 | child: Icon( 35 | Icons.exit_to_app, 36 | size: 30.0, 37 | color: Colors.white, 38 | ), 39 | margin: EdgeInsets.only(bottom: 10.0), 40 | ), 41 | ChatWidget.widgetShowText( 42 | 'Are you sure to exit app?', '', ''), 43 | ], 44 | ), 45 | ), 46 | SimpleDialogOption( 47 | onPressed: () { 48 | Navigator.pop(context, 0); 49 | }, 50 | child: Row( 51 | children: [ 52 | Container( 53 | child: Icon( 54 | Icons.cancel, 55 | color: Colors.white70, 56 | ), 57 | margin: EdgeInsets.only(right: 10.0), 58 | ), 59 | ChatWidget.widgetShowText('Cancel', '', ''), 60 | ], 61 | ), 62 | ), 63 | SimpleDialogOption( 64 | onPressed: () { 65 | Navigator.pop(context, 1); 66 | }, 67 | child: Row( 68 | children: [ 69 | Container( 70 | child: Icon( 71 | Icons.check_circle, 72 | color: Colors.white70, 73 | ), 74 | margin: EdgeInsets.only(right: 10.0), 75 | ), 76 | ChatWidget.widgetShowText('Yes', '', ''), 77 | ], 78 | ), 79 | ), 80 | ], 81 | ); 82 | })) { 83 | case 0: 84 | break; 85 | case 1: 86 | exit(0); 87 | break; 88 | } 89 | } 90 | 91 | static Future handleSignOut(BuildContext context) async { 92 | await Firebase.initializeApp(); 93 | final GoogleSignIn googleSignIn = GoogleSignIn(); 94 | 95 | await FirebaseAuth.instance.signOut(); 96 | await googleSignIn.disconnect(); 97 | await googleSignIn.signOut(); 98 | 99 | Navigator.pushReplacement( 100 | context, MaterialPageRoute(builder: (context) => LoginScreen())); 101 | } 102 | 103 | static Future authUsersGoogle(BuildContext context) async { 104 | await Firebase.initializeApp(); 105 | final GoogleSignIn googleSignIn = GoogleSignIn(); 106 | final FirebaseAuth firebaseAuth = FirebaseAuth.instance; 107 | 108 | GoogleSignInAccount googleUser = await googleSignIn.signIn(); 109 | GoogleSignInAuthentication googleAuth = await googleUser.authentication; 110 | 111 | final AuthCredential credential = GoogleAuthProvider.credential( 112 | accessToken: googleAuth.accessToken, 113 | idToken: googleAuth.idToken, 114 | ); 115 | 116 | final UserCredential logInUser = 117 | await firebaseAuth.signInWithCredential(credential); 118 | 119 | if (logInUser != null) { 120 | // Check is already sign up 121 | await ChatDBFireStore.checkUserExists(firebaseAuth.currentUser); 122 | 123 | final User logInUser = 124 | (await firebaseAuth.signInWithCredential(credential)).user; 125 | 126 | /** 127 | * Make user online 128 | */ 129 | await ChatDBFireStore.makeUserOnline(logInUser); 130 | 131 | Navigator.pushReplacement( 132 | context, 133 | MaterialPageRoute( 134 | builder: (context) => 135 | DashboardScreen(currentUserId: logInUser.uid))); 136 | return true; 137 | } else { 138 | return false; 139 | } 140 | } 141 | 142 | static Future checkUserLoggedin(BuildContext context) async { 143 | User user = FirebaseAuth.instance.currentUser; 144 | if (user != null) 145 | return true; 146 | else 147 | return false; 148 | } 149 | 150 | static String getGroupChatID(String logInUserId, String peerId) { 151 | if (logInUserId.hashCode <= peerId.hashCode) { 152 | return '$logInUserId-$peerId'; 153 | } else { 154 | return '$peerId-$logInUserId'; 155 | } 156 | } 157 | 158 | static Future isSignedIn() async { 159 | final GoogleSignIn googleSignIn = GoogleSignIn(); 160 | bool isLoggedIn = await googleSignIn.isSignedIn(); 161 | return isLoggedIn; 162 | } 163 | 164 | static void authUser(BuildContext context) async { 165 | bool isValidUser = await ChatData.authUsersGoogle(context); 166 | //print('isValid' + isValidUser.toString()); 167 | if (isValidUser) { 168 | //if (await ChatData.isSignedIn()) { 169 | ////print('sign in signin'); 170 | //ChatData.checkUserLogin(context); 171 | 172 | } else { 173 | //print('sign in fail'); 174 | Fluttertoast.showToast(msg: "Sign in fail"); 175 | } 176 | } 177 | 178 | static init(String applicationName, BuildContext context) { 179 | appName = applicationName; 180 | //startTime(context); 181 | checkUserLogin(context); 182 | } 183 | 184 | static checkUserLogin(BuildContext context) async { 185 | await Firebase.initializeApp(); 186 | final GoogleSignIn googleSignIn = GoogleSignIn(); 187 | final FirebaseAuth firebaseAuth = FirebaseAuth.instance; 188 | 189 | if (await isSignedIn() == true) { 190 | GoogleSignInAccount googleUser = await googleSignIn.signIn(); 191 | GoogleSignInAuthentication googleAuth = await googleUser.authentication; 192 | 193 | final AuthCredential credential = GoogleAuthProvider.credential( 194 | accessToken: googleAuth.accessToken, 195 | idToken: googleAuth.idToken, 196 | ); 197 | 198 | final User logInUser = 199 | (await firebaseAuth.signInWithCredential(credential)).user; 200 | 201 | /** 202 | * Make user online 203 | */ 204 | await ChatDBFireStore.makeUserOnline(logInUser); 205 | 206 | // Navigator.pushReplacement( 207 | // context, 208 | // MaterialPageRoute( 209 | // builder: (context) => 210 | // DashboardScreen(currentUserId: logInUser.uid))); 211 | 212 | Navigator.pushReplacement( 213 | context, 214 | MaterialPageRoute( 215 | builder: (context) => 216 | DashboardScreen(currentUserId: logInUser.uid))); 217 | } else { 218 | //return ChatData.widgetLoginScreen(context); 219 | Navigator.pushReplacement( 220 | context, MaterialPageRoute(builder: (context) => LoginScreen())); 221 | } 222 | } 223 | 224 | static startTime(BuildContext context) async { 225 | var _duration = new Duration(seconds: 2); 226 | return new Timer(_duration, checkUserLogin(context)); 227 | } 228 | 229 | static bool isLastMessageLeft(var listMessage, String id, int index) { 230 | if ((index > 0 && 231 | listMessage != null && 232 | listMessage[index - 1].get('idFrom') == id) || 233 | index == 0) { 234 | return true; 235 | } else { 236 | return false; 237 | } 238 | } 239 | 240 | static bool isLastMessageRight(var listMessage, String id, int index) { 241 | if ((index > 0 && 242 | listMessage != null && 243 | listMessage[index - 1].get('idFrom') != id) || 244 | index == 0) { 245 | return true; 246 | } else { 247 | return false; 248 | } 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /lib/chatWidget.dart: -------------------------------------------------------------------------------- 1 | import 'package:cached_network_image/cached_network_image.dart'; 2 | import 'package:cloud_firestore/cloud_firestore.dart'; 3 | import 'package:flutter/foundation.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_linkify/flutter_linkify.dart'; 6 | import 'package:intl/intl.dart'; 7 | import 'package:photo_view/photo_view.dart'; 8 | import 'package:url_launcher/url_launcher.dart'; 9 | import 'chatDB.dart'; 10 | import 'chatData.dart'; 11 | import 'constants.dart'; 12 | import 'screens/chat.dart'; 13 | import 'screens/zoomImage.dart'; 14 | 15 | class ChatWidget { 16 | static getFriendList() { 17 | List data = [ 18 | "h7127ZCne0OzSRwNXtktMPei0CH3", 19 | "pdj9dlfEQMTUD6s954qiPeplEog2" 20 | ]; 21 | return data; 22 | } 23 | 24 | //static Future userListStack(String currentUserId, BuildContext context) async{ 25 | // 26 | // //List friendList=await getFriendList(); 27 | // List friendList=await getFriendList(); 28 | // 29 | // return 30 | // } 31 | 32 | static Widget userListbuildItem( 33 | BuildContext context, String currentUserId, DocumentSnapshot document) { 34 | //print('adgbasdg_userbuildItem'); 35 | 36 | //print(currentUserId); 37 | if (document.get('userId') == currentUserId) { 38 | return Container(); 39 | } else { 40 | return Container( 41 | child: ElevatedButton( 42 | child: Row( 43 | children: [ 44 | Material( 45 | child: document.get('photoUrl') != null 46 | ? widgetShowImages(document.get('photoUrl'), 50) 47 | : Icon( 48 | Icons.account_circle, 49 | size: 50.0, 50 | color: colorPrimaryDark, 51 | ), 52 | borderRadius: BorderRadius.all(Radius.circular(25.0)), 53 | clipBehavior: Clip.hardEdge, 54 | ), 55 | Flexible( 56 | child: Container( 57 | child: Column( 58 | children: [ 59 | Container( 60 | child: Text( 61 | 'Nickname: ${document.get('nickname')}', 62 | style: TextStyle(color: primaryColor), 63 | ), 64 | alignment: Alignment.centerLeft, 65 | margin: EdgeInsets.fromLTRB(10.0, 0.0, 0.0, 5.0), 66 | ), 67 | ], 68 | ), 69 | margin: EdgeInsets.only(left: 20.0), 70 | ), 71 | ), 72 | ConstrainedBox( 73 | constraints: new BoxConstraints( 74 | minHeight: 10.0, 75 | minWidth: 10.0, 76 | maxHeight: 30.0, 77 | maxWidth: 30.0, 78 | ), 79 | child: new DecoratedBox( 80 | decoration: new BoxDecoration( 81 | color: document.get('online') == 'online' 82 | ? Colors.greenAccent 83 | : Colors.transparent), 84 | ), 85 | ), 86 | ], 87 | ), 88 | onPressed: () { 89 | Navigator.push( 90 | context, 91 | MaterialPageRoute( 92 | builder: (context) => Chat( 93 | currentUserId: currentUserId, 94 | peerId: document.id, 95 | peerName: document.get('nickname'), 96 | peerAvatar: document.get('photoUrl'), 97 | ))); 98 | }, 99 | style: ElevatedButton.styleFrom( 100 | primary: viewBg, 101 | onPrimary: viewBg, 102 | padding: EdgeInsets.fromLTRB(25.0, 10.0, 25.0, 10.0))), 103 | margin: EdgeInsets.only(bottom: 10.0, left: 5.0, right: 5.0), 104 | ); 105 | } 106 | } 107 | 108 | static Widget widgetLoginScreen(BuildContext context) { 109 | return Padding( 110 | padding: EdgeInsets.symmetric(horizontal: 24.0), 111 | child: Column( 112 | mainAxisAlignment: MainAxisAlignment.center, 113 | crossAxisAlignment: CrossAxisAlignment.stretch, 114 | children: [ 115 | Container( 116 | child: Row( 117 | crossAxisAlignment: CrossAxisAlignment.center, 118 | mainAxisAlignment: MainAxisAlignment.center, 119 | children: [ 120 | Container( 121 | child: Icon( 122 | Icons.message, 123 | color: Colors.greenAccent, 124 | ), 125 | height: 25.0, 126 | ), 127 | Text( 128 | ChatData.appName, 129 | style: TextStyle( 130 | fontSize: 25.0, 131 | fontWeight: FontWeight.w900, 132 | ), 133 | ), 134 | ], 135 | ), 136 | ), 137 | SizedBox( 138 | height: 48.0, 139 | ), 140 | Center( 141 | child: ElevatedButton( 142 | onPressed: () { 143 | ChatData.authUser(context); 144 | }, 145 | child: Text( 146 | 'SIGN IN WITH GOOGLE', 147 | style: TextStyle(fontSize: 16.0, color: Colors.white), 148 | ), 149 | style: ElevatedButton.styleFrom( 150 | primary: Color(0xffdd4b39), 151 | onPrimary: Color(0xffff7f7f), 152 | padding: EdgeInsets.fromLTRB(30.0, 15.0, 30.0, 15.0))), 153 | ), 154 | ], 155 | ), 156 | ); 157 | } 158 | 159 | static Widget getAppBar() { 160 | return AppBar( 161 | leading: null, 162 | title: Text(ChatData.appName), 163 | backgroundColor: themeColor, 164 | ); 165 | } 166 | 167 | static Widget widgetWelcomeScreen(BuildContext context) { 168 | return Center( 169 | child: Container( 170 | child: Text( 171 | ChatData.appName, 172 | style: TextStyle(fontSize: 28), 173 | )), 174 | ); 175 | } 176 | 177 | static Widget widgetFullPhoto(BuildContext context, String url) { 178 | return Container(child: PhotoView(imageProvider: NetworkImage(url))); 179 | } 180 | 181 | static Widget widgetChatBuildItem(BuildContext context, var listMessage, 182 | String id, int index, DocumentSnapshot document, String peerAvatar) { 183 | if (document.get('idFrom') == id) { 184 | return Row( 185 | children: [ 186 | document.get('type') == 0 187 | ? chatText(document.get('content'), id, listMessage, index, true) 188 | : chatImage(context, id, listMessage, document.get('content'), 189 | index, true) 190 | ], 191 | mainAxisAlignment: MainAxisAlignment.end, 192 | ); 193 | } else { 194 | return Container( 195 | child: Column( 196 | children: [ 197 | Row( 198 | children: [ 199 | ChatData.isLastMessageLeft(listMessage, id, index) 200 | ? Material( 201 | child: widgetShowImages(peerAvatar, 35), 202 | borderRadius: BorderRadius.all( 203 | Radius.circular(18.0), 204 | ), 205 | clipBehavior: Clip.hardEdge, 206 | ) 207 | : Container(width: 35.0), 208 | document.get('type') == 0 209 | ? chatText( 210 | document.get('content'), id, listMessage, index, false) 211 | : chatImage(context, id, listMessage, 212 | document.get('content'), index, false) 213 | ], 214 | ), 215 | 216 | // Time 217 | ChatData.isLastMessageLeft(listMessage, id, index) 218 | ? Container( 219 | child: Text( 220 | DateFormat('dd MMM kk:mm').format( 221 | DateTime.fromMillisecondsSinceEpoch( 222 | int.parse(document.get('timestamp')))), 223 | style: TextStyle( 224 | color: greyColor, 225 | fontSize: 12.0, 226 | fontStyle: FontStyle.italic), 227 | ), 228 | margin: EdgeInsets.only(left: 50.0, top: 5.0, bottom: 5.0), 229 | ) 230 | : Container() 231 | ], 232 | crossAxisAlignment: CrossAxisAlignment.start, 233 | ), 234 | margin: EdgeInsets.only(bottom: 10.0), 235 | ); 236 | } 237 | } 238 | 239 | static Widget widgetChatBuildListMessage(groupChatId, listMessage, 240 | currentUserId, peerAvatar, listScrollController, var stCollection) { 241 | Stream _streamChatData; 242 | _streamChatData = 243 | ChatDBFireStore().streamChatDataList(stCollection, groupChatId); 244 | 245 | return Flexible( 246 | child: groupChatId == '' 247 | ? Center( 248 | child: CircularProgressIndicator( 249 | valueColor: AlwaysStoppedAnimation(themeColor))) 250 | : StreamBuilder( 251 | stream: _streamChatData, 252 | builder: (context, snapshot) { 253 | if (!snapshot.hasData) { 254 | return Center( 255 | child: CircularProgressIndicator( 256 | valueColor: 257 | AlwaysStoppedAnimation(themeColor))); 258 | } else { 259 | listMessage = snapshot.data.docs; 260 | return ListView.builder( 261 | padding: EdgeInsets.all(10.0), 262 | itemBuilder: (context, index) => 263 | ChatWidget.widgetChatBuildItem( 264 | context, 265 | listMessage, 266 | currentUserId, 267 | index, 268 | snapshot.data.docs[index], 269 | peerAvatar), 270 | itemCount: snapshot.data.docs.length, 271 | reverse: true, 272 | controller: listScrollController, 273 | ); 274 | } 275 | }, 276 | ), 277 | ); 278 | } 279 | 280 | static Widget chatText(String chatContent, String id, var listMessage, 281 | int index, bool logUserMsg) { 282 | return Container( 283 | child: Linkify( 284 | onOpen: (link) async { 285 | if (await canLaunch(link.url)) { 286 | await launch(link.url); 287 | } else { 288 | throw 'Could not launch $link'; 289 | } 290 | }, 291 | text: chatContent, 292 | style: TextStyle(color: logUserMsg ? primaryColor : Colors.white), 293 | linkStyle: TextStyle(color: Colors.blueGrey), 294 | ), 295 | // Text( 296 | // chatContent, 297 | // style: TextStyle(color: logUserMsg ? primaryColor : Colors.white), 298 | // ), 299 | padding: EdgeInsets.fromLTRB(15.0, 10.0, 15.0, 10.0), 300 | width: kIsWeb ? 400 : 200.0, 301 | decoration: BoxDecoration( 302 | color: logUserMsg ? greyColor2 : primaryColor, 303 | borderRadius: BorderRadius.circular(8.0)), 304 | margin: logUserMsg 305 | ? EdgeInsets.only( 306 | bottom: ChatData.isLastMessageRight(listMessage, id, index) 307 | ? 20.0 308 | : 10.0, 309 | right: 10.0) 310 | : EdgeInsets.only(left: 10.0), 311 | ); 312 | } 313 | 314 | static Widget chatImage(BuildContext context, String id, var listMessage, 315 | String chatContent, int index, bool logUserMsg) { 316 | return Container( 317 | child: ElevatedButton( 318 | child: Material( 319 | child: kIsWeb 320 | ? widgetShowImages(chatContent, 250) 321 | : widgetShowImages(chatContent, 100), 322 | borderRadius: BorderRadius.all(Radius.circular(10.0)), 323 | clipBehavior: Clip.hardEdge, 324 | ), 325 | onPressed: () { 326 | Navigator.push( 327 | context, 328 | MaterialPageRoute( 329 | builder: (context) => ZoomImage(url: chatContent))); 330 | }, 331 | style: ElevatedButton.styleFrom(padding: EdgeInsets.all(10.0))), 332 | margin: logUserMsg 333 | ? EdgeInsets.only( 334 | bottom: ChatData.isLastMessageRight(listMessage, id, index) 335 | ? 20.0 336 | : 10.0, 337 | right: 10.0) 338 | : EdgeInsets.only(left: 10.0), 339 | ); 340 | } 341 | 342 | // Show Images from network 343 | static Widget widgetShowImages(String imageUrl, double imageSize) { 344 | return CachedNetworkImage( 345 | imageUrl: imageUrl, 346 | imageBuilder: (context, imageProvider) => Container( 347 | decoration: BoxDecoration( 348 | image: DecorationImage( 349 | image: imageProvider, 350 | fit: BoxFit.cover, 351 | //colorFilter:ColorFilter.mode(Colors.red, BlendMode.colorBurn) 352 | ), 353 | ), 354 | ), 355 | height: imageSize, 356 | width: imageSize, 357 | placeholder: (context, url) => CircularProgressIndicator(), 358 | errorWidget: (context, url, error) => Icon(Icons.error), 359 | ); 360 | } 361 | 362 | static Widget widgetShowText( 363 | String text, dynamic textSize, dynamic textColor) { 364 | return Text( 365 | '$text', 366 | style: TextStyle( 367 | color: (textColor == '') ? Colors.white70 : textColor, 368 | fontSize: textSize == '' ? 14.0 : textSize), 369 | ); 370 | } 371 | } 372 | -------------------------------------------------------------------------------- /lib/constants.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'dart:ui'; 3 | import 'hexColor.dart'; 4 | 5 | final themeColor = HexColor('#ad1a7f'); 6 | final colorPrimaryDark = HexColor('#6d0094'); 7 | final colorAccent = HexColor('#ff2068'); 8 | final colorAccentSecondary = HexColor('#6d0094'); 9 | final viewBg = HexColor('#f8f8f8'); 10 | final lblValue = HexColor('#222222'); 11 | 12 | final themeColor2 = Color(0xff203152); 13 | final primaryColor = Color(0xff203152); 14 | final greyColor = Color(0xffaeaeae); 15 | final greyColor2 = Color(0xffE8E8E8); 16 | -------------------------------------------------------------------------------- /lib/hexColor.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | 3 | class HexColor extends Color { 4 | static int _getColorFromHex(String hexColor) { 5 | hexColor = hexColor.toUpperCase().replaceAll("#", ""); 6 | if (hexColor.length == 6) { 7 | hexColor = "FF" + hexColor; 8 | } 9 | return int.parse(hexColor, radix: 16); 10 | } 11 | 12 | HexColor(final String hexColor) : super(_getColorFromHex(hexColor)); 13 | } 14 | -------------------------------------------------------------------------------- /lib/screens/chat.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | import 'package:cloud_firestore/cloud_firestore.dart'; 4 | import 'package:firebase_storage/firebase_storage.dart' as firebase_storage; 5 | import 'package:flutter/foundation.dart'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:flutter_native_image/flutter_native_image.dart'; 8 | import 'package:image_picker/image_picker.dart'; 9 | import '../chatWidget.dart'; 10 | import '../constants.dart'; 11 | import 'package:fluttertoast/fluttertoast.dart'; 12 | 13 | class Chat extends StatelessWidget { 14 | final String peerId; 15 | final String peerAvatar; 16 | final String peerName; 17 | final String currentUserId; 18 | static const String id = "chat"; 19 | 20 | Chat( 21 | {Key key, 22 | @required this.currentUserId, 23 | @required this.peerId, 24 | @required this.peerAvatar, 25 | @required this.peerName}) 26 | : super(key: key); 27 | 28 | @override 29 | Widget build(BuildContext context) { 30 | return new Scaffold( 31 | backgroundColor: Colors.white, 32 | appBar: AppBar( 33 | leading: null, 34 | title: Text(peerName), 35 | backgroundColor: themeColor, 36 | ), 37 | body: new _ChatScreen( 38 | currentUserId: currentUserId, 39 | peerId: peerId, 40 | peerAvatar: peerAvatar, 41 | ), 42 | ); 43 | } 44 | } 45 | 46 | class _ChatScreen extends StatefulWidget { 47 | final String peerId; 48 | final String peerAvatar; 49 | final String currentUserId; 50 | 51 | _ChatScreen( 52 | {Key key, 53 | @required this.peerId, 54 | @required this.peerAvatar, 55 | @required this.currentUserId}) 56 | : super(key: key); 57 | 58 | @override 59 | State createState() => 60 | new _ChatScreenState(peerId: peerId, peerAvatar: peerAvatar); 61 | } 62 | 63 | class _ChatScreenState extends State<_ChatScreen> { 64 | _ChatScreenState({Key key, @required this.peerId, @required this.peerAvatar}); 65 | 66 | String peerId; 67 | String peerAvatar; 68 | String id; 69 | 70 | var listMessage; 71 | String groupChatId; 72 | 73 | File imageFile; 74 | bool isLoading; 75 | bool isShowSticker; 76 | String imageUrl; 77 | var stCollection = 'messages'; 78 | 79 | final TextEditingController textEditingController = 80 | new TextEditingController(); 81 | final ScrollController listScrollController = new ScrollController(); 82 | final FocusNode focusNode = new FocusNode(); 83 | 84 | @override 85 | void initState() { 86 | super.initState(); 87 | focusNode.addListener(onFocusChange); 88 | 89 | groupChatId = ''; 90 | 91 | isLoading = false; 92 | isShowSticker = false; 93 | imageUrl = ''; 94 | 95 | readLocal(); 96 | } 97 | 98 | void onFocusChange() { 99 | if (focusNode.hasFocus) { 100 | // Hide sticker when keyboard appear 101 | setState(() { 102 | isShowSticker = false; 103 | }); 104 | } 105 | } 106 | 107 | readLocal() async { 108 | id = widget.currentUserId ?? ''; 109 | if (id.hashCode <= peerId.hashCode) { 110 | groupChatId = '$id-$peerId'; 111 | } else { 112 | groupChatId = '$peerId-$id'; 113 | } 114 | 115 | FirebaseFirestore.instance 116 | .collection('users') 117 | .doc(id) 118 | .update({'chattingWith': peerId}); 119 | 120 | setState(() {}); 121 | } 122 | 123 | Future getImage(int index) async { 124 | PickedFile selectedFile; 125 | 126 | if (kIsWeb) { 127 | //selectedFile=await ImagePicker.platform.pickImage(source: ImageSource.gallery); 128 | selectedFile = await ImagePicker().getImage(source: ImageSource.gallery); 129 | } else { 130 | if (index == 0) 131 | selectedFile = 132 | await ImagePicker().getImage(source: ImageSource.gallery); 133 | else 134 | selectedFile = await ImagePicker().getImage(source: ImageSource.camera); 135 | 136 | //imageFile =File(selectedFile.path); 137 | 138 | } 139 | 140 | if (selectedFile != null) { 141 | setState(() { 142 | //orFile=selectedFile; 143 | isLoading = true; 144 | }); 145 | uploadFile(selectedFile); 146 | } 147 | } 148 | 149 | Future uploadFile(PickedFile orFile) async { 150 | String fileName = DateTime.now().millisecondsSinceEpoch.toString(); 151 | firebase_storage.Reference reference = 152 | firebase_storage.FirebaseStorage.instance.ref().child(fileName); 153 | 154 | File compressedFile; 155 | if (!kIsWeb) 156 | compressedFile = await FlutterNativeImage.compressImage(orFile.path, 157 | quality: 80, percentage: 90); 158 | 159 | try { 160 | firebase_storage.UploadTask uploadTask; 161 | 162 | final metadata = firebase_storage.SettableMetadata( 163 | contentType: 'image/jpeg', 164 | customMetadata: {'picked-file-path': orFile.path}); 165 | 166 | if (kIsWeb) 167 | uploadTask = reference.putData(await orFile.readAsBytes(), metadata); 168 | else 169 | uploadTask = reference.putFile(compressedFile); 170 | 171 | firebase_storage.TaskSnapshot storageTaskSnapshot = await uploadTask; 172 | 173 | storageTaskSnapshot.ref.getDownloadURL().then((downloadUrl) { 174 | imageUrl = downloadUrl; 175 | setState(() { 176 | isLoading = false; 177 | onSendMessage(imageUrl, 1); 178 | }); 179 | }, onError: (err) { 180 | setState(() { 181 | isLoading = false; 182 | }); 183 | }); 184 | } catch (e) { 185 | print(e.toString()); 186 | } 187 | } 188 | 189 | void onSendMessage(String content, int type) { 190 | // type: 0 = text, 1 = image, 2 = sticker 191 | if (content.trim() != '') { 192 | textEditingController.clear(); 193 | 194 | var documentReference = FirebaseFirestore.instance 195 | .collection('messages') 196 | .doc(groupChatId) 197 | .collection(groupChatId) 198 | .doc(DateTime.now().millisecondsSinceEpoch.toString()); 199 | 200 | FirebaseFirestore.instance.runTransaction((transaction) async { 201 | await transaction.set( 202 | documentReference, 203 | { 204 | 'idFrom': id, 205 | 'idTo': peerId, 206 | 'timestamp': DateTime.now().millisecondsSinceEpoch.toString(), 207 | 'content': content, 208 | 'type': type 209 | }, 210 | ); 211 | }); 212 | listScrollController.animateTo(0.0, 213 | duration: Duration(milliseconds: 300), curve: Curves.easeOut); 214 | } else { 215 | Fluttertoast.showToast(msg: 'Nothing to send'); 216 | } 217 | } 218 | 219 | Future onBackPress() { 220 | Navigator.pop(context); 221 | return Future.value(false); 222 | } 223 | 224 | @override 225 | Widget build(BuildContext context) { 226 | return WillPopScope( 227 | child: Stack( 228 | children: [ 229 | Column( 230 | children: [ 231 | // List of messages 232 | ChatWidget.widgetChatBuildListMessage( 233 | groupChatId, 234 | listMessage, 235 | widget.currentUserId, 236 | peerAvatar, 237 | listScrollController, 238 | stCollection), 239 | 240 | // Input content 241 | buildInput(), 242 | ], 243 | ), 244 | 245 | // Loading 246 | buildLoading() 247 | ], 248 | ), 249 | onWillPop: onBackPress, 250 | ); 251 | } 252 | 253 | Widget buildLoading() { 254 | return Positioned( 255 | child: isLoading 256 | ? Container( 257 | child: Center( 258 | child: CircularProgressIndicator( 259 | valueColor: AlwaysStoppedAnimation(themeColor)), 260 | ), 261 | color: Colors.white.withOpacity(0.8), 262 | ) 263 | : Container(), 264 | ); 265 | } 266 | 267 | Widget buildInput() { 268 | return Container( 269 | child: Row( 270 | children: [ 271 | // Button send image 272 | Material( 273 | child: new Container( 274 | margin: new EdgeInsets.symmetric(horizontal: 1.0), 275 | child: new IconButton( 276 | icon: new Icon(Icons.image), 277 | onPressed: () => getImage(0), 278 | color: primaryColor, 279 | ), 280 | ), 281 | color: Colors.white, 282 | ), 283 | Visibility( 284 | visible: !kIsWeb, 285 | child: Material( 286 | child: new Container( 287 | margin: new EdgeInsets.symmetric(horizontal: 1.0), 288 | child: new IconButton( 289 | icon: new Icon(Icons.camera_alt), 290 | onPressed: () => getImage(1), 291 | color: primaryColor, 292 | ), 293 | ), 294 | color: Colors.white, 295 | ), 296 | ), 297 | // Edit text 298 | Flexible( 299 | child: Container( 300 | child: TextField( 301 | style: TextStyle(color: primaryColor, fontSize: 15.0), 302 | controller: textEditingController, 303 | decoration: InputDecoration.collapsed( 304 | hintText: 'Type your message...', 305 | hintStyle: TextStyle(color: greyColor), 306 | ), 307 | focusNode: focusNode, 308 | ), 309 | ), 310 | ), 311 | 312 | // Button send message 313 | Material( 314 | child: new Container( 315 | margin: new EdgeInsets.symmetric(horizontal: 8.0), 316 | child: new IconButton( 317 | icon: new Icon(Icons.send), 318 | onPressed: () => onSendMessage(textEditingController.text, 0), 319 | color: primaryColor, 320 | ), 321 | ), 322 | color: Colors.white, 323 | ), 324 | ], 325 | ), 326 | width: double.infinity, 327 | height: 50.0, 328 | decoration: new BoxDecoration( 329 | border: 330 | new Border(top: new BorderSide(color: greyColor2, width: 0.5)), 331 | color: Colors.white), 332 | ); 333 | } 334 | } 335 | -------------------------------------------------------------------------------- /lib/screens/dashboard_screen.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:cloud_firestore/cloud_firestore.dart'; 3 | import 'package:flutter/cupertino.dart'; 4 | import 'package:flutter/material.dart'; 5 | import '../chatDB.dart'; 6 | import '../chatData.dart'; 7 | import '../chatWidget.dart'; 8 | import '../constants.dart'; 9 | import 'chat.dart'; 10 | import 'package:fluttertoast/fluttertoast.dart'; 11 | 12 | List friendList = []; 13 | 14 | class DashboardScreen extends StatefulWidget { 15 | static const String id = "dashboard_screen"; 16 | final String currentUserId; 17 | 18 | DashboardScreen({Key key, @required this.currentUserId}) : super(key: key); 19 | 20 | @override 21 | _DashboardScreenState createState() => _DashboardScreenState(); 22 | } 23 | 24 | class _DashboardScreenState extends State { 25 | _DashboardScreenState({Key key}); 26 | List unreadSubscriptions = []; 27 | List controllers = []; 28 | 29 | bool isLoading = false; 30 | bool addNewFriend = false; 31 | List choices = const [ 32 | const Choice(title: 'Settings', icon: Icons.settings), 33 | const Choice(title: 'Log out', icon: Icons.exit_to_app), 34 | ]; 35 | 36 | final friendController = TextEditingController(); 37 | 38 | Stream _streamFriendList; 39 | 40 | @override 41 | void initState() { 42 | super.initState(); 43 | } 44 | 45 | Future> getFriendList(bool onLoad) async { 46 | await FirebaseFirestore.instance 47 | .collection('users') 48 | .doc(widget.currentUserId) 49 | .get() 50 | .then((DocumentSnapshot documentSnapshot) { 51 | if (documentSnapshot.exists) { 52 | //print('Document data: ${documentSnapshot.data()}'); 53 | //setState(() { 54 | friendList = documentSnapshot.get('friends'); 55 | return friendList; 56 | //}); 57 | } else { 58 | return friendList; 59 | } 60 | }); 61 | return friendList; 62 | } 63 | 64 | void goExit() async { 65 | await showDialog( 66 | context: context, 67 | builder: (context) { 68 | return AlertDialog( 69 | title: Text('Confirmation'), 70 | content: Text('Do you want to logout?'), 71 | actions: [ 72 | new TextButton( 73 | onPressed: () { 74 | Navigator.of(context, rootNavigator: true).pop( 75 | false); // dismisses only the dialog and returns false 76 | }, 77 | child: Text('No'), 78 | ), 79 | TextButton( 80 | onPressed: () { 81 | ChatData.handleSignOut(context); 82 | }, 83 | child: Text('Yes'), 84 | ), 85 | ], 86 | ); 87 | }); 88 | } 89 | 90 | @override 91 | Widget build(BuildContext context) { 92 | return DefaultTabController( 93 | length: 2, 94 | child: Scaffold( 95 | backgroundColor: Colors.white, 96 | body: WillPopScope( 97 | child: showFriendList(widget.currentUserId), 98 | onWillPop: onBackPress, 99 | ), 100 | ), 101 | ); 102 | } 103 | 104 | Future onBackPress() { 105 | ChatData.openDialog(context); 106 | return Future.value(false); 107 | } 108 | 109 | Widget showAddFriend() { 110 | return Container( 111 | padding: EdgeInsets.all(10.0), 112 | child: TextButton( 113 | child: Text('Add New Friend'), 114 | onPressed: _showAddFriendDialog, 115 | ), 116 | ); 117 | } 118 | 119 | _showAddFriendDialog() async { 120 | await showDialog( 121 | context: context, 122 | builder: (context) { 123 | return _SystemPadding( 124 | child: new AlertDialog( 125 | contentPadding: const EdgeInsets.all(16.0), 126 | content: new Row( 127 | children: [ 128 | new Expanded( 129 | child: new TextField( 130 | autofocus: true, 131 | controller: friendController, 132 | decoration: new InputDecoration( 133 | labelText: 'user Email', 134 | hintText: 'smartmobilevilla@gmail.com'), 135 | ), 136 | ) 137 | ], 138 | ), 139 | actions: [ 140 | new TextButton( 141 | child: const Text('CANCEL'), 142 | onPressed: () { 143 | Navigator.pop(context); 144 | }), 145 | new TextButton( 146 | child: const Text('Add'), 147 | onPressed: () { 148 | Navigator.pop(context); 149 | if (friendController.text != '') _addNewFriend(); 150 | }) 151 | ], 152 | ), 153 | ); 154 | }); 155 | } 156 | 157 | void _addNewFriend() async { 158 | friendList = await getFriendList(true); 159 | 160 | FirebaseFirestore.instance 161 | .collection('users') 162 | .where('email', isEqualTo: friendController.text) 163 | .get() 164 | .then((value) { 165 | if (value.docs.length > 0) { 166 | value.docs.forEach((result) { 167 | bool alreadyExist = false; 168 | 169 | for (var fr in friendList) { 170 | if (fr == result.data()['userId']) alreadyExist = true; 171 | } 172 | if (alreadyExist == true) { 173 | showToast("already friend", true); 174 | } else { 175 | friendList.add(result.data()['userId']); 176 | FirebaseFirestore.instance 177 | .collection('users') 178 | .doc(widget.currentUserId) 179 | .update({"friends": friendList}).whenComplete(() { 180 | // Navigator.pop(context); 181 | setState(() { 182 | getFriendList(true); 183 | }); 184 | // getFriendList(true); 185 | }); 186 | } 187 | friendController.text = ""; 188 | }); 189 | } else { 190 | showToast("No user found with this email.", true); 191 | Navigator.pop(context); 192 | } 193 | }); 194 | } 195 | 196 | showToast(var text, bool error) { 197 | // if (error == false){ 198 | // 199 | // } 200 | 201 | Fluttertoast.showToast( 202 | msg: text, 203 | toastLength: Toast.LENGTH_SHORT, 204 | gravity: ToastGravity.CENTER, 205 | timeInSecForIosWeb: 1, 206 | backgroundColor: error ? Colors.red : Colors.blue, 207 | textColor: Colors.white, 208 | fontSize: 16.0); 209 | } 210 | 211 | Widget showFriendList(var currentUserId) { 212 | return Stack( 213 | children: [ 214 | // List 215 | Container( 216 | width: double.infinity, 217 | child: Column( 218 | crossAxisAlignment: CrossAxisAlignment.end, 219 | children: [ 220 | showAddFriend(), 221 | ], 222 | ), 223 | ), 224 | 225 | FutureBuilder>( 226 | future: getFriendList(true), 227 | builder: (context, snapshot) { 228 | if (snapshot.hasData) { 229 | List newFrList = snapshot.data; 230 | if (newFrList.length > 0) { 231 | return widgetFriendList(currentUserId, newFrList); 232 | } else { 233 | return Column( 234 | crossAxisAlignment: CrossAxisAlignment.center, 235 | mainAxisAlignment: MainAxisAlignment.center, 236 | children: [ 237 | Center( 238 | child: Text( 239 | 'No Friend in your list\nAdd New Friend to start chat', 240 | style: TextStyle(fontSize: 16.0), 241 | )), 242 | ], 243 | ); 244 | } 245 | } else { 246 | return Center( 247 | child: Text('Loading FriendList'), 248 | ); 249 | } 250 | }), 251 | ], 252 | ); 253 | } 254 | 255 | Widget widgetFriendList(var currentUserId, List friendLists) { 256 | return Container( 257 | margin: EdgeInsets.fromLTRB(0, 35, 0, 0), 258 | child: StreamBuilder( 259 | stream: streamFriendList(), 260 | builder: (context, snapshot) { 261 | if (!snapshot.hasData) { 262 | return Center( 263 | child: CircularProgressIndicator( 264 | valueColor: AlwaysStoppedAnimation(themeColor), 265 | ), 266 | ); 267 | } 268 | if (snapshot.connectionState == ConnectionState.waiting) { 269 | return Text("Loading"); 270 | } 271 | 272 | return new ListView( 273 | children: snapshot.data.docs.map((DocumentSnapshot document) { 274 | return (document.get('userId') == currentUserId) 275 | ? SizedBox( 276 | height: 2, 277 | ) 278 | : new ListTile( 279 | leading: Material( 280 | child: document.get('photoUrl') != null 281 | ? ChatWidget.widgetShowImages( 282 | document.get('photoUrl'), 50) 283 | : Icon( 284 | Icons.account_circle, 285 | size: 50.0, 286 | color: colorPrimaryDark, 287 | ), 288 | borderRadius: BorderRadius.all(Radius.circular(25.0)), 289 | clipBehavior: Clip.hardEdge, 290 | ), 291 | title: new Text(document.get('nickname')), 292 | subtitle: new Text(document.get('nickname')), 293 | trailing: Wrap( 294 | children: [ 295 | Container( 296 | height: 40, 297 | width: 40, 298 | child: Column( 299 | crossAxisAlignment: CrossAxisAlignment.center, 300 | mainAxisAlignment: MainAxisAlignment.center, 301 | children: [ 302 | SizedBox( 303 | height: 10, 304 | width: 10, 305 | child: new DecoratedBox( 306 | decoration: new BoxDecoration( 307 | color: document.get('online') == 'online' 308 | ? Colors.greenAccent 309 | : Colors.red), 310 | ), 311 | ), 312 | ], 313 | ), 314 | ), 315 | SizedBox( 316 | width: 5, 317 | ), 318 | StreamBuilder( 319 | stream: getUnread(document.get('userId')), 320 | builder: (context, 321 | AsyncSnapshot unreadData) { 322 | int unreadMsg = 323 | 0; //unreadData.data.snapshot.docs.length -1; 324 | if (unreadData.hasData && 325 | unreadData.data.snapshot.docs.isNotEmpty) { 326 | for (int i = 0; 327 | i < unreadData.data.snapshot.docs.length; 328 | i++) { 329 | try { 330 | if (int.parse(unreadData.data.lastSeen 331 | .toString()) < 332 | int.parse(unreadData.data.snapshot 333 | .docs[i]['timestamp'] 334 | .toString()) && 335 | unreadData 336 | .data.snapshot.docs[i]['idFrom'] 337 | .toString() != 338 | currentUserId.toString()) 339 | unreadMsg = unreadMsg + 1; 340 | } catch (ex) { 341 | print('exception' + ex.toString()); 342 | } 343 | } 344 | } 345 | return unreadMsg > 0 346 | ? Container( 347 | height: 40, 348 | width: 40, 349 | child: Column( 350 | crossAxisAlignment: 351 | CrossAxisAlignment.center, 352 | mainAxisAlignment: 353 | MainAxisAlignment.center, 354 | children: [ 355 | Text(unreadMsg.toString(), 356 | style: TextStyle( 357 | fontSize: 16, 358 | color: Colors.black, 359 | fontWeight: 360 | FontWeight.normal)), 361 | ], 362 | ), 363 | //: Container(width: 0, height: 0), 364 | 365 | decoration: BoxDecoration( 366 | border: Border.all(width: 2), 367 | shape: BoxShape.circle, 368 | color: Colors.amber, 369 | ), 370 | ) 371 | : SizedBox( 372 | height: 10, 373 | ); 374 | }), 375 | ], 376 | ), 377 | onTap: () { 378 | Navigator.push( 379 | context, 380 | MaterialPageRoute( 381 | builder: (context) => Chat( 382 | currentUserId: widget.currentUserId, 383 | peerId: document.get('userId'), 384 | peerName: document.get('nickname'), 385 | peerAvatar: document.get('photoUrl'), 386 | ))); 387 | }, 388 | ); 389 | }).toList()); 390 | }, 391 | ), 392 | ); 393 | } 394 | 395 | Stream getUnread(var peerId) { 396 | var id = widget.currentUserId ?? ''; 397 | var groupChatId = ChatData.getGroupChatID(id, peerId); 398 | 399 | //ChatDBFireStore().setChatLastSeen(widget.currentUserId,'messages',groupChatId); 400 | //ChatDBFireStore().setChatLastSeen(peerId,'messages',groupChatId); 401 | 402 | try { 403 | print('unreadData ' + groupChatId); 404 | var controller = StreamController.broadcast(); 405 | 406 | unreadSubscriptions.add(FirebaseFirestore.instance 407 | .collection('messages') 408 | .doc(groupChatId) 409 | .snapshots() 410 | .listen((doc) { 411 | if (doc[id] != null) { 412 | unreadSubscriptions.add(FirebaseFirestore.instance 413 | .collection('messages') 414 | .doc(groupChatId) 415 | .collection(groupChatId) 416 | .snapshots() 417 | .listen((snapshot) { 418 | controller.add(MessageData(snapshot: snapshot, lastSeen: doc[id])); 419 | })); 420 | } 421 | })); 422 | controllers.add(controller); 423 | return controller.stream; 424 | } catch (e) { 425 | print('unreadExcept' + e.toString()); 426 | } 427 | return null; 428 | } 429 | 430 | Stream streamFriendList() { 431 | return FirebaseFirestore.instance 432 | .collection(ChatDBFireStore.getDocName()) 433 | .where('userId', whereIn: friendList) 434 | .snapshots(); 435 | 436 | // friendList = documentSnapshot.data()['friends']; 437 | } 438 | } 439 | 440 | class MessageData { 441 | int lastSeen; 442 | QuerySnapshot snapshot; 443 | MessageData({@required this.snapshot, @required this.lastSeen}); 444 | } 445 | 446 | class _SystemPadding extends StatelessWidget { 447 | final Widget child; 448 | 449 | _SystemPadding({Key key, this.child}) : super(key: key); 450 | 451 | @override 452 | Widget build(BuildContext context) { 453 | var mediaQuery = MediaQuery.of(context); 454 | return new AnimatedContainer( 455 | padding: mediaQuery.viewInsets / 2, 456 | duration: const Duration(milliseconds: 300), 457 | child: child); 458 | } 459 | } 460 | 461 | class Choice { 462 | const Choice({this.title, this.icon}); 463 | 464 | final String title; 465 | final IconData icon; 466 | } 467 | -------------------------------------------------------------------------------- /lib/screens/login_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import '../chatWidget.dart'; 3 | 4 | class LoginScreen extends StatefulWidget { 5 | static const String id = "login_screen"; 6 | 7 | @override 8 | _LoginScreenState createState() => _LoginScreenState(); 9 | } 10 | 11 | class _LoginScreenState extends State { 12 | bool isLoggedIn = false; 13 | 14 | @override 15 | void initState() { 16 | super.initState(); 17 | } 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | return Scaffold( 22 | backgroundColor: Colors.white, 23 | body: ChatWidget.widgetLoginScreen(context)); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/screens/zoomImage.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import '../chatWidget.dart'; 3 | import '../constants.dart'; 4 | 5 | class ZoomImage extends StatelessWidget { 6 | final String url; 7 | static const String id = "ZoomImage"; 8 | 9 | ZoomImage({Key key, @required this.url}) : super(key: key); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return new Scaffold( 14 | backgroundColor: Colors.white, 15 | appBar: new AppBar( 16 | title: new Text( 17 | 'FULL PHOTO', 18 | style: TextStyle(color: primaryColor, fontWeight: FontWeight.bold), 19 | ), 20 | centerTitle: true, 21 | ), 22 | body: new ZoomImageScreen(url: url), 23 | ); 24 | } 25 | } 26 | 27 | class ZoomImageScreen extends StatefulWidget { 28 | final String url; 29 | 30 | ZoomImageScreen({Key key, @required this.url}) : super(key: key); 31 | 32 | @override 33 | State createState() => new ZoomImageScreenState(url: url); 34 | } 35 | 36 | class ZoomImageScreenState extends State { 37 | final String url; 38 | 39 | ZoomImageScreenState({Key key, @required this.url}); 40 | 41 | @override 42 | void initState() { 43 | super.initState(); 44 | } 45 | 46 | @override 47 | Widget build(BuildContext context) { 48 | return ChatWidget.widgetFullPhoto(context, url); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | archive: 5 | dependency: transitive 6 | description: 7 | name: archive 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "2.0.13" 11 | args: 12 | dependency: transitive 13 | description: 14 | name: args 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "1.6.0" 18 | async: 19 | dependency: transitive 20 | description: 21 | name: async 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "2.5.0" 25 | boolean_selector: 26 | dependency: transitive 27 | description: 28 | name: boolean_selector 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "2.1.0" 32 | cached_network_image: 33 | dependency: "direct main" 34 | description: 35 | name: cached_network_image 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "2.5.1" 39 | characters: 40 | dependency: transitive 41 | description: 42 | name: characters 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.1.0" 46 | charcode: 47 | dependency: transitive 48 | description: 49 | name: charcode 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "1.2.0" 53 | clock: 54 | dependency: transitive 55 | description: 56 | name: clock 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "1.1.0" 60 | cloud_firestore: 61 | dependency: "direct main" 62 | description: 63 | name: cloud_firestore 64 | url: "https://pub.dartlang.org" 65 | source: hosted 66 | version: "2.2.2" 67 | cloud_firestore_platform_interface: 68 | dependency: transitive 69 | description: 70 | name: cloud_firestore_platform_interface 71 | url: "https://pub.dartlang.org" 72 | source: hosted 73 | version: "5.1.2" 74 | cloud_firestore_web: 75 | dependency: transitive 76 | description: 77 | name: cloud_firestore_web 78 | url: "https://pub.dartlang.org" 79 | source: hosted 80 | version: "2.1.2" 81 | collection: 82 | dependency: transitive 83 | description: 84 | name: collection 85 | url: "https://pub.dartlang.org" 86 | source: hosted 87 | version: "1.15.0" 88 | convert: 89 | dependency: transitive 90 | description: 91 | name: convert 92 | url: "https://pub.dartlang.org" 93 | source: hosted 94 | version: "2.1.1" 95 | crypto: 96 | dependency: transitive 97 | description: 98 | name: crypto 99 | url: "https://pub.dartlang.org" 100 | source: hosted 101 | version: "2.1.5" 102 | cupertino_icons: 103 | dependency: "direct main" 104 | description: 105 | name: cupertino_icons 106 | url: "https://pub.dartlang.org" 107 | source: hosted 108 | version: "1.0.0" 109 | fake_async: 110 | dependency: transitive 111 | description: 112 | name: fake_async 113 | url: "https://pub.dartlang.org" 114 | source: hosted 115 | version: "1.2.0" 116 | ffi: 117 | dependency: transitive 118 | description: 119 | name: ffi 120 | url: "https://pub.dartlang.org" 121 | source: hosted 122 | version: "1.0.0" 123 | file: 124 | dependency: transitive 125 | description: 126 | name: file 127 | url: "https://pub.dartlang.org" 128 | source: hosted 129 | version: "6.1.0" 130 | firebase_auth: 131 | dependency: "direct main" 132 | description: 133 | name: firebase_auth 134 | url: "https://pub.dartlang.org" 135 | source: hosted 136 | version: "1.1.3" 137 | firebase_auth_platform_interface: 138 | dependency: transitive 139 | description: 140 | name: firebase_auth_platform_interface 141 | url: "https://pub.dartlang.org" 142 | source: hosted 143 | version: "4.2.1" 144 | firebase_auth_web: 145 | dependency: transitive 146 | description: 147 | name: firebase_auth_web 148 | url: "https://pub.dartlang.org" 149 | source: hosted 150 | version: "1.1.1" 151 | firebase_core: 152 | dependency: "direct main" 153 | description: 154 | name: firebase_core 155 | url: "https://pub.dartlang.org" 156 | source: hosted 157 | version: "1.3.0" 158 | firebase_core_platform_interface: 159 | dependency: transitive 160 | description: 161 | name: firebase_core_platform_interface 162 | url: "https://pub.dartlang.org" 163 | source: hosted 164 | version: "4.0.1" 165 | firebase_core_web: 166 | dependency: transitive 167 | description: 168 | name: firebase_core_web 169 | url: "https://pub.dartlang.org" 170 | source: hosted 171 | version: "1.1.0" 172 | firebase_database: 173 | dependency: "direct main" 174 | description: 175 | name: firebase_database 176 | url: "https://pub.dartlang.org" 177 | source: hosted 178 | version: "6.1.2" 179 | firebase_messaging: 180 | dependency: "direct main" 181 | description: 182 | name: firebase_messaging 183 | url: "https://pub.dartlang.org" 184 | source: hosted 185 | version: "10.0.2" 186 | firebase_messaging_platform_interface: 187 | dependency: transitive 188 | description: 189 | name: firebase_messaging_platform_interface 190 | url: "https://pub.dartlang.org" 191 | source: hosted 192 | version: "3.0.2" 193 | firebase_messaging_web: 194 | dependency: transitive 195 | description: 196 | name: firebase_messaging_web 197 | url: "https://pub.dartlang.org" 198 | source: hosted 199 | version: "2.0.2" 200 | firebase_storage: 201 | dependency: "direct main" 202 | description: 203 | name: firebase_storage 204 | url: "https://pub.dartlang.org" 205 | source: hosted 206 | version: "8.0.5" 207 | firebase_storage_platform_interface: 208 | dependency: transitive 209 | description: 210 | name: firebase_storage_platform_interface 211 | url: "https://pub.dartlang.org" 212 | source: hosted 213 | version: "2.0.3" 214 | firebase_storage_web: 215 | dependency: transitive 216 | description: 217 | name: firebase_storage_web 218 | url: "https://pub.dartlang.org" 219 | source: hosted 220 | version: "1.0.5" 221 | flutter: 222 | dependency: "direct main" 223 | description: flutter 224 | source: sdk 225 | version: "0.0.0" 226 | flutter_blurhash: 227 | dependency: transitive 228 | description: 229 | name: flutter_blurhash 230 | url: "https://pub.dartlang.org" 231 | source: hosted 232 | version: "0.5.0" 233 | flutter_cache_manager: 234 | dependency: transitive 235 | description: 236 | name: flutter_cache_manager 237 | url: "https://pub.dartlang.org" 238 | source: hosted 239 | version: "2.1.2" 240 | flutter_linkify: 241 | dependency: "direct main" 242 | description: 243 | name: flutter_linkify 244 | url: "https://pub.dartlang.org" 245 | source: hosted 246 | version: "4.1.0" 247 | flutter_local_notifications: 248 | dependency: "direct main" 249 | description: 250 | name: flutter_local_notifications 251 | url: "https://pub.dartlang.org" 252 | source: hosted 253 | version: "5.0.0+4" 254 | flutter_local_notifications_platform_interface: 255 | dependency: transitive 256 | description: 257 | name: flutter_local_notifications_platform_interface 258 | url: "https://pub.dartlang.org" 259 | source: hosted 260 | version: "3.0.0" 261 | flutter_native_image: 262 | dependency: "direct main" 263 | description: 264 | name: flutter_native_image 265 | url: "https://pub.dartlang.org" 266 | source: hosted 267 | version: "0.0.5+2" 268 | flutter_plugin_android_lifecycle: 269 | dependency: transitive 270 | description: 271 | name: flutter_plugin_android_lifecycle 272 | url: "https://pub.dartlang.org" 273 | source: hosted 274 | version: "2.0.1" 275 | flutter_test: 276 | dependency: "direct dev" 277 | description: flutter 278 | source: sdk 279 | version: "0.0.0" 280 | flutter_web_plugins: 281 | dependency: transitive 282 | description: flutter 283 | source: sdk 284 | version: "0.0.0" 285 | fluttertoast: 286 | dependency: "direct main" 287 | description: 288 | name: fluttertoast 289 | url: "https://pub.dartlang.org" 290 | source: hosted 291 | version: "7.1.8" 292 | google_sign_in: 293 | dependency: "direct main" 294 | description: 295 | name: google_sign_in 296 | url: "https://pub.dartlang.org" 297 | source: hosted 298 | version: "5.0.2" 299 | google_sign_in_platform_interface: 300 | dependency: transitive 301 | description: 302 | name: google_sign_in_platform_interface 303 | url: "https://pub.dartlang.org" 304 | source: hosted 305 | version: "2.0.1" 306 | google_sign_in_web: 307 | dependency: transitive 308 | description: 309 | name: google_sign_in_web 310 | url: "https://pub.dartlang.org" 311 | source: hosted 312 | version: "0.10.0" 313 | http: 314 | dependency: "direct main" 315 | description: 316 | name: http 317 | url: "https://pub.dartlang.org" 318 | source: hosted 319 | version: "0.13.3" 320 | http_parser: 321 | dependency: transitive 322 | description: 323 | name: http_parser 324 | url: "https://pub.dartlang.org" 325 | source: hosted 326 | version: "4.0.0" 327 | image: 328 | dependency: transitive 329 | description: 330 | name: image 331 | url: "https://pub.dartlang.org" 332 | source: hosted 333 | version: "2.1.19" 334 | image_picker: 335 | dependency: "direct main" 336 | description: 337 | name: image_picker 338 | url: "https://pub.dartlang.org" 339 | source: hosted 340 | version: "0.7.4" 341 | image_picker_for_web: 342 | dependency: "direct main" 343 | description: 344 | name: image_picker_for_web 345 | url: "https://pub.dartlang.org" 346 | source: hosted 347 | version: "2.0.0" 348 | image_picker_platform_interface: 349 | dependency: transitive 350 | description: 351 | name: image_picker_platform_interface 352 | url: "https://pub.dartlang.org" 353 | source: hosted 354 | version: "2.1.0" 355 | intl: 356 | dependency: "direct main" 357 | description: 358 | name: intl 359 | url: "https://pub.dartlang.org" 360 | source: hosted 361 | version: "0.17.0" 362 | js: 363 | dependency: transitive 364 | description: 365 | name: js 366 | url: "https://pub.dartlang.org" 367 | source: hosted 368 | version: "0.6.3" 369 | linkify: 370 | dependency: transitive 371 | description: 372 | name: linkify 373 | url: "https://pub.dartlang.org" 374 | source: hosted 375 | version: "3.0.0" 376 | matcher: 377 | dependency: transitive 378 | description: 379 | name: matcher 380 | url: "https://pub.dartlang.org" 381 | source: hosted 382 | version: "0.12.10" 383 | meta: 384 | dependency: transitive 385 | description: 386 | name: meta 387 | url: "https://pub.dartlang.org" 388 | source: hosted 389 | version: "1.3.0" 390 | octo_image: 391 | dependency: transitive 392 | description: 393 | name: octo_image 394 | url: "https://pub.dartlang.org" 395 | source: hosted 396 | version: "0.3.0" 397 | path: 398 | dependency: "direct main" 399 | description: 400 | name: path 401 | url: "https://pub.dartlang.org" 402 | source: hosted 403 | version: "1.8.0" 404 | path_provider: 405 | dependency: transitive 406 | description: 407 | name: path_provider 408 | url: "https://pub.dartlang.org" 409 | source: hosted 410 | version: "2.0.1" 411 | path_provider_linux: 412 | dependency: transitive 413 | description: 414 | name: path_provider_linux 415 | url: "https://pub.dartlang.org" 416 | source: hosted 417 | version: "2.0.0" 418 | path_provider_macos: 419 | dependency: transitive 420 | description: 421 | name: path_provider_macos 422 | url: "https://pub.dartlang.org" 423 | source: hosted 424 | version: "2.0.0" 425 | path_provider_platform_interface: 426 | dependency: transitive 427 | description: 428 | name: path_provider_platform_interface 429 | url: "https://pub.dartlang.org" 430 | source: hosted 431 | version: "2.0.1" 432 | path_provider_windows: 433 | dependency: transitive 434 | description: 435 | name: path_provider_windows 436 | url: "https://pub.dartlang.org" 437 | source: hosted 438 | version: "2.0.1" 439 | pedantic: 440 | dependency: transitive 441 | description: 442 | name: pedantic 443 | url: "https://pub.dartlang.org" 444 | source: hosted 445 | version: "1.11.0" 446 | petitparser: 447 | dependency: transitive 448 | description: 449 | name: petitparser 450 | url: "https://pub.dartlang.org" 451 | source: hosted 452 | version: "3.1.0" 453 | photo_view: 454 | dependency: "direct main" 455 | description: 456 | name: photo_view 457 | url: "https://pub.dartlang.org" 458 | source: hosted 459 | version: "0.11.1" 460 | platform: 461 | dependency: transitive 462 | description: 463 | name: platform 464 | url: "https://pub.dartlang.org" 465 | source: hosted 466 | version: "3.0.0" 467 | plugin_platform_interface: 468 | dependency: transitive 469 | description: 470 | name: plugin_platform_interface 471 | url: "https://pub.dartlang.org" 472 | source: hosted 473 | version: "2.0.0" 474 | process: 475 | dependency: transitive 476 | description: 477 | name: process 478 | url: "https://pub.dartlang.org" 479 | source: hosted 480 | version: "4.2.1" 481 | quiver: 482 | dependency: transitive 483 | description: 484 | name: quiver 485 | url: "https://pub.dartlang.org" 486 | source: hosted 487 | version: "3.0.1" 488 | rxdart: 489 | dependency: transitive 490 | description: 491 | name: rxdart 492 | url: "https://pub.dartlang.org" 493 | source: hosted 494 | version: "0.24.1" 495 | shared_preferences: 496 | dependency: "direct main" 497 | description: 498 | name: shared_preferences 499 | url: "https://pub.dartlang.org" 500 | source: hosted 501 | version: "2.0.5" 502 | shared_preferences_linux: 503 | dependency: transitive 504 | description: 505 | name: shared_preferences_linux 506 | url: "https://pub.dartlang.org" 507 | source: hosted 508 | version: "2.0.0" 509 | shared_preferences_macos: 510 | dependency: transitive 511 | description: 512 | name: shared_preferences_macos 513 | url: "https://pub.dartlang.org" 514 | source: hosted 515 | version: "2.0.0" 516 | shared_preferences_platform_interface: 517 | dependency: transitive 518 | description: 519 | name: shared_preferences_platform_interface 520 | url: "https://pub.dartlang.org" 521 | source: hosted 522 | version: "2.0.0" 523 | shared_preferences_web: 524 | dependency: transitive 525 | description: 526 | name: shared_preferences_web 527 | url: "https://pub.dartlang.org" 528 | source: hosted 529 | version: "2.0.0" 530 | shared_preferences_windows: 531 | dependency: transitive 532 | description: 533 | name: shared_preferences_windows 534 | url: "https://pub.dartlang.org" 535 | source: hosted 536 | version: "2.0.0" 537 | sky_engine: 538 | dependency: transitive 539 | description: flutter 540 | source: sdk 541 | version: "0.0.99" 542 | source_span: 543 | dependency: transitive 544 | description: 545 | name: source_span 546 | url: "https://pub.dartlang.org" 547 | source: hosted 548 | version: "1.8.0" 549 | sqflite: 550 | dependency: transitive 551 | description: 552 | name: sqflite 553 | url: "https://pub.dartlang.org" 554 | source: hosted 555 | version: "2.0.0+3" 556 | sqflite_common: 557 | dependency: transitive 558 | description: 559 | name: sqflite_common 560 | url: "https://pub.dartlang.org" 561 | source: hosted 562 | version: "2.0.0+2" 563 | stack_trace: 564 | dependency: transitive 565 | description: 566 | name: stack_trace 567 | url: "https://pub.dartlang.org" 568 | source: hosted 569 | version: "1.10.0" 570 | stream_channel: 571 | dependency: transitive 572 | description: 573 | name: stream_channel 574 | url: "https://pub.dartlang.org" 575 | source: hosted 576 | version: "2.1.0" 577 | string_scanner: 578 | dependency: transitive 579 | description: 580 | name: string_scanner 581 | url: "https://pub.dartlang.org" 582 | source: hosted 583 | version: "1.1.0" 584 | synchronized: 585 | dependency: transitive 586 | description: 587 | name: synchronized 588 | url: "https://pub.dartlang.org" 589 | source: hosted 590 | version: "3.0.0" 591 | term_glyph: 592 | dependency: transitive 593 | description: 594 | name: term_glyph 595 | url: "https://pub.dartlang.org" 596 | source: hosted 597 | version: "1.2.0" 598 | test_api: 599 | dependency: transitive 600 | description: 601 | name: test_api 602 | url: "https://pub.dartlang.org" 603 | source: hosted 604 | version: "0.2.19" 605 | timezone: 606 | dependency: transitive 607 | description: 608 | name: timezone 609 | url: "https://pub.dartlang.org" 610 | source: hosted 611 | version: "0.7.0" 612 | typed_data: 613 | dependency: transitive 614 | description: 615 | name: typed_data 616 | url: "https://pub.dartlang.org" 617 | source: hosted 618 | version: "1.3.0" 619 | url_launcher: 620 | dependency: "direct main" 621 | description: 622 | name: url_launcher 623 | url: "https://pub.dartlang.org" 624 | source: hosted 625 | version: "6.0.6" 626 | url_launcher_linux: 627 | dependency: transitive 628 | description: 629 | name: url_launcher_linux 630 | url: "https://pub.dartlang.org" 631 | source: hosted 632 | version: "2.0.0" 633 | url_launcher_macos: 634 | dependency: transitive 635 | description: 636 | name: url_launcher_macos 637 | url: "https://pub.dartlang.org" 638 | source: hosted 639 | version: "2.0.0" 640 | url_launcher_platform_interface: 641 | dependency: transitive 642 | description: 643 | name: url_launcher_platform_interface 644 | url: "https://pub.dartlang.org" 645 | source: hosted 646 | version: "2.0.3" 647 | url_launcher_web: 648 | dependency: transitive 649 | description: 650 | name: url_launcher_web 651 | url: "https://pub.dartlang.org" 652 | source: hosted 653 | version: "2.0.1" 654 | url_launcher_windows: 655 | dependency: transitive 656 | description: 657 | name: url_launcher_windows 658 | url: "https://pub.dartlang.org" 659 | source: hosted 660 | version: "2.0.0" 661 | uuid: 662 | dependency: transitive 663 | description: 664 | name: uuid 665 | url: "https://pub.dartlang.org" 666 | source: hosted 667 | version: "2.2.2" 668 | vector_math: 669 | dependency: transitive 670 | description: 671 | name: vector_math 672 | url: "https://pub.dartlang.org" 673 | source: hosted 674 | version: "2.1.0" 675 | win32: 676 | dependency: transitive 677 | description: 678 | name: win32 679 | url: "https://pub.dartlang.org" 680 | source: hosted 681 | version: "2.0.5" 682 | xdg_directories: 683 | dependency: transitive 684 | description: 685 | name: xdg_directories 686 | url: "https://pub.dartlang.org" 687 | source: hosted 688 | version: "0.2.0" 689 | xml: 690 | dependency: transitive 691 | description: 692 | name: xml 693 | url: "https://pub.dartlang.org" 694 | source: hosted 695 | version: "4.5.1" 696 | sdks: 697 | dart: ">=2.12.0 <3.0.0" 698 | flutter: ">=2.0.0" 699 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_chat 2 | description: Messaging app for Flutter android , iOS and flutter-web using firebase as backend services. 3 | 4 | version: 2.0.2 5 | homepage: https://github.com/ankesh-kumar/Flutter-chat-sdk 6 | 7 | environment: 8 | sdk: ">=2.7.0 <3.0.0" 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | 14 | 15 | # The following adds the Cupertino Icons font to your application. 16 | # Use with the CupertinoIcons class for iOS style icons. 17 | cupertino_icons: ^1.0.0 18 | shared_preferences: ^2.0.3 19 | firebase_core: ^1.0.1 20 | firebase_auth: ^1.0.1 21 | firebase_storage: ^8.0.0 22 | cloud_firestore: ^2.2.2 23 | fluttertoast: ^7.1.8 24 | photo_view: ^0.11.1 25 | google_sign_in: ^5.0.0 26 | flutter_native_image: ^0.0.4 27 | cached_network_image: ^2.5.1 28 | intl: ^0.17.0 29 | firebase_database: ^6.0.0 30 | image_picker: ^0.7.2+1 31 | image_picker_for_web: ^2.0.0 32 | path: ^1.8.0 33 | firebase_messaging: ^10.0.0 34 | flutter_local_notifications: ^5.0.0+2 35 | url_launcher: ^6.0.2 36 | flutter_linkify: ^4.0.2 37 | http: ^0.13.0 38 | 39 | dev_dependencies: 40 | flutter_test: 41 | sdk: flutter 42 | 43 | # For information on the generic Dart part of this file, see the 44 | # following page: https://dart.dev/tools/pub/pubspec 45 | 46 | # The following section is specific to Flutter. 47 | flutter: 48 | 49 | # The following line ensures that the Material Icons font is 50 | # included with your application, so that you can use the icons in 51 | # the material Icons class. 52 | uses-material-design: true 53 | 54 | 55 | assets: 56 | - images/ 57 | 58 | # To add assets to your application, add an assets section, like this: 59 | # assets: 60 | # - images/a_dot_burr.jpeg 61 | # - images/a_dot_ham.jpeg 62 | 63 | # An image asset can refer to one or more resolution-specific "variants", see 64 | # https://flutter.dev/assets-and-images/#resolution-aware. 65 | 66 | # For details regarding adding assets from package dependencies, see 67 | # https://flutter.dev/assets-and-images/#from-packages 68 | 69 | # To add custom fonts to your application, add a fonts section here, 70 | # in this "flutter" section. Each entry in this list should have a 71 | # "family" key with the font family name, and a "fonts" key with a 72 | # list giving the asset and other descriptors for the font. For 73 | # example: 74 | # fonts: 75 | # - family: Schyler 76 | # fonts: 77 | # - asset: fonts/Schyler-Regular.ttf 78 | # - asset: fonts/Schyler-Italic.ttf 79 | # style: italic 80 | # - family: Trajan Pro 81 | # fonts: 82 | # - asset: fonts/TrajanPro.ttf 83 | # - asset: fonts/TrajanPro_Bold.ttf 84 | # weight: 700 85 | # 86 | # For details regarding fonts from package dependencies, 87 | # see https://flutter.dev/custom-fonts/#from-packages 88 | --------------------------------------------------------------------------------