add messaging
This commit is contained in:
@@ -148,6 +148,9 @@
|
||||
"pm.save.btn":"Save Payment Method",
|
||||
"pm.delete.confirm":"Delete this Payment Method?",
|
||||
|
||||
"message.view.detail":"View Deatil",
|
||||
"message.hint.input":"Type your message...",
|
||||
|
||||
"btn.save": "Save",
|
||||
"btn.approve":"Approve",
|
||||
"btn.delete":"Delete",
|
||||
|
||||
@@ -150,6 +150,9 @@
|
||||
"pm.save.btn":"သိမ်းဆည်းရန်",
|
||||
"pm.delete.confirm":"ငွေပေးချေစနစ်ကို ဖျက်မလား?",
|
||||
|
||||
"message.view.detail":"အသေးစိတ် ကြည့်ရန်",
|
||||
"message.hint.input":"စာကို ဒီမှာ ရိုက်ထည့်ပါ...",
|
||||
|
||||
"btn.save":"သိမ်းဆည်းရန်",
|
||||
"btn.approve":"အတည်ပြုရန်",
|
||||
"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/transalation.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/staff/model/staff_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/delivery_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/storage_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/login_page.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:provider/provider.dart';
|
||||
|
||||
import 'fcs/common/pages/chat/model/message_model.dart';
|
||||
import 'fcs/common/pages/home_page.dart';
|
||||
import 'fcs/common/pages/splash_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/device_model.dart';
|
||||
import 'model/do_model.dart';
|
||||
import 'model/employee_model.dart';
|
||||
import 'model/invoice_model.dart';
|
||||
import 'model/log_model.dart';
|
||||
import 'model/main_model.dart';
|
||||
@@ -110,6 +106,7 @@ class _AppState extends State<App> {
|
||||
..addModel(staffModel)
|
||||
..addModel(shipmentModel)
|
||||
..addModel(packageModel)
|
||||
..addModel(messageModel)
|
||||
..addModel(marketModel);
|
||||
mainModel2.init();
|
||||
|
||||
@@ -138,69 +135,10 @@ class _AppState extends State<App> {
|
||||
..addModel(pickUpModel)
|
||||
..addModel(shipmentRateModel)
|
||||
..addModel(boxModel)
|
||||
..addModel(messageModel)
|
||||
..addModel(shipmentRateModel)
|
||||
..addModel(invoiceModel)
|
||||
..addModel(discountModel);
|
||||
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) {
|
||||
|
||||
@@ -125,24 +125,24 @@ class AuthFb {
|
||||
|
||||
log.info("Claims:${idToken.claims}");
|
||||
|
||||
User user = User();
|
||||
user.status = idToken.claims["st"];
|
||||
String cid = idToken.claims["cid"];
|
||||
User user;
|
||||
if (cid != null && cid != "") {
|
||||
user = await getUserFromFirestore(cid);
|
||||
}
|
||||
if (user == null) {
|
||||
user = User();
|
||||
user.id = cid;
|
||||
user.phoneNumber = firebaseUser.phoneNumber;
|
||||
user.status = idToken.claims["st"];
|
||||
}
|
||||
|
||||
// add privileges
|
||||
String privileges = idToken.claims["pr"];
|
||||
if (privileges != null && privileges != "") {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
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/firebase_helper.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
@@ -20,4 +21,15 @@ class CommonDataProvider {
|
||||
return await requestAPI("/payment_methods", "DELETE",
|
||||
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;
|
||||
|
||||
MessagingFCM(OnNotify onMessage, {OnNotify onLaunch, OnNotify onResume}) {
|
||||
MessagingFCM(OnNotify onMessage,
|
||||
{OnNotify onLaunch, OnNotify onResume, OnSetupComplete onSetupComplete}) {
|
||||
_firebaseMessaging = FirebaseMessaging();
|
||||
_firebaseMessaging.configure(
|
||||
onMessage: (Map<String, dynamic> message) async {
|
||||
@@ -48,6 +49,7 @@ class MessagingFCM {
|
||||
log.info("Settings registered: $settings");
|
||||
});
|
||||
_firebaseMessaging.getToken().then((String token) {
|
||||
if (onSetupComplete != null) onSetupComplete(token);
|
||||
log.info("Messaging Token:$token");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -27,6 +27,16 @@ class UserDataProvider {
|
||||
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 {
|
||||
QuerySnapshot querySnap = await Firestore.instance
|
||||
.collection(user_collection)
|
||||
|
||||
@@ -5,6 +5,7 @@ const setting_doc_id = "setting";
|
||||
const privilege_collection = "privileges";
|
||||
const markets_collection = "markets";
|
||||
const packages_collection = "packages";
|
||||
const messages_collection = "messages";
|
||||
|
||||
const user_requested_status = "requested";
|
||||
const user_invited_status = "invited";
|
||||
@@ -14,6 +15,10 @@ const pkg_files_path = "/packages";
|
||||
// Link page
|
||||
const page_payment_methods = "payment_methods";
|
||||
const page_buying_instructions = "buying_instructions";
|
||||
|
||||
// Message type
|
||||
const message_type_package = "t_p";
|
||||
|
||||
//////////////////////////////
|
||||
|
||||
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/pages/package/package_info.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
DateFormat dayFormat = DateFormat("MMM dd yyyy");
|
||||
DateFormat timeFormat = DateFormat("HH:mm");
|
||||
|
||||
class User {
|
||||
String id;
|
||||
@@ -6,6 +12,37 @@ class User {
|
||||
String phoneNumber;
|
||||
String status;
|
||||
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 = [];
|
||||
|
||||
@@ -16,14 +53,17 @@ class User {
|
||||
bool get invited => status != null && status == userStatusInvited;
|
||||
bool get requested => status != null && status == userStatusRequested;
|
||||
String get share => "Your phone number:$phoneNumber";
|
||||
User({
|
||||
this.id,
|
||||
User(
|
||||
{this.id,
|
||||
this.name,
|
||||
this.phoneNumber,
|
||||
this.fcsID,
|
||||
this.status,
|
||||
this.privileges,
|
||||
});
|
||||
this.lastMessage,
|
||||
this.lastMessageTime,
|
||||
this.userUnseenCount,
|
||||
this.fcsUnseenCount});
|
||||
|
||||
factory User.fromJson(Map<String, dynamic> json) {
|
||||
return User(
|
||||
@@ -32,6 +72,7 @@ class User {
|
||||
fcsID: json['fcs_id'],
|
||||
phoneNumber: json['phone_number'],
|
||||
status: json['status'],
|
||||
lastMessage: json['last_message'],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -49,6 +90,8 @@ class User {
|
||||
}
|
||||
|
||||
factory User.fromMap(Map<String, dynamic> map, String docID) {
|
||||
var _date = (map['message_time'] as Timestamp);
|
||||
|
||||
List<String> _privileges =
|
||||
map['privileges'] == null ? [] : map['privileges'].cast<String>();
|
||||
|
||||
@@ -58,7 +101,11 @@ class User {
|
||||
phoneNumber: map['phone_number'],
|
||||
status: map['status'],
|
||||
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() {
|
||||
|
||||
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(
|
||||
labelTextKey: 'contact.usa.address',
|
||||
iconData: CupertinoIcons.location,
|
||||
maxLines: 3,
|
||||
controller: _usaAddress);
|
||||
final mmAddressBox = InputText(
|
||||
labelTextKey: 'contact.mm.address',
|
||||
iconData: CupertinoIcons.location,
|
||||
maxLines: 3,
|
||||
controller: _mmAddress);
|
||||
final emailBox = InputText(
|
||||
labelTextKey: 'contact.email',
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import 'package:fcs/fcs/common/domain/constants.dart';
|
||||
import 'package:fcs/fcs/common/domain/entities/user.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/model/customer_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/widgets/bottom_up_page_route.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/material.dart';
|
||||
import 'package:flutter_icons/flutter_icons.dart';
|
||||
@@ -69,10 +71,9 @@ class _CustomerListState extends State<CustomerList> {
|
||||
Expanded(
|
||||
child: ListView.separated(
|
||||
separatorBuilder: (context, index) => Divider(
|
||||
color: Colors.black,
|
||||
color: Colors.grey,
|
||||
),
|
||||
scrollDirection: Axis.vertical,
|
||||
padding: EdgeInsets.only(left: 15, right: 15),
|
||||
shrinkWrap: true,
|
||||
itemCount: customerModel.customers.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
@@ -88,32 +89,52 @@ class _CustomerListState extends State<CustomerList> {
|
||||
|
||||
Widget _item(User customer) {
|
||||
return InkWell(
|
||||
onTap: () => _select(customer),
|
||||
onTap: () => _gotoMsg(customer),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 12.0, right: 12),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: new Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
padding: const EdgeInsets.symmetric(vertical: 2.0),
|
||||
child: new Row(
|
||||
children: <Widget>[
|
||||
Icon(
|
||||
Feather.user,
|
||||
InkWell(
|
||||
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,
|
||||
size: 40,
|
||||
borderRadius:
|
||||
BorderRadius.all(Radius.circular(35.0))),
|
||||
child: Text(
|
||||
customer.initial,
|
||||
style: TextStyle(fontSize: 30, color: Colors.white),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
new Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 8.0),
|
||||
child: new Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
new Text(
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 2.0),
|
||||
child: new Text(
|
||||
customer.name,
|
||||
style: new TextStyle(
|
||||
fontSize: 15.0, color: primaryColor),
|
||||
fontSize: 20.0, color: primaryColor),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
padding: const EdgeInsets.only(top: 2.0),
|
||||
child: new Text(
|
||||
customer.phoneNumber,
|
||||
customer.getLastMessage,
|
||||
style: new TextStyle(
|
||||
fontSize: 15.0, color: Colors.grey),
|
||||
),
|
||||
@@ -121,16 +142,23 @@ class _CustomerListState extends State<CustomerList> {
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 5),
|
||||
child: _status(customer.status),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 5),
|
||||
child: Text(customer.getLastMessageTime),
|
||||
),
|
||||
getCount(customer),
|
||||
customer.status == user_invited_status
|
||||
? FlatButton(
|
||||
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) {
|
||||
return user_requested_status == status
|
||||
? Text(status, style: TextStyle(color: primaryColor, fontSize: 14))
|
||||
@@ -166,6 +207,26 @@ class _CustomerListState extends State<CustomerList> {
|
||||
.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 {
|
||||
MainModel mainModel = Provider.of<MainModel>(context, listen: false);
|
||||
String appUrl = mainModel.setting.appUrl;
|
||||
|
||||
@@ -50,6 +50,7 @@ class CustomerModel extends BaseModel {
|
||||
customerListener = Firestore.instance
|
||||
.collection("/$user_collection")
|
||||
.where("is_sys_admin", isEqualTo: false)
|
||||
.orderBy("message_time", descending: true)
|
||||
.snapshots()
|
||||
.listen((QuerySnapshot snapshot) {
|
||||
customers.clear();
|
||||
@@ -87,4 +88,10 @@ class CustomerModel extends BaseModel {
|
||||
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/localization/transalation.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/model/customer_model.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/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/util.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/services/services.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/term.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/material.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:intl/intl.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
@@ -49,10 +58,129 @@ class _HomePageState extends State<HomePage> {
|
||||
bool login = false;
|
||||
bool customer = true;
|
||||
List<bool> isSelected = [true, false];
|
||||
static FlutterLocalNotificationsPlugin _flutterLocalNotificationsPlugin =
|
||||
FlutterLocalNotificationsPlugin();
|
||||
|
||||
@override
|
||||
void 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() {
|
||||
@@ -73,6 +201,7 @@ class _HomePageState extends State<HomePage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
User user = Provider.of<MainModel>(context).user;
|
||||
customer = Provider.of<MainModel>(context).isCustomer();
|
||||
if (user == null) {
|
||||
return Container();
|
||||
}
|
||||
@@ -130,13 +259,26 @@ class _HomePageState extends State<HomePage> {
|
||||
);
|
||||
});
|
||||
|
||||
final notiBtn =
|
||||
final notiBtnOrg =
|
||||
_buildBtn("message.btn", icon: Icons.message, btnCallback: () {
|
||||
MessageModel messageModel =
|
||||
Provider.of<MessageModel>(context, listen: false);
|
||||
messageModel.initQuery(user.id);
|
||||
Navigator.push(
|
||||
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(
|
||||
"staff.title",
|
||||
@@ -182,7 +324,7 @@ class _HomePageState extends State<HomePage> {
|
||||
// customer ? widgets.add(buyingBtn) : "";
|
||||
// customer || owner ? widgets.add(pickUpBtn) : "";
|
||||
// !customer ? widgets.add(shipmentBtn) : "";
|
||||
// customer || owner ? widgets.add(notiBtn) : "";
|
||||
customer ? widgets.add(notiBtn) : "";
|
||||
user.hasStaffs() ? widgets.add(staffBtn) : "";
|
||||
// owner ? widgets.add(fcsProfileBtn) : "";
|
||||
// widgets.add(shipmentCostBtn);
|
||||
|
||||
@@ -17,9 +17,15 @@ class MainModel extends ChangeNotifier {
|
||||
final log = Logger('MainModel');
|
||||
List<BaseModel> models = [];
|
||||
|
||||
String messagingToken;
|
||||
User user;
|
||||
PackageInfo packageInfo;
|
||||
|
||||
set setMessaginToken(token) {
|
||||
this.messagingToken = token;
|
||||
uploadMsgToken();
|
||||
}
|
||||
|
||||
Setting setting;
|
||||
|
||||
bool isLoaded = false;
|
||||
@@ -36,10 +42,12 @@ class MainModel extends ChangeNotifier {
|
||||
notifyListeners();
|
||||
});
|
||||
Services.instance.authService.onAuthStatus().listen((event) async {
|
||||
this.user = event;
|
||||
this.user =
|
||||
await Services.instance.authService.getUser(refreshIdToken: true);
|
||||
_initUser(user);
|
||||
if (user != null) {
|
||||
uploadMsgToken();
|
||||
}
|
||||
notifyListeners();
|
||||
});
|
||||
}
|
||||
@@ -99,6 +107,7 @@ class MainModel extends ChangeNotifier {
|
||||
.getUserStream(user.id)
|
||||
.listen((event) {
|
||||
this.user = event;
|
||||
|
||||
models.forEach((m) => m.initUser(user));
|
||||
notifyListeners();
|
||||
});
|
||||
@@ -166,11 +175,23 @@ class MainModel extends ChangeNotifier {
|
||||
return authResult;
|
||||
}
|
||||
|
||||
Future<void> signout() {
|
||||
this.user = null;
|
||||
Future<void> uploadMsgToken() {
|
||||
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
|
||||
models.forEach((m) => m.logout());
|
||||
await removeMsgToken();
|
||||
this.user = null;
|
||||
notifyListeners();
|
||||
|
||||
return Services.instance.authService.signout();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:fcs/fcs/common/domain/entities/package.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/util.dart';
|
||||
import 'package:fcs/fcs/common/pages/widgets/bottom_up_page_route.dart';
|
||||
@@ -53,6 +54,8 @@ class _PackageInfoState extends State<PackageInfo> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
bool isCustomer = Provider.of<MainModel>(context).isCustomer();
|
||||
|
||||
final trackingIdBox = DisplayText(
|
||||
text: _package.trackingID,
|
||||
labelText: getLocalString(context, "package.tracking.id"),
|
||||
@@ -107,7 +110,9 @@ class _PackageInfoState extends State<PackageInfo> {
|
||||
color: primaryColor,
|
||||
),
|
||||
actions: <Widget>[
|
||||
IconButton(
|
||||
isCustomer
|
||||
? Container()
|
||||
: IconButton(
|
||||
icon: Icon(Icons.edit, color: primaryColor),
|
||||
onPressed: _gotoEditor,
|
||||
)
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
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/main_model.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:flutter/material.dart';
|
||||
import 'package:flutter_colorpicker/flutter_colorpicker.dart';
|
||||
@@ -613,3 +617,21 @@ Widget fcsButton(BuildContext context, String text,
|
||||
String getLocalString(BuildContext context, String 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) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: SizedBox(
|
||||
child: Container(
|
||||
width: 25,
|
||||
height: 25,
|
||||
child: FittedBox(
|
||||
child: Image.asset("assets/logo.jpg"),
|
||||
child: Image.asset(
|
||||
"assets/logo.jpg",
|
||||
),
|
||||
fit: BoxFit.fill,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -113,11 +113,19 @@ class _MultiImageFileState extends State<MultiImageFile> {
|
||||
),
|
||||
child: fileContainers[index].file == null
|
||||
? CachedNetworkImage(
|
||||
imageUrl: fileContainers[index].url,
|
||||
placeholder: (context, url) => Container(
|
||||
width: 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()),
|
||||
],
|
||||
),
|
||||
errorWidget: (context, url, 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/user_data_provider.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 'common_service.dart';
|
||||
@@ -26,4 +27,14 @@ class CommonServiceImp implements CommonService {
|
||||
Future<void> updatePaymentMethod(PaymentMethod 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/vo/message.dart';
|
||||
|
||||
abstract class CommonService {
|
||||
// Payment Service
|
||||
// Payment
|
||||
Future<void> createPaymentMethod(PaymentMethod paymentMethod);
|
||||
Future<void> updatePaymentMethod(PaymentMethod paymentMethod);
|
||||
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;
|
||||
|
||||
@override
|
||||
void init(onMessage, {OnNotify onLaunch, OnNotify onResume}) {
|
||||
messagingFCM =
|
||||
MessagingFCM(onMessage, onLaunch: onLaunch, onResume: onResume);
|
||||
void init(onMessage,
|
||||
{OnNotify onLaunch, OnNotify onResume, OnSetupComplete onSetupComplete}) {
|
||||
messagingFCM = MessagingFCM(onMessage,
|
||||
onLaunch: onLaunch,
|
||||
onResume: onResume,
|
||||
onSetupComplete: onSetupComplete);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
typedef OnNotify(Map<String, dynamic> message);
|
||||
typedef OnSetupComplete(String token);
|
||||
|
||||
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> unsubscribe(String topic);
|
||||
}
|
||||
|
||||
@@ -38,4 +38,14 @@ class UserServiceImp implements UserService {
|
||||
Future<List<User>> searchUser(String 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<User> findUser(String phoneNumber);
|
||||
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,
|
||||
color: Colors.blue,
|
||||
apiURL: "https://asia-northeast1-fcs-dev1.cloudfunctions.net/API",
|
||||
reportURL: "http://petrok.mokkon.com:8091",
|
||||
reportProjectID: "fcs-dev",
|
||||
level: Level.ALL);
|
||||
runApp(App());
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:async';
|
||||
|
||||
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/vo/message.dart';
|
||||
import 'package:fcs/vo/package.dart';
|
||||
@@ -23,8 +24,7 @@ class MessageModel extends BaseModel {
|
||||
senderName: "FCS System",
|
||||
receiverName: "Ko Myo Min",
|
||||
date: DateTime(2020, 6, 1, 1, 1, 1),
|
||||
message:
|
||||
"'A202-3 #1'",
|
||||
message: "'A202-3 #1'",
|
||||
),
|
||||
Message(
|
||||
senderName: "FCS System",
|
||||
@@ -43,8 +43,7 @@ class MessageModel extends BaseModel {
|
||||
senderName: "FCS System",
|
||||
receiverName: "Ko Myo Min",
|
||||
date: DateTime(2020, 6, 1, 2, 1, 1),
|
||||
message:
|
||||
"'INV202005010387'",
|
||||
message: "'INV202005010387'",
|
||||
),
|
||||
Message(
|
||||
senderName: "FCS System",
|
||||
@@ -64,8 +63,7 @@ class MessageModel extends BaseModel {
|
||||
senderName: "FCS System",
|
||||
receiverName: "Shipper",
|
||||
date: DateTime(2020, 6, 1, 1, 1, 1),
|
||||
message:
|
||||
"'A202-3 #1'",
|
||||
message: "'A202-3 #1'",
|
||||
),
|
||||
Message(
|
||||
senderName: "FCS System",
|
||||
@@ -78,8 +76,7 @@ class MessageModel extends BaseModel {
|
||||
senderName: "FCS System",
|
||||
receiverName: "Shipper",
|
||||
date: DateTime(2020, 6, 1, 2, 1, 1),
|
||||
message:
|
||||
"'INV202005010387'",
|
||||
message: "'INV202005010387'",
|
||||
),
|
||||
Message(
|
||||
senderName: "FCS System",
|
||||
|
||||
@@ -13,7 +13,7 @@ class DateUtil {
|
||||
}
|
||||
|
||||
String updatePhoneNumber(String phoneNumber) {
|
||||
if(phoneNumber==null) return null;
|
||||
if (phoneNumber == null) return null;
|
||||
if (phoneNumber.startsWith("09")) {
|
||||
return "959" + phoneNumber.substring(2);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
class Message {
|
||||
String id;
|
||||
String receiverName;
|
||||
String message;
|
||||
DateTime date;
|
||||
String senderName;
|
||||
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() {
|
||||
var now = DateTime.now();
|
||||
return date.day == now.day &&
|
||||
|
||||
Reference in New Issue
Block a user