add messaging
This commit is contained in:
@@ -148,6 +148,9 @@
|
|||||||
"pm.save.btn":"Save Payment Method",
|
"pm.save.btn":"Save Payment Method",
|
||||||
"pm.delete.confirm":"Delete this Payment Method?",
|
"pm.delete.confirm":"Delete this Payment Method?",
|
||||||
|
|
||||||
|
"message.view.detail":"View Deatil",
|
||||||
|
"message.hint.input":"Type your message...",
|
||||||
|
|
||||||
"btn.save": "Save",
|
"btn.save": "Save",
|
||||||
"btn.approve":"Approve",
|
"btn.approve":"Approve",
|
||||||
"btn.delete":"Delete",
|
"btn.delete":"Delete",
|
||||||
|
|||||||
@@ -150,6 +150,9 @@
|
|||||||
"pm.save.btn":"သိမ်းဆည်းရန်",
|
"pm.save.btn":"သိမ်းဆည်းရန်",
|
||||||
"pm.delete.confirm":"ငွေပေးချေစနစ်ကို ဖျက်မလား?",
|
"pm.delete.confirm":"ငွေပေးချေစနစ်ကို ဖျက်မလား?",
|
||||||
|
|
||||||
|
"message.view.detail":"အသေးစိတ် ကြည့်ရန်",
|
||||||
|
"message.hint.input":"စာကို ဒီမှာ ရိုက်ထည့်ပါ...",
|
||||||
|
|
||||||
"btn.save":"သိမ်းဆည်းရန်",
|
"btn.save":"သိမ်းဆည်းရန်",
|
||||||
"btn.approve":"အတည်ပြုရန်",
|
"btn.approve":"အတည်ပြုရန်",
|
||||||
"btn.delete":"ဖျက်ရန်",
|
"btn.delete":"ဖျက်ရန်",
|
||||||
|
|||||||
66
lib/app.dart
66
lib/app.dart
@@ -1,5 +1,3 @@
|
|||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:fcs/fcs/common/localization/app_translations_delegate.dart';
|
import 'package:fcs/fcs/common/localization/app_translations_delegate.dart';
|
||||||
import 'package:fcs/fcs/common/localization/transalation.dart';
|
import 'package:fcs/fcs/common/localization/transalation.dart';
|
||||||
import 'package:fcs/fcs/common/pages/contact/model/contact_model.dart';
|
import 'package:fcs/fcs/common/pages/contact/model/contact_model.dart';
|
||||||
@@ -14,7 +12,6 @@ import 'package:fcs/fcs/common/pages/package/model/shipment_model.dart';
|
|||||||
import 'package:fcs/fcs/common/pages/payment_methods/model/payment_method_model.dart';
|
import 'package:fcs/fcs/common/pages/payment_methods/model/payment_method_model.dart';
|
||||||
import 'package:fcs/fcs/common/pages/staff/model/staff_model.dart';
|
import 'package:fcs/fcs/common/pages/staff/model/staff_model.dart';
|
||||||
import 'package:fcs/fcs/common/pages/term/model/term_model.dart';
|
import 'package:fcs/fcs/common/pages/term/model/term_model.dart';
|
||||||
import 'package:fcs/fcs/common/services/services.dart';
|
|
||||||
import 'package:fcs/model/buyer_model.dart';
|
import 'package:fcs/model/buyer_model.dart';
|
||||||
import 'package:fcs/model/delivery_model.dart';
|
import 'package:fcs/model/delivery_model.dart';
|
||||||
import 'package:fcs/model/discount_model.dart';
|
import 'package:fcs/model/discount_model.dart';
|
||||||
@@ -25,7 +22,6 @@ import 'package:fcs/model/reg_model.dart';
|
|||||||
import 'package:fcs/model/report_model.dart';
|
import 'package:fcs/model/report_model.dart';
|
||||||
import 'package:fcs/model/storage_model.dart';
|
import 'package:fcs/model/storage_model.dart';
|
||||||
import 'package:fcs/model/test_model.dart';
|
import 'package:fcs/model/test_model.dart';
|
||||||
import 'package:fcs/model_fcs/message_model.dart';
|
|
||||||
import 'package:fcs/pages/email_page.dart';
|
import 'package:fcs/pages/email_page.dart';
|
||||||
import 'package:fcs/pages/login_page.dart';
|
import 'package:fcs/pages/login_page.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
@@ -34,6 +30,7 @@ import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
|||||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import 'fcs/common/pages/chat/model/message_model.dart';
|
||||||
import 'fcs/common/pages/home_page.dart';
|
import 'fcs/common/pages/home_page.dart';
|
||||||
import 'fcs/common/pages/splash_page.dart';
|
import 'fcs/common/pages/splash_page.dart';
|
||||||
import 'fcs/common/pages/welcome_page.dart';
|
import 'fcs/common/pages/welcome_page.dart';
|
||||||
@@ -41,7 +38,6 @@ import 'model/announcement_model.dart';
|
|||||||
import 'model/chart_model.dart';
|
import 'model/chart_model.dart';
|
||||||
import 'model/device_model.dart';
|
import 'model/device_model.dart';
|
||||||
import 'model/do_model.dart';
|
import 'model/do_model.dart';
|
||||||
import 'model/employee_model.dart';
|
|
||||||
import 'model/invoice_model.dart';
|
import 'model/invoice_model.dart';
|
||||||
import 'model/log_model.dart';
|
import 'model/log_model.dart';
|
||||||
import 'model/main_model.dart';
|
import 'model/main_model.dart';
|
||||||
@@ -110,6 +106,7 @@ class _AppState extends State<App> {
|
|||||||
..addModel(staffModel)
|
..addModel(staffModel)
|
||||||
..addModel(shipmentModel)
|
..addModel(shipmentModel)
|
||||||
..addModel(packageModel)
|
..addModel(packageModel)
|
||||||
|
..addModel(messageModel)
|
||||||
..addModel(marketModel);
|
..addModel(marketModel);
|
||||||
mainModel2.init();
|
mainModel2.init();
|
||||||
|
|
||||||
@@ -138,69 +135,10 @@ class _AppState extends State<App> {
|
|||||||
..addModel(pickUpModel)
|
..addModel(pickUpModel)
|
||||||
..addModel(shipmentRateModel)
|
..addModel(shipmentRateModel)
|
||||||
..addModel(boxModel)
|
..addModel(boxModel)
|
||||||
..addModel(messageModel)
|
|
||||||
..addModel(shipmentRateModel)
|
..addModel(shipmentRateModel)
|
||||||
..addModel(invoiceModel)
|
..addModel(invoiceModel)
|
||||||
..addModel(discountModel);
|
..addModel(discountModel);
|
||||||
this.mainModel.init();
|
this.mainModel.init();
|
||||||
|
|
||||||
_initLocalNotifications();
|
|
||||||
Services.instance.messagingService.init((message) {
|
|
||||||
print("Message from FCM:$message");
|
|
||||||
_showNotification(message);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_initLocalNotifications() {
|
|
||||||
var initializationSettingsAndroid =
|
|
||||||
new AndroidInitializationSettings('@mipmap/ic_launcher');
|
|
||||||
var initializationSettingsIOS = new IOSInitializationSettings();
|
|
||||||
var initializationSettings = new InitializationSettings(
|
|
||||||
initializationSettingsAndroid, initializationSettingsIOS);
|
|
||||||
_flutterLocalNotificationsPlugin.initialize(initializationSettings);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future _showNotification(Map<String, dynamic> message) async {
|
|
||||||
var pushTitle;
|
|
||||||
var pushText;
|
|
||||||
var action;
|
|
||||||
|
|
||||||
if (Platform.isAndroid) {
|
|
||||||
var nodeData = message['notification'];
|
|
||||||
pushTitle = nodeData['title'];
|
|
||||||
pushText = nodeData['body'];
|
|
||||||
action = nodeData['action'];
|
|
||||||
} else {
|
|
||||||
pushTitle = message['title'];
|
|
||||||
pushText = message['body'];
|
|
||||||
action = message['action'];
|
|
||||||
}
|
|
||||||
print("AppPushs params pushTitle : $pushTitle");
|
|
||||||
print("AppPushs params pushText : $pushText");
|
|
||||||
print("AppPushs params pushAction : $action");
|
|
||||||
|
|
||||||
// @formatter:off
|
|
||||||
var platformChannelSpecificsAndroid = new AndroidNotificationDetails(
|
|
||||||
'your channel id', 'your channel name', 'your channel description',
|
|
||||||
playSound: true,
|
|
||||||
enableVibration: true,
|
|
||||||
importance: Importance.Max,
|
|
||||||
priority: Priority.High);
|
|
||||||
// @formatter:on
|
|
||||||
var platformChannelSpecificsIos =
|
|
||||||
new IOSNotificationDetails(presentSound: true);
|
|
||||||
var platformChannelSpecifics = new NotificationDetails(
|
|
||||||
platformChannelSpecificsAndroid, platformChannelSpecificsIos);
|
|
||||||
|
|
||||||
new Future.delayed(Duration.zero, () {
|
|
||||||
_flutterLocalNotificationsPlugin.show(
|
|
||||||
0,
|
|
||||||
pushTitle,
|
|
||||||
pushText,
|
|
||||||
platformChannelSpecifics,
|
|
||||||
payload: 'No_Sound',
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void onLocaleChange(Locale locale) {
|
void onLocaleChange(Locale locale) {
|
||||||
|
|||||||
@@ -125,24 +125,24 @@ class AuthFb {
|
|||||||
|
|
||||||
log.info("Claims:${idToken.claims}");
|
log.info("Claims:${idToken.claims}");
|
||||||
|
|
||||||
User user = User();
|
String cid = idToken.claims["cid"];
|
||||||
user.status = idToken.claims["st"];
|
User user;
|
||||||
|
if (cid != null && cid != "") {
|
||||||
|
user = await getUserFromFirestore(cid);
|
||||||
|
}
|
||||||
|
if (user == null) {
|
||||||
|
user = User();
|
||||||
|
user.id = cid;
|
||||||
user.phoneNumber = firebaseUser.phoneNumber;
|
user.phoneNumber = firebaseUser.phoneNumber;
|
||||||
|
user.status = idToken.claims["st"];
|
||||||
|
}
|
||||||
|
|
||||||
// add privileges
|
// add privileges
|
||||||
String privileges = idToken.claims["pr"];
|
String privileges = idToken.claims["pr"];
|
||||||
if (privileges != null && privileges != "") {
|
if (privileges != null && privileges != "") {
|
||||||
user.privileges = privileges.split(":").toList();
|
user.privileges = privileges.split(":").toList();
|
||||||
}
|
}
|
||||||
String cid = idToken.claims["cid"];
|
|
||||||
if (cid != null && cid != "") {
|
|
||||||
User _user = await getUserFromFirestore(cid);
|
|
||||||
if (_user != null) {
|
|
||||||
user.id = cid;
|
|
||||||
user.fcsID = _user.fcsID;
|
|
||||||
user.name = _user.name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:fcs/fcs/common/domain/entities/payment_method.dart';
|
import 'package:fcs/fcs/common/domain/entities/payment_method.dart';
|
||||||
|
import 'package:fcs/fcs/common/domain/vo/message.dart';
|
||||||
import 'package:fcs/fcs/common/helpers/api_helper.dart';
|
import 'package:fcs/fcs/common/helpers/api_helper.dart';
|
||||||
import 'package:fcs/fcs/common/helpers/firebase_helper.dart';
|
import 'package:fcs/fcs/common/helpers/firebase_helper.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
@@ -20,4 +21,15 @@ class CommonDataProvider {
|
|||||||
return await requestAPI("/payment_methods", "DELETE",
|
return await requestAPI("/payment_methods", "DELETE",
|
||||||
payload: {"id": id}, token: await getToken());
|
payload: {"id": id}, token: await getToken());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> sendMessage(Message message) async {
|
||||||
|
return await requestAPI("/messages", "POST",
|
||||||
|
payload: message.toMap(), token: await getToken());
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> seenMessage(String ownerID, bool seenByOwner) async {
|
||||||
|
return await requestAPI("/messages/seen", "POST",
|
||||||
|
payload: {"owner_id": ownerID, "seen_by_owner": seenByOwner},
|
||||||
|
token: await getToken());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,8 @@ class MessagingFCM {
|
|||||||
|
|
||||||
FirebaseMessaging _firebaseMessaging;
|
FirebaseMessaging _firebaseMessaging;
|
||||||
|
|
||||||
MessagingFCM(OnNotify onMessage, {OnNotify onLaunch, OnNotify onResume}) {
|
MessagingFCM(OnNotify onMessage,
|
||||||
|
{OnNotify onLaunch, OnNotify onResume, OnSetupComplete onSetupComplete}) {
|
||||||
_firebaseMessaging = FirebaseMessaging();
|
_firebaseMessaging = FirebaseMessaging();
|
||||||
_firebaseMessaging.configure(
|
_firebaseMessaging.configure(
|
||||||
onMessage: (Map<String, dynamic> message) async {
|
onMessage: (Map<String, dynamic> message) async {
|
||||||
@@ -48,6 +49,7 @@ class MessagingFCM {
|
|||||||
log.info("Settings registered: $settings");
|
log.info("Settings registered: $settings");
|
||||||
});
|
});
|
||||||
_firebaseMessaging.getToken().then((String token) {
|
_firebaseMessaging.getToken().then((String token) {
|
||||||
|
if (onSetupComplete != null) onSetupComplete(token);
|
||||||
log.info("Messaging Token:$token");
|
log.info("Messaging Token:$token");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,16 @@ class UserDataProvider {
|
|||||||
payload: {"id": userID}, token: await getToken());
|
payload: {"id": userID}, token: await getToken());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> uploadMsgToken(String token) async {
|
||||||
|
return await requestAPI("/messages/token", "POST",
|
||||||
|
payload: {"token": token}, token: await getToken());
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> removeMsgToken(String token) async {
|
||||||
|
return await requestAPI("/messages/token", "DELETE",
|
||||||
|
payload: {"token": token}, token: await getToken());
|
||||||
|
}
|
||||||
|
|
||||||
Future<User> findUser(String phoneNumber) async {
|
Future<User> findUser(String phoneNumber) async {
|
||||||
QuerySnapshot querySnap = await Firestore.instance
|
QuerySnapshot querySnap = await Firestore.instance
|
||||||
.collection(user_collection)
|
.collection(user_collection)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ const setting_doc_id = "setting";
|
|||||||
const privilege_collection = "privileges";
|
const privilege_collection = "privileges";
|
||||||
const markets_collection = "markets";
|
const markets_collection = "markets";
|
||||||
const packages_collection = "packages";
|
const packages_collection = "packages";
|
||||||
|
const messages_collection = "messages";
|
||||||
|
|
||||||
const user_requested_status = "requested";
|
const user_requested_status = "requested";
|
||||||
const user_invited_status = "invited";
|
const user_invited_status = "invited";
|
||||||
@@ -14,6 +15,10 @@ const pkg_files_path = "/packages";
|
|||||||
// Link page
|
// Link page
|
||||||
const page_payment_methods = "payment_methods";
|
const page_payment_methods = "payment_methods";
|
||||||
const page_buying_instructions = "buying_instructions";
|
const page_buying_instructions = "buying_instructions";
|
||||||
|
|
||||||
|
// Message type
|
||||||
|
const message_type_package = "t_p";
|
||||||
|
|
||||||
//////////////////////////////
|
//////////////////////////////
|
||||||
|
|
||||||
const ok_doc_id = "ok";
|
const ok_doc_id = "ok";
|
||||||
|
|||||||
@@ -1,4 +1,10 @@
|
|||||||
|
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||||
import 'package:fcs/fcs/common/helpers/const.dart';
|
import 'package:fcs/fcs/common/helpers/const.dart';
|
||||||
|
import 'package:fcs/fcs/common/pages/package/package_info.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
|
DateFormat dayFormat = DateFormat("MMM dd yyyy");
|
||||||
|
DateFormat timeFormat = DateFormat("HH:mm");
|
||||||
|
|
||||||
class User {
|
class User {
|
||||||
String id;
|
String id;
|
||||||
@@ -6,6 +12,37 @@ class User {
|
|||||||
String phoneNumber;
|
String phoneNumber;
|
||||||
String status;
|
String status;
|
||||||
String fcsID;
|
String fcsID;
|
||||||
|
DateTime lastMessageTime;
|
||||||
|
String lastMessage;
|
||||||
|
int userUnseenCount;
|
||||||
|
int fcsUnseenCount;
|
||||||
|
|
||||||
|
String get initial => name != null && name != "" ? name.substring(0, 1) : "?";
|
||||||
|
|
||||||
|
String get getLastMessage {
|
||||||
|
var msg = lastMessage ?? "Say hi to $name";
|
||||||
|
if (msg.length > 30) return msg.substring(0, 30) + " ... ";
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
String get getLastMessageTime {
|
||||||
|
if (lastMessageTime == null) return "";
|
||||||
|
DateTime today = DateTime.now();
|
||||||
|
if (lastMessageTime.year == today.year &&
|
||||||
|
lastMessageTime.month == today.month &&
|
||||||
|
lastMessageTime.day == today.day) {
|
||||||
|
return timeFormat.format(lastMessageTime);
|
||||||
|
} else {
|
||||||
|
return dateFormat.format(lastMessageTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String get getUserUnseenCount => userUnseenCount != null
|
||||||
|
? userUnseenCount > 100 ? "99+" : userUnseenCount.toString()
|
||||||
|
: "0";
|
||||||
|
String get getFcsUnseenCount => fcsUnseenCount != null
|
||||||
|
? fcsUnseenCount > 100 ? "99+" : fcsUnseenCount.toString()
|
||||||
|
: "0";
|
||||||
|
|
||||||
List<String> privileges = [];
|
List<String> privileges = [];
|
||||||
|
|
||||||
@@ -16,14 +53,17 @@ class User {
|
|||||||
bool get invited => status != null && status == userStatusInvited;
|
bool get invited => status != null && status == userStatusInvited;
|
||||||
bool get requested => status != null && status == userStatusRequested;
|
bool get requested => status != null && status == userStatusRequested;
|
||||||
String get share => "Your phone number:$phoneNumber";
|
String get share => "Your phone number:$phoneNumber";
|
||||||
User({
|
User(
|
||||||
this.id,
|
{this.id,
|
||||||
this.name,
|
this.name,
|
||||||
this.phoneNumber,
|
this.phoneNumber,
|
||||||
this.fcsID,
|
this.fcsID,
|
||||||
this.status,
|
this.status,
|
||||||
this.privileges,
|
this.privileges,
|
||||||
});
|
this.lastMessage,
|
||||||
|
this.lastMessageTime,
|
||||||
|
this.userUnseenCount,
|
||||||
|
this.fcsUnseenCount});
|
||||||
|
|
||||||
factory User.fromJson(Map<String, dynamic> json) {
|
factory User.fromJson(Map<String, dynamic> json) {
|
||||||
return User(
|
return User(
|
||||||
@@ -32,6 +72,7 @@ class User {
|
|||||||
fcsID: json['fcs_id'],
|
fcsID: json['fcs_id'],
|
||||||
phoneNumber: json['phone_number'],
|
phoneNumber: json['phone_number'],
|
||||||
status: json['status'],
|
status: json['status'],
|
||||||
|
lastMessage: json['last_message'],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,6 +90,8 @@ class User {
|
|||||||
}
|
}
|
||||||
|
|
||||||
factory User.fromMap(Map<String, dynamic> map, String docID) {
|
factory User.fromMap(Map<String, dynamic> map, String docID) {
|
||||||
|
var _date = (map['message_time'] as Timestamp);
|
||||||
|
|
||||||
List<String> _privileges =
|
List<String> _privileges =
|
||||||
map['privileges'] == null ? [] : map['privileges'].cast<String>();
|
map['privileges'] == null ? [] : map['privileges'].cast<String>();
|
||||||
|
|
||||||
@@ -58,7 +101,11 @@ class User {
|
|||||||
phoneNumber: map['phone_number'],
|
phoneNumber: map['phone_number'],
|
||||||
status: map['status'],
|
status: map['status'],
|
||||||
fcsID: map['fcs_id'],
|
fcsID: map['fcs_id'],
|
||||||
privileges: _privileges);
|
privileges: _privileges,
|
||||||
|
lastMessage: map['last_message'],
|
||||||
|
userUnseenCount: map['user_unseen_count'],
|
||||||
|
fcsUnseenCount: map['fcs_unseen_count'],
|
||||||
|
lastMessageTime: _date == null ? null : _date.toDate());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isCustomer() {
|
bool isCustomer() {
|
||||||
|
|||||||
58
lib/fcs/common/domain/vo/message.dart
Normal file
58
lib/fcs/common/domain/vo/message.dart
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||||
|
|
||||||
|
class Message {
|
||||||
|
String id;
|
||||||
|
String message;
|
||||||
|
DateTime date;
|
||||||
|
String receiverID;
|
||||||
|
String receiverName;
|
||||||
|
String senderID;
|
||||||
|
String senderName;
|
||||||
|
String messageType;
|
||||||
|
String messageID;
|
||||||
|
|
||||||
|
Message(
|
||||||
|
{this.id,
|
||||||
|
this.message,
|
||||||
|
this.date,
|
||||||
|
this.receiverID,
|
||||||
|
this.receiverName,
|
||||||
|
this.senderID,
|
||||||
|
this.senderName,
|
||||||
|
this.messageType,
|
||||||
|
this.messageID});
|
||||||
|
bool fromToday() {
|
||||||
|
var now = DateTime.now();
|
||||||
|
return date.day == now.day &&
|
||||||
|
date.month == now.month &&
|
||||||
|
date.year == now.year;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toMap() {
|
||||||
|
return {
|
||||||
|
'message': message,
|
||||||
|
"receiver_id": receiverID,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sameDay(Message another) {
|
||||||
|
return date.year == another.date.year &&
|
||||||
|
date.month == another.date.month &&
|
||||||
|
date.day == another.date.day;
|
||||||
|
}
|
||||||
|
|
||||||
|
factory Message.fromMap(Map<String, dynamic> map, String id) {
|
||||||
|
var date = (map['date'] as Timestamp);
|
||||||
|
return Message(
|
||||||
|
id: id,
|
||||||
|
message: map['message'],
|
||||||
|
senderID: map['sender_id'],
|
||||||
|
senderName: map['sender_name'],
|
||||||
|
receiverID: map['receiver_id'],
|
||||||
|
receiverName: map['receiver_name'],
|
||||||
|
messageType: map['msg_type'],
|
||||||
|
messageID: map['msg_id'],
|
||||||
|
date: date != null ? date.toDate() : null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
152
lib/fcs/common/helpers/pagination_model.dart
Normal file
152
lib/fcs/common/helpers/pagination_model.dart
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
|
||||||
|
/*
|
||||||
|
* PaginationModel load data in page
|
||||||
|
* and listen to document change based on 'update_time' and 'delete_time' fields
|
||||||
|
* of the document
|
||||||
|
*/
|
||||||
|
class PaginationModel<T> {
|
||||||
|
final log = Logger('PaginationModel');
|
||||||
|
|
||||||
|
List<String> ids = [];
|
||||||
|
DocumentSnapshot prev;
|
||||||
|
int rowPerLoad = 10;
|
||||||
|
bool ended = false;
|
||||||
|
|
||||||
|
StreamSubscription<QuerySnapshot> listener;
|
||||||
|
CollectionReference listeningCol;
|
||||||
|
Query pageQuery;
|
||||||
|
|
||||||
|
PaginationModel(CollectionReference listeningCol, Query pageQuery,
|
||||||
|
{this.rowPerLoad = 10}) {
|
||||||
|
this.listeningCol = listeningCol;
|
||||||
|
this.pageQuery = pageQuery;
|
||||||
|
initData();
|
||||||
|
}
|
||||||
|
|
||||||
|
void initData() async {
|
||||||
|
_clearState();
|
||||||
|
_initListener();
|
||||||
|
load();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _clearState() {
|
||||||
|
prev = null;
|
||||||
|
ids = [];
|
||||||
|
ended = false;
|
||||||
|
if (listener != null) listener.cancel();
|
||||||
|
listener = null;
|
||||||
|
if (controller != null) controller.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
StreamController<Result> controller;
|
||||||
|
Stream<Result> listen() {
|
||||||
|
if (controller != null) {
|
||||||
|
controller.close();
|
||||||
|
}
|
||||||
|
controller = StreamController<Result>(onCancel: _clearState);
|
||||||
|
return controller.stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
void close() {
|
||||||
|
_clearState();
|
||||||
|
}
|
||||||
|
|
||||||
|
final String updateTimeField = 'update_time';
|
||||||
|
final String deleteTimeField = 'delete_time';
|
||||||
|
void _initListener() {
|
||||||
|
Query _query =
|
||||||
|
listeningCol.orderBy(updateTimeField, descending: true).limit(1);
|
||||||
|
_query.getDocuments(source: Source.server).then((QuerySnapshot snapshot) {
|
||||||
|
int count = snapshot.documents.length;
|
||||||
|
int updateTime = 0;
|
||||||
|
if (count == 1) {
|
||||||
|
updateTime = snapshot.documents[0].data[updateTimeField];
|
||||||
|
}
|
||||||
|
|
||||||
|
Query _queryListener = listeningCol
|
||||||
|
.where(updateTimeField, isGreaterThan: updateTime)
|
||||||
|
.orderBy(updateTimeField, descending: true);
|
||||||
|
|
||||||
|
listener =
|
||||||
|
_queryListener.snapshots(includeMetadataChanges: true).listen((qs) {
|
||||||
|
qs.documentChanges.forEach((c) {
|
||||||
|
switch (c.type) {
|
||||||
|
case DocumentChangeType.added:
|
||||||
|
log.info("added!! $c");
|
||||||
|
_update(c.document.documentID, c.document.data);
|
||||||
|
break;
|
||||||
|
case DocumentChangeType.modified:
|
||||||
|
log.info("modified!! $c");
|
||||||
|
_update(c.document.documentID, c.document.data);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _update(String id, Map<String, dynamic> data) {
|
||||||
|
if (ids.contains(id)) {
|
||||||
|
var deleted = data[deleteTimeField];
|
||||||
|
if (deleted > 0) {
|
||||||
|
ids.remove(id);
|
||||||
|
controller.add(Result(
|
||||||
|
id: id,
|
||||||
|
data: data,
|
||||||
|
documentChangeType: DocumentChangeType.removed));
|
||||||
|
} else {
|
||||||
|
controller.add(Result(
|
||||||
|
id: id,
|
||||||
|
data: data,
|
||||||
|
documentChangeType: DocumentChangeType.modified));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ids.add(id);
|
||||||
|
controller.add(Result(
|
||||||
|
id: id, data: data, documentChangeType: DocumentChangeType.added));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> load() async {
|
||||||
|
Query _query =
|
||||||
|
prev != null ? pageQuery.startAfterDocument(prev) : pageQuery;
|
||||||
|
try {
|
||||||
|
await _query
|
||||||
|
.where(deleteTimeField, isEqualTo: 0)
|
||||||
|
.limit(rowPerLoad)
|
||||||
|
.getDocuments(source: Source.server)
|
||||||
|
.then((QuerySnapshot snapshot) {
|
||||||
|
int count = snapshot.documents.length;
|
||||||
|
ended = count < rowPerLoad;
|
||||||
|
prev = count > 0 ? snapshot.documents[count - 1] : prev;
|
||||||
|
snapshot.documents.forEach((e) {
|
||||||
|
if (!ids.contains(e.documentID)) log.shout("load!! $e");
|
||||||
|
ids.add(e.documentID);
|
||||||
|
controller.add(Result(
|
||||||
|
id: e.documentID,
|
||||||
|
data: e.data,
|
||||||
|
documentChangeType: DocumentChangeType.added));
|
||||||
|
});
|
||||||
|
if (ended) {
|
||||||
|
controller.add(Result(isEnded: true));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
log.warning("Error!! $e");
|
||||||
|
}
|
||||||
|
return ended;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Result {
|
||||||
|
String id;
|
||||||
|
Map<String, dynamic> data;
|
||||||
|
DocumentChangeType documentChangeType;
|
||||||
|
bool isEnded;
|
||||||
|
Result({this.id, this.data, this.documentChangeType, this.isEnded = false});
|
||||||
|
}
|
||||||
135
lib/fcs/common/pages/chat/bubble.dart
Normal file
135
lib/fcs/common/pages/chat/bubble.dart
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
import 'package:fcs/fcs/common/helpers/theme.dart';
|
||||||
|
import 'package:fcs/fcs/common/pages/package/package_info.dart';
|
||||||
|
import 'package:fcs/fcs/common/pages/util.dart';
|
||||||
|
import 'package:fcs/fcs/common/pages/widgets/fcs_id_icon.dart';
|
||||||
|
import 'package:fcs/fcs/common/pages/widgets/local_text.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
|
DateFormat dayFormat = DateFormat("MMM dd yyyy");
|
||||||
|
DateFormat timeFormat = DateFormat("HH:mm");
|
||||||
|
|
||||||
|
typedef CallbackOnViewDetail();
|
||||||
|
|
||||||
|
class Bubble extends StatelessWidget {
|
||||||
|
Bubble(
|
||||||
|
{this.message,
|
||||||
|
this.date,
|
||||||
|
this.delivered,
|
||||||
|
this.isMine,
|
||||||
|
this.sender,
|
||||||
|
this.isSystem,
|
||||||
|
this.isCustomer,
|
||||||
|
this.showDate,
|
||||||
|
this.callbackOnViewDetail});
|
||||||
|
|
||||||
|
final CallbackOnViewDetail callbackOnViewDetail;
|
||||||
|
final DateTime date;
|
||||||
|
final String message, sender;
|
||||||
|
final bool delivered, isMine, isSystem, isCustomer, showDate;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final bg = isMine ? Colors.greenAccent.shade100 : Colors.white;
|
||||||
|
final align = isMine ? CrossAxisAlignment.end : CrossAxisAlignment.start;
|
||||||
|
final icon = delivered ? Icons.done_all : Icons.done;
|
||||||
|
final radius = isMine
|
||||||
|
? BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(25.0),
|
||||||
|
bottomLeft: Radius.circular(25.0),
|
||||||
|
bottomRight: Radius.circular(30.0),
|
||||||
|
)
|
||||||
|
: BorderRadius.only(
|
||||||
|
topRight: Radius.circular(25.0),
|
||||||
|
bottomLeft: Radius.circular(30.0),
|
||||||
|
bottomRight: Radius.circular(25.0),
|
||||||
|
);
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: align,
|
||||||
|
children: <Widget>[
|
||||||
|
showDate ? Center(child: Text(dateFormat.format(date))) : Container(),
|
||||||
|
Container(
|
||||||
|
constraints: BoxConstraints(
|
||||||
|
maxWidth: MediaQuery.of(context).size.width * 0.8, minWidth: 10),
|
||||||
|
margin: const EdgeInsets.all(3.0),
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
blurRadius: .5,
|
||||||
|
spreadRadius: 1.0,
|
||||||
|
color: Colors.black.withOpacity(.32))
|
||||||
|
],
|
||||||
|
color: bg,
|
||||||
|
borderRadius: radius,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: align,
|
||||||
|
children: (isMine && isCustomer) || (!isMine && !isCustomer)
|
||||||
|
? [getMsg(context, icon)]
|
||||||
|
: isSystem
|
||||||
|
? [
|
||||||
|
FcsIDIcon(),
|
||||||
|
getMsg(context, icon),
|
||||||
|
FlatButton(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(10.0),
|
||||||
|
),
|
||||||
|
color: Colors.blue[50],
|
||||||
|
onPressed: () => _viewDetail(),
|
||||||
|
child: Text(
|
||||||
|
getLocalString(context, "message.view.detail"),
|
||||||
|
style: TextStyle(
|
||||||
|
color: primaryColor,
|
||||||
|
fontWeight: FontWeight.bold)))
|
||||||
|
]
|
||||||
|
: [
|
||||||
|
Text(isCustomer ? "FCS Team" : sender,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.black38,
|
||||||
|
fontSize: 10.0,
|
||||||
|
)),
|
||||||
|
getMsg(context, icon),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getMsg(BuildContext context, IconData iconData) {
|
||||||
|
return Stack(
|
||||||
|
children: <Widget>[
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.only(right: 48.0),
|
||||||
|
child: Text(message,
|
||||||
|
style: hasUnicode(message)
|
||||||
|
? newLabelStyleMM(color: primaryColor)
|
||||||
|
: newLabelStyle(color: primaryColor))),
|
||||||
|
Positioned(
|
||||||
|
bottom: 0.0,
|
||||||
|
right: 0.0,
|
||||||
|
child: Row(
|
||||||
|
children: <Widget>[
|
||||||
|
Text(timeFormat.format(date),
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.black38,
|
||||||
|
fontSize: 10.0,
|
||||||
|
)),
|
||||||
|
SizedBox(width: 3.0),
|
||||||
|
Icon(
|
||||||
|
iconData,
|
||||||
|
size: 12.0,
|
||||||
|
color: Colors.black38,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_viewDetail() {
|
||||||
|
if (callbackOnViewDetail != null) callbackOnViewDetail();
|
||||||
|
}
|
||||||
|
}
|
||||||
576
lib/fcs/common/pages/chat/chat_page.dart
Normal file
576
lib/fcs/common/pages/chat/chat_page.dart
Normal file
@@ -0,0 +1,576 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
|
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||||
|
import 'package:firebase_storage/firebase_storage.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:image_picker/image_picker.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
import 'full_photo.dart';
|
||||||
|
import 'loading.dart';
|
||||||
|
|
||||||
|
final themeColor = Color(0xfff5a623);
|
||||||
|
final primaryColor = Color(0xff203152);
|
||||||
|
final greyColor = Color(0xffaeaeae);
|
||||||
|
final greyColor2 = Color(0xffE8E8E8);
|
||||||
|
|
||||||
|
class Chat extends StatelessWidget {
|
||||||
|
final String peerId;
|
||||||
|
final String peerAvatar;
|
||||||
|
|
||||||
|
Chat({Key key, @required this.peerId, @required this.peerAvatar})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(
|
||||||
|
'CHAT',
|
||||||
|
style: TextStyle(color: primaryColor, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
centerTitle: true,
|
||||||
|
),
|
||||||
|
body: ChatScreen(
|
||||||
|
peerId: peerId,
|
||||||
|
peerAvatar: peerAvatar,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ChatScreen extends StatefulWidget {
|
||||||
|
final String peerId;
|
||||||
|
final String peerAvatar;
|
||||||
|
|
||||||
|
ChatScreen({Key key, @required this.peerId, @required this.peerAvatar})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State createState() =>
|
||||||
|
ChatScreenState(peerId: peerId, peerAvatar: peerAvatar);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ChatScreenState extends State<ChatScreen> {
|
||||||
|
ChatScreenState({Key key, @required this.peerId, @required this.peerAvatar});
|
||||||
|
GlobalKey key = GlobalKey();
|
||||||
|
|
||||||
|
String peerId;
|
||||||
|
String peerAvatar;
|
||||||
|
String id;
|
||||||
|
|
||||||
|
List<DocumentSnapshot> listMessage = new List.from([]);
|
||||||
|
int _limit = 20;
|
||||||
|
final int _limitIncrement = 20;
|
||||||
|
String groupChatId;
|
||||||
|
SharedPreferences prefs;
|
||||||
|
|
||||||
|
File imageFile;
|
||||||
|
bool isLoading;
|
||||||
|
String imageUrl;
|
||||||
|
|
||||||
|
final TextEditingController textEditingController = TextEditingController();
|
||||||
|
final ScrollController listScrollController = ScrollController();
|
||||||
|
|
||||||
|
_scrollListener() {
|
||||||
|
if (listScrollController.offset >=
|
||||||
|
listScrollController.position.maxScrollExtent &&
|
||||||
|
!listScrollController.position.outOfRange) {
|
||||||
|
print("reach the bottom");
|
||||||
|
setState(() {
|
||||||
|
print("reach the bottom");
|
||||||
|
_limit += _limitIncrement;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (listScrollController.offset <=
|
||||||
|
listScrollController.position.minScrollExtent &&
|
||||||
|
!listScrollController.position.outOfRange) {
|
||||||
|
print("reach the top");
|
||||||
|
setState(() {
|
||||||
|
print("reach the top");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
listScrollController.addListener(_scrollListener);
|
||||||
|
|
||||||
|
groupChatId = '';
|
||||||
|
|
||||||
|
isLoading = false;
|
||||||
|
imageUrl = '';
|
||||||
|
|
||||||
|
readLocal();
|
||||||
|
}
|
||||||
|
|
||||||
|
readLocal() async {
|
||||||
|
prefs = await SharedPreferences.getInstance();
|
||||||
|
id = prefs.getString('id') ?? '';
|
||||||
|
if (id.hashCode <= peerId.hashCode) {
|
||||||
|
groupChatId = '$id-$peerId';
|
||||||
|
} else {
|
||||||
|
groupChatId = '$peerId-$id';
|
||||||
|
}
|
||||||
|
|
||||||
|
Firestore.instance
|
||||||
|
.collection('users')
|
||||||
|
.document(id)
|
||||||
|
.updateData({'chattingWith': peerId});
|
||||||
|
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future getImage() async {
|
||||||
|
ImagePicker imagePicker = ImagePicker();
|
||||||
|
PickedFile pickedFile;
|
||||||
|
|
||||||
|
pickedFile = await imagePicker.getImage(source: ImageSource.gallery);
|
||||||
|
imageFile = File(pickedFile.path);
|
||||||
|
|
||||||
|
if (imageFile != null) {
|
||||||
|
setState(() {
|
||||||
|
isLoading = true;
|
||||||
|
});
|
||||||
|
uploadFile();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future uploadFile() async {
|
||||||
|
String fileName = DateTime.now().millisecondsSinceEpoch.toString();
|
||||||
|
StorageReference reference = FirebaseStorage.instance.ref().child(fileName);
|
||||||
|
StorageUploadTask uploadTask = reference.putFile(imageFile);
|
||||||
|
StorageTaskSnapshot storageTaskSnapshot = await uploadTask.onComplete;
|
||||||
|
storageTaskSnapshot.ref.getDownloadURL().then((downloadUrl) {
|
||||||
|
imageUrl = downloadUrl;
|
||||||
|
setState(() {
|
||||||
|
isLoading = false;
|
||||||
|
onSendMessage(imageUrl, 1);
|
||||||
|
});
|
||||||
|
}, onError: (err) {
|
||||||
|
setState(() {
|
||||||
|
isLoading = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void onSendMessage(String content, int type) {
|
||||||
|
// type: 0 = text, 1 = image, 2 = sticker
|
||||||
|
if (content.trim() != '') {
|
||||||
|
textEditingController.clear();
|
||||||
|
|
||||||
|
var documentReference = Firestore.instance
|
||||||
|
.collection('messages')
|
||||||
|
.document(groupChatId)
|
||||||
|
.collection(groupChatId)
|
||||||
|
.document(DateTime.now().millisecondsSinceEpoch.toString());
|
||||||
|
|
||||||
|
Firestore.instance.runTransaction((transaction) async {
|
||||||
|
transaction.set(
|
||||||
|
documentReference,
|
||||||
|
{
|
||||||
|
'idFrom': id,
|
||||||
|
'idTo': peerId,
|
||||||
|
'timestamp': DateTime.now().millisecondsSinceEpoch.toString(),
|
||||||
|
'content': content,
|
||||||
|
'type': type
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
listScrollController.animateTo(0.0,
|
||||||
|
duration: Duration(milliseconds: 300), curve: Curves.easeOut);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildItem(int index, DocumentSnapshot document) {
|
||||||
|
if (document.data['idFrom'] == id) {
|
||||||
|
// Right (my message)
|
||||||
|
return Row(
|
||||||
|
children: <Widget>[
|
||||||
|
document.data['type'] == 0
|
||||||
|
// Text
|
||||||
|
? Container(
|
||||||
|
child: Text(
|
||||||
|
document.data['content'],
|
||||||
|
style: TextStyle(color: primaryColor),
|
||||||
|
),
|
||||||
|
padding: EdgeInsets.fromLTRB(15.0, 10.0, 15.0, 10.0),
|
||||||
|
width: 200.0,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: greyColor2,
|
||||||
|
borderRadius: BorderRadius.circular(8.0)),
|
||||||
|
margin: EdgeInsets.only(
|
||||||
|
bottom: isLastMessageRight(index) ? 20.0 : 10.0,
|
||||||
|
right: 10.0),
|
||||||
|
)
|
||||||
|
: document.data['type'] == 1
|
||||||
|
// Image
|
||||||
|
? Container(
|
||||||
|
child: FlatButton(
|
||||||
|
child: Material(
|
||||||
|
child: CachedNetworkImage(
|
||||||
|
placeholder: (context, url) => Container(
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
valueColor:
|
||||||
|
AlwaysStoppedAnimation<Color>(themeColor),
|
||||||
|
),
|
||||||
|
width: 200.0,
|
||||||
|
height: 200.0,
|
||||||
|
padding: EdgeInsets.all(70.0),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: greyColor2,
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(8.0),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
errorWidget: (context, url, error) => Material(
|
||||||
|
child: Image.asset(
|
||||||
|
'images/img_not_available.jpeg',
|
||||||
|
width: 200.0,
|
||||||
|
height: 200.0,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(8.0),
|
||||||
|
),
|
||||||
|
clipBehavior: Clip.hardEdge,
|
||||||
|
),
|
||||||
|
imageUrl: document.data['content'],
|
||||||
|
width: 200.0,
|
||||||
|
height: 200.0,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(8.0)),
|
||||||
|
clipBehavior: Clip.hardEdge,
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => FullPhoto(
|
||||||
|
url: document.data['content'])));
|
||||||
|
},
|
||||||
|
padding: EdgeInsets.all(0),
|
||||||
|
),
|
||||||
|
margin: EdgeInsets.only(
|
||||||
|
bottom: isLastMessageRight(index) ? 20.0 : 10.0,
|
||||||
|
right: 10.0),
|
||||||
|
)
|
||||||
|
// Sticker
|
||||||
|
: Container(
|
||||||
|
child: Image.asset(
|
||||||
|
'images/${document.data['content']}.gif',
|
||||||
|
width: 100.0,
|
||||||
|
height: 100.0,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
|
margin: EdgeInsets.only(
|
||||||
|
bottom: isLastMessageRight(index) ? 20.0 : 10.0,
|
||||||
|
right: 10.0),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Left (peer message)
|
||||||
|
return Container(
|
||||||
|
child: Column(
|
||||||
|
children: <Widget>[
|
||||||
|
Row(
|
||||||
|
children: <Widget>[
|
||||||
|
isLastMessageLeft(index)
|
||||||
|
? Material(
|
||||||
|
child: CachedNetworkImage(
|
||||||
|
placeholder: (context, url) => Container(
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
strokeWidth: 1.0,
|
||||||
|
valueColor:
|
||||||
|
AlwaysStoppedAnimation<Color>(themeColor),
|
||||||
|
),
|
||||||
|
width: 35.0,
|
||||||
|
height: 35.0,
|
||||||
|
padding: EdgeInsets.all(10.0),
|
||||||
|
),
|
||||||
|
imageUrl: peerAvatar,
|
||||||
|
width: 35.0,
|
||||||
|
height: 35.0,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(18.0),
|
||||||
|
),
|
||||||
|
clipBehavior: Clip.hardEdge,
|
||||||
|
)
|
||||||
|
: Container(width: 35.0),
|
||||||
|
document.data['type'] == 0
|
||||||
|
? Container(
|
||||||
|
child: Text(
|
||||||
|
document.data['content'],
|
||||||
|
style: TextStyle(color: Colors.white),
|
||||||
|
),
|
||||||
|
padding: EdgeInsets.fromLTRB(15.0, 10.0, 15.0, 10.0),
|
||||||
|
width: 200.0,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: primaryColor,
|
||||||
|
borderRadius: BorderRadius.circular(8.0)),
|
||||||
|
margin: EdgeInsets.only(left: 10.0),
|
||||||
|
)
|
||||||
|
: document.data['type'] == 1
|
||||||
|
? Container(
|
||||||
|
child: FlatButton(
|
||||||
|
child: Material(
|
||||||
|
child: CachedNetworkImage(
|
||||||
|
placeholder: (context, url) => Container(
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
valueColor: AlwaysStoppedAnimation<Color>(
|
||||||
|
themeColor),
|
||||||
|
),
|
||||||
|
width: 200.0,
|
||||||
|
height: 200.0,
|
||||||
|
padding: EdgeInsets.all(70.0),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: greyColor2,
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(8.0),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
errorWidget: (context, url, error) =>
|
||||||
|
Material(
|
||||||
|
child: Image.asset(
|
||||||
|
'images/img_not_available.jpeg',
|
||||||
|
width: 200.0,
|
||||||
|
height: 200.0,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(8.0),
|
||||||
|
),
|
||||||
|
clipBehavior: Clip.hardEdge,
|
||||||
|
),
|
||||||
|
imageUrl: document.data['content'],
|
||||||
|
width: 200.0,
|
||||||
|
height: 200.0,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
|
borderRadius:
|
||||||
|
BorderRadius.all(Radius.circular(8.0)),
|
||||||
|
clipBehavior: Clip.hardEdge,
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => FullPhoto(
|
||||||
|
url: document.data['content'])));
|
||||||
|
},
|
||||||
|
padding: EdgeInsets.all(0),
|
||||||
|
),
|
||||||
|
margin: EdgeInsets.only(left: 10.0),
|
||||||
|
)
|
||||||
|
: Container(
|
||||||
|
child: Image.asset(
|
||||||
|
'images/${document.data['content']}.gif',
|
||||||
|
width: 100.0,
|
||||||
|
height: 100.0,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
|
margin: EdgeInsets.only(
|
||||||
|
bottom: isLastMessageRight(index) ? 20.0 : 10.0,
|
||||||
|
right: 10.0),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
// Time
|
||||||
|
isLastMessageLeft(index)
|
||||||
|
? Container(
|
||||||
|
child: Text(
|
||||||
|
DateFormat('dd MMM kk:mm').format(
|
||||||
|
DateTime.fromMillisecondsSinceEpoch(
|
||||||
|
int.parse(document.data['timestamp']))),
|
||||||
|
style: TextStyle(
|
||||||
|
color: greyColor,
|
||||||
|
fontSize: 12.0,
|
||||||
|
fontStyle: FontStyle.italic),
|
||||||
|
),
|
||||||
|
margin: EdgeInsets.only(left: 50.0, top: 5.0, bottom: 5.0),
|
||||||
|
)
|
||||||
|
: Container()
|
||||||
|
],
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
),
|
||||||
|
margin: EdgeInsets.only(bottom: 10.0),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isLastMessageLeft(int index) {
|
||||||
|
if ((index > 0 &&
|
||||||
|
listMessage != null &&
|
||||||
|
listMessage[index - 1].data['idFrom'] == id) ||
|
||||||
|
index == 0) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isLastMessageRight(int index) {
|
||||||
|
if ((index > 0 &&
|
||||||
|
listMessage != null &&
|
||||||
|
listMessage[index - 1].data['idFrom'] != id) ||
|
||||||
|
index == 0) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> onBackPress() {
|
||||||
|
Firestore.instance
|
||||||
|
.collection('users')
|
||||||
|
.document(id)
|
||||||
|
.updateData({'chattingWith': null});
|
||||||
|
Navigator.pop(context);
|
||||||
|
|
||||||
|
return Future.value(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return WillPopScope(
|
||||||
|
child: Stack(
|
||||||
|
children: <Widget>[
|
||||||
|
Column(
|
||||||
|
children: <Widget>[
|
||||||
|
// List of messages
|
||||||
|
buildListMessage(),
|
||||||
|
|
||||||
|
// Input content
|
||||||
|
buildInput(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
// Loading
|
||||||
|
buildLoading()
|
||||||
|
],
|
||||||
|
),
|
||||||
|
onWillPop: onBackPress,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildLoading() {
|
||||||
|
return Positioned(
|
||||||
|
child: isLoading ? const Loading() : Container(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildInput() {
|
||||||
|
return Container(
|
||||||
|
child: Row(
|
||||||
|
children: <Widget>[
|
||||||
|
// Button send image
|
||||||
|
Material(
|
||||||
|
child: Container(
|
||||||
|
margin: EdgeInsets.symmetric(horizontal: 1.0),
|
||||||
|
child: IconButton(
|
||||||
|
icon: Icon(Icons.image),
|
||||||
|
onPressed: getImage,
|
||||||
|
color: primaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
Material(
|
||||||
|
child: Container(
|
||||||
|
margin: EdgeInsets.symmetric(horizontal: 1.0),
|
||||||
|
child: IconButton(
|
||||||
|
icon: Icon(Icons.face),
|
||||||
|
onPressed: () => {},
|
||||||
|
color: primaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
|
||||||
|
// Edit text
|
||||||
|
Flexible(
|
||||||
|
child: Container(
|
||||||
|
child: TextField(
|
||||||
|
onSubmitted: (value) {
|
||||||
|
onSendMessage(textEditingController.text, 0);
|
||||||
|
},
|
||||||
|
style: TextStyle(color: primaryColor, fontSize: 15.0),
|
||||||
|
controller: textEditingController,
|
||||||
|
decoration: InputDecoration.collapsed(
|
||||||
|
hintText: 'Type your message...',
|
||||||
|
hintStyle: TextStyle(color: greyColor),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Button send message
|
||||||
|
Material(
|
||||||
|
child: Container(
|
||||||
|
margin: EdgeInsets.symmetric(horizontal: 8.0),
|
||||||
|
child: IconButton(
|
||||||
|
icon: Icon(Icons.send),
|
||||||
|
onPressed: () => onSendMessage(textEditingController.text, 0),
|
||||||
|
color: primaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
width: double.infinity,
|
||||||
|
height: 50.0,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border(top: BorderSide(color: greyColor2, width: 0.5)),
|
||||||
|
color: Colors.white),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildListMessage() {
|
||||||
|
return Flexible(
|
||||||
|
child: groupChatId == ''
|
||||||
|
? Center(
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
valueColor: AlwaysStoppedAnimation<Color>(themeColor)))
|
||||||
|
: StreamBuilder(
|
||||||
|
stream: Firestore.instance
|
||||||
|
.collection('messages')
|
||||||
|
.document(groupChatId)
|
||||||
|
.collection(groupChatId)
|
||||||
|
.orderBy('timestamp', descending: true)
|
||||||
|
.limit(_limit)
|
||||||
|
.snapshots(),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (!snapshot.hasData) {
|
||||||
|
return Center(
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
valueColor:
|
||||||
|
AlwaysStoppedAnimation<Color>(themeColor)));
|
||||||
|
} else {
|
||||||
|
listMessage.addAll(snapshot.data.documents);
|
||||||
|
return ListView.builder(
|
||||||
|
padding: EdgeInsets.all(10.0),
|
||||||
|
itemBuilder: (context, index) =>
|
||||||
|
buildItem(index, snapshot.data.documents[index]),
|
||||||
|
itemCount: snapshot.data.documents.length,
|
||||||
|
reverse: true,
|
||||||
|
controller: listScrollController,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
51
lib/fcs/common/pages/chat/full_photo.dart
Normal file
51
lib/fcs/common/pages/chat/full_photo.dart
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:photo_view/photo_view.dart';
|
||||||
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
|
|
||||||
|
import 'chat_page.dart';
|
||||||
|
|
||||||
|
class FullPhoto extends StatelessWidget {
|
||||||
|
final String url;
|
||||||
|
|
||||||
|
FullPhoto({Key key, @required this.url}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(
|
||||||
|
'FULL PHOTO',
|
||||||
|
style: TextStyle(color: primaryColor, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
centerTitle: true,
|
||||||
|
),
|
||||||
|
body: FullPhotoScreen(url: url),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FullPhotoScreen extends StatefulWidget {
|
||||||
|
final String url;
|
||||||
|
|
||||||
|
FullPhotoScreen({Key key, @required this.url}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State createState() => FullPhotoScreenState(url: url);
|
||||||
|
}
|
||||||
|
|
||||||
|
class FullPhotoScreenState extends State<FullPhotoScreen> {
|
||||||
|
final String url;
|
||||||
|
|
||||||
|
FullPhotoScreenState({Key key, @required this.url});
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
child: PhotoView(imageProvider: CachedNetworkImageProvider(url)));
|
||||||
|
}
|
||||||
|
}
|
||||||
18
lib/fcs/common/pages/chat/loading.dart
Normal file
18
lib/fcs/common/pages/chat/loading.dart
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import 'package:fcs/fcs/common/helpers/theme.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class Loading extends StatelessWidget {
|
||||||
|
const Loading();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
child: Center(
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
valueColor: AlwaysStoppedAnimation<Color>(primaryColor),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
color: Colors.white.withOpacity(0.8),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
172
lib/fcs/common/pages/chat/message_detail.dart
Normal file
172
lib/fcs/common/pages/chat/message_detail.dart
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
import 'package:fcs/fcs/common/domain/constants.dart';
|
||||||
|
import 'package:fcs/fcs/common/domain/entities/package.dart';
|
||||||
|
import 'package:fcs/fcs/common/domain/vo/message.dart';
|
||||||
|
import 'package:fcs/fcs/common/helpers/theme.dart';
|
||||||
|
import 'package:fcs/fcs/common/pages/chat/model/message_model.dart';
|
||||||
|
import 'package:fcs/fcs/common/pages/package/model/package_model.dart';
|
||||||
|
import 'package:fcs/fcs/common/pages/package/package_info.dart';
|
||||||
|
import 'package:fcs/fcs/common/pages/util.dart';
|
||||||
|
import 'package:fcs/fcs/common/pages/widgets/bottom_up_page_route.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import 'bubble.dart';
|
||||||
|
|
||||||
|
class MessageDetail extends StatelessWidget {
|
||||||
|
final String receiverName;
|
||||||
|
final String receiverID;
|
||||||
|
final MessageModel messageModel;
|
||||||
|
final TextEditingController textEditingController = TextEditingController();
|
||||||
|
final ScrollController listScrollController = ScrollController();
|
||||||
|
|
||||||
|
MessageDetail(
|
||||||
|
{Key key, this.messageModel, this.receiverName, this.receiverID})
|
||||||
|
: super(key: key) {
|
||||||
|
listScrollController.addListener(() {
|
||||||
|
if (listScrollController.offset >=
|
||||||
|
listScrollController.position.maxScrollExtent &&
|
||||||
|
!listScrollController.position.outOfRange) {
|
||||||
|
if (!messageModel.isEnded) messageModel.load();
|
||||||
|
}
|
||||||
|
if (listScrollController.offset <=
|
||||||
|
listScrollController.position.minScrollExtent &&
|
||||||
|
!listScrollController.position.outOfRange) {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
String userID = Provider.of<MessageModel>(context).user.id;
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
backgroundColor: primaryColor,
|
||||||
|
elevation: .9,
|
||||||
|
title: Text(
|
||||||
|
receiverName ?? "FCS Team",
|
||||||
|
),
|
||||||
|
actions: <Widget>[],
|
||||||
|
),
|
||||||
|
body: Padding(
|
||||||
|
padding: EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: ListView.builder(
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
var msg = messageModel.messages[index];
|
||||||
|
Message next;
|
||||||
|
bool showDate = false;
|
||||||
|
if (messageModel.messages.length > index + 1) {
|
||||||
|
next = messageModel.messages[index + 1];
|
||||||
|
if (!msg.sameDay(next)) {
|
||||||
|
showDate = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (messageModel.messages.length - 1 == index &&
|
||||||
|
messageModel.isEnded) {
|
||||||
|
showDate = true;
|
||||||
|
}
|
||||||
|
return buildBubble(
|
||||||
|
msg, userID, showDate, () => _viewDetail(context, msg));
|
||||||
|
},
|
||||||
|
itemCount: messageModel.messages.length,
|
||||||
|
reverse: true,
|
||||||
|
controller: listScrollController,
|
||||||
|
)),
|
||||||
|
buildInput(context),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildBubble(Message msg, String userID, bool showDate,
|
||||||
|
CallbackOnViewDetail callback) {
|
||||||
|
return Bubble(
|
||||||
|
message: msg.message,
|
||||||
|
date: msg.date,
|
||||||
|
delivered: true,
|
||||||
|
sender: msg.senderName,
|
||||||
|
isMine: msg.senderID == userID || msg.receiverID == receiverID,
|
||||||
|
isCustomer: receiverID == null,
|
||||||
|
showDate: showDate,
|
||||||
|
isSystem: msg.messageType != null && msg.messageType != "",
|
||||||
|
callbackOnViewDetail: callback,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildInput(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
padding: EdgeInsets.only(top: 3),
|
||||||
|
child: Row(
|
||||||
|
children: <Widget>[
|
||||||
|
Flexible(
|
||||||
|
child: Container(
|
||||||
|
child: TextField(
|
||||||
|
onSubmitted: (value) {
|
||||||
|
Provider.of<MessageModel>(context, listen: false)
|
||||||
|
.sendMessage(textEditingController.text, receiverID);
|
||||||
|
textEditingController.text = "";
|
||||||
|
},
|
||||||
|
style: TextStyle(color: primaryColor, fontSize: 15.0),
|
||||||
|
maxLines: 10,
|
||||||
|
minLines: 1,
|
||||||
|
keyboardType: TextInputType.multiline,
|
||||||
|
controller: textEditingController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
focusedBorder: const OutlineInputBorder(
|
||||||
|
borderSide:
|
||||||
|
const BorderSide(color: primaryColor, width: 1.0),
|
||||||
|
),
|
||||||
|
border: new OutlineInputBorder(
|
||||||
|
borderRadius: const BorderRadius.all(
|
||||||
|
const Radius.circular(10.0),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
hintText: getLocalString(context, "message.hint.input"),
|
||||||
|
hintStyle: TextStyle(
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Button send message
|
||||||
|
Material(
|
||||||
|
child: Container(
|
||||||
|
margin: EdgeInsets.symmetric(horizontal: 8.0),
|
||||||
|
child: IconButton(
|
||||||
|
icon: Icon(Icons.send),
|
||||||
|
onPressed: () {
|
||||||
|
Provider.of<MessageModel>(context, listen: false)
|
||||||
|
.sendMessage(textEditingController.text, receiverID);
|
||||||
|
textEditingController.text = "";
|
||||||
|
},
|
||||||
|
color: primaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
width: double.infinity,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border(top: BorderSide(color: Colors.grey[700], width: 0.5)),
|
||||||
|
color: Colors.white),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_viewDetail(BuildContext context, Message message) async {
|
||||||
|
if (message.messageType == message_type_package &&
|
||||||
|
message.messageID != null &&
|
||||||
|
message.messageID != "") {
|
||||||
|
PackageModel packageModel =
|
||||||
|
Provider.of<PackageModel>(context, listen: false);
|
||||||
|
Package p = await packageModel.getPackage(message.messageID);
|
||||||
|
Navigator.push<bool>(context, BottomUpPageRoute(PackageInfo(package: p)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
96
lib/fcs/common/pages/chat/model/message_model.dart
Normal file
96
lib/fcs/common/pages/chat/model/message_model.dart
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||||
|
import 'package:fcs/fcs/common/domain/constants.dart';
|
||||||
|
import 'package:fcs/fcs/common/domain/vo/message.dart';
|
||||||
|
import 'package:fcs/fcs/common/pages/model/base_model.dart';
|
||||||
|
import 'package:fcs/fcs/common/services/services.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
|
||||||
|
class MessageModel extends BaseModel {
|
||||||
|
final log = Logger('MessageModel');
|
||||||
|
|
||||||
|
List<Message> messages;
|
||||||
|
|
||||||
|
void initUser(user) {
|
||||||
|
super.initUser(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
logout() async {
|
||||||
|
if (listener != null) await listener.cancel();
|
||||||
|
messages = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
Query query;
|
||||||
|
DocumentSnapshot prevSnap;
|
||||||
|
bool isEnded;
|
||||||
|
bool isLoading;
|
||||||
|
String userID;
|
||||||
|
StreamSubscription<QuerySnapshot> listener;
|
||||||
|
|
||||||
|
static const int rowPerLoad = 20;
|
||||||
|
void initQuery(String userID) {
|
||||||
|
this.messages = [];
|
||||||
|
this.userID = userID;
|
||||||
|
this.prevSnap = null;
|
||||||
|
query = Firestore.instance
|
||||||
|
.collection("$user_collection/$userID/$messages_collection")
|
||||||
|
.orderBy('date', descending: true);
|
||||||
|
load();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> load() async {
|
||||||
|
Query _query =
|
||||||
|
prevSnap != null ? query.startAfterDocument(prevSnap) : query;
|
||||||
|
QuerySnapshot snapshot =
|
||||||
|
await _query.limit(rowPerLoad).getDocuments(source: Source.server);
|
||||||
|
|
||||||
|
int count = snapshot.documents.length;
|
||||||
|
isEnded = count < rowPerLoad;
|
||||||
|
prevSnap = count > 0 ? snapshot.documents[count - 1] : prevSnap;
|
||||||
|
|
||||||
|
snapshot.documents.forEach((e) {
|
||||||
|
messages.add(Message.fromMap(e.data, e.documentID));
|
||||||
|
if (messages.length == 1) {
|
||||||
|
_initListener(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _initListener(DocumentSnapshot snap) {
|
||||||
|
if (listener != null) listener.cancel();
|
||||||
|
|
||||||
|
listener = Firestore.instance
|
||||||
|
.collection("$user_collection/$userID/$messages_collection")
|
||||||
|
.endBeforeDocument(snap)
|
||||||
|
.orderBy('date', descending: true)
|
||||||
|
.snapshots(includeMetadataChanges: true)
|
||||||
|
.listen((qs) {
|
||||||
|
qs.documentChanges.forEach((c) {
|
||||||
|
switch (c.type) {
|
||||||
|
case DocumentChangeType.added:
|
||||||
|
log.info("added!! $c");
|
||||||
|
messages.insert(
|
||||||
|
0, Message.fromMap(c.document.data, c.document.documentID));
|
||||||
|
notifyListeners();
|
||||||
|
break;
|
||||||
|
case DocumentChangeType.modified:
|
||||||
|
log.info("modified!! $c");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> sendMessage(String msg, String receiverID) {
|
||||||
|
Message message = Message(message: msg, receiverID: receiverID);
|
||||||
|
return Services.instance.commonService.sendMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> seenMessages(String ownerID, bool seenByOwner) {
|
||||||
|
return Services.instance.commonService.seenMessage(ownerID, seenByOwner);
|
||||||
|
}
|
||||||
|
}
|
||||||
129
lib/fcs/common/pages/chat/notification_list.dart
Normal file
129
lib/fcs/common/pages/chat/notification_list.dart
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
import 'package:fcs/fcs/common/domain/vo/message.dart';
|
||||||
|
import 'package:fcs/fcs/common/helpers/theme.dart';
|
||||||
|
import 'package:fcs/fcs/common/pages/chat/message_detail.dart';
|
||||||
|
import 'package:fcs/fcs/common/pages/chat/model/message_model.dart';
|
||||||
|
import 'package:fcs/fcs/common/pages/widgets/bottom_up_page_route.dart';
|
||||||
|
import 'package:fcs/fcs/common/pages/widgets/local_text.dart';
|
||||||
|
import 'package:fcs/fcs/common/pages/widgets/progress.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
class NotificationList extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
_NotificationListState createState() => _NotificationListState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _NotificationListState extends State<NotificationList> {
|
||||||
|
var timeFormatter = new DateFormat('KK:mm a');
|
||||||
|
var dateFormatter = new DateFormat('dd MMM yyyy');
|
||||||
|
final double dotSize = 25.0;
|
||||||
|
int _selectedIndex = 0;
|
||||||
|
bool _isLoading = false;
|
||||||
|
bool _isClicked = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
MessageModel messageModel = Provider.of<MessageModel>(context);
|
||||||
|
|
||||||
|
return LocalProgress(
|
||||||
|
inAsyncCall: _isLoading,
|
||||||
|
child: Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
centerTitle: true,
|
||||||
|
leading: new IconButton(
|
||||||
|
icon: new Icon(
|
||||||
|
Icons.close,
|
||||||
|
),
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
),
|
||||||
|
backgroundColor: primaryColor,
|
||||||
|
title: LocalText(
|
||||||
|
context,
|
||||||
|
'message.title',
|
||||||
|
fontSize: 20,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
body: new ListView.separated(
|
||||||
|
separatorBuilder: (context, index) => Divider(
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
scrollDirection: Axis.vertical,
|
||||||
|
padding: EdgeInsets.only(top: 5),
|
||||||
|
shrinkWrap: true,
|
||||||
|
itemCount: messageModel.messages.length,
|
||||||
|
itemBuilder: (BuildContext context, int index) {
|
||||||
|
Message msg = messageModel.messages[index];
|
||||||
|
return Stack(
|
||||||
|
children: <Widget>[
|
||||||
|
InkWell(
|
||||||
|
onTap: () => _display(msg),
|
||||||
|
child: Row(
|
||||||
|
children: <Widget>[
|
||||||
|
Expanded(
|
||||||
|
child: new Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 10.0),
|
||||||
|
child: new Row(
|
||||||
|
children: <Widget>[
|
||||||
|
new Padding(
|
||||||
|
padding: new EdgeInsets.symmetric(
|
||||||
|
horizontal: 22.0 - dotSize / 2),
|
||||||
|
child: Icon(
|
||||||
|
Icons.account_circle,
|
||||||
|
color: primaryColor,
|
||||||
|
size: 60,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
new Expanded(
|
||||||
|
child: new Column(
|
||||||
|
crossAxisAlignment:
|
||||||
|
CrossAxisAlignment.start,
|
||||||
|
children: <Widget>[
|
||||||
|
new Text(
|
||||||
|
msg.receiverName,
|
||||||
|
style: new TextStyle(fontSize: 15.0),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 18.0),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: <Widget>[
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Text(
|
||||||
|
timeFormatter.format(msg.date),
|
||||||
|
style: TextStyle(color: Colors.grey),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
msg.fromToday()
|
||||||
|
? Container()
|
||||||
|
: Text(
|
||||||
|
dateFormatter.format(msg.date),
|
||||||
|
style: TextStyle(color: Colors.grey),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_display(Message msg) {
|
||||||
|
Navigator.push(context,
|
||||||
|
BottomUpPageRoute(MessageDetail(receiverName: msg.receiverName)));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -56,10 +56,12 @@ class _ContactEditorState extends State<ContactEditor> {
|
|||||||
final usaAddreesBox = InputText(
|
final usaAddreesBox = InputText(
|
||||||
labelTextKey: 'contact.usa.address',
|
labelTextKey: 'contact.usa.address',
|
||||||
iconData: CupertinoIcons.location,
|
iconData: CupertinoIcons.location,
|
||||||
|
maxLines: 3,
|
||||||
controller: _usaAddress);
|
controller: _usaAddress);
|
||||||
final mmAddressBox = InputText(
|
final mmAddressBox = InputText(
|
||||||
labelTextKey: 'contact.mm.address',
|
labelTextKey: 'contact.mm.address',
|
||||||
iconData: CupertinoIcons.location,
|
iconData: CupertinoIcons.location,
|
||||||
|
maxLines: 3,
|
||||||
controller: _mmAddress);
|
controller: _mmAddress);
|
||||||
final emailBox = InputText(
|
final emailBox = InputText(
|
||||||
labelTextKey: 'contact.email',
|
labelTextKey: 'contact.email',
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
import 'package:fcs/fcs/common/domain/constants.dart';
|
import 'package:fcs/fcs/common/domain/constants.dart';
|
||||||
import 'package:fcs/fcs/common/domain/entities/user.dart';
|
import 'package:fcs/fcs/common/domain/entities/user.dart';
|
||||||
import 'package:fcs/fcs/common/helpers/theme.dart';
|
import 'package:fcs/fcs/common/helpers/theme.dart';
|
||||||
|
import 'package:fcs/fcs/common/pages/chat/message_detail.dart';
|
||||||
|
import 'package:fcs/fcs/common/pages/chat/model/message_model.dart';
|
||||||
import 'package:fcs/fcs/common/pages/customer/customer_editor.dart';
|
import 'package:fcs/fcs/common/pages/customer/customer_editor.dart';
|
||||||
import 'package:fcs/fcs/common/pages/customer/model/customer_model.dart';
|
import 'package:fcs/fcs/common/pages/customer/model/customer_model.dart';
|
||||||
import 'package:fcs/fcs/common/pages/model/main_model.dart';
|
import 'package:fcs/fcs/common/pages/model/main_model.dart';
|
||||||
import 'package:fcs/fcs/common/pages/user_search/user_serach.dart';
|
import 'package:fcs/fcs/common/pages/user_search/user_serach.dart';
|
||||||
import 'package:fcs/fcs/common/pages/widgets/bottom_up_page_route.dart';
|
import 'package:fcs/fcs/common/pages/widgets/bottom_up_page_route.dart';
|
||||||
import 'package:fcs/fcs/common/pages/widgets/local_text.dart';
|
import 'package:fcs/fcs/common/pages/widgets/local_text.dart';
|
||||||
import 'package:fcs/widget/progress.dart';
|
import 'package:fcs/fcs/common/pages/widgets/progress.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_icons/flutter_icons.dart';
|
import 'package:flutter_icons/flutter_icons.dart';
|
||||||
@@ -69,10 +71,9 @@ class _CustomerListState extends State<CustomerList> {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: ListView.separated(
|
child: ListView.separated(
|
||||||
separatorBuilder: (context, index) => Divider(
|
separatorBuilder: (context, index) => Divider(
|
||||||
color: Colors.black,
|
color: Colors.grey,
|
||||||
),
|
),
|
||||||
scrollDirection: Axis.vertical,
|
scrollDirection: Axis.vertical,
|
||||||
padding: EdgeInsets.only(left: 15, right: 15),
|
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
itemCount: customerModel.customers.length,
|
itemCount: customerModel.customers.length,
|
||||||
itemBuilder: (BuildContext context, int index) {
|
itemBuilder: (BuildContext context, int index) {
|
||||||
@@ -88,32 +89,52 @@ class _CustomerListState extends State<CustomerList> {
|
|||||||
|
|
||||||
Widget _item(User customer) {
|
Widget _item(User customer) {
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: () => _select(customer),
|
onTap: () => _gotoMsg(customer),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 12.0, right: 12),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Expanded(
|
Expanded(
|
||||||
child: new Padding(
|
child: new Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
padding: const EdgeInsets.symmetric(vertical: 2.0),
|
||||||
child: new Row(
|
child: new Row(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Icon(
|
InkWell(
|
||||||
Feather.user,
|
onTap: () => _select(customer),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(5.0),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
left: 10.0, right: 10, top: 6, bottom: 6),
|
||||||
|
decoration: BoxDecoration(
|
||||||
color: primaryColor,
|
color: primaryColor,
|
||||||
size: 40,
|
borderRadius:
|
||||||
|
BorderRadius.all(Radius.circular(35.0))),
|
||||||
|
child: Text(
|
||||||
|
customer.initial,
|
||||||
|
style: TextStyle(fontSize: 30, color: Colors.white),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
new Expanded(
|
new Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 8.0),
|
||||||
child: new Column(
|
child: new Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
new Text(
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 2.0),
|
||||||
|
child: new Text(
|
||||||
customer.name,
|
customer.name,
|
||||||
style: new TextStyle(
|
style: new TextStyle(
|
||||||
fontSize: 15.0, color: primaryColor),
|
fontSize: 20.0, color: primaryColor),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(top: 8.0),
|
padding: const EdgeInsets.only(top: 2.0),
|
||||||
child: new Text(
|
child: new Text(
|
||||||
customer.phoneNumber,
|
customer.getLastMessage,
|
||||||
style: new TextStyle(
|
style: new TextStyle(
|
||||||
fontSize: 15.0, color: Colors.grey),
|
fontSize: 15.0, color: Colors.grey),
|
||||||
),
|
),
|
||||||
@@ -121,16 +142,23 @@ class _CustomerListState extends State<CustomerList> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Column(
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(right: 5),
|
padding: const EdgeInsets.only(right: 5),
|
||||||
child: _status(customer.status),
|
child: _status(customer.status),
|
||||||
),
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 5),
|
||||||
|
child: Text(customer.getLastMessageTime),
|
||||||
|
),
|
||||||
|
getCount(customer),
|
||||||
customer.status == user_invited_status
|
customer.status == user_invited_status
|
||||||
? FlatButton(
|
? FlatButton(
|
||||||
onPressed: () => _share(customer),
|
onPressed: () => _share(customer),
|
||||||
@@ -152,9 +180,22 @@ class _CustomerListState extends State<CustomerList> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget getCount(User customer) {
|
||||||
|
return customer.fcsUnseenCount != null && customer.fcsUnseenCount > 0
|
||||||
|
? Container(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
decoration:
|
||||||
|
BoxDecoration(shape: BoxShape.circle, color: secondaryColor),
|
||||||
|
child: Text(customer.getFcsUnseenCount,
|
||||||
|
style: TextStyle(color: Colors.white)),
|
||||||
|
)
|
||||||
|
: Container();
|
||||||
|
}
|
||||||
|
|
||||||
Widget _status(String status) {
|
Widget _status(String status) {
|
||||||
return user_requested_status == status
|
return user_requested_status == status
|
||||||
? Text(status, style: TextStyle(color: primaryColor, fontSize: 14))
|
? Text(status, style: TextStyle(color: primaryColor, fontSize: 14))
|
||||||
@@ -166,6 +207,26 @@ class _CustomerListState extends State<CustomerList> {
|
|||||||
.push(BottomUpPageRoute(CustomerEditor(customer: customer)));
|
.push(BottomUpPageRoute(CustomerEditor(customer: customer)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_gotoMsg(User customer) {
|
||||||
|
MessageModel messageModel =
|
||||||
|
Provider.of<MessageModel>(context, listen: false);
|
||||||
|
messageModel.initQuery(customer.id);
|
||||||
|
Navigator.of(context)
|
||||||
|
.push(BottomUpPageRoute(MessageDetail(
|
||||||
|
receiverID: customer.id,
|
||||||
|
receiverName: customer.name,
|
||||||
|
messageModel: messageModel,
|
||||||
|
)))
|
||||||
|
.then((value) {
|
||||||
|
if (customer.fcsUnseenCount > 0) {
|
||||||
|
messageModel.seenMessages(customer.id, false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (customer.fcsUnseenCount > 0) {
|
||||||
|
messageModel.seenMessages(customer.id, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_share(User user) async {
|
_share(User user) async {
|
||||||
MainModel mainModel = Provider.of<MainModel>(context, listen: false);
|
MainModel mainModel = Provider.of<MainModel>(context, listen: false);
|
||||||
String appUrl = mainModel.setting.appUrl;
|
String appUrl = mainModel.setting.appUrl;
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ class CustomerModel extends BaseModel {
|
|||||||
customerListener = Firestore.instance
|
customerListener = Firestore.instance
|
||||||
.collection("/$user_collection")
|
.collection("/$user_collection")
|
||||||
.where("is_sys_admin", isEqualTo: false)
|
.where("is_sys_admin", isEqualTo: false)
|
||||||
|
.orderBy("message_time", descending: true)
|
||||||
.snapshots()
|
.snapshots()
|
||||||
.listen((QuerySnapshot snapshot) {
|
.listen((QuerySnapshot snapshot) {
|
||||||
customers.clear();
|
customers.clear();
|
||||||
@@ -87,4 +88,10 @@ class CustomerModel extends BaseModel {
|
|||||||
log.warning("error:$e");
|
log.warning("error:$e");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<User> getUser(String id) async {
|
||||||
|
String path = "/$user_collection";
|
||||||
|
var snap = await Firestore.instance.collection(path).document(id).get();
|
||||||
|
return User.fromMap(snap.data, snap.documentID);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:fcs/fcs/common/domain/entities/user.dart';
|
import 'package:fcs/fcs/common/domain/entities/user.dart';
|
||||||
import 'package:fcs/fcs/common/localization/transalation.dart';
|
import 'package:fcs/fcs/common/localization/transalation.dart';
|
||||||
import 'package:fcs/fcs/common/pages/buying_instruction/buying_online.dart';
|
import 'package:fcs/fcs/common/pages/buying_instruction/buying_online.dart';
|
||||||
|
import 'package:fcs/fcs/common/pages/chat/message_detail.dart';
|
||||||
|
import 'package:fcs/fcs/common/pages/chat/model/message_model.dart';
|
||||||
|
import 'package:fcs/fcs/common/pages/chat/notification_list.dart';
|
||||||
import 'package:fcs/fcs/common/pages/customer/customer_list.dart';
|
import 'package:fcs/fcs/common/pages/customer/customer_list.dart';
|
||||||
|
import 'package:fcs/fcs/common/pages/customer/model/customer_model.dart';
|
||||||
import 'package:fcs/fcs/common/pages/faq/faq_list_page.dart';
|
import 'package:fcs/fcs/common/pages/faq/faq_list_page.dart';
|
||||||
import 'package:fcs/fcs/common/pages/model/language_model.dart';
|
import 'package:fcs/fcs/common/pages/model/language_model.dart';
|
||||||
import 'package:fcs/fcs/common/pages/model/main_model.dart';
|
import 'package:fcs/fcs/common/pages/model/main_model.dart';
|
||||||
@@ -10,9 +17,10 @@ import 'package:fcs/fcs/common/pages/payment_methods/payment_method_page.dart';
|
|||||||
import 'package:fcs/fcs/common/pages/staff/staff_list.dart';
|
import 'package:fcs/fcs/common/pages/staff/staff_list.dart';
|
||||||
import 'package:fcs/fcs/common/pages/util.dart';
|
import 'package:fcs/fcs/common/pages/util.dart';
|
||||||
import 'package:fcs/fcs/common/pages/widgets/action_button.dart';
|
import 'package:fcs/fcs/common/pages/widgets/action_button.dart';
|
||||||
|
import 'package:fcs/fcs/common/pages/widgets/badge.dart';
|
||||||
import 'package:fcs/fcs/common/pages/widgets/bottom_widgets.dart';
|
import 'package:fcs/fcs/common/pages/widgets/bottom_widgets.dart';
|
||||||
|
import 'package:fcs/fcs/common/services/services.dart';
|
||||||
import 'package:fcs/pages/discount_list.dart';
|
import 'package:fcs/pages/discount_list.dart';
|
||||||
import 'package:fcs/pages/notification_list.dart';
|
|
||||||
import 'package:fcs/pages/shipment_list.dart';
|
import 'package:fcs/pages/shipment_list.dart';
|
||||||
import 'package:fcs/pages/term.dart';
|
import 'package:fcs/pages/term.dart';
|
||||||
import 'package:fcs/pages_fcs/box_list.dart';
|
import 'package:fcs/pages_fcs/box_list.dart';
|
||||||
@@ -24,6 +32,7 @@ import 'package:fcs/widget/right_left_page_rout.dart';
|
|||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_icons/flutter_icons.dart';
|
import 'package:flutter_icons/flutter_icons.dart';
|
||||||
|
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
@@ -49,10 +58,129 @@ class _HomePageState extends State<HomePage> {
|
|||||||
bool login = false;
|
bool login = false;
|
||||||
bool customer = true;
|
bool customer = true;
|
||||||
List<bool> isSelected = [true, false];
|
List<bool> isSelected = [true, false];
|
||||||
|
static FlutterLocalNotificationsPlugin _flutterLocalNotificationsPlugin =
|
||||||
|
FlutterLocalNotificationsPlugin();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
MainModel mainModel = Provider.of<MainModel>(context, listen: false);
|
||||||
|
|
||||||
|
Services.instance.messagingService.init(
|
||||||
|
(message) {
|
||||||
|
print("Message from FCM:$message");
|
||||||
|
_showNotification(message);
|
||||||
|
},
|
||||||
|
onLaunch: (m) => _showNotiContent(m),
|
||||||
|
onResume: (m) => _showNotiContent(m),
|
||||||
|
onSetupComplete: (token) {
|
||||||
|
mainModel.setMessaginToken = token;
|
||||||
|
});
|
||||||
|
_initLocalNotifications();
|
||||||
|
}
|
||||||
|
|
||||||
|
String notiUserID, notiUserName;
|
||||||
|
_showNotiContent(Map<String, dynamic> message) {
|
||||||
|
try {
|
||||||
|
Map<String, dynamic> map = Map<String, dynamic>.from(message["data"]);
|
||||||
|
notiUserID = map['user_id'];
|
||||||
|
notiUserName = map['user_name'];
|
||||||
|
_startNotiTimer();
|
||||||
|
print("Notification:$map");
|
||||||
|
} catch (e) {
|
||||||
|
print("Error:$e");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_startNotiTimer() async {
|
||||||
|
var _duration = new Duration(milliseconds: 500);
|
||||||
|
new Timer.periodic(_duration, (t) => displayNoti(t));
|
||||||
|
}
|
||||||
|
|
||||||
|
void displayNoti(Timer timer) async {
|
||||||
|
MainModel mainModel = Provider.of<MainModel>(context, listen: false);
|
||||||
|
if (mainModel.isLogin()) {
|
||||||
|
timer.cancel();
|
||||||
|
bool isCustomer = mainModel.isCustomer();
|
||||||
|
String receiverID = isCustomer ? mainModel.user.id : notiUserID;
|
||||||
|
String receiverName = isCustomer ? mainModel.user.name : notiUserName;
|
||||||
|
MessageModel messageModel =
|
||||||
|
Provider.of<MessageModel>(context, listen: false);
|
||||||
|
messageModel.initQuery(receiverID);
|
||||||
|
User user = mainModel.user;
|
||||||
|
if (!isCustomer) {
|
||||||
|
CustomerModel customerModel =
|
||||||
|
Provider.of<CustomerModel>(context, listen: false);
|
||||||
|
user = await customerModel.getUser(receiverID);
|
||||||
|
}
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => MessageDetail(
|
||||||
|
messageModel: messageModel,
|
||||||
|
receiverID: receiverID,
|
||||||
|
receiverName: receiverName,
|
||||||
|
))).then((value) {
|
||||||
|
if (user.userUnseenCount > 0) {
|
||||||
|
messageModel.seenMessages(user.id, true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (user.userUnseenCount > 0) {
|
||||||
|
messageModel.seenMessages(user.id, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_initLocalNotifications() {
|
||||||
|
var initializationSettingsAndroid =
|
||||||
|
new AndroidInitializationSettings('@mipmap/ic_launcher');
|
||||||
|
var initializationSettingsIOS = new IOSInitializationSettings();
|
||||||
|
var initializationSettings = new InitializationSettings(
|
||||||
|
initializationSettingsAndroid, initializationSettingsIOS);
|
||||||
|
_flutterLocalNotificationsPlugin.initialize(initializationSettings);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future _showNotification(Map<String, dynamic> message) async {
|
||||||
|
var pushTitle;
|
||||||
|
var pushText;
|
||||||
|
var action;
|
||||||
|
|
||||||
|
if (Platform.isAndroid) {
|
||||||
|
var nodeData = message['notification'];
|
||||||
|
pushTitle = nodeData['title'];
|
||||||
|
pushText = nodeData['body'];
|
||||||
|
action = nodeData['action'];
|
||||||
|
} else {
|
||||||
|
pushTitle = message['title'];
|
||||||
|
pushText = message['body'];
|
||||||
|
action = message['action'];
|
||||||
|
}
|
||||||
|
print("AppPushs params pushTitle : $pushTitle");
|
||||||
|
print("AppPushs params pushText : $pushText");
|
||||||
|
print("AppPushs params pushAction : $action");
|
||||||
|
|
||||||
|
// @formatter:off
|
||||||
|
var platformChannelSpecificsAndroid = new AndroidNotificationDetails(
|
||||||
|
'your channel id', 'your channel name', 'your channel description',
|
||||||
|
playSound: true,
|
||||||
|
enableVibration: true,
|
||||||
|
importance: Importance.Max,
|
||||||
|
priority: Priority.High);
|
||||||
|
// @formatter:on
|
||||||
|
var platformChannelSpecificsIos =
|
||||||
|
new IOSNotificationDetails(presentSound: true);
|
||||||
|
var platformChannelSpecifics = new NotificationDetails(
|
||||||
|
platformChannelSpecificsAndroid, platformChannelSpecificsIos);
|
||||||
|
|
||||||
|
new Future.delayed(Duration.zero, () {
|
||||||
|
_flutterLocalNotificationsPlugin.show(
|
||||||
|
0,
|
||||||
|
pushTitle,
|
||||||
|
pushText,
|
||||||
|
platformChannelSpecifics,
|
||||||
|
payload: 'No_Sound',
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void dispose() {
|
void dispose() {
|
||||||
@@ -73,6 +201,7 @@ class _HomePageState extends State<HomePage> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
User user = Provider.of<MainModel>(context).user;
|
User user = Provider.of<MainModel>(context).user;
|
||||||
|
customer = Provider.of<MainModel>(context).isCustomer();
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
return Container();
|
return Container();
|
||||||
}
|
}
|
||||||
@@ -130,13 +259,26 @@ class _HomePageState extends State<HomePage> {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
final notiBtn =
|
final notiBtnOrg =
|
||||||
_buildBtn("message.btn", icon: Icons.message, btnCallback: () {
|
_buildBtn("message.btn", icon: Icons.message, btnCallback: () {
|
||||||
|
MessageModel messageModel =
|
||||||
|
Provider.of<MessageModel>(context, listen: false);
|
||||||
|
messageModel.initQuery(user.id);
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
BottomUpPageRoute(NotificationList()),
|
BottomUpPageRoute(MessageDetail(
|
||||||
);
|
messageModel: messageModel,
|
||||||
|
)),
|
||||||
|
).then((value) {
|
||||||
|
if (user.userUnseenCount > 0) {
|
||||||
|
messageModel.seenMessages(user.id, true);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
if (user.userUnseenCount > 0) {
|
||||||
|
messageModel.seenMessages(user.id, true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
final notiBtn = badgeCounter(notiBtnOrg, user.userUnseenCount);
|
||||||
|
|
||||||
final staffBtn = _buildBtn(
|
final staffBtn = _buildBtn(
|
||||||
"staff.title",
|
"staff.title",
|
||||||
@@ -182,7 +324,7 @@ class _HomePageState extends State<HomePage> {
|
|||||||
// customer ? widgets.add(buyingBtn) : "";
|
// customer ? widgets.add(buyingBtn) : "";
|
||||||
// customer || owner ? widgets.add(pickUpBtn) : "";
|
// customer || owner ? widgets.add(pickUpBtn) : "";
|
||||||
// !customer ? widgets.add(shipmentBtn) : "";
|
// !customer ? widgets.add(shipmentBtn) : "";
|
||||||
// customer || owner ? widgets.add(notiBtn) : "";
|
customer ? widgets.add(notiBtn) : "";
|
||||||
user.hasStaffs() ? widgets.add(staffBtn) : "";
|
user.hasStaffs() ? widgets.add(staffBtn) : "";
|
||||||
// owner ? widgets.add(fcsProfileBtn) : "";
|
// owner ? widgets.add(fcsProfileBtn) : "";
|
||||||
// widgets.add(shipmentCostBtn);
|
// widgets.add(shipmentCostBtn);
|
||||||
|
|||||||
@@ -17,9 +17,15 @@ class MainModel extends ChangeNotifier {
|
|||||||
final log = Logger('MainModel');
|
final log = Logger('MainModel');
|
||||||
List<BaseModel> models = [];
|
List<BaseModel> models = [];
|
||||||
|
|
||||||
|
String messagingToken;
|
||||||
User user;
|
User user;
|
||||||
PackageInfo packageInfo;
|
PackageInfo packageInfo;
|
||||||
|
|
||||||
|
set setMessaginToken(token) {
|
||||||
|
this.messagingToken = token;
|
||||||
|
uploadMsgToken();
|
||||||
|
}
|
||||||
|
|
||||||
Setting setting;
|
Setting setting;
|
||||||
|
|
||||||
bool isLoaded = false;
|
bool isLoaded = false;
|
||||||
@@ -36,10 +42,12 @@ class MainModel extends ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
});
|
});
|
||||||
Services.instance.authService.onAuthStatus().listen((event) async {
|
Services.instance.authService.onAuthStatus().listen((event) async {
|
||||||
this.user = event;
|
|
||||||
this.user =
|
this.user =
|
||||||
await Services.instance.authService.getUser(refreshIdToken: true);
|
await Services.instance.authService.getUser(refreshIdToken: true);
|
||||||
_initUser(user);
|
_initUser(user);
|
||||||
|
if (user != null) {
|
||||||
|
uploadMsgToken();
|
||||||
|
}
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -99,6 +107,7 @@ class MainModel extends ChangeNotifier {
|
|||||||
.getUserStream(user.id)
|
.getUserStream(user.id)
|
||||||
.listen((event) {
|
.listen((event) {
|
||||||
this.user = event;
|
this.user = event;
|
||||||
|
|
||||||
models.forEach((m) => m.initUser(user));
|
models.forEach((m) => m.initUser(user));
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
});
|
});
|
||||||
@@ -166,11 +175,23 @@ class MainModel extends ChangeNotifier {
|
|||||||
return authResult;
|
return authResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> signout() {
|
Future<void> uploadMsgToken() {
|
||||||
this.user = null;
|
if (messagingToken == null || user == null) return null;
|
||||||
|
return Services.instance.userService.uploadMsgToken(messagingToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> removeMsgToken() {
|
||||||
|
if (messagingToken == null || user == null) return null;
|
||||||
|
return Services.instance.userService.removeMsgToken(messagingToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> signout() async {
|
||||||
// logout models
|
// logout models
|
||||||
models.forEach((m) => m.logout());
|
models.forEach((m) => m.logout());
|
||||||
|
await removeMsgToken();
|
||||||
|
this.user = null;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
||||||
return Services.instance.authService.signout();
|
return Services.instance.authService.signout();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:fcs/fcs/common/domain/entities/package.dart';
|
import 'package:fcs/fcs/common/domain/entities/package.dart';
|
||||||
import 'package:fcs/fcs/common/helpers/theme.dart';
|
import 'package:fcs/fcs/common/helpers/theme.dart';
|
||||||
|
import 'package:fcs/fcs/common/pages/model/main_model.dart';
|
||||||
import 'package:fcs/fcs/common/pages/package/package_editor.dart';
|
import 'package:fcs/fcs/common/pages/package/package_editor.dart';
|
||||||
import 'package:fcs/fcs/common/pages/util.dart';
|
import 'package:fcs/fcs/common/pages/util.dart';
|
||||||
import 'package:fcs/fcs/common/pages/widgets/bottom_up_page_route.dart';
|
import 'package:fcs/fcs/common/pages/widgets/bottom_up_page_route.dart';
|
||||||
@@ -53,6 +54,8 @@ class _PackageInfoState extends State<PackageInfo> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
bool isCustomer = Provider.of<MainModel>(context).isCustomer();
|
||||||
|
|
||||||
final trackingIdBox = DisplayText(
|
final trackingIdBox = DisplayText(
|
||||||
text: _package.trackingID,
|
text: _package.trackingID,
|
||||||
labelText: getLocalString(context, "package.tracking.id"),
|
labelText: getLocalString(context, "package.tracking.id"),
|
||||||
@@ -107,7 +110,9 @@ class _PackageInfoState extends State<PackageInfo> {
|
|||||||
color: primaryColor,
|
color: primaryColor,
|
||||||
),
|
),
|
||||||
actions: <Widget>[
|
actions: <Widget>[
|
||||||
IconButton(
|
isCustomer
|
||||||
|
? Container()
|
||||||
|
: IconButton(
|
||||||
icon: Icon(Icons.edit, color: primaryColor),
|
icon: Icon(Icons.edit, color: primaryColor),
|
||||||
onPressed: _gotoEditor,
|
onPressed: _gotoEditor,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import 'package:fcs/fcs/common/localization/app_translations.dart';
|
import 'package:fcs/fcs/common/localization/app_translations.dart';
|
||||||
|
import 'package:fcs/fcs/common/pages/chat/message_detail.dart';
|
||||||
|
import 'package:fcs/fcs/common/pages/chat/model/message_model.dart';
|
||||||
import 'package:fcs/fcs/common/pages/model/language_model.dart';
|
import 'package:fcs/fcs/common/pages/model/language_model.dart';
|
||||||
|
import 'package:fcs/fcs/common/pages/model/main_model.dart';
|
||||||
import 'package:fcs/fcs/common/pages/widgets/local_text.dart';
|
import 'package:fcs/fcs/common/pages/widgets/local_text.dart';
|
||||||
|
import 'package:fcs/fcs/common/services/services.dart';
|
||||||
import 'package:fcs/widget/label_widgets.dart';
|
import 'package:fcs/widget/label_widgets.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_colorpicker/flutter_colorpicker.dart';
|
import 'package:flutter_colorpicker/flutter_colorpicker.dart';
|
||||||
@@ -613,3 +617,21 @@ Widget fcsButton(BuildContext context, String text,
|
|||||||
String getLocalString(BuildContext context, String key) {
|
String getLocalString(BuildContext context, String key) {
|
||||||
return AppTranslations.of(context).text(key);
|
return AppTranslations.of(context).text(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void showToast(GlobalKey key, String text) {
|
||||||
|
final ScaffoldState scaffold = key.currentState;
|
||||||
|
scaffold.showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text(text),
|
||||||
|
backgroundColor: secondaryColor,
|
||||||
|
duration: Duration(seconds: 1),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hasUnicode(String text) {
|
||||||
|
final int maxBits = 128;
|
||||||
|
List<int> unicodeSymbols =
|
||||||
|
text.codeUnits.where((ch) => ch > maxBits).toList();
|
||||||
|
return unicodeSymbols.length > 0;
|
||||||
|
}
|
||||||
|
|||||||
38
lib/fcs/common/pages/widgets/badge.dart
Normal file
38
lib/fcs/common/pages/widgets/badge.dart
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import 'package:fcs/fcs/common/helpers/theme.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
Widget badgeCounter(Widget child, int counter) {
|
||||||
|
return Container(
|
||||||
|
width: 120,
|
||||||
|
height: 130,
|
||||||
|
child: new Stack(
|
||||||
|
children: <Widget>[
|
||||||
|
child,
|
||||||
|
counter != null && counter != 0
|
||||||
|
? new Positioned(
|
||||||
|
right: 12,
|
||||||
|
top: 12,
|
||||||
|
child: new Container(
|
||||||
|
padding: EdgeInsets.all(8),
|
||||||
|
decoration: new BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
color: secondaryColor,
|
||||||
|
),
|
||||||
|
constraints: BoxConstraints(
|
||||||
|
minWidth: 14,
|
||||||
|
minHeight: 14,
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
counter > 99 ? '+99' : '$counter',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: new Container()
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -5,11 +5,13 @@ class FcsIDIcon extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
child: SizedBox(
|
child: Container(
|
||||||
width: 25,
|
width: 25,
|
||||||
height: 25,
|
height: 25,
|
||||||
child: FittedBox(
|
child: FittedBox(
|
||||||
child: Image.asset("assets/logo.jpg"),
|
child: Image.asset(
|
||||||
|
"assets/logo.jpg",
|
||||||
|
),
|
||||||
fit: BoxFit.fill,
|
fit: BoxFit.fill,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -113,11 +113,19 @@ class _MultiImageFileState extends State<MultiImageFile> {
|
|||||||
),
|
),
|
||||||
child: fileContainers[index].file == null
|
child: fileContainers[index].file == null
|
||||||
? CachedNetworkImage(
|
? CachedNetworkImage(
|
||||||
imageUrl: fileContainers[index].url,
|
|
||||||
placeholder: (context, url) => Container(
|
|
||||||
width: 50,
|
width: 50,
|
||||||
height: 50,
|
height: 50,
|
||||||
|
imageUrl: fileContainers[index].url,
|
||||||
|
placeholder: (context, url) => Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: 30,
|
||||||
|
height: 30,
|
||||||
child: CircularProgressIndicator()),
|
child: CircularProgressIndicator()),
|
||||||
|
],
|
||||||
|
),
|
||||||
errorWidget: (context, url, error) =>
|
errorWidget: (context, url, error) =>
|
||||||
Icon(Icons.error),
|
Icon(Icons.error),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:fcs/fcs/common/data/providers/common_data_provider.dart';
|
import 'package:fcs/fcs/common/data/providers/common_data_provider.dart';
|
||||||
import 'package:fcs/fcs/common/data/providers/user_data_provider.dart';
|
import 'package:fcs/fcs/common/data/providers/user_data_provider.dart';
|
||||||
import 'package:fcs/fcs/common/domain/entities/payment_method.dart';
|
import 'package:fcs/fcs/common/domain/entities/payment_method.dart';
|
||||||
|
import 'package:fcs/fcs/common/domain/vo/message.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'common_service.dart';
|
import 'common_service.dart';
|
||||||
@@ -26,4 +27,14 @@ class CommonServiceImp implements CommonService {
|
|||||||
Future<void> updatePaymentMethod(PaymentMethod paymentMethod) {
|
Future<void> updatePaymentMethod(PaymentMethod paymentMethod) {
|
||||||
return commonDataProvider.updatePaymentMethod(paymentMethod);
|
return commonDataProvider.updatePaymentMethod(paymentMethod);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> sendMessage(Message message) {
|
||||||
|
return commonDataProvider.sendMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> seenMessage(String ownerID, bool seenByOwner) {
|
||||||
|
return commonDataProvider.seenMessage(ownerID, seenByOwner);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
import 'package:fcs/fcs/common/domain/entities/payment_method.dart';
|
import 'package:fcs/fcs/common/domain/entities/payment_method.dart';
|
||||||
|
import 'package:fcs/fcs/common/domain/vo/message.dart';
|
||||||
|
|
||||||
abstract class CommonService {
|
abstract class CommonService {
|
||||||
// Payment Service
|
// Payment
|
||||||
Future<void> createPaymentMethod(PaymentMethod paymentMethod);
|
Future<void> createPaymentMethod(PaymentMethod paymentMethod);
|
||||||
Future<void> updatePaymentMethod(PaymentMethod paymentMethod);
|
Future<void> updatePaymentMethod(PaymentMethod paymentMethod);
|
||||||
Future<void> deletePayment(String id);
|
Future<void> deletePayment(String id);
|
||||||
|
|
||||||
|
// Messaging
|
||||||
|
Future<void> sendMessage(Message message);
|
||||||
|
Future<void> seenMessage(String ownerID, bool seenByOwner);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,9 +8,12 @@ class MessagingServiceImp implements MessagingService {
|
|||||||
static MessagingFCM messagingFCM;
|
static MessagingFCM messagingFCM;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void init(onMessage, {OnNotify onLaunch, OnNotify onResume}) {
|
void init(onMessage,
|
||||||
messagingFCM =
|
{OnNotify onLaunch, OnNotify onResume, OnSetupComplete onSetupComplete}) {
|
||||||
MessagingFCM(onMessage, onLaunch: onLaunch, onResume: onResume);
|
messagingFCM = MessagingFCM(onMessage,
|
||||||
|
onLaunch: onLaunch,
|
||||||
|
onResume: onResume,
|
||||||
|
onSetupComplete: onSetupComplete);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
typedef OnNotify(Map<String, dynamic> message);
|
typedef OnNotify(Map<String, dynamic> message);
|
||||||
|
typedef OnSetupComplete(String token);
|
||||||
|
|
||||||
abstract class MessagingService {
|
abstract class MessagingService {
|
||||||
void init(OnNotify onMessage, {OnNotify onLaunch, OnNotify onResume});
|
void init(OnNotify onMessage,
|
||||||
|
{OnNotify onLaunch, OnNotify onResume, OnSetupComplete onSetupComplete});
|
||||||
Future<void> subscribe(String topic);
|
Future<void> subscribe(String topic);
|
||||||
Future<void> unsubscribe(String topic);
|
Future<void> unsubscribe(String topic);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,4 +38,14 @@ class UserServiceImp implements UserService {
|
|||||||
Future<List<User>> searchUser(String term) {
|
Future<List<User>> searchUser(String term) {
|
||||||
return userDataProvider.searchUser(term);
|
return userDataProvider.searchUser(term);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> removeMsgToken(String token) {
|
||||||
|
return userDataProvider.removeMsgToken(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> uploadMsgToken(String token) {
|
||||||
|
return userDataProvider.uploadMsgToken(token);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,4 +6,6 @@ abstract class UserService {
|
|||||||
Future<void> acceptRequest(String userID);
|
Future<void> acceptRequest(String userID);
|
||||||
Future<User> findUser(String phoneNumber);
|
Future<User> findUser(String phoneNumber);
|
||||||
Future<List<User>> searchUser(String term);
|
Future<List<User>> searchUser(String term);
|
||||||
|
Future<void> uploadMsgToken(String token);
|
||||||
|
Future<void> removeMsgToken(String token);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ void main() {
|
|||||||
flavor: Flavor.DEV,
|
flavor: Flavor.DEV,
|
||||||
color: Colors.blue,
|
color: Colors.blue,
|
||||||
apiURL: "https://asia-northeast1-fcs-dev1.cloudfunctions.net/API",
|
apiURL: "https://asia-northeast1-fcs-dev1.cloudfunctions.net/API",
|
||||||
|
reportURL: "http://petrok.mokkon.com:8091",
|
||||||
|
reportProjectID: "fcs-dev",
|
||||||
level: Level.ALL);
|
level: Level.ALL);
|
||||||
runApp(App());
|
runApp(App());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||||
|
import 'package:fcs/fcs/common/domain/constants.dart';
|
||||||
import 'package:fcs/model/base_model.dart';
|
import 'package:fcs/model/base_model.dart';
|
||||||
import 'package:fcs/vo/message.dart';
|
import 'package:fcs/vo/message.dart';
|
||||||
import 'package:fcs/vo/package.dart';
|
import 'package:fcs/vo/package.dart';
|
||||||
@@ -23,8 +24,7 @@ class MessageModel extends BaseModel {
|
|||||||
senderName: "FCS System",
|
senderName: "FCS System",
|
||||||
receiverName: "Ko Myo Min",
|
receiverName: "Ko Myo Min",
|
||||||
date: DateTime(2020, 6, 1, 1, 1, 1),
|
date: DateTime(2020, 6, 1, 1, 1, 1),
|
||||||
message:
|
message: "'A202-3 #1'",
|
||||||
"'A202-3 #1'",
|
|
||||||
),
|
),
|
||||||
Message(
|
Message(
|
||||||
senderName: "FCS System",
|
senderName: "FCS System",
|
||||||
@@ -43,8 +43,7 @@ class MessageModel extends BaseModel {
|
|||||||
senderName: "FCS System",
|
senderName: "FCS System",
|
||||||
receiverName: "Ko Myo Min",
|
receiverName: "Ko Myo Min",
|
||||||
date: DateTime(2020, 6, 1, 2, 1, 1),
|
date: DateTime(2020, 6, 1, 2, 1, 1),
|
||||||
message:
|
message: "'INV202005010387'",
|
||||||
"'INV202005010387'",
|
|
||||||
),
|
),
|
||||||
Message(
|
Message(
|
||||||
senderName: "FCS System",
|
senderName: "FCS System",
|
||||||
@@ -64,8 +63,7 @@ class MessageModel extends BaseModel {
|
|||||||
senderName: "FCS System",
|
senderName: "FCS System",
|
||||||
receiverName: "Shipper",
|
receiverName: "Shipper",
|
||||||
date: DateTime(2020, 6, 1, 1, 1, 1),
|
date: DateTime(2020, 6, 1, 1, 1, 1),
|
||||||
message:
|
message: "'A202-3 #1'",
|
||||||
"'A202-3 #1'",
|
|
||||||
),
|
),
|
||||||
Message(
|
Message(
|
||||||
senderName: "FCS System",
|
senderName: "FCS System",
|
||||||
@@ -78,8 +76,7 @@ class MessageModel extends BaseModel {
|
|||||||
senderName: "FCS System",
|
senderName: "FCS System",
|
||||||
receiverName: "Shipper",
|
receiverName: "Shipper",
|
||||||
date: DateTime(2020, 6, 1, 2, 1, 1),
|
date: DateTime(2020, 6, 1, 2, 1, 1),
|
||||||
message:
|
message: "'INV202005010387'",
|
||||||
"'INV202005010387'",
|
|
||||||
),
|
),
|
||||||
Message(
|
Message(
|
||||||
senderName: "FCS System",
|
senderName: "FCS System",
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
class Message {
|
class Message {
|
||||||
|
String id;
|
||||||
String receiverName;
|
String receiverName;
|
||||||
String message;
|
String message;
|
||||||
DateTime date;
|
DateTime date;
|
||||||
String senderName;
|
String senderName;
|
||||||
bool isMe;
|
bool isMe;
|
||||||
Message({this.receiverName, this.message, this.date, this.senderName,this.isMe});
|
|
||||||
|
Message(
|
||||||
|
{this.receiverName, this.message, this.date, this.senderName, this.isMe});
|
||||||
|
|
||||||
bool fromToday() {
|
bool fromToday() {
|
||||||
var now = DateTime.now();
|
var now = DateTime.now();
|
||||||
return date.day == now.day &&
|
return date.day == now.day &&
|
||||||
|
|||||||
Reference in New Issue
Block a user