Skip to content

Commit

Permalink
✨(feat) Add fcm notification on tweet user tag.
Browse files Browse the repository at this point in the history
1.✨A notification will send to user if someone tag him in a tweet.
2.After tapping on notification from system notification tray, you will redirect to user profile who tagged you.
3.:bulb: (doc) Documenting source code
  • Loading branch information
TheAlphamerc committed Apr 26, 2020
1 parent 8103bf9 commit d9bfdc4
Show file tree
Hide file tree
Showing 8 changed files with 199 additions and 24 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
**/android/local.properties
**/android/**/GeneratedPluginRegistrant.java
**/android/app/google-services.json
**/android/app/key.jks

# iOS/XCode related
**/ios/**/*.mode1v3
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
* Three tabs are added to filter tweet and commnet tweet and tweet with media on profile page.
* User profile pic view added to view profile picture of any user.
* User can tag other users in tweet, comment and in retweet.
* A notification will send to you if someone tag you in a tweet.
* After tapping on notification from system notification tray, you will redirect to user profile who tagged you.
* You can check the tweet on his profile.

## [1.0.5] - 15 Apr 2020

Expand Down
1 change: 1 addition & 0 deletions lib/helper/enum.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,5 @@ enum NotificationType{
Reply,
Retweet,
Follow,
Mention
}
51 changes: 43 additions & 8 deletions lib/page/feed/composeTweet/composeTweet.dart
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ class _ComposeTweetReplyPageState extends State<ComposeTweetPage> {
});
}

/// Submit tweet to save in firebase database
void _submitButton() async {
if (_textEditingController.text == null ||
_textEditingController.text.isEmpty ||
Expand All @@ -89,30 +90,65 @@ class _ComposeTweetReplyPageState extends State<ComposeTweetPage> {
kScreenloader.showLoader(context);

FeedModel tweetModel = createTweetModel();

/// If tweet contain image
/// First image is uploaded on firebase storage
/// After sucessfull image upload to firebase storage it returns image path
/// Add this image path to tweet model and save to firebase database
if (_image != null) {
await state.uploadFile(_image).then((imagePath) {
if (imagePath != null) {
tweetModel.imagePath = imagePath;

/// If type of tweet is new tweet
if (widget.isTweet) {
state.createTweet(tweetModel);
} else if (widget.isRetweet) {
}

/// If type of tweet is retweet
else if (widget.isRetweet) {
state.createReTweet(tweetModel);
} else {
}

/// If type of tweet is new comment tweet
else {
state.addcommentToPost(tweetModel);
}
}
});
} else {
}

/// If tweet did not contain image
else {
/// If type of tweet is new tweet
if (widget.isTweet) {
state.createTweet(tweetModel);
} else if (widget.isRetweet) {
}

/// If type of tweet is retweet
else if (widget.isRetweet) {
state.createReTweet(tweetModel);
} else {
}

/// If type of tweet is new comment tweet
else {
state.addcommentToPost(tweetModel);
}
}
kScreenloader.hideLoader();
Navigator.pop(context);

/// Checks for username in tweet description
/// If foud sends notification to all tagged user
/// If no user found or not compost tweet screen is closed and redirect back to home page.
await Provider.of<ComposeTweetState>(context, listen: false)
.sendNotification(
tweetModel, Provider.of<SearchState>(context, listen: false))
.then((_) {
/// Hide running loader on screen
kScreenloader.hideLoader();

/// Navigate back to home page
Navigator.pop(context);
});
}

/// Return Tweet model which is either a new Tweet , retweet model or comment model
Expand Down Expand Up @@ -147,7 +183,6 @@ class _ComposeTweetReplyPageState extends State<ComposeTweetPage> {

@override
Widget build(BuildContext context) {
// final composeState = Provider.of<ComposeTweetState>(context);
return Scaffold(
appBar: CustomAppBar(
title: customTitleText(''),
Expand Down
97 changes: 94 additions & 3 deletions lib/page/feed/composeTweet/state/composeTweetState.dart
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
import 'dart:convert';
import 'package:flutter_twitter_clone/helper/utility.dart';
import 'package:flutter_twitter_clone/model/user.dart';
import 'package:http/http.dart' as http;
import 'package:firebase_remote_config/firebase_remote_config.dart';
import 'package:flutter/material.dart';
import 'package:flutter_twitter_clone/helper/enum.dart';
import 'package:flutter_twitter_clone/model/feedModel.dart';
import 'package:flutter_twitter_clone/state/searchState.dart';

class ComposeTweetState extends ChangeNotifier {
bool showUserList = false;
bool enableSubmitButton = false;
bool hideUserList = false;
String description = "";
String serverToken;
final usernameRegex = r'(@\w*[a-zA-Z1-9]$)';

bool _isScrollingDown = false;
bool get isScrollingDown => _isScrollingDown;
set setIsScrolllingDown(bool value){
bool _isScrollingDown = false;
bool get isScrollingDown => _isScrollingDown;
set setIsScrolllingDown(bool value) {
_isScrollingDown = value;
notifyListeners();
}
Expand Down Expand Up @@ -79,4 +87,87 @@ class ComposeTweetState extends ChangeNotifier {
description = '$name $username';
return description;
}

/// Fecth FCM server key from firebase Remote config
Future<Null> getFCMServerKey() async {
final RemoteConfig remoteConfig = await RemoteConfig.instance;
await remoteConfig.fetch(expiration: const Duration(hours: 5));
await remoteConfig.activateFetched();
var data = remoteConfig.getString('FcmServerKey');
if (data != null) {
serverToken = jsonDecode(data)["key"];
}
}

Future<void> sendNotification(FeedModel model, SearchState state) async {
final usernameRegex = r"(@\w*[a-zA-Z1-9])";
RegExp regExp = new RegExp(usernameRegex);
var status = regExp.hasMatch(description);
if (status) {
/// Fecth FCM server key from firebase Remote config
/// send notification to user once fcmToken is retrieved from firebase
getFCMServerKey().then((val) async {
/// Reset userlist
state.filterByUsername("");

/// Search all username from description
Iterable<Match> _matches = regExp.allMatches(description);
print("${_matches.length} name found in description");

/// Send notification to user one by one
await Future.forEach(_matches, (Match match) async {
var name = description.substring(match.start, match.end);
if (state.userlist.any((x) => x.userName == name)) {
/// Fetch user model from userlist
/// UserId, FCMtoken is needed to send notification
final user = state.userlist.firstWhere((x) => x.userName == name);
await sendNotificationToUser(model, user);
} else {
cprint("Name: $name ,", errorIn: "UserNot found");
}
});
});
}
}

/// Send notificatinn by using firebase notification rest api;
Future<void> sendNotificationToUser(FeedModel model, User user) async {
print("Send notification to: ${user.userName}");

/// Return from here if fcmToken is null
if (user.fcmToken == null) {
return;
}

/// Create notification payload
var body = jsonEncode(<String, dynamic>{
'notification': <String, dynamic>{
'body': model.description,
'title': "${model.user.displayName} metioned you in a tweet"
},
'priority': 'high',
'data': <String, dynamic>{
'click_action': 'FLUTTER_NOTIFICATION_CLICK',
'id': '1',
'status': 'done',
"type": NotificationType.Mention.toString(),
"senderId": model.user.userId,
"receiverId": user.userId,
"title": "title",
"body": "",
"tweetId": ""
},
'to': user.fcmToken
});

var response = await http.post(
'https://fcm.googleapis.com/fcm/send',
headers: <String, String>{
'Content-Type': 'application/json',
'Authorization': 'key=$serverToken',
},
body: body,
);
cprint(response.body.toString());
}
}
44 changes: 38 additions & 6 deletions lib/page/homePage.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class HomePage extends StatefulWidget {

class _HomePageState extends State<HomePage> {
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
final refreshIndicatorKey =new GlobalKey<RefreshIndicatorState>();
final refreshIndicatorKey = new GlobalKey<RefreshIndicatorState>();
int pageIndex = 0;
@override
void initState() {
Expand Down Expand Up @@ -66,28 +66,60 @@ class _HomePageState extends State<HomePage> {
final chatState = Provider.of<ChatState>(context, listen: false);
final state = Provider.of<AuthState>(context, listen: false);
chatState.databaseInit(state.userId, state.userId);
/// It will update fcm token in database
/// fcm token is required to send firebase notification
state.updateFCMToken();
/// It get fcm server key
/// Server key is required to configure firebase notification
/// Without fcm server notification can not be sent
chatState.getFCMServerKey();
}

Widget _body() {

/// On app launch it checks if app is launch by tapping on notification from notification tray
/// If yes, it checks for which type of notification is recieve
/// If notification type is `NotificationType.Message` then chat screen will open
/// If notification type is `NotificationType.Mention` then user profile will open who taged you in a tweet
///
void _checkNotification() {
final authstate = Provider.of<AuthState>(context, listen: false);
WidgetsBinding.instance.addPostFrameCallback((_) {
var state = Provider.of<NotificationState>(context);

/// Check if user recieve chat notification from firebase
/// Redirect to chat screen
if (state.notificationType == NotificationType.Message && state.notificationReciverId == authstate.userModel.userId) {
state.setrNotificationType = null;
/// `notificationSenderId` is a user id who sends you a message
/// `notificationReciverId` is a your user id.
if (state.notificationType == NotificationType.Message &&
state.notificationReciverId == authstate.userModel.userId) {
state.setNotificationType = null;
state.getuserDetail(state.notificationSenderId).then((user) {
cprint("Opening user chat screen");
final chatState = Provider.of<ChatState>(context, listen: false);
chatState.setChatUser = user;
Navigator.pushNamed(context, '/ChatScreenPage');
});
}

/// Checks for user tag tweet notification
/// If you are mentioned in tweet then it redirect to user profile who mentioed you in a tweet
/// You can check that tweet on his profile timeline
/// `notificationSenderId` is user id who tagged you in a tweet
else if (state.notificationType == NotificationType.Mention &&
state.notificationReciverId == authstate.userModel.userId) {
state.setNotificationType = null;
Navigator.of(context)
.pushNamed('/ProfilePage/' + state.notificationSenderId);
}
});
return SafeArea(child: Container(child: _getPage(Provider.of<AppState>(context).pageIndex)));
}

Widget _body() {
_checkNotification();
return SafeArea(
child: Container(
child: _getPage(Provider.of<AppState>(context).pageIndex),
),
);
}

Widget _getPage(int index) {
Expand Down
1 change: 0 additions & 1 deletion lib/state/chats/chatState.dart
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,6 @@ class ChatState extends AppState {
}

void onMessageSubmitted(ChatMessage message, {User myUser, User secondUser}) {
// print('2RhfEy0MPzdfOp9MtqtSDABau2d25kyFBK0vfDc3AkF6a8zfaudQOHw12RhfEy0MPzdfOp9MtqtSDABau2d25kyFBK0vfDc3AkF6a8zfaudQOHw1');
try {
if (_messageList == null || _messageList.length < 1) {
_database
Expand Down
25 changes: 19 additions & 6 deletions lib/state/notificationState.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ import 'package:flutter_twitter_clone/state/appState.dart';
class NotificationState extends AppState {
String fcmToken;
NotificationType _notificationType = NotificationType.NOT_DETERMINED;
String notificationReciverId;
String notificationReciverId, notificationTweetId;
FeedModel notificationTweetModel;
NotificationType get notificationType => _notificationType;
set setrNotificationType(NotificationType type){
set setNotificationType(NotificationType type){
_notificationType = type;
}
// FcmNotificationModel notification;
Expand Down Expand Up @@ -152,7 +153,7 @@ class NotificationState extends AppState {
print("Notification Removed");
}
}

/// Configure notification services
void initfirebaseService(){
_firebaseMessaging.configure(
onMessage: (Map<String, dynamic> message) async {
Expand All @@ -164,8 +165,15 @@ class NotificationState extends AppState {
cprint("Notification ",event: "onLaunch");
var data = message['data'];
// print(message['data']);
notificationSenderId = data["userId"];
setrNotificationType = NotificationType.Message;
notificationSenderId = data["senderId"];
notificationReciverId = data["receiverId"];
notificationReciverId = data["receiverId"];
if(data["type"] == "NotificationType.Mention"){
setNotificationType = NotificationType.Mention;
}
else if(data["type"] == "NotificationType.Message"){
setNotificationType = NotificationType.Message;
}
notifyListeners();
},
onResume: (Map<String, dynamic> message) async {
Expand All @@ -174,7 +182,12 @@ class NotificationState extends AppState {
// print(message['data']);
notificationSenderId = data["senderId"];
notificationReciverId = data["receiverId"];
setrNotificationType = NotificationType.Message;
if(data["type"] == "NotificationType.Mention"){
setNotificationType = NotificationType.Mention;
}
else if(data["type"] == "NotificationType.Message"){
setNotificationType = NotificationType.Message;
}
notifyListeners();
},
);
Expand Down

0 comments on commit d9bfdc4

Please sign in to comment.