Merge branch 'feat/implementing-user-management' of beep/frontend_flutter into master
commit
2d68c03347
|
@ -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",
|
||||
);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -13,6 +13,7 @@ class ConversationsBloc {
|
|||
|
||||
fetchConversations() async {
|
||||
List<Conversation> conversationList = await _provider.fetchConversations();
|
||||
print(conversationList);
|
||||
_conversationsFetcher.sink.add(conversationList);
|
||||
}
|
||||
|
||||
|
|
|
@ -19,4 +19,4 @@ class MessageBloc {
|
|||
}
|
||||
|
||||
// global instance for access throughout the app
|
||||
final messageBloc = MessageBloc();
|
||||
final messageChannel = MessageBloc();
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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");
|
||||
}),
|
||||
]),
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)))),
|
||||
]))
|
||||
]));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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}");
|
||||
|
|
|
@ -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());
|
||||
})
|
||||
]))
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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())))
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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),
|
||||
|
|
|
@ -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)))),
|
||||
]))
|
||||
]))));
|
||||
}
|
||||
}
|
|
@ -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(
|
|
@ -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))),
|
||||
]))));
|
||||
}
|
||||
}
|
|
@ -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)),
|
||||
));
|
||||
}
|
||||
|
|
|
@ -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)))));
|
||||
}
|
||||
}
|
|
@ -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: [
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import "package:flutter/material.dart";
|
||||
import 'dart:ui' as ui;
|
||||
import "dart:async";
|
||||
|
||||
import "../../models/user_model.dart";
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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:
|
||||
|
|
Loading…
Reference in New Issue