Skip to content

Instantly share code, notes, and snippets.

@dpossas
Last active December 10, 2019 08:43
Show Gist options
  • Save dpossas/457c1429931f06e1f5d3cf64cb019408 to your computer and use it in GitHub Desktop.
Save dpossas/457c1429931f06e1f5d3cf64cb019408 to your computer and use it in GitHub Desktop.
import 'package:flutter/material.dart';
import 'dart:math';
///Author: Douglas Bezerra Possas
///Contact: [email protected]
///Gist: https://gist.github.com/dpossas/457c1429931f06e1f5d3cf64cb019408
///GitHub: https://github.com/dpossas/flutter-whatsapp/
final Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MaterialApp(
title: 'ChatUI',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Material(child: MainWidget()),
));
}
class MainWidget extends StatefulWidget {
@override
_MainWidgetState createState() => _MainWidgetState();
}
class _MainWidgetState extends State<MainWidget>
with SingleTickerProviderStateMixin {
TabController _tabController;
@override
void initState() {
super.initState();
_tabController = TabController(length: 3, vsync: this);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("ChatUI"),
actions: <Widget>[
IconButton(
onPressed: () {},
icon: Icon(Icons.search),
),
PopupMenuButton(
icon: Icon(Icons.more_vert),
itemBuilder: (context) {
return _moreMenuOptions();
},
offset: Offset(0, 60),
),
],
bottom: PreferredSize(
preferredSize: Size.fromHeight(40),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
IconButton(
onPressed: () {},
padding: EdgeInsets.all(0),
icon: Icon(Icons.camera_alt),
),
Expanded(
child: TabBar(
controller: _tabController,
labelPadding: EdgeInsets.all(0),
tabs: <Widget>[
Tab(
text: "CONVERSAS",
),
Tab(text: "STATUS"),
Tab(
text: "CHAMADAS",
)
],
),
),
],
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {},
child: Icon(Icons.chat),
),
body: TabBarView(
controller: _tabController,
children: <Widget>[
ChatListWidget(),
StausListWidget(),
CallListWidget()
],
),
);
}
List<PopupMenuItem> _moreMenuOptions() {
return [
const PopupMenuItem(
child: Text("Novo Grupo"),
),
const PopupMenuItem(
child: Text("Nova Transmissão"),
),
const PopupMenuItem(
child: Text("Chat Web"),
),
const PopupMenuItem(
child: Text("Mensagens Favoritas"),
),
const PopupMenuItem(
child: Text("Configurações"),
),
];
}
}
class Contact {
int _id;
String _firstName;
String _lastName;
String _contactName;
String _initials;
Contact(this._id, this._firstName, this._lastName, this._contactName,
this._initials);
String get initials => _initials;
String get contactName => _contactName;
String get lastName => _lastName;
String get firstName => _firstName;
int get id => _id;
}
class ChatListWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
List<Contact> contactList = ContactDataSource.fetchContacts();
return ListView.separated(
itemBuilder: (context, index) {
Contact contact = contactList[index];
return ListTile(
leading: CircleAvatar(
child: Text(contact.initials),
),
title: Text(contact.contactName),
subtitle: Text("Subtitulo"),
trailing: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text("00:00"),
SizedBox(
height: 5,
),
index == 0
? CircleAvatar(
radius: 12,
backgroundColor: Colors.black12,
child: Icon(
Icons.lock,
color: Colors.white,
size: 15,
),
)
: Icon(Icons.volume_off)
],
),
onTap: () {
Navigator.push(context, MaterialPageRoute(builder: (_) {
return ChatIndividualWidget();
}));
},
);
},
separatorBuilder: (context, index) {
return Divider();
},
itemCount: contactList.length);
}
}
class ContactDataSource {
static List<Contact> fetchContacts({int size = 25}) {
List<Contact> listContact = [];
for (int i = 0; i < size; i++) {
String firstName = "Pessoa";
String lastName = "$i";
String contactName = "$firstName $lastName";
String initials =
"${firstName.substring(0, 1)}${lastName.substring(0, 1)}";
listContact.add(Contact(i, firstName, lastName, contactName, initials));
}
return listContact;
}
}
class ChatIndividualWidget extends StatelessWidget {
final _messageInputController = TextEditingController();
final _bgColor = Color.fromRGBO(250, 249, 227, 1);
final _messageScrollController = ScrollController();
static bool lastMessageIsMine = true;
@override
Widget build(BuildContext context) {
List<Message> _messages = MessageDataSource.fetchMessages();
return Scaffold(
bottomNavigationBar: Container(
color: _bgColor,
child: Row(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: SizedBox(
height: 40,
child: TextFormField(
controller: _messageInputController,
decoration: InputDecoration(
filled: true,
enabled: true,
contentPadding: EdgeInsets.all(10),
focusColor: Colors.white,
fillColor: Colors.white,
hoverColor: Colors.white,
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(50)),
borderSide: BorderSide.none)),
),
),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: CircleAvatar(
child: IconButton(
onPressed: () {},
icon: Icon(
Icons.keyboard_voice,
color: Colors.white,
),
),
),
)
],
),
),
appBar: AppBar(
titleSpacing: 0,
automaticallyImplyLeading: false,
title: Row(
children: <Widget>[
BackButton(),
CircleAvatar(
child: Text("CX"),
),
SizedBox(
width: 10,
),
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
"Contato X",
style: TextStyle(color: Colors.white, fontSize: 15),
),
Text(
"online agora",
style: TextStyle(color: Colors.white, fontSize: 12),
)
],
)
],
),
actions: <Widget>[
IconButton(
onPressed: () {},
icon: Icon(Icons.videocam),
),
IconButton(
onPressed: () {},
icon: Icon(Icons.phone),
),
PopupMenuButton(
icon: Icon(Icons.more_vert),
itemBuilder: (context) {
return _moreMenuOptions();
},
offset: Offset(0, 60),
),
],
),
body: Container(
color: _bgColor,
padding: EdgeInsets.all(8),
child: ListView.separated(
controller: _messageScrollController,
physics: ClampingScrollPhysics(),
itemBuilder: (context, index) {
Message message = _messages[index];
if (index > 0) lastMessageIsMine = _messages[index - 1].itsMe;
return _message(message);
},
separatorBuilder: (context, index) {
return SizedBox(
height: 4,
);
},
itemCount: _messages.length)),
);
}
Widget _message(Message message) {
return Padding(
padding: EdgeInsets.only(
right: message.itsMe ? 10 : 50,
left: message.itsMe ? 50 : 10,
top: message.itsMe != lastMessageIsMine ? 10 : 0),
child: Container(
color: Colors.white,
padding: EdgeInsets.all(8),
child: Text(message.content),
),
);
}
List<PopupMenuItem> _moreMenuOptions() {
return [
const PopupMenuItem(
child: Text("Ver contato"),
),
const PopupMenuItem(
child: Text("Mídia"),
),
const PopupMenuItem(
child: Text("Buscar"),
),
const PopupMenuItem(
child: Text("Silenciar notificações"),
),
const PopupMenuItem(
child: Text("Papel de parede"),
),
];
}
}
class MessageDataSource {
static List<Message> fetchMessages({int size = 25}) {
final _random = Random();
List<Message> listMessage = [];
for (int i = 0; i < size; i++) {
String msg = "Mensagem $i";
bool itsMe = _random.nextBool();
listMessage.add(Message(i, msg, itsMe));
}
return listMessage;
}
}
class Message {
int _id;
String _content;
bool _itsMe;
Message(this._id, this._content, this._itsMe);
bool get itsMe => _itsMe;
String get content => _content;
int get id => _id;
}
class StausListWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
List<Contact> contactStatusList = ContactDataSource.fetchContacts(size: 8);
return SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
ListTile(
leading: Stack(
children: <Widget>[
CircleAvatar(
child: Text("DP"),
),
Positioned(
right: 0,
bottom: 0,
child: SizedBox(
width: 15,
height: 15,
child: RawMaterialButton(
elevation: 0,
onPressed: () {},
child: Icon(
Icons.add,
size: 15,
color: Colors.white,
),
shape: CircleBorder(),
padding: EdgeInsets.all(0),
fillColor: Colors.green,
),
),
)
],
),
title: Text("Meu status"),
subtitle: Text("Toque para atualizar seu status"),
),
Container(
padding: EdgeInsets.all(4),
color: Colors.black12,
child: Row(
children: <Widget>[
Expanded(
child: Text("Atualizações recentes"),
)
],
),
),
ListView.separated(
physics: ClampingScrollPhysics(),
shrinkWrap: true,
itemBuilder: (context, index) {
Contact status = contactStatusList[index];
return ListTile(
leading: Container(
padding: EdgeInsets.all(2),
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(color: Colors.green, width: 2)),
child: CircleAvatar(
child: Text(status.initials),
),
),
title: Text(status.contactName),
subtitle: Text("Subtitulo"),
);
},
separatorBuilder: (context, index) {
return Divider();
},
itemCount: contactStatusList.length)
],
),
);
}
}
class CallListWidget extends StatelessWidget {
final _random = Random();
@override
Widget build(BuildContext context) {
List<Contact> contactCallList = ContactDataSource.fetchContacts(size: 3);
List<IconData> callIcons = [Icons.call_made, Icons.call_received];
List<Color> callColors = [Colors.green, Colors.red];
List<IconData> typeCallList = [Icons.videocam, Icons.phone];
return ListView.separated(
itemBuilder: (context, index) {
Contact contactCall = contactCallList[index];
return ListTile(
leading: CircleAvatar(
child: Text(contactCall.initials),
),
title: Text(contactCall.contactName),
subtitle: Row(
children: <Widget>[
Icon(
callIcons[_random.nextInt(callIcons.length)],
color: callColors[_random.nextInt(callColors.length)],
size: 12,
),
SizedBox(
width: 10,
),
Text("+55 47 9999-9999"),
],
),
trailing:
Icon(typeCallList[_random.nextInt(typeCallList.length)]));
},
separatorBuilder: (context, index) {
return Divider();
},
itemCount: contactCallList.length);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment