feat: adding some new UI widgets, search bar, and tab navigation
parent
2b3891f2fe
commit
f8acdeb4f3
|
@ -18,8 +18,8 @@ class BottomBar extends StatelessWidget {
|
|||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(40.0), topRight: Radius.circular(40.0)),
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(
|
||||
top: 20.0, left: 20.0, right: 20.0, bottom: 30.0 + bottomPadding),
|
||||
padding:
|
||||
EdgeInsets.only(top: 20.0, left: 20.0, right: 20.0, bottom: 30.0),
|
||||
child: (conversationId == "")
|
||||
? ConversationInactiveView()
|
||||
: ConversationActiveView(conversationId: conversationId),
|
||||
|
|
|
@ -1,39 +1,45 @@
|
|||
import "package:flutter/material.dart";
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import "./widgets/top_bar.dart";
|
||||
import "./widgets/conversation_list.dart";
|
||||
import "./widgets/contact_list.dart";
|
||||
import "./widgets/conversation_view.dart";
|
||||
import "./widgets/contact_view.dart";
|
||||
import "../bottom_bar/bottom_bar.dart";
|
||||
import "../../services/heartbeat_manager.dart";
|
||||
import "../../services/conversation_manager.dart";
|
||||
import "../../blocs/message_bloc.dart";
|
||||
import "../../blocs/heartbeat_bloc.dart";
|
||||
|
||||
class Home extends StatefulWidget {
|
||||
@override
|
||||
_HomeState createState() => _HomeState();
|
||||
}
|
||||
|
||||
class _HomeState extends State<Home> {
|
||||
final List<String> titleList = ["Conversations", "Contacts", "Settings"];
|
||||
|
||||
class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
|
||||
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
|
||||
final PageController controller = PageController();
|
||||
final heartbeatSendManager = HeartbeatSendManager();
|
||||
final conversationManager = ConversationManager();
|
||||
|
||||
int _pageNumber = 0;
|
||||
// Bottom Bar navigation
|
||||
final List<String> titleList = ["Contacts", "Conversations", "Settings"];
|
||||
int _tabNumber = 1;
|
||||
List<IconData> itemsList = [
|
||||
Icons.contacts,
|
||||
Icons.chat,
|
||||
Icons.settings,
|
||||
];
|
||||
TabController controller;
|
||||
|
||||
PersistentBottomSheetController bottomBarController;
|
||||
// Current conversaton
|
||||
String currentConversationId = "";
|
||||
|
||||
@override
|
||||
initState() {
|
||||
super.initState();
|
||||
controller.addListener(_updatePageNumber);
|
||||
controller = TabController(vsync: this, length: 3);
|
||||
controller.index = 1; // Set default page to conversation page
|
||||
|
||||
messageBloc.bus.listen(
|
||||
(Map<String, String> data) async => await _processMessage(data));
|
||||
_setupBottomState();
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -42,53 +48,75 @@ class _HomeState extends State<Home> {
|
|||
super.dispose();
|
||||
}
|
||||
|
||||
_updatePageNumber() {
|
||||
setState(() {
|
||||
_pageNumber = controller.page.round();
|
||||
});
|
||||
}
|
||||
|
||||
_processMessage(Map<String, String> data) async {
|
||||
if (data["state"] == "disconnect") {
|
||||
// Disconnect and change state
|
||||
await conversationManager.exit();
|
||||
bottomBarController.close();
|
||||
bottomBarController = _scaffoldKey.currentState.showBottomSheet<void>(
|
||||
(BuildContext context) => BottomBar(conversationId: ""));
|
||||
setState(() {
|
||||
currentConversationId = "";
|
||||
});
|
||||
} else if (data["state"] == "connect") {
|
||||
// Connect and change state
|
||||
await conversationManager.join(data["conversationId"]);
|
||||
bottomBarController.close();
|
||||
bottomBarController = _scaffoldKey.currentState.showBottomSheet<void>(
|
||||
(BuildContext context) =>
|
||||
BottomBar(conversationId: data["conversationId"]));
|
||||
setState(() {
|
||||
currentConversationId = data["conversationId"];
|
||||
});
|
||||
} else {
|
||||
// show default
|
||||
await conversationManager.exit();
|
||||
bottomBarController.close();
|
||||
bottomBarController = _scaffoldKey.currentState.showBottomSheet<void>(
|
||||
(BuildContext context) => BottomBar(conversationId: ""));
|
||||
setState(() {
|
||||
currentConversationId = "";
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_setupBottomState() {
|
||||
conversationManager.get().then((conversationId) {
|
||||
bottomBarController = _scaffoldKey.currentState.showBottomSheet<void>(
|
||||
(BuildContext context) => BottomBar(conversationId: conversationId));
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
key: _scaffoldKey,
|
||||
body: Column(children: <Widget>[
|
||||
TopBar(title: titleList[_pageNumber], pageNumber: _pageNumber),
|
||||
TopBar(title: titleList[_tabNumber]),
|
||||
Expanded(
|
||||
child: PageView(controller: controller, children: <Widget>[
|
||||
ConversationList(),
|
||||
ContactList(),
|
||||
])),
|
||||
child: TabBarView(
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
controller: controller,
|
||||
children: <Widget>[
|
||||
ContactView(),
|
||||
ConversationView(),
|
||||
Container(),
|
||||
])),
|
||||
]),
|
||||
bottomNavigationBar:
|
||||
Column(mainAxisSize: MainAxisSize.min, children: <Widget>[
|
||||
BottomBar(conversationId: currentConversationId),
|
||||
BottomNavigationBar(
|
||||
onTap: (int index) {
|
||||
setState(() {
|
||||
_tabNumber = index;
|
||||
controller.index = _tabNumber;
|
||||
});
|
||||
},
|
||||
items: itemsList.map((data) {
|
||||
return BottomNavigationBarItem(
|
||||
icon: itemsList[_tabNumber] == data
|
||||
? ShaderMask(
|
||||
blendMode: BlendMode.srcIn,
|
||||
shaderCallback: (Rect bounds) {
|
||||
return ui.Gradient.linear(
|
||||
Offset(4.0, 24.0),
|
||||
Offset(24.0, 4.0),
|
||||
[
|
||||
Theme.of(context).primaryColor,
|
||||
Theme.of(context).primaryColorDark,
|
||||
],
|
||||
);
|
||||
},
|
||||
child: Icon(data),
|
||||
)
|
||||
: Icon(data, color: Colors.grey),
|
||||
title: Container(),
|
||||
);
|
||||
}).toList())
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -5,14 +5,14 @@ import "../../../blocs/contact_bloc.dart";
|
|||
|
||||
import "contact_item.dart";
|
||||
|
||||
class ContactList extends StatefulWidget {
|
||||
class ContactView extends StatefulWidget {
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return _ContactListState();
|
||||
return _ContactViewState();
|
||||
}
|
||||
}
|
||||
|
||||
class _ContactListState extends State<ContactList> {
|
||||
class _ContactViewState extends State<ContactView> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
|
@ -57,12 +57,12 @@ class _ConversationItemState extends State<ConversationItem> {
|
|||
Text(conversation.title,
|
||||
style: Theme.of(context).textTheme.title),
|
||||
Spacer(),
|
||||
Text("1m ago",
|
||||
Text("12:25 PM",
|
||||
style: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.body1
|
||||
.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).primaryColorDark)),
|
||||
]),
|
||||
Padding(
|
||||
|
|
|
@ -1,47 +0,0 @@
|
|||
import "package:flutter/material.dart";
|
||||
|
||||
import "../../../models/conversation_model.dart";
|
||||
import "../../../blocs/conversation_bloc.dart";
|
||||
|
||||
import "conversation_item.dart";
|
||||
|
||||
class ConversationList extends StatefulWidget {
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return _ConversationListState();
|
||||
}
|
||||
}
|
||||
|
||||
class _ConversationListState extends State<ConversationList> {
|
||||
@override
|
||||
initState() {
|
||||
super.initState();
|
||||
conversationsBloc.fetchConversations();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(top: 10.0),
|
||||
child: StreamBuilder(
|
||||
stream: conversationsBloc.conversations,
|
||||
builder: (context, AsyncSnapshot<List<Conversation>> snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
return buildList(snapshot.data);
|
||||
} else if (snapshot.hasError) {
|
||||
return Text(snapshot.error.toString());
|
||||
}
|
||||
return Center(child: CircularProgressIndicator());
|
||||
}));
|
||||
}
|
||||
|
||||
Widget buildList(List<Conversation> data) {
|
||||
return ListView.builder(
|
||||
padding: EdgeInsets.only(top: 0.0),
|
||||
itemCount: data.length,
|
||||
itemBuilder: (context, index) {
|
||||
return ConversationItem(conversation: data[index]);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
import "package:flutter/material.dart";
|
||||
|
||||
import "../../../models/conversation_model.dart";
|
||||
import "../../../blocs/conversation_bloc.dart";
|
||||
|
||||
import "conversation_item.dart";
|
||||
import "search_input.dart";
|
||||
|
||||
class ConversationView extends StatefulWidget {
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return _ConversationViewState();
|
||||
}
|
||||
}
|
||||
|
||||
class _ConversationViewState extends State<ConversationView> {
|
||||
final searchController = TextEditingController();
|
||||
|
||||
@override
|
||||
initState() {
|
||||
super.initState();
|
||||
conversationsBloc.fetchConversations();
|
||||
}
|
||||
|
||||
@override
|
||||
dispose() {
|
||||
searchController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(top: 10.0),
|
||||
child: Column(mainAxisSize: MainAxisSize.min, children: <Widget>[
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 20.0, right: 20.0, bottom: 10.0),
|
||||
child: SearchInput(
|
||||
controller: searchController,
|
||||
hintText: "Search for messages or users")),
|
||||
Flexible(
|
||||
child: StreamBuilder(
|
||||
stream: conversationsBloc.conversations,
|
||||
builder:
|
||||
(context, AsyncSnapshot<List<Conversation>> snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
return buildList(snapshot.data);
|
||||
} else if (snapshot.hasError) {
|
||||
return Text(snapshot.error.toString());
|
||||
}
|
||||
return Center(child: CircularProgressIndicator());
|
||||
}))
|
||||
]));
|
||||
}
|
||||
|
||||
Widget buildList(List<Conversation> data) {
|
||||
return ListView.builder(
|
||||
padding: EdgeInsets.only(top: 0.0),
|
||||
itemCount: data.length,
|
||||
itemBuilder: (context, index) {
|
||||
return ConversationItem(conversation: data[index]);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
import "package:flutter/material.dart";
|
||||
|
||||
class SearchInput extends StatelessWidget {
|
||||
final TextEditingController controller;
|
||||
final String hintText;
|
||||
|
||||
SearchInput({@required this.controller, @required this.hintText});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(left: 10.0, right: 10.0),
|
||||
child: Row(mainAxisAlignment: MainAxisAlignment.center, children: <
|
||||
Widget>[
|
||||
Padding(
|
||||
padding: EdgeInsets.only(right: 5.0),
|
||||
child: Icon(Icons.search, color: Colors.grey[500])),
|
||||
Flexible(
|
||||
child: TextField(
|
||||
controller: controller,
|
||||
autocorrect: false,
|
||||
cursorWidth: 2.0,
|
||||
cursorColor: Colors.grey[500],
|
||||
style: Theme.of(context).textTheme.body2.copyWith(
|
||||
color: Colors.grey[500], fontWeight: FontWeight.w300),
|
||||
decoration: InputDecoration(
|
||||
border: InputBorder.none,
|
||||
filled: false,
|
||||
hintText: hintText,
|
||||
hintStyle: Theme.of(context).textTheme.body2.copyWith(
|
||||
color: Colors.grey[500],
|
||||
fontWeight: FontWeight.w300,
|
||||
)))),
|
||||
])),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[200],
|
||||
borderRadius: BorderRadius.all(Radius.circular(10.00)),
|
||||
));
|
||||
}
|
||||
}
|
|
@ -2,83 +2,42 @@ import "package:flutter/material.dart";
|
|||
|
||||
class TopBar extends StatelessWidget {
|
||||
final String title;
|
||||
final int pageNumber;
|
||||
final double barHeight = 100.0;
|
||||
final String logo = "assets/logo.png";
|
||||
|
||||
TopBar({@required this.title, @required this.pageNumber});
|
||||
TopBar({@required this.title});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final double statusbarHeight = MediaQuery.of(context).padding.top;
|
||||
|
||||
// TODO: Fix cropping by moving onto stack, refactor widget into smaller parts
|
||||
return Material(
|
||||
type: MaterialType.canvas,
|
||||
elevation: 10.0,
|
||||
elevation: 5.0,
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(top: statusbarHeight, bottom: 10.0),
|
||||
padding: EdgeInsets.only(top: statusbarHeight, bottom: 2.0),
|
||||
child: Material(
|
||||
type: MaterialType.transparency,
|
||||
elevation: 10.0,
|
||||
elevation: 0.0,
|
||||
color: Colors.transparent,
|
||||
child: Column(children: <Widget>[
|
||||
Stack(alignment: Alignment.center, children: <Widget>[
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 10.0),
|
||||
child: Image.asset(logo,
|
||||
semanticLabel: "Beep Logo",
|
||||
width: 24.0,
|
||||
height: 24.0)),
|
||||
Spacer(),
|
||||
IconButton(icon: Icon(Icons.search), onPressed: () {}),
|
||||
IconButton(
|
||||
icon: Icon(Icons.add_comment), onPressed: () {})
|
||||
],
|
||||
),
|
||||
Positioned(
|
||||
child: Text(title,
|
||||
style: Theme.of(context).accentTextTheme.display1)),
|
||||
]),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Opacity(
|
||||
opacity: (pageNumber == 0) ? 1.0 : 0.6,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(left: 5.0),
|
||||
child: Container(
|
||||
width: 5.0,
|
||||
height: 5.0,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
shape: BoxShape.circle)))),
|
||||
Opacity(
|
||||
opacity: (pageNumber == 1) ? 1.0 : 0.6,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(left: 5.0),
|
||||
child: Container(
|
||||
width: 5.0,
|
||||
height: 5.0,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
shape: BoxShape.circle)))),
|
||||
Opacity(
|
||||
opacity: (pageNumber == 2) ? 1.0 : 0.6,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(left: 5.0),
|
||||
child: Container(
|
||||
width: 5.0,
|
||||
height: 5.0,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
shape: BoxShape.circle)))),
|
||||
]),
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 13.0),
|
||||
child: Text("Edit",
|
||||
style: Theme.of(context)
|
||||
.accentTextTheme
|
||||
.display2
|
||||
.copyWith(fontWeight: FontWeight.w400))),
|
||||
Spacer(),
|
||||
Text(title,
|
||||
style: Theme.of(context).accentTextTheme.display1),
|
||||
Spacer(),
|
||||
IconButton(icon: Icon(Icons.add_comment), onPressed: () {})
|
||||
],
|
||||
),
|
||||
])),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
|
|
|
@ -38,7 +38,7 @@ TextTheme buildTextTheme(TextTheme base) {
|
|||
title:
|
||||
base.title.copyWith(fontSize: 18.0, fontWeight: FontWeight.w500),
|
||||
subtitle: base.subtitle
|
||||
.copyWith(fontSize: 12.0, fontWeight: FontWeight.w300),
|
||||
.copyWith(fontSize: 14.0, fontWeight: FontWeight.w300),
|
||||
body2:
|
||||
base.body2.copyWith(fontSize: 16.0, fontWeight: FontWeight.w600),
|
||||
body1:
|
||||
|
|
Loading…
Reference in New Issue