Last active
December 10, 2019 08:43
-
-
Save dpossas/457c1429931f06e1f5d3cf64cb019408 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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