clean up
This commit is contained in:
135
lib/pages/chat/bubble.dart
Normal file
135
lib/pages/chat/bubble.dart
Normal file
@@ -0,0 +1,135 @@
|
||||
import 'package:fcs/helpers/theme.dart';
|
||||
import 'package:fcs/pages/package/package_info.dart';
|
||||
import 'package:fcs/pages/main/util.dart';
|
||||
import 'package:fcs/pages/widgets/fcs_id_icon.dart';
|
||||
import 'package:fcs/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();
|
||||
}
|
||||
}
|
||||
191
lib/pages/chat/message_detail.dart
Normal file
191
lib/pages/chat/message_detail.dart
Normal file
@@ -0,0 +1,191 @@
|
||||
import 'package:fcs/domain/constants.dart';
|
||||
import 'package:fcs/domain/entities/package.dart';
|
||||
import 'package:fcs/domain/entities/user.dart';
|
||||
import 'package:fcs/domain/vo/message.dart';
|
||||
import 'package:fcs/helpers/theme.dart';
|
||||
import 'package:fcs/pages/chat/model/message_model.dart';
|
||||
import 'package:fcs/pages/customer/customer_editor.dart';
|
||||
import 'package:fcs/pages/customer/model/customer_model.dart';
|
||||
import 'package:fcs/pages/main/model/main_model.dart';
|
||||
import 'package:fcs/pages/package/model/package_model.dart';
|
||||
import 'package:fcs/pages/package/package_info.dart';
|
||||
import 'package:fcs/pages/profile/profile_page.dart';
|
||||
import 'package:fcs/pages/main/util.dart';
|
||||
import 'package:fcs/pages/widgets/bottom_up_page_route.dart';
|
||||
import 'package:flutter/material.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)));
|
||||
}
|
||||
if (message.messageType == message_type_profile &&
|
||||
message.messageID != null &&
|
||||
message.messageID != "") {
|
||||
MainModel mainModel = Provider.of<MainModel>(context, listen: false);
|
||||
|
||||
if (mainModel.user.isCustomer()) {
|
||||
Navigator.push<bool>(context, BottomUpPageRoute(Profile()));
|
||||
} else {
|
||||
CustomerModel customerModel =
|
||||
Provider.of<CustomerModel>(context, listen: false);
|
||||
User user = await customerModel.getUser(message.messageID);
|
||||
Navigator.of(context)
|
||||
.push(BottomUpPageRoute(CustomerEditor(customer: user)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
92
lib/pages/chat/model/message_model.dart
Normal file
92
lib/pages/chat/model/message_model.dart
Normal file
@@ -0,0 +1,92 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:fcs/data/services/services.dart';
|
||||
import 'package:fcs/domain/constants.dart';
|
||||
import 'package:fcs/domain/vo/message.dart';
|
||||
import 'package:fcs/pages/main/model/base_model.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
class MessageModel extends BaseModel {
|
||||
final log = Logger('MessageModel');
|
||||
|
||||
List<Message> messages;
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user