blob: 5bc9445cd5c6cb02ff8451772c4e4f72c8e9c414 [file] [log] [blame]
// Copyright 2016 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/http.dart' as http;
import 'keys.dart';
const String setMessageUrl = 'http://baku-flutter-chat.appspot.com/set_message';
const String getHistoryUrl = 'http://baku-flutter-chat.appspot.com/get_history';
String myname;
int myColor;
void start(String name, int color) {
myname = name;
myColor = color;
runApp(
new MaterialApp(
title: 'Chat Demo',
theme: new ThemeData(
primarySwatch: Colors.primaries[myColor],
accentColor: Colors.orangeAccent[400]
),
home: new ChatScreen()
)
);
}
class ChatScreen extends StatefulWidget {
ChatScreen({ Key key }) : super(key: key);
@override
State createState() => new ChatScreenState();
}
class ChatScreenState extends State<ChatScreen> {
String _name = myname;
InputValue _currentMessage = InputValue.empty;
bool get _isComposing => _currentMessage.text.isNotEmpty;
int _color = myColor;
List<ChatMessage> _messages = <ChatMessage>[];
@override
void initState() {
super.initState();
listenToHistory();
}
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Chatting as $_name')
),
body: new Column(
children: <Widget>[
new Flexible(
child: new Block(
padding: new EdgeInsets.symmetric(horizontal: 8.0),
scrollAnchor: ViewportAnchor.end,
children: _messages.map((m) => new ChatMessageListItem(m)).toList()
)
),
_buildTextComposer()
]
)
);
}
Widget _buildTextComposer() {
ThemeData themeData = Theme.of(context);
return new Row(
children: <Widget>[
new Flexible(
child: new Input(
key: new ValueKey(inputKey),
value: _currentMessage,
hintText: 'Enter message',
onSubmitted: _handleMessageAdded,
onChanged: _handleMessageChanged
)
),
new Container(
margin: new EdgeInsets.symmetric(horizontal: 4.0),
child: new IconButton(
key: new ValueKey(buttonKey),
icon: new Icon(Icons.send),
onPressed: _isComposing ? () => _handleMessageAdded(_currentMessage) : null,
color: themeData.accentColor
)
)
]
);
}
Future<Null> listenToHistory() async {
while (true) {
getMessages();
await new Future<Null>.delayed(new Duration(milliseconds: 500));
}
}
void postMessage(ChatMessage message) {
ChatUser user = message.sender;
Map<String, String> headers = {
'Content-type': 'application/json',
'Accept': 'application/json'
};
Map<String, String> body = <String, String>{
'user': user.name,
'color': '${user.color}',
'text': message.text
};
http.post(
setMessageUrl,
headers: headers,
body: JSON.encode(body)
).then((http.Response response) {
String json = response.body;
if (json == null) {
print('Fail to post $message to $setMessageUrl');
return;
}
JsonDecoder decoder = new JsonDecoder();
dynamic result = decoder.convert(json);
print('Response: ${result["answer"]}');
});
}
void getMessages() {
http.get(getHistoryUrl).then((http.Response response) {
String json = response.body;
if (json == null) {
print('Fail to get message history from $getHistoryUrl');
return;
}
JsonDecoder decoder = new JsonDecoder();
dynamic result = decoder.convert(json);
List<ChatMessage> history = new List.from(
result['history'].map(
(message) {
return new ChatMessage(
sender: new ChatUser(
name: message['user'],
color: int.parse(message['color'])
),
text: message['text'],
animationController: new AnimationController(
duration: new Duration(milliseconds: 700)
)
);
}
)
);
if (_messages.length > history.length) {
print('Local messages size is greater than the remote messages size!');
setState(() {
_messages.clear();
});
}
if (_messages.length == history.length) {
return;
}
for (int i = _messages.length; i < history.length; i++) {
setState(() {
_messages.add(history[i]);
});
history[i].animationController.forward();
}
});
}
void _addMessage({ String name, int color, String text }) {
AnimationController animationController = new AnimationController(
duration: new Duration(milliseconds: 700)
);
ChatUser sender = new ChatUser(name: name, color: color);
ChatMessage message = new ChatMessage(
sender: sender,
text: text,
animationController: animationController
);
postMessage(message);
}
void _handleMessageChanged(InputValue value) {
setState(() {
_currentMessage = value;
});
}
void _handleMessageAdded(InputValue value) {
setState(() {
_currentMessage = InputValue.empty;
});
_addMessage(name: _name, color: _color, text: value.text);
}
@override
void dispose() {
for (ChatMessage message in _messages) {
message.animationController.dispose();
}
super.dispose();
}
}
class ChatMessageListItem extends StatelessWidget {
ChatMessageListItem(this.message);
final ChatMessage message;
Widget build(BuildContext context) {
ListItem item;
if (message.sender.name == myname) {
item = new ListItem(
dense: true,
trailing: new CircleAvatar(
child: new Text(message.sender.name[0]),
backgroundColor: Colors.accents[message.sender.color][700]
),
title: new Align(
alignment: FractionalOffset.centerRight,
child: new Text(
message.sender.name,
textAlign: TextAlign.center
)
),
subtitle: new Align(
alignment: FractionalOffset.centerRight,
child: new Text(
message.text,
textAlign: TextAlign.center
)
)
);
} else {
item = new ListItem(
dense: true,
leading: new CircleAvatar(
child: new Text(message.sender.name[0]),
backgroundColor: Colors.accents[message.sender.color][700]
),
title: new Align(
alignment: FractionalOffset.centerLeft,
child: new Text(
message.sender.name,
textAlign: TextAlign.center
)
),
subtitle: new Align(
alignment: FractionalOffset.centerLeft,
child: new Text(
message.text,
textAlign: TextAlign.center
)
)
);
}
return new SizeTransition(
sizeFactor: new CurvedAnimation(
parent: message.animationController,
curve: Curves.easeOut
),
axisAlignment: 0.0,
child: item
);
}
}
class ChatUser {
ChatUser({ this.name, this.color });
final String name;
final int color;
@override
bool operator ==(other) {
if (other is! ChatUser) return false;
ChatUser user = other;
return user.name == name && user.color == color;
}
@override
int get hashCode {
return hashValues(name, color);
}
@override
String toString() {
return 'username: $name, color: $color';
}
}
class ChatMessage {
ChatMessage({ this.sender, this.text, this.animationController });
final ChatUser sender;
final String text;
final AnimationController animationController;
@override
bool operator ==(other) {
if (other is! ChatMessage) return false;
ChatMessage message = other;
return message.sender == sender && message.text == text;
}
@override
int get hashCode {
return hashValues(sender, text);
}
@override
String toString() {
return '<${sender.toString()}, text: $text>';
}
}