4
2
Fork 0

Merge branch 'feat/implementing-user-management' of beep/frontend_flutter into master

pull/55/head^2
Sudharshan S. 2019-06-29 06:57:49 +00:00 committed by Gitea
commit 2d68c03347
25 changed files with 801 additions and 178 deletions

View File

@ -283,6 +283,7 @@
"${PODS_ROOT}/GoogleWebRTC/Frameworks/frameworks/WebRTC.framework",
"${BUILT_PRODUCTS_DIR}/Just/Just.framework",
"${BUILT_PRODUCTS_DIR}/PercentEncoder/PercentEncoder.framework",
"${BUILT_PRODUCTS_DIR}/image_picker_modern/image_picker_modern.framework",
"${BUILT_PRODUCTS_DIR}/shared_preferences/shared_preferences.framework",
"${BUILT_PRODUCTS_DIR}/sqflite/sqflite.framework",
);
@ -293,6 +294,7 @@
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/WebRTC.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Just.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/PercentEncoder.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/image_picker_modern.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/shared_preferences.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/sqflite.framework",
);

View File

@ -46,6 +46,10 @@
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>NSPhotoLibraryUsageDescription</key>
<string>Profile/Group picture</string>
<key>NSCameraUsageDescription</key>
<string>Selfie for profile/group picture</string>
<key>NSMicrophoneUsageDescription</key>
<string>$(PRODUCT_NAME) Microphone Usage!</string>
</dict>

View File

@ -13,6 +13,7 @@ class ConversationsBloc {
fetchConversations() async {
List<Conversation> conversationList = await _provider.fetchConversations();
print(conversationList);
_conversationsFetcher.sink.add(conversationList);
}

View File

@ -19,4 +19,4 @@ class MessageBloc {
}
// global instance for access throughout the app
final messageBloc = MessageBloc();
final messageChannel = MessageBloc();

View File

@ -65,6 +65,17 @@ class ConversationApiProvider {
}
}
Future<void> createConversationMember(
String conversationId, String userId) async {
final jwt = await loginManager.getToken();
await http.post("$baseUrlCore/user/conversation/$conversationId/member",
headers: {
HttpHeaders.contentTypeHeader: "application/json",
HttpHeaders.authorizationHeader: "Bearer $jwt"
},
body: jsonEncode({"id": userId}));
}
Future<List<User>> fetchConversationMembers(String id) async {
final jwt = await loginManager.getToken();
try {

View File

@ -18,7 +18,6 @@ class ConversationActiveView extends StatefulWidget {
}
class _ConversationActiveViewState extends State<ConversationActiveView> {
final bus = messageBloc;
final conversationApiProvider = ConversationApiProvider();
Conversation _conversation;
List<Widget> _users;
@ -91,7 +90,8 @@ class _ConversationActiveViewState extends State<ConversationActiveView> {
icon: Icon(Icons.close),
onPressed: () async {
// Call method to close connection
await bus.publish({"state": "disconnect"});
await messageChannel
.publish({"target": "home", "state": "disconnect"});
print("Pressed close");
}),
]),

View File

@ -1,6 +1,7 @@
import "package:flutter/material.dart";
import "./widgets/home_view.dart";
import "../../blocs/message_bloc.dart";
class ContactTab extends StatefulWidget {
@override
@ -10,17 +11,25 @@ class ContactTab extends StatefulWidget {
}
class _ContactTabState extends State<ContactTab> {
final GlobalKey<NavigatorState> navigatorKey =
new GlobalKey<NavigatorState>();
@override
initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return Navigator(
initialRoute: "conversation/home",
initialRoute: "contact/home",
onGenerateRoute: (RouteSettings settings) {
WidgetBuilder builder;
switch (settings.name) {
case "conversation/home":
case "contact/home":
builder = (BuildContext _) => HomeView();
break;
case "conversation/new":
case "contact/new":
builder = (BuildContext _) => Center(child: Text("SOON"));
break;
default:

View File

@ -1,40 +0,0 @@
import "package:flutter/material.dart";
import "../../../models/user_model.dart";
import "../../widgets/user_avatar.dart";
class ContactItem extends StatelessWidget {
final User user;
ContactItem({@required this.user});
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.only(top: 10.0, bottom: 10.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
UserAvatar(
user: user,
radius: 18.0,
padding: EdgeInsets.only(left: 15.0)),
Padding(
padding: EdgeInsets.only(left: 15.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(user.firstName + " " + user.lastName,
style: Theme.of(context).textTheme.title,
overflow: TextOverflow.ellipsis),
Padding(
padding: EdgeInsets.only(top: 2),
child: Text("Last seen x ago",
style: Theme.of(context)
.textTheme
.subtitle
.copyWith(color: Color(0xFF455A64)))),
]))
]));
}
}

View File

@ -4,8 +4,11 @@ import 'package:sticky_headers/sticky_headers.dart';
import "../../../models/user_model.dart";
import "../../../blocs/contact_bloc.dart";
import "../widgets/contact_item.dart";
import "../../widgets/contact_item.dart";
import "../../widgets/top_bar.dart";
import "../../widgets/search_input.dart";
import "../../widgets/small_text_button.dart";
import "../../widgets/list_button.dart";
class HomeView extends StatefulWidget {
@override
@ -31,16 +34,36 @@ class _HomeViewState extends State<HomeView> {
@override
Widget build(BuildContext context) {
return StreamBuilder(
stream: contactBloc.contacts,
builder: (context, AsyncSnapshot<List<User>> snapshot) {
if (snapshot.hasData) {
return buildList(snapshot);
} else if (snapshot.hasError) {
return Text(snapshot.error.toString());
}
return Center(child: CircularProgressIndicator());
});
return Column(children: <Widget>[
TopBar(
title: "Contacts",
search: SearchInput(
controller: searchController, hintText: "Search for people"),
children: <Widget>[
SmallTextButton(
text: "Edit",
onClickCallback: () {
print("hello");
}),
Spacer(),
IconButton(
icon: Icon(Icons.add),
onPressed: () {
Navigator.pushNamed(context, "contact/new");
}),
]),
Expanded(
child: StreamBuilder(
stream: contactBloc.contacts,
builder: (context, AsyncSnapshot<List<User>> snapshot) {
if (snapshot.hasData) {
return buildList(snapshot);
} else if (snapshot.hasError) {
return Text(snapshot.error.toString());
}
return Center(child: CircularProgressIndicator());
}))
]);
}
Widget buildList(AsyncSnapshot<List<User>> snapshot) {
@ -89,7 +112,7 @@ class _HomeViewState extends State<HomeView> {
return Column(mainAxisSize: MainAxisSize.min, children: <Widget>[
StickyHeader(
header: Container(
height: 20.0,
height: 21.0,
color: Colors.grey[200],
padding: EdgeInsets.symmetric(horizontal: 15.0),
alignment: Alignment.centerLeft,
@ -111,24 +134,12 @@ class _HomeViewState extends State<HomeView> {
}).toList();
children.insertAll(0, [
Padding(
padding: EdgeInsets.only(left: 15.0, right: 15.0),
child: Column(mainAxisSize: MainAxisSize.min, children: <Widget>[
SearchInput(
controller: searchController, hintText: "Search for people"),
Padding(
padding: EdgeInsets.only(top: 10.0, bottom: 10.0),
child: Row(children: <Widget>[
Icon(Icons.people_outline,
color: Theme.of(context).primaryColorDark, size: 30.0),
Padding(
padding: EdgeInsets.only(left: 20.0),
child: Text("Invite Friends",
style: Theme.of(context).textTheme.title.copyWith(
color: Theme.of(context).primaryColorDark))),
])),
])),
ListButton(
icon: Icons.people_outline,
text: "Invite Friends",
onClickCallback: () {}),
]);
return ListView(padding: EdgeInsets.only(top: 10.0), children: children);
return ListView(padding: EdgeInsets.only(top: 0.0), children: children);
}
}

View File

@ -1,6 +1,11 @@
import "package:flutter/material.dart";
import "./widgets/home_view.dart";
import "./widgets/new_conversation_view.dart";
import "./widgets/new_group_view.dart";
import "./widgets/new_group_info_view.dart";
import "../../models/user_model.dart";
class ConversationTab extends StatefulWidget {
@override
@ -21,7 +26,14 @@ class _ConversationTabState extends State<ConversationTab> {
builder = (BuildContext _) => HomeView();
break;
case "conversation/new":
builder = (BuildContext _) => Center(child: Text("SOON"));
builder = (BuildContext _) => NewConversationView();
break;
case "conversation/new/group":
builder = (BuildContext _) => NewGroupView();
break;
case "conversation/new/groupinfo":
final List<User> users = settings.arguments;
builder = (BuildContext _) => NewGroupInfoView(users: users);
break;
default:
throw Exception("Invalid route: ${settings.name}");

View File

@ -3,8 +3,10 @@ import "package:flutter/material.dart";
import "../../../models/conversation_model.dart";
import "../../../blocs/conversation_bloc.dart";
import "../widgets/conversation_item.dart";
import "../../widgets/conversation_item.dart";
import "../../widgets/top_bar.dart";
import "../../widgets/search_input.dart";
import "../../widgets/small_text_button.dart";
class HomeView extends StatefulWidget {
@override
@ -30,22 +32,38 @@ class _HomeViewState extends State<HomeView> {
@override
Widget build(BuildContext context) {
return ListView(padding: EdgeInsets.only(top: 10.0), 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")),
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());
})
return Column(children: <Widget>[
TopBar(
title: "Conversations",
search: SearchInput(
controller: searchController, hintText: "Search for people"),
children: <Widget>[
SmallTextButton(
text: "Edit",
onClickCallback: () {
print("hello");
}),
Spacer(),
IconButton(
icon: Icon(Icons.add_comment),
onPressed: () {
Navigator.pushNamed(context, "conversation/new");
}),
]),
Expanded(
child:
ListView(padding: EdgeInsets.only(top: 10.0), children: <Widget>[
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());
})
]))
]);
}

View File

@ -0,0 +1,141 @@
import "package:flutter/material.dart";
import 'package:sticky_headers/sticky_headers.dart';
import "../../../models/user_model.dart";
import "../../../blocs/contact_bloc.dart";
import "../../widgets/contact_item.dart";
import "../../widgets/top_bar.dart";
import "../../widgets/search_input.dart";
import "../../widgets/list_button.dart";
class NewConversationView extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return _NewConversationViewState();
}
}
class _NewConversationViewState extends State<NewConversationView> {
final searchController = TextEditingController();
@override
void initState() {
super.initState();
contactBloc.fetchContacts();
}
@override
void dispose() {
searchController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Column(children: <Widget>[
TopBar(
title: "New Conversation",
search: SearchInput(
controller: searchController, hintText: "Search for people"),
children: <Widget>[
IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () {
Navigator.pop(context);
}),
Spacer(),
]),
Expanded(
child: StreamBuilder(
stream: contactBloc.contacts,
builder: (context, AsyncSnapshot<List<User>> snapshot) {
if (snapshot.hasData) {
return buildList(snapshot);
} else if (snapshot.hasError) {
return Text(snapshot.error.toString());
}
return Center(child: CircularProgressIndicator());
}))
]);
}
Widget buildList(AsyncSnapshot<List<User>> snapshot) {
final Map<String, List<User>> sortedList = {
"A": null,
"B": null,
"C": null,
"D": null,
"E": null,
"F": null,
"G": null,
"H": null,
"I": null,
"J": null,
"K": null,
"L": null,
"M": null,
"N": null,
"O": null,
"P": null,
"Q": null,
"R": null,
"S": null,
"T": null,
"U": null,
"V": null,
"W": null,
"X": null,
"Y": null,
"Z": null
};
// Sort the list into alphabets
sortedList.forEach((letter, list) {
sortedList[letter] = snapshot.data
.where((user) => user.firstName.startsWith(letter))
.toList();
});
// Create list of children
final children = sortedList.entries.map<Widget>((entry) {
if (entry.value.length == 0) {
return Container();
}
return Column(mainAxisSize: MainAxisSize.min, children: <Widget>[
StickyHeader(
header: Container(
height: 21.0,
color: Colors.grey[200],
padding: EdgeInsets.symmetric(horizontal: 15.0),
alignment: Alignment.centerLeft,
child: Text(entry.key,
style: Theme.of(context)
.primaryTextTheme
.display1
.copyWith(color: Theme.of(context).primaryColorDark)),
),
content: ListView.builder(
physics: const NeverScrollableScrollPhysics(),
padding: EdgeInsets.only(top: 0.0),
shrinkWrap: true,
itemCount: entry.value.length,
itemBuilder: (context, index) {
return ContactItem(user: entry.value[index]);
}))
]);
}).toList();
children.insertAll(0, [
ListButton(
icon: Icons.group_add,
text: "New Group",
onClickCallback: () {
Navigator.pushNamed(context, "conversation/new/group");
}),
]);
return ListView(padding: EdgeInsets.only(top: 0.0), children: children);
}
}

View File

@ -0,0 +1,139 @@
import "package:flutter/material.dart";
import 'package:image_picker_modern/image_picker_modern.dart';
import "dart:io";
import "../../../models/user_model.dart";
import "../../../resources/conversation_api_provider.dart";
import "../../widgets/contact_item.dart";
import "../../widgets/top_bar.dart";
import "../../widgets/small_text_button.dart";
import "../../widgets/list_button.dart";
class NewGroupInfoView extends StatefulWidget {
final List<User> users;
NewGroupInfoView({@required this.users});
@override
State<StatefulWidget> createState() {
return _NewGroupInfoViewState();
}
}
class _NewGroupInfoViewState extends State<NewGroupInfoView> {
final descriptionController = TextEditingController();
final nameController = TextEditingController();
final conversationApiProvider = ConversationApiProvider();
File _image;
@override
void dispose() {
descriptionController.dispose();
nameController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Column(children: <Widget>[
TopBar(title: "New Group", children: <Widget>[
IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () {
Navigator.pop(context);
}),
Spacer(),
SmallTextButton(
text: "Create",
onClickCallback: () async {
final conversation = await conversationApiProvider
.createConversation(nameController.text);
widget.users.forEach((user) async => await conversationApiProvider
.createConversationMember(conversation.id, user.id));
Navigator.pushNamed(context, "conversation/home");
})
]),
Padding(
padding: EdgeInsets.only(left: 15.0, right: 15.0, top: 10.0),
child: Row(crossAxisAlignment: CrossAxisAlignment.center, children: <
Widget>[
(_image != null)
? CircleAvatar(radius: 50, backgroundImage: FileImage(_image))
: CircleAvatar(radius: 50, backgroundColor: Colors.grey[300]),
Flexible(
child:
Column(mainAxisSize: MainAxisSize.min, children: <Widget>[
Container(
margin: EdgeInsets.only(left: 8.0),
padding: EdgeInsets.only(left: 10.0, right: 10.0),
color: Colors.grey[100],
child: TextField(
controller: nameController,
autocorrect: false,
cursorWidth: 2.0,
cursorColor: Colors.grey[500],
style: Theme.of(context).textTheme.subtitle.copyWith(
color: Colors.grey[500], fontWeight: FontWeight.w300),
decoration: InputDecoration(
border: InputBorder.none,
filled: false,
hintText: "Enter group name",
hintStyle: Theme.of(context)
.textTheme
.subtitle
.copyWith(color: Colors.grey[500])))),
Container(
margin: EdgeInsets.only(left: 8.0, top: 5.0),
padding: EdgeInsets.only(left: 10.0, right: 10.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5.0),
color: Colors.grey[100],
),
child: TextField(
controller: descriptionController,
autocorrect: false,
maxLines: 3,
cursorWidth: 2.0,
cursorColor: Colors.grey[500],
style: Theme.of(context).textTheme.subtitle.copyWith(
color: Colors.grey[500],
fontWeight: FontWeight.w300,
),
decoration: InputDecoration(
border: InputBorder.none,
filled: false,
hintText: "Enter group description",
hintStyle: Theme.of(context)
.textTheme
.subtitle
.copyWith(color: Colors.grey[500])))),
])),
])),
Padding(
padding: EdgeInsets.only(top: 10.0),
child: ListButton(
icon: Icons.insert_photo,
text: "Add a group photo",
onClickCallback: () async {
var image =
await ImagePicker.pickImage(source: ImageSource.gallery);
setState(() {
_image = image;
});
})),
Expanded(
child: Padding(
padding: EdgeInsets.only(top: 0.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: widget.users
.map((user) => ContactItem(user: user))
.toList())))
]);
}
}

View File

@ -0,0 +1,167 @@
import "package:flutter/material.dart";
import 'package:sticky_headers/sticky_headers.dart';
import "../../../models/user_model.dart";
import "../../../blocs/contact_bloc.dart";
import "../../widgets/contact_item.dart";
import "../../widgets/top_bar.dart";
import "../../widgets/search_input.dart";
import "../../widgets/small_text_button.dart";
import "../../widgets/user_chip.dart";
class NewGroupView extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return _NewGroupViewState();
}
}
class _NewGroupViewState extends State<NewGroupView> {
final searchController = TextEditingController();
List<User> selectedUsers = [];
@override
void initState() {
super.initState();
contactBloc.fetchContacts();
}
@override
void dispose() {
searchController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Column(children: <Widget>[
TopBar(
title: "New Group",
children: <Widget>[
IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () {
Navigator.pop(context);
}),
Spacer(),
SmallTextButton(
text: "Next",
onClickCallback: () {
if (selectedUsers.length <= 1) {
return;
} else {
Navigator.pushNamed(context, "conversation/new/groupinfo",
arguments: selectedUsers);
}
})
],
search: SearchInput(
controller: searchController, hintText: "Search for people"),
),
Expanded(
child: StreamBuilder(
stream: contactBloc.contacts,
builder: (context, AsyncSnapshot<List<User>> snapshot) {
if (snapshot.hasData) {
return buildList(snapshot);
} else if (snapshot.hasError) {
return Text(snapshot.error.toString());
}
return Center(child: CircularProgressIndicator());
}))
]);
}
Widget buildList(AsyncSnapshot<List<User>> snapshot) {
final Map<String, List<User>> sortedList = {
"A": null,
"B": null,
"C": null,
"D": null,
"E": null,
"F": null,
"G": null,
"H": null,
"I": null,
"J": null,
"K": null,
"L": null,
"M": null,
"N": null,
"O": null,
"P": null,
"Q": null,
"R": null,
"S": null,
"T": null,
"U": null,
"V": null,
"W": null,
"X": null,
"Y": null,
"Z": null
};
// Sort the list into alphabets
sortedList.forEach((letter, list) {
sortedList[letter] = snapshot.data
.where((user) => user.firstName.startsWith(letter))
.toList();
});
// Create list of children
final children = sortedList.entries.map<Widget>((entry) {
if (entry.value.length == 0) {
return Container();
}
return Column(mainAxisSize: MainAxisSize.min, children: <Widget>[
StickyHeader(
header: Container(
height: 21.0,
color: Colors.grey[200],
padding: EdgeInsets.symmetric(horizontal: 15.0),
alignment: Alignment.centerLeft,
child: Text(entry.key,
style: Theme.of(context)
.primaryTextTheme
.display1
.copyWith(color: Theme.of(context).primaryColorDark)),
),
content: ListView.builder(
physics: const NeverScrollableScrollPhysics(),
padding: EdgeInsets.only(top: 0.0),
shrinkWrap: true,
itemCount: entry.value.length,
itemBuilder: (context, index) {
return ContactItem(
user: entry.value[index],
selectable: true,
onClickCallback: (selected) {
setState(() {
if (selected) {
selectedUsers.add(entry.value[index]);
} else {
selectedUsers.remove(entry.value[index]);
}
});
});
}))
]);
}).toList();
children.insertAll(0, [
Padding(
padding: EdgeInsets.only(left: 15.0, right: 15.0),
child: Wrap(
spacing: 5.0,
runSpacing: 0.0,
children:
selectedUsers.map((user) => UserChip(user: user)).toList(),
))
]);
return ListView(padding: EdgeInsets.only(top: 0.0), children: children);
}
}

View File

@ -1,7 +1,6 @@
import "package:flutter/material.dart";
import 'dart:ui' as ui;
import "./widgets/top_bar.dart";
import "./conversation_tab/conversation_tab.dart";
import "./contact_tab/contact_tab.dart";
import "./bottom_bar/bottom_bar.dart";
@ -37,7 +36,7 @@ class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
controller = TabController(vsync: this, length: 3);
controller.index = 1; // Set default page to conversation page
messageBloc.bus.listen(
messageChannel.bus.listen(
(Map<String, String> data) async => await _processMessage(data));
}
@ -48,24 +47,26 @@ class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
}
_processMessage(Map<String, String> data) async {
if (data["state"] == "disconnect") {
// Disconnect and change state
await conversationManager.exit();
setState(() {
currentConversationId = "";
});
} else if (data["state"] == "connect") {
// Connect and change state
await conversationManager.join(data["conversationId"]);
setState(() {
currentConversationId = data["conversationId"];
});
} else {
// show default
await conversationManager.exit();
setState(() {
currentConversationId = "";
});
if (data["target"] == "home") {
if (data["state"] == "disconnect") {
// Disconnect and change state
await conversationManager.exit();
setState(() {
currentConversationId = "";
});
} else if (data["state"] == "connect") {
// Connect and change state
await conversationManager.join(data["conversationId"]);
setState(() {
currentConversationId = data["conversationId"];
});
} else {
// show default
await conversationManager.exit();
setState(() {
currentConversationId = "";
});
}
}
}
@ -73,18 +74,14 @@ class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
body: Column(children: <Widget>[
TopBar(state: _tabNumber),
Expanded(
child: TabBarView(
physics: NeverScrollableScrollPhysics(),
controller: controller,
children: <Widget>[
ContactTab(),
ConversationTab(),
Container(),
])),
]),
body: TabBarView(
physics: NeverScrollableScrollPhysics(),
controller: controller,
children: <Widget>[
ContactTab(),
ConversationTab(),
Container(),
]),
bottomNavigationBar:
Column(mainAxisSize: MainAxisSize.min, children: <Widget>[
BottomBar(conversationId: currentConversationId),

View File

@ -0,0 +1,84 @@
import "package:flutter/material.dart";
import "../../models/user_model.dart";
import "../widgets/user_avatar.dart";
typedef void OnClickCallback(bool state);
class ContactItem extends StatefulWidget {
final User user;
final bool selectable;
final OnClickCallback onClickCallback;
ContactItem(
{@required this.user, this.onClickCallback, this.selectable: false});
@override
State<StatefulWidget> createState() {
return _ContactItemState();
}
}
class _ContactItemState extends State<ContactItem> {
bool selected = false;
@override
Widget build(BuildContext context) {
return Material(
type: MaterialType.transparency,
elevation: 1,
child: InkWell(
onTap: () async {
if (widget.selectable == true) {
setState(() {
selected = !selected;
});
widget.onClickCallback(selected);
}
},
child: Container(
padding: EdgeInsets.only(top: 10.0, bottom: 10.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
(widget.selectable)
? Checkbox(
value: selected,
activeColor: Theme.of(context).primaryColorDark,
onChanged: (state) {
setState(() {
selected = !selected;
});
widget.onClickCallback(selected);
})
: Container(),
UserAvatar(
user: widget.user,
radius: 18.0,
padding: EdgeInsets.only(
left: ((widget.selectable) ? 0 : 15.0))),
Padding(
padding: EdgeInsets.only(left: 15.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
widget.user.firstName +
" " +
widget.user.lastName,
style: Theme.of(context).textTheme.title,
overflow: TextOverflow.ellipsis),
Padding(
padding: EdgeInsets.only(top: 2),
child: Text("Last seen x ago",
style: Theme.of(context)
.textTheme
.subtitle
.copyWith(
color: Color(0xFF455A64)))),
]))
]))));
}
}

View File

@ -1,11 +1,11 @@
import "package:flutter/material.dart";
import "../../../models/user_model.dart";
import "../../../models/conversation_model.dart";
import "../../../blocs/conversation_bloc.dart";
import "../../../blocs/message_bloc.dart";
import "../../models/user_model.dart";
import "../../models/conversation_model.dart";
import "../../blocs/conversation_bloc.dart";
import "../../blocs/message_bloc.dart";
import "../../widgets/user_avatar.dart";
import "../widgets/user_avatar.dart";
class ConversationItem extends StatefulWidget {
final Conversation conversation;
@ -20,7 +20,6 @@ class ConversationItem extends StatefulWidget {
class _ConversationItemState extends State<ConversationItem> {
final bloc;
final Conversation conversation;
final bus = messageBloc;
_ConversationItemState({@required this.conversation})
: bloc = ConversationMembersBloc(conversation.id);
@ -44,8 +43,11 @@ class _ConversationItemState extends State<ConversationItem> {
elevation: 1,
child: InkWell(
onTap: () async {
await bus.publish(
{"state": "connect", "conversationId": conversation.id});
await messageChannel.publish({
"target": "home",
"state": "connect",
"conversationId": conversation.id
});
},
child: Container(
padding: EdgeInsets.only(

View File

@ -0,0 +1,38 @@
import "package:flutter/material.dart";
typedef void OnClickCallback();
class ListButton extends StatelessWidget {
final IconData icon;
final String text;
final OnClickCallback onClickCallback;
ListButton(
{@required this.icon,
@required this.text,
@required this.onClickCallback});
@override
Widget build(BuildContext context) {
return Material(
type: MaterialType.transparency,
elevation: 1,
child: InkWell(
onTap: onClickCallback,
child: Container(
padding: EdgeInsets.only(
left: 15.0, right: 15.0, top: 12.0, bottom: 12.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Icon(icon,
size: 30.0,
color: Theme.of(context).primaryColorDark),
Padding(
padding: EdgeInsets.only(left: 20.0),
child: Text(text,
style: Theme.of(context).textTheme.title.copyWith(
color: Theme.of(context).primaryColorDark))),
]))));
}
}

View File

@ -16,16 +16,15 @@ class SearchInput extends StatelessWidget {
children: <Widget>[
Padding(
padding: EdgeInsets.only(right: 5.0),
child: Icon(Icons.search, color: Colors.grey[500])),
child: Icon(Icons.search, color: Colors.white)),
Flexible(
child: TextField(
controller: controller,
autocorrect: false,
cursorWidth: 2.0,
cursorColor: Colors.grey[500],
cursorWidth: 3.0,
cursorColor: Colors.white,
style: Theme.of(context).textTheme.subtitle.copyWith(
color: Colors.grey[500],
fontWeight: FontWeight.w300),
color: Colors.white, fontWeight: FontWeight.w300),
decoration: InputDecoration(
border: InputBorder.none,
filled: false,
@ -33,10 +32,10 @@ class SearchInput extends StatelessWidget {
hintStyle: Theme.of(context)
.textTheme
.subtitle
.copyWith(color: Colors.grey[500])))),
.copyWith(color: Colors.white)))),
])),
decoration: BoxDecoration(
color: Colors.grey[200],
color: Colors.black.withOpacity(0.05),
borderRadius: BorderRadius.all(Radius.circular(10.00)),
));
}

View File

@ -0,0 +1,25 @@
import "package:flutter/material.dart";
typedef void OnClickCallback();
class SmallTextButton extends StatelessWidget {
final String text;
final OnClickCallback onClickCallback;
SmallTextButton({@required this.text, @required this.onClickCallback});
@override
Widget build(BuildContext context) {
return Material(
type: MaterialType.transparency,
elevation: 1,
child: Padding(
padding: EdgeInsets.all(10.0),
child: GestureDetector(
onTap: onClickCallback,
child: Text(text,
style: Theme.of(context)
.accentTextTheme
.title
.copyWith(fontWeight: FontWeight.w300)))));
}
}

View File

@ -1,13 +1,13 @@
import "package:flutter/material.dart";
import "search_input.dart";
class TopBar extends StatelessWidget {
final int state;
final String logo = "assets/logo.png";
final List<String> titleList = ["Contacts", "Conversations", "Settings"];
final SearchInput search;
final List<Widget> children;
final String title;
TopBar({@required this.state});
TopBar({@required this.children, @required this.title, this.search});
@override
Widget build(BuildContext context) {
@ -19,41 +19,26 @@ class TopBar extends StatelessWidget {
child: Container(
padding: EdgeInsets.only(top: statusbarHeight, bottom: 0),
child: Material(
type: MaterialType.transparency,
elevation: 0.0,
color: Colors.transparent,
child: Column(children: <Widget>[
type: MaterialType.transparency,
elevation: 0.0,
color: Colors.transparent,
child: Column(children: <Widget>[
Stack(alignment: Alignment.center, children: [
Text(title, style: Theme.of(context).accentTextTheme.display1),
Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Padding(
padding: EdgeInsets.only(left: 13.0),
child: Text("Edit",
style: Theme.of(context)
.accentTextTheme
.title
.copyWith(fontWeight: FontWeight.w300))),
Spacer(),
Text(titleList[state],
style: Theme.of(context).accentTextTheme.display1),
Spacer(),
(state == 1)
? IconButton(
icon: Icon(Icons.add_comment), onPressed: () {})
: Container(),
(state == 0)
? IconButton(icon: Icon(Icons.add), onPressed: () {})
: Container(),
(state == 2)
? Opacity(
opacity: 0.0,
child: IconButton(
icon: Icon(Icons.edit), onPressed: () {}))
: Container(),
],
),
])),
children: children,
)
]),
(search != null)
? Padding(
padding: EdgeInsets.only(
left: 10.0, right: 10.0, bottom: 10.0),
child: search)
: Container(),
]),
),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [

View File

@ -1,5 +1,4 @@
import "package:flutter/material.dart";
import 'dart:ui' as ui;
import "dart:async";
import "../../models/user_model.dart";

View File

@ -0,0 +1,18 @@
import "package:flutter/material.dart";
import "../../models/user_model.dart";
import "./user_avatar.dart";
class UserChip extends StatelessWidget {
final User user;
UserChip({@required this.user});
@override
Widget build(BuildContext context) {
return Chip(
avatar: UserAvatar(user: user, radius: 12.0),
elevation: 1.5,
label: Text(user.firstName + " " + user.lastName));
}
}

View File

@ -17,6 +17,7 @@ dependencies:
sqflite: ^1.1.0
eventsource: ^0.2.1
sticky_headers: ^0.1.7
image_picker_modern: ^0.4.12+2
dev_dependencies:
flutter_test: