Skip to content

Instantly share code, notes, and snippets.

@stegrams
Last active May 18, 2020 23:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save stegrams/47784372f62c3ab4dbb52c177e69c209 to your computer and use it in GitHub Desktop.
Save stegrams/47784372f62c3ab4dbb52c177e69c209 to your computer and use it in GitHub Desktop.
Chat sample that displays a dotted balloon as editing activity indicator.
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'dart:async';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Chat(),
);
}
}
class Chat extends StatefulWidget {
@override
ChatState createState() => ChatState();
}
class ChatState extends State<Chat> {
Timer timer;
TextEditingController controller = TextEditingController();
ScrollController scroller = ScrollController();
@override
void dispose() {
scroller.dispose();
super.dispose();
}
void createTimer() {
timer?.cancel();
timer = Timer.periodic(
Duration(seconds: 3),
(t) => setState(t.cancel),
);
}
List<Widget> bubbles = [
BubbleMessage(
message: 'Hi there, this is a message',
time: '12:00',
delivered: true,
isMe: false,
),
BubbleMessage(
message: 'Whatsapp like bubble talk',
time: '12:01',
delivered: true,
isMe: false,
),
BubbleMessage(
message: 'Nice one, Flutter is awesome',
time: '12:00',
delivered: true,
isMe: true,
),
BubbleMessage(
message: 'I\'ve told you so dude!',
time: '12:00',
delivered: true,
isMe: false,
),
];
@override
Widget build(BuildContext context) {
List<Widget> dottedMessage = [];
if (timer?.isActive ?? false) {
dottedMessage = [BubbleDotted()];
}
// Scroll to the latest bubble when typing.
SchedulerBinding.instance.addPostFrameCallback((_) {
scroller.jumpTo(scroller.position.maxScrollExtent);
});
return Scaffold(
backgroundColor: Colors.blueGrey.shade50,
appBar: AppBar(
backgroundColor: Colors.white,
elevation: .9,
title: Text(
'Chat',
style: TextStyle(color: Colors.green),
),
),
body: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: ListView(
controller: scroller,
children: [...bubbles, ...dottedMessage],
),
),
TextField(
controller: controller,
onChanged: (String t) => setState(createTimer),
onSubmitted: (String t) => setState(() {
timer.cancel();
if (t.isNotEmpty)
bubbles.add(BubbleMessage(message: t, delivered: true));
controller.clear();
}),
// Scroll to the latest bubble when keyboard pops out.
onTap: () => Timer(
Duration(milliseconds: 300),
() => scroller.jumpTo(scroller.position.maxScrollExtent),
),
decoration: InputDecoration(
hintText: 'Type a message and then Enter to send',
border: OutlineInputBorder(),
),
),
],
),
);
}
}
// Responsible for bubble's shape and color.
abstract class Bubble extends StatelessWidget {
Bubble({
this.align = CrossAxisAlignment.start,
this.color = Colors.white,
});
final CrossAxisAlignment align;
final Color color;
Widget body(BuildContext context);
@override
Widget build(BuildContext context) {
BorderRadius radius = BorderRadius.all(Radius.circular(5));
CrossAxisAlignment direction;
if (align == CrossAxisAlignment.start) {
radius = radius.add(
BorderRadius.only(
bottomLeft: Radius.circular(10.0),
topLeft: Radius.circular(-5.0),
),
);
direction = align;
} else {
radius = radius.add(
BorderRadius.only(
bottomRight: Radius.circular(10.0),
topRight: Radius.circular(-5.0),
),
);
direction = CrossAxisAlignment.end;
}
return Column(
crossAxisAlignment: direction,
children: <Widget>[
Container(
margin: const EdgeInsets.all(10),
padding: const EdgeInsets.all(8.0),
decoration: BoxDecoration(
boxShadow: [
BoxShadow(
blurRadius: .5,
spreadRadius: 1.0,
color: Colors.black.withOpacity(.12))
],
color: color,
borderRadius: radius,
),
child: body(context),
),
],
);
}
}
// Responsible for the message's layout
class BubbleMessage extends Bubble {
BubbleMessage({
this.message,
this.time,
this.delivered = false,
bool isMe = true,
}) : super(
align: isMe ? CrossAxisAlignment.start : CrossAxisAlignment.end,
color: isMe ? Colors.white : Colors.greenAccent.shade100,
);
final String message, time;
final delivered;
@override
Widget body(BuildContext context) {
final icon = delivered ? Icons.done_all : Icons.done;
final now = time ??
'${DateTime.now().hour}:'.padLeft(3, '0') +
'${DateTime.now().minute}'.padLeft(2, '0');
return Stack(
children: <Widget>[
Padding(padding: EdgeInsets.only(right: 48.0), child: Text(message)),
Positioned(
bottom: 0.0,
right: 0.0,
child: Row(
children: <Widget>[
Text(
now,
style: TextStyle(color: Colors.black38, fontSize: 10.0),
),
SizedBox(width: 3.0),
Icon(icon, size: 12.0, color: Colors.black38),
],
),
)
],
);
}
}
// Simple animated dottes image inside a bubble.
class BubbleDotted extends Bubble {
BubbleDotted({
bool isMe = true,
}) : super(
align: isMe ? CrossAxisAlignment.start : CrossAxisAlignment.end,
color: isMe ? Colors.white : Colors.greenAccent.shade100,
);
@override
Widget body(BuildContext context) {
return Image.network(
'https://oneecosystem.pensoft.net/i/simple_loading.gif',
width: 50,
height: 15,
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment