Merge branch 'feature/new-ui' into feature/image-detail

This commit is contained in:
Salvatore Giordano
2020-11-26 17:17:24 +01:00
committed by GitHub
134 changed files with 3100 additions and 2585 deletions
+19 -13
View File
@@ -108,8 +108,6 @@ PODS:
- GoogleUtilities/Logger
- image_picker (0.0.1):
- Flutter
- media_gallery (0.0.1):
- Flutter
- nanopb (1.30906.0):
- nanopb/decode (= 1.30906.0)
- nanopb/encode (= 1.30906.0)
@@ -119,11 +117,13 @@ PODS:
- Flutter
- "permission_handler (5.0.1+1)":
- Flutter
- photo_manager (0.0.1):
- Flutter
- PromisesObjC (1.2.11)
- Protobuf (3.13.0)
- SDWebImage (5.9.4):
- SDWebImage/Core (= 5.9.4)
- SDWebImage/Core (5.9.4)
- SDWebImage (5.10.0):
- SDWebImage/Core (= 5.10.0)
- SDWebImage/Core (5.10.0)
- shared_preferences (0.0.1):
- Flutter
- sqflite (0.0.2):
@@ -148,11 +148,13 @@ PODS:
- sqlite3/perf-threadsafe
- sqlite3/rtree
- Starscream (4.0.4)
- StreamChatClient (2.4.1):
- StreamChatClient (2.4.2):
- Starscream (~> 4.0)
- SwiftyGif (5.3.0)
- url_launcher (0.0.1):
- Flutter
- video_compress (0.3.0):
- Flutter
- video_player (0.0.1):
- Flutter
- wakelock (0.0.1):
@@ -169,14 +171,15 @@ DEPENDENCIES:
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
- image_picker (from `.symlinks/plugins/image_picker/ios`)
- media_gallery (from `.symlinks/plugins/media_gallery/ios`)
- path_provider (from `.symlinks/plugins/path_provider/ios`)
- permission_handler (from `.symlinks/plugins/permission_handler/ios`)
- photo_manager (from `.symlinks/plugins/photo_manager/ios`)
- shared_preferences (from `.symlinks/plugins/shared_preferences/ios`)
- sqflite (from `.symlinks/plugins/sqflite/ios`)
- sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/ios`)
- StreamChatClient
- url_launcher (from `.symlinks/plugins/url_launcher/ios`)
- video_compress (from `.symlinks/plugins/video_compress/ios`)
- video_player (from `.symlinks/plugins/video_player/ios`)
- wakelock (from `.symlinks/plugins/wakelock/ios`)
@@ -223,12 +226,12 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/flutter_secure_storage/ios"
image_picker:
:path: ".symlinks/plugins/image_picker/ios"
media_gallery:
:path: ".symlinks/plugins/media_gallery/ios"
path_provider:
:path: ".symlinks/plugins/path_provider/ios"
permission_handler:
:path: ".symlinks/plugins/permission_handler/ios"
photo_manager:
:path: ".symlinks/plugins/photo_manager/ios"
shared_preferences:
:path: ".symlinks/plugins/shared_preferences/ios"
sqflite:
@@ -237,6 +240,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/sqlite3_flutter_libs/ios"
url_launcher:
:path: ".symlinks/plugins/url_launcher/ios"
video_compress:
:path: ".symlinks/plugins/video_compress/ios"
video_player:
:path: ".symlinks/plugins/video_player/ios"
wakelock:
@@ -264,24 +269,25 @@ SPEC CHECKSUMS:
GoogleDataTransport: f56af7caa4ed338dc8e138a5d7c5973e66440833
GoogleUtilities: 7f2f5a07f888cdb145101d6042bc4422f57e70b3
image_picker: 9c3312491f862b28d21ecd8fdf0ee14e601b3f09
media_gallery: 834a04455a476b975c280aa1f24ed2853dfe36de
nanopb: 59317e09cf1f1a0af72f12af412d54edf52603fc
path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c
permission_handler: eac8e15b4a1a3fba55b761d19f3f4e6b005d15b6
photo_manager: f7c619c2cc8c2adb8d85c63363babac477de9c67
PromisesObjC: 8c196f5a328c2cba3e74624585467a557dcb482f
Protobuf: 3dac39b34a08151c6d949560efe3f86134a3f748
SDWebImage: b69257f4ab14e9b6a2ef53e910fdf914d8f757c1
SDWebImage: 9169792e9eec3e45bba2a0c02f74bf8bd922d1ee
shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d
sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904
sqlite3: 8f7d2078ae27778699a622a94b853285793422a2
sqlite3_flutter_libs: 5651f8ff48e3b44d910863c4ea5916085b1b245f
Starscream: 5178aed56b316f13fa3bc55694e583d35dd414d9
StreamChatClient: d061cf52babcd9df930aee9cf864ab0379427eff
StreamChatClient: fc6419fa7ceda9c048188bb7c8e9f07755ced05f
SwiftyGif: e466e86c660d343357ab944a819a101c4127cb40
url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef
video_compress: fce97e4fb1dfd88175aa07d2ffc8a2f297f87fbe
video_player: 9cc823b1d9da7e8427ee591e8438bfbcde500e6e
wakelock: bfc7955c418d0db797614075aabbc58a39ab5107
PODFILE CHECKSUM: eb001256612a59f8f9e4d083ad8b9671e69dd184
COCOAPODS: 1.10.0.rc.1
COCOAPODS: 1.10.0
+4 -2
View File
@@ -327,14 +327,15 @@
"${BUILT_PRODUCTS_DIR}/flutter_local_notifications/flutter_local_notifications.framework",
"${BUILT_PRODUCTS_DIR}/flutter_secure_storage/flutter_secure_storage.framework",
"${BUILT_PRODUCTS_DIR}/image_picker/image_picker.framework",
"${BUILT_PRODUCTS_DIR}/media_gallery/media_gallery.framework",
"${BUILT_PRODUCTS_DIR}/nanopb/nanopb.framework",
"${BUILT_PRODUCTS_DIR}/path_provider/path_provider.framework",
"${BUILT_PRODUCTS_DIR}/photo_manager/photo_manager.framework",
"${BUILT_PRODUCTS_DIR}/shared_preferences/shared_preferences.framework",
"${BUILT_PRODUCTS_DIR}/sqflite/sqflite.framework",
"${BUILT_PRODUCTS_DIR}/sqlite3/sqlite3.framework",
"${BUILT_PRODUCTS_DIR}/sqlite3_flutter_libs/sqlite3_flutter_libs.framework",
"${BUILT_PRODUCTS_DIR}/url_launcher/url_launcher.framework",
"${BUILT_PRODUCTS_DIR}/video_compress/video_compress.framework",
"${BUILT_PRODUCTS_DIR}/video_player/video_player.framework",
"${BUILT_PRODUCTS_DIR}/wakelock/wakelock.framework",
);
@@ -358,14 +359,15 @@
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_local_notifications.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_secure_storage.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/image_picker.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/media_gallery.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/nanopb.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/path_provider.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/photo_manager.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/shared_preferences.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/sqflite.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/sqlite3.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/sqlite3_flutter_libs.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/url_launcher.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/video_compress.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/video_player.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/wakelock.framework",
);
+17 -2
View File
@@ -7,6 +7,8 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'main.dart';
import 'notifications_service.dart';
import 'choose_user_page.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
class AdvancedOptionsPage extends StatefulWidget {
@override
@@ -44,8 +46,7 @@ class _AdvancedOptionsPageState extends State<AdvancedOptionsPage> {
),
),
leading: IconButton(
icon: Icon(
StreamIcons.left,
icon: StreamSvgIcon.left(
color: Colors.black,
),
onPressed: () {
@@ -286,6 +287,20 @@ class _AdvancedOptionsPageState extends State<AdvancedOptionsPage> {
if (!kIsWeb) {
initNotifications(client);
}
final secureStorage = FlutterSecureStorage();
secureStorage.write(
key: kStreamApiKey,
value: apiKey,
);
secureStorage.write(
key: kStreamUserId,
value: userId,
);
secureStorage.write(
key: kStreamToken,
value: userToken,
);
} catch (e) {
var errorText = 'Error connecting, retry';
if (e is Map) {
+14 -19
View File
@@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/stream_icons.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
typedef ChipBuilder<T> = Widget Function(BuildContext context, T chip);
typedef OnChipAdded<T> = void Function(T chip);
@@ -43,7 +43,6 @@ class ChipInputTextFieldState<T> extends State<ChipsInputTextField<T>> {
_chips.remove(item);
if (_chips.isEmpty) resumeItemAddition();
});
if (widget.focusNode != null) widget.focusNode.requestFocus();
if (widget.onChipRemoved != null) widget.onChipRemoved(item);
}
@@ -51,25 +50,20 @@ class ChipInputTextFieldState<T> extends State<ChipsInputTextField<T>> {
if (!_pauseItemAddition) {
setState(() => _pauseItemAddition = true);
}
widget.focusNode?.unfocus();
}
void resumeItemAddition() {
if (_pauseItemAddition) {
setState(() => _pauseItemAddition = false);
}
widget.focusNode?.requestFocus();
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: _pauseItemAddition
? () {
setState(() {
_pauseItemAddition = false;
widget.focusNode?.requestFocus();
});
}
: null,
onTap: _pauseItemAddition ? resumeItemAddition : null,
child: Material(
elevation: 1,
color: Colors.white,
@@ -130,15 +124,16 @@ class ChipInputTextFieldState<T> extends State<ChipsInputTextField<T>> {
Align(
alignment: Alignment.bottomCenter,
child: IconButton(
icon: Icon(
_chips.isEmpty
? StreamIcons.user
: StreamIcons.user_add,
color: Colors.black.withOpacity(0.5),
size: 24,
),
onPressed:
!_pauseItemAddition ? null : resumeItemAddition,
icon: _chips.isEmpty
? StreamSvgIcon.user(
color: Colors.black.withOpacity(0.5),
size: 24,
)
: StreamSvgIcon.userAdd(
color: Colors.black.withOpacity(0.5),
size: 24,
),
onPressed: resumeItemAddition,
alignment: Alignment.topRight,
visualDensity: VisualDensity.compact,
padding: const EdgeInsets.all(0),
+8 -20
View File
@@ -8,6 +8,7 @@ import 'package:flutter_svg/flutter_svg.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'notifications_service.dart';
import 'routes/routes.dart';
const kStreamApiKey = 'STREAM_API_KEY';
const kStreamUserId = 'STREAM_USER_ID';
@@ -132,7 +133,7 @@ class ChooseUserPage extends StatelessWidget {
final secureStorage = FlutterSecureStorage();
final client = StreamChat.of(context).client;
client.apiKey = kDefaultStreamApiKey;
await client.setUser(
user,
token,
@@ -154,18 +155,11 @@ class ChooseUserPage extends StatelessWidget {
if (!kIsWeb) {
initNotifications(client);
}
Navigator.pop(context);
await Navigator.pushReplacement(
Navigator.pushNamedAndRemoveUntil(
context,
MaterialPageRoute(
builder: (context) {
return StreamChat(
client: client,
child: ChannelListPage(),
);
},
),
Routes.CHANNEL_LIST,
ModalRoute.withName(Routes.CHANNEL_LIST),
arguments: client,
);
},
leading: UserAvatar(
@@ -188,16 +182,10 @@ class ChooseUserPage extends StatelessWidget {
}),
ListTile(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AdvancedOptionsPage(),
),
);
Navigator.pushNamed(context, Routes.ADVANCED_OPTIONS);
},
leading: CircleAvatar(
child: Icon(
StreamIcons.settings,
child: StreamSvgIcon.settings(
color: Colors.black,
),
backgroundColor:
+152 -146
View File
@@ -3,7 +3,7 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:uuid/uuid.dart';
import 'main.dart';
import 'neumorphic_button.dart';
import 'routes/routes.dart';
class GroupChatDetailsScreen extends StatefulWidget {
final List<User> selectedUsers;
@@ -53,167 +53,173 @@ class _GroupChatDetailsScreenState extends State<GroupChatDetailsScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color.fromRGBO(252, 252, 252, 1),
appBar: AppBar(
elevation: 1,
backgroundColor: Colors.white,
leading: const StreamBackButton(),
title: Text(
'Name of Group Chat',
style: TextStyle(
color: Colors.black,
fontSize: 16,
return WillPopScope(
onWillPop: () async {
Navigator.pop(context, _selectedUsers);
return false;
},
child: Scaffold(
backgroundColor: Color.fromRGBO(252, 252, 252, 1),
appBar: AppBar(
elevation: 1,
backgroundColor: Colors.white,
leading: const StreamBackButton(),
title: Text(
'Name of Group Chat',
style: TextStyle(
color: Colors.black,
fontSize: 16,
),
),
),
centerTitle: true,
bottom: PreferredSize(
preferredSize: Size.fromHeight(kToolbarHeight),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 18, horizontal: 16),
child: Row(
children: [
Text(
'NAME',
style: TextStyle(
fontSize: 12,
color: Colors.black.withOpacity(0.5),
),
),
SizedBox(width: 16),
Expanded(
child: TextField(
controller: _groupNameController,
decoration: InputDecoration(
isDense: true,
border: InputBorder.none,
focusedBorder: InputBorder.none,
enabledBorder: InputBorder.none,
errorBorder: InputBorder.none,
disabledBorder: InputBorder.none,
contentPadding: const EdgeInsets.all(0),
hintText: 'Choose a group chat name',
hintStyle: TextStyle(
fontSize: 14, color: Colors.black.withOpacity(.5)),
centerTitle: true,
bottom: PreferredSize(
preferredSize: Size.fromHeight(kToolbarHeight),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 18, horizontal: 16),
child: Row(
children: [
Text(
'NAME',
style: TextStyle(
fontSize: 12,
color: Colors.black.withOpacity(0.5),
),
),
),
],
SizedBox(width: 16),
Expanded(
child: TextField(
controller: _groupNameController,
decoration: InputDecoration(
isDense: true,
border: InputBorder.none,
focusedBorder: InputBorder.none,
enabledBorder: InputBorder.none,
errorBorder: InputBorder.none,
disabledBorder: InputBorder.none,
contentPadding: const EdgeInsets.all(0),
hintText: 'Choose a group chat name',
hintStyle: TextStyle(
fontSize: 14, color: Colors.black.withOpacity(.5)),
),
),
),
],
),
),
),
),
actions: [
NeumorphicButton(
child: IconButton(
padding: const EdgeInsets.all(0),
icon: Icon(StreamIcons.check),
color: Color(0xFF006CFF),
onPressed: _isGroupNameEmpty
? null
: () async {
final groupName = _groupNameController.text;
final client = StreamChat.of(context).client;
final channel = client
.channel('messaging', id: Uuid().v4(), extraData: {
'members': [
client.state.user.id,
..._selectedUsers.map((e) => e.id),
],
'name': groupName,
});
await channel.watch();
Navigator.of(context)
..pop()
..pushReplacement(
MaterialPageRoute(
builder: (context) {
return StreamChannel(
child: ChannelPage(),
channel: channel,
);
},
),
actions: [
StreamNeumorphicButton(
child: IconButton(
padding: const EdgeInsets.all(0),
icon: StreamSvgIcon.check(
size: 24,
color: _isGroupNameEmpty ? Colors.grey : Color(0xFF006CFF),
),
onPressed: _isGroupNameEmpty
? null
: () async {
final groupName = _groupNameController.text;
final client = StreamChat.of(context).client;
final channel = client
.channel('messaging', id: Uuid().v4(), extraData: {
'members': [
client.state.user.id,
..._selectedUsers.map((e) => e.id),
],
'name': groupName,
});
await channel.watch();
Navigator.pushNamedAndRemoveUntil(
context,
Routes.CHANNEL_PAGE,
ModalRoute.withName(Routes.CHANNEL_LIST),
arguments: channel,
);
},
),
),
],
),
body: Column(
children: [
Container(
width: double.maxFinite,
color: Colors.grey.shade50,
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 8,
horizontal: 8,
},
),
child: Text(
'$_totalUsers ${_totalUsers > 1 ? 'Members' : 'Member'}',
style: TextStyle(
fontWeight: FontWeight.w500,
),
],
),
body: Column(
children: [
Container(
width: double.maxFinite,
color: Colors.grey.shade50,
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 8,
horizontal: 8,
),
child: Text(
'$_totalUsers ${_totalUsers > 1 ? 'Members' : 'Member'}',
style: TextStyle(
fontWeight: FontWeight.w500,
),
),
),
),
),
Expanded(
child: ListView.separated(
itemCount: _selectedUsers.length + 1,
separatorBuilder: (_, __) => Container(
height: 1,
color: Theme.of(context).brightness == Brightness.dark
? Colors.white.withOpacity(0.1)
: Colors.black.withOpacity(0.1),
),
itemBuilder: (_, index) {
if (index == _selectedUsers.length) {
return Container(
Expanded(
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onPanDown: (_) => FocusScope.of(context).unfocus(),
child: ListView.separated(
itemCount: _selectedUsers.length + 1,
separatorBuilder: (_, __) => Container(
height: 1,
color: Theme.of(context).brightness == Brightness.dark
? Colors.white.withOpacity(0.1)
: Colors.black.withOpacity(0.1),
);
}
final user = _selectedUsers[index];
return ListTile(
key: ObjectKey(user),
leading: UserAvatar(
user: user,
constraints: BoxConstraints.tightFor(
width: 40,
height: 40,
),
),
title: Text(
user.name,
style: TextStyle(fontWeight: FontWeight.bold),
),
contentPadding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 8,
),
trailing: IconButton(
icon: Icon(
Icons.clear_rounded,
color: Colors.black,
),
padding: const EdgeInsets.all(0),
splashRadius: 24,
onPressed: () {
setState(() {
_selectedUsers.remove(user);
});
if (_selectedUsers.isEmpty) {
Navigator.pop(context);
}
},
),
);
},
itemBuilder: (_, index) {
if (index == _selectedUsers.length) {
return Container(
height: 1,
color: Theme.of(context).brightness == Brightness.dark
? Colors.white.withOpacity(0.1)
: Colors.black.withOpacity(0.1),
);
}
final user = _selectedUsers[index];
return ListTile(
key: ObjectKey(user),
leading: UserAvatar(
user: user,
constraints: BoxConstraints.tightFor(
width: 40,
height: 40,
),
),
title: Text(
user.name,
style: TextStyle(fontWeight: FontWeight.bold),
),
contentPadding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 8,
),
trailing: IconButton(
icon: Icon(
Icons.clear_rounded,
color: Colors.black,
),
padding: const EdgeInsets.all(0),
splashRadius: 24,
onPressed: () {
setState(() {
_selectedUsers.remove(user);
});
if (_selectedUsers.isEmpty) {
Navigator.pop(context, _selectedUsers);
}
},
),
);
},
),
),
),
),
],
],
),
),
);
}
+54 -47
View File
@@ -10,6 +10,8 @@ import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'notifications_service.dart';
import 'routes/app_routes.dart';
import 'routes/routes.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
@@ -47,19 +49,19 @@ class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData.light(),
darkTheme: ThemeData.dark(),
//TODO change to system once dark theme is implemented
themeMode: ThemeMode.light,
builder: (context, widget) {
return StreamChat(
child: widget,
client: client,
);
},
home: client.state.user == null ? ChooseUserPage() : ChannelListPage(),
return StreamChat(
client: client,
child: MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData.light(),
darkTheme: ThemeData.dark(),
//TODO change to system once dark theme is implemented
themeMode: ThemeMode.light,
onGenerateRoute: AppRoutes.generateRoute,
initialRoute: client.state.user == null
? Routes.CHOOSE_USER
: Routes.CHANNEL_LIST,
),
);
}
}
@@ -69,21 +71,17 @@ class ChannelListPage extends StatelessWidget {
Widget build(BuildContext context) {
final user = StreamChat.of(context).user;
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.white,
title: Text(
'Stream Chat',
style: TextStyle(color: Colors.black),
),
appBar: ChannelListHeader(
onNewChatButtonTap: () {
Navigator.pushNamed(context, Routes.NEW_CHAT);
},
),
drawer: _buildDrawer(context, user),
drawerEdgeDragWidth: 50,
body: ChannelsBloc(
child: ChannelListView(
onStartChatPressed: () {
Navigator.of(context).push(MaterialPageRoute(builder: (context) {
return NewChatScreen();
}));
Navigator.pushNamed(context, Routes.NEW_CHAT);
},
swipeToAction: true,
filter: {
@@ -141,13 +139,14 @@ class ChannelListPage extends StatelessWidget {
),
),
ListTile(
leading: Icon(StreamIcons.edit),
leading: StreamSvgIcon.penWrite(
color: Colors.black.withOpacity(.5),
),
onTap: () {
Navigator.of(context)
..pop()
..push(MaterialPageRoute(builder: (context) {
return NewChatScreen();
}));
Navigator.popAndPushNamed(
context,
Routes.NEW_CHAT,
);
},
title: Text(
'New direct message',
@@ -157,13 +156,14 @@ class ChannelListPage extends StatelessWidget {
),
),
ListTile(
leading: Icon(StreamIcons.group),
leading: StreamSvgIcon.contacts(
color: Colors.black.withOpacity(.5),
),
onTap: () {
Navigator.of(context)
..pop()
..push(MaterialPageRoute(builder: (context) {
return NewGroupChatScreen();
}));
Navigator.popAndPushNamed(
context,
Routes.NEW_GROUP_CHAT,
);
},
title: Text(
'New group',
@@ -182,14 +182,14 @@ class ChannelListPage extends StatelessWidget {
final secureStorage = FlutterSecureStorage();
await secureStorage.deleteAll();
Navigator.pop(context);
await Navigator.pushReplacement(
Navigator.pushReplacementNamed(
context,
MaterialPageRoute(
builder: (context) => ChooseUserPage(),
),
Routes.CHOOSE_USER,
);
},
leading: Icon(StreamIcons.user),
leading: StreamSvgIcon.user(
color: Colors.black.withOpacity(.5),
),
title: Text(
'Sign out',
style: TextStyle(
@@ -215,7 +215,9 @@ class ChannelPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: ChannelHeader(),
appBar: ChannelHeader(
showTypingIndicator: false,
),
body: Column(
children: <Widget>[
Expanded(
@@ -228,14 +230,19 @@ class ChannelPage extends StatelessWidget {
);
},
),
Positioned.fill(
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8.0,
vertical: 4,
),
Positioned(
bottom: 0,
left: 0,
right: 0,
child: Container(
alignment: Alignment.centerLeft,
color: Color(0xffFCFCFC).withOpacity(.9),
child: TypingIndicator(
alignment: Alignment.bottomRight,
alignment: Alignment.centerLeft,
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 4,
),
),
),
),
-40
View File
@@ -1,40 +0,0 @@
import 'package:flutter/material.dart';
class NeumorphicButton extends StatelessWidget {
final Widget child;
final Color backgroundColor;
const NeumorphicButton({
Key key,
@required this.child,
this.backgroundColor = Colors.white,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
child: child,
margin: EdgeInsets.all(8.0),
height: 40,
width: 40,
decoration: BoxDecoration(
color: backgroundColor,
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: Colors.grey[700],
offset: Offset(0, 1.0),
blurRadius: 0.5,
spreadRadius: 0,
),
BoxShadow(
color: Colors.white,
offset: Offset.zero,
blurRadius: 0.5,
spreadRadius: 0,
),
],
),
);
}
}
+74 -75
View File
@@ -5,8 +5,8 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'chips_input_text_field.dart';
import 'main.dart';
import 'neumorphic_button.dart';
import 'new_group_chat_screen.dart';
import 'routes/routes.dart';
class NewChatScreen extends StatefulWidget {
@override
@@ -164,8 +164,7 @@ class _NewChatScreenState extends State<NewChatScreen> {
),
),
Positioned(
child: Icon(
StreamIcons.close,
child: StreamSvgIcon.close(
color: Colors.white,
),
),
@@ -184,19 +183,21 @@ class _NewChatScreenState extends State<NewChatScreen> {
Container(
child: InkWell(
onTap: () {
Navigator.push(
Navigator.pushNamed(
context,
MaterialPageRoute(builder: (_) => NewGroupChatScreen()),
Routes.NEW_GROUP_CHAT,
);
},
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Row(
children: [
NeumorphicButton(
child: Icon(
StreamIcons.group,
color: Color(0xFF006CFF),
StreamNeumorphicButton(
child: Center(
child: StreamSvgIcon.contacts(
color: Color(0xFF006CFF),
size: 24,
),
),
),
SizedBox(width: 8),
@@ -243,68 +244,71 @@ class _NewChatScreenState extends State<NewChatScreen> {
),
Expanded(
child: _showUserList
? UsersBloc(
child: UserListView(
selectedUsers: _selectedUsers,
groupAlphabetically: _isSearchActive ? false : true,
onUserTap: (user, _) {
_controller.clear();
if (!_selectedUsers.contains(user)) {
_chipInputTextFieldState
..addItem(user)
..pauseItemAddition();
} else {
_chipInputTextFieldState.removeItem(user);
}
},
pagination: PaginationParams(
limit: 25,
),
filter: {
if (_userNameQuery.isNotEmpty)
'name': {
r'$autocomplete': _userNameQuery,
},
'id': {
r'$ne': StreamChat.of(context).user.id,
? GestureDetector(
behavior: HitTestBehavior.opaque,
onPanDown: (_) => FocusScope.of(context).unfocus(),
child: UsersBloc(
child: UserListView(
selectedUsers: _selectedUsers,
groupAlphabetically: _isSearchActive ? false : true,
onUserTap: (user, _) {
_controller.clear();
if (!_selectedUsers.contains(user)) {
_chipInputTextFieldState
..addItem(user)
..pauseItemAddition();
} else {
_chipInputTextFieldState.removeItem(user);
}
},
},
sort: [
SortOption(
'name',
direction: 1,
pagination: PaginationParams(
limit: 25,
),
],
emptyBuilder: (_) {
return LayoutBuilder(
builder: (context, viewportConstraints) {
return SingleChildScrollView(
physics: AlwaysScrollableScrollPhysics(),
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: viewportConstraints.maxHeight,
),
child: Center(
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(24),
child: Icon(
StreamIcons.search,
size: 96,
color: Colors.grey,
filter: {
if (_userNameQuery.isNotEmpty)
'name': {
r'$autocomplete': _userNameQuery,
},
'id': {
r'$ne': StreamChat.of(context).user.id,
},
},
sort: [
SortOption(
'name',
direction: 1,
),
],
emptyBuilder: (_) {
return LayoutBuilder(
builder: (context, viewportConstraints) {
return SingleChildScrollView(
physics: AlwaysScrollableScrollPhysics(),
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: viewportConstraints.maxHeight,
),
child: Center(
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(24),
child: StreamSvgIcon.search(
size: 96,
color: Colors.grey,
),
),
),
Text(
'No user matches these keywords...'),
],
Text(
'No user matches these keywords...'),
],
),
),
),
),
);
},
);
},
);
},
);
},
),
),
)
: MessageListView(),
@@ -331,16 +335,11 @@ class _NewChatScreenState extends State<NewChatScreen> {
channel.update({
'draft': false,
});
Navigator.pushReplacement(
Navigator.pushNamedAndRemoveUntil(
context,
MaterialPageRoute(
builder: (context) {
return StreamChannel(
child: ChannelPage(),
channel: channel,
);
},
),
Routes.CHANNEL_PAGE,
ModalRoute.withName(Routes.CHANNEL_LIST),
arguments: channel,
);
}
}
+180 -157
View File
@@ -4,6 +4,8 @@ import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'group_chat_details_screen.dart';
import 'search_text_field.dart';
import 'routes/routes.dart';
class NewGroupChatScreen extends StatefulWidget {
@override
@@ -24,11 +26,12 @@ class _NewGroupChatScreenState extends State<NewGroupChatScreen> {
void _userNameListener() {
if (_debounce?.isActive ?? false) _debounce.cancel();
_debounce = Timer(const Duration(milliseconds: 350), () {
if (mounted)
if (mounted) {
setState(() {
_userNameQuery = _controller.text;
_isSearchActive = _userNameQuery.isNotEmpty;
});
}
});
}
@@ -48,174 +51,167 @@ class _NewGroupChatScreenState extends State<NewGroupChatScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color.fromRGBO(252, 252, 252, 1),
appBar: AppBar(
elevation: 1,
backgroundColor: Colors.white,
leading: const StreamBackButton(),
title: Text(
'Add Group Members',
style: TextStyle(
color: Colors.black,
fontSize: 16,
),
),
centerTitle: true,
actions: [
if (_selectedUsers.isNotEmpty)
IconButton(
icon: Icon(
StreamIcons.arrow_right,
color: Color(0xFF006CFF),
),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => GroupChatDetailsScreen(
selectedUsers: _selectedUsers.toList(growable: false),
),
),
);
},
)
],
),
body: UsersBloc(
child: Column(
children: [
Container(
height: 36,
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(
color: Colors.grey.shade300,
),
borderRadius: BorderRadius.circular(24),
),
margin: const EdgeInsets.symmetric(
vertical: 8,
horizontal: 8,
),
child: TextField(
controller: _controller,
decoration: InputDecoration(
prefixIcon: Icon(
StreamIcons.search,
return SafeArea(
child: Scaffold(
backgroundColor: Color.fromRGBO(252, 252, 252, 1),
body: NestedScrollView(
floatHeaderSlivers: true,
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverAppBar(
pinned: true,
forceElevated: true,
elevation: 1,
backgroundColor: Colors.white,
leading: const StreamBackButton(),
title: Text(
'Add Group Members',
style: TextStyle(
color: Colors.black,
size: 24,
),
hintText: 'Search',
hintStyle: TextStyle(
color: Colors.black.withOpacity(0.5),
fontSize: 14,
),
contentPadding: const EdgeInsets.all(0),
border: OutlineInputBorder(
borderSide: BorderSide.none,
borderRadius: BorderRadius.circular(24),
fontSize: 16,
),
),
centerTitle: true,
actions: [
if (_selectedUsers.isNotEmpty)
IconButton(
icon: StreamSvgIcon.arrow_right(
color: Color(0xFF006CFF),
),
onPressed: () async {
final updatedList = await Navigator.pushNamed(
context,
Routes.NEW_GROUP_CHAT_DETAILS,
arguments: _selectedUsers.toList(growable: false),
);
if (updatedList != null) {
setState(() {
_selectedUsers
..clear()
..addAll(updatedList);
});
}
},
)
],
),
),
if (_selectedUsers.isNotEmpty)
Container(
height: 104,
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: _selectedUsers.length,
padding: const EdgeInsets.all(8),
separatorBuilder: (_, __) => SizedBox(width: 16),
itemBuilder: (_, index) {
final user = _selectedUsers.elementAt(index);
return Column(
children: [
Stack(
SliverToBoxAdapter(
child: SearchTextField(
controller: _controller,
),
),
if (_selectedUsers.isNotEmpty)
SliverToBoxAdapter(
child: Container(
height: 104,
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: _selectedUsers.length,
padding: const EdgeInsets.all(8),
separatorBuilder: (_, __) => SizedBox(width: 16),
itemBuilder: (_, index) {
final user = _selectedUsers.elementAt(index);
return Column(
children: [
UserAvatar(
onlineIndicatorAlignment: Alignment(0.9, 0.9),
user: user,
showOnlineStatus: true,
borderRadius: BorderRadius.circular(32),
constraints: BoxConstraints.tightFor(
height: 64,
width: 64,
),
),
Positioned(
top: -4,
right: -4,
child: GestureDetector(
onTap: () {
if (_selectedUsers.contains(user)) {
setState(() => _selectedUsers.remove(user));
}
},
child: Container(
decoration: BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
border: Border.all(
color: Colors.grey.shade100,
),
),
child: Padding(
padding: const EdgeInsets.all(0.0),
child: Icon(
StreamIcons.close,
size: 24,
),
Stack(
children: [
UserAvatar(
onlineIndicatorAlignment: Alignment(0.9, 0.9),
user: user,
showOnlineStatus: true,
borderRadius: BorderRadius.circular(32),
constraints: BoxConstraints.tightFor(
height: 64,
width: 64,
),
),
Positioned(
top: -4,
right: -4,
child: GestureDetector(
onTap: () {
if (_selectedUsers.contains(user)) {
setState(
() => _selectedUsers.remove(user));
}
},
child: Container(
decoration: BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
border: Border.all(
color: Colors.grey.shade100,
),
),
child: Padding(
padding: const EdgeInsets.all(0.0),
child: StreamSvgIcon.close(
color: Colors.black,
size: 24,
),
),
),
),
)
],
),
SizedBox(height: 4),
Text(
user.name.split(' ')[0],
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 12,
),
)
),
],
);
},
),
),
),
SliverPersistentHeader(
pinned: true,
delegate: _HeaderDelegate(
height: 30,
child: Container(
width: double.maxFinite,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.centerLeft,
end: Alignment.centerRight,
colors: [
Colors.black.withOpacity(0.02),
Colors.white.withOpacity(0.05),
],
stops: [0, 1],
),
),
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 8,
horizontal: 8,
),
child: Text(
_isSearchActive
? 'Matches for \"$_userNameQuery\"'
: 'On the platform',
style: TextStyle(
color: Colors.black.withOpacity(0.5),
),
SizedBox(height: 4),
Text(
user.name.split(' ')[0],
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 12,
),
),
],
);
},
),
),
Container(
width: double.maxFinite,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.centerLeft,
end: Alignment.centerRight,
colors: [
Colors.black.withOpacity(0.02),
Colors.white.withOpacity(0.05),
],
stops: [0, 1],
),
),
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 8,
horizontal: 8,
),
child: Text(
_isSearchActive
? 'Matches for \"$_userNameQuery\"'
: 'On the platform',
style: TextStyle(
color: Colors.black.withOpacity(0.5),
),
),
),
),
),
),
Expanded(
];
},
body: GestureDetector(
behavior: HitTestBehavior.opaque,
onPanDown: (_) => FocusScope.of(context).unfocus(),
child: UsersBloc(
child: UserListView(
selectedUsers: _selectedUsers,
pullToRefresh: false,
groupAlphabetically: _isSearchActive ? false : true,
onUserTap: (user, _) {
if (!_selectedUsers.contains(user)) {
@@ -260,8 +256,7 @@ class _NewGroupChatScreenState extends State<NewGroupChatScreen> {
children: [
Padding(
padding: const EdgeInsets.all(24),
child: Icon(
StreamIcons.search,
child: StreamSvgIcon.search(
size: 96,
color: Colors.grey,
),
@@ -277,9 +272,37 @@ class _NewGroupChatScreenState extends State<NewGroupChatScreen> {
},
),
),
],
),
),
),
);
}
}
class _HeaderDelegate extends SliverPersistentHeaderDelegate {
final Widget child;
final double height;
const _HeaderDelegate({
@required this.child,
@required this.height,
});
@override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
return Container(
color: Colors.white,
child: child,
);
}
@override
double get maxExtent => height;
@override
double get minExtent => height;
@override
bool shouldRebuild(_HeaderDelegate oldDelegate) => true;
}
+64
View File
@@ -0,0 +1,64 @@
import 'routes.dart';
import 'package:flutter/material.dart';
import '../choose_user_page.dart';
import '../advanced_options_page.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import '../main.dart';
import '../group_chat_details_screen.dart';
import '../new_group_chat_screen.dart';
import '../new_chat_screen.dart';
class AppRoutes {
/// Add entry for new route here
static Route<dynamic> generateRoute(RouteSettings settings) {
final args = settings.arguments;
switch (settings.name) {
case Routes.CHANNEL_LIST:
return MaterialPageRoute(
settings: const RouteSettings(name: Routes.CHANNEL_LIST),
builder: (_) {
return ChannelListPage();
});
case Routes.CHOOSE_USER:
return MaterialPageRoute(
settings: const RouteSettings(name: Routes.CHOOSE_USER),
builder: (_) {
return ChooseUserPage();
});
case Routes.ADVANCED_OPTIONS:
return MaterialPageRoute(
settings: const RouteSettings(name: Routes.ADVANCED_OPTIONS),
builder: (_) => AdvancedOptionsPage(),
);
case Routes.CHANNEL_PAGE:
return MaterialPageRoute(
settings: const RouteSettings(name: Routes.CHANNEL_PAGE),
builder: (_) {
return StreamChannel(
channel: args as Channel,
child: ChannelPage(),
);
});
case Routes.NEW_CHAT:
return MaterialPageRoute(
settings: const RouteSettings(name: Routes.NEW_CHAT),
builder: (_) {
return NewChatScreen();
});
case Routes.NEW_GROUP_CHAT:
return MaterialPageRoute(
settings: const RouteSettings(name: Routes.NEW_GROUP_CHAT),
builder: (_) {
return NewGroupChatScreen();
});
case Routes.NEW_GROUP_CHAT_DETAILS:
return MaterialPageRoute(
settings: const RouteSettings(name: Routes.NEW_GROUP_CHAT_DETAILS),
builder: (_) {
return GroupChatDetailsScreen(
selectedUsers: args,
);
});
}
}
}
+10
View File
@@ -0,0 +1,10 @@
/// Define all the route names here
class Routes {
static const String CHANNEL_LIST = '/channel_list';
static const String CHOOSE_USER = '/choose_user';
static const String ADVANCED_OPTIONS = '/advance_options';
static const String CHANNEL_PAGE = '/channel_page';
static const String NEW_CHAT = '/new_chat';
static const String NEW_GROUP_CHAT = '/new_group_chat';
static const String NEW_GROUP_CHAT_DETAILS = '/new_group_chat_details';
}
+92
View File
@@ -0,0 +1,92 @@
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
class SearchTextField extends StatelessWidget {
final TextEditingController controller;
final ValueChanged<String> onChanged;
final String hintText;
final VoidCallback onTap;
final bool showCloseButton;
const SearchTextField({
Key key,
@required this.controller,
this.onChanged,
this.onTap,
this.hintText = 'Search',
this.showCloseButton = true,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
height: 36,
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(
color: Colors.grey.shade300,
),
borderRadius: BorderRadius.circular(24),
),
margin: const EdgeInsets.symmetric(
vertical: 8,
horizontal: 8,
),
child: Row(
children: [
Expanded(
child: TextField(
onTap: onTap,
controller: controller,
onChanged: onChanged,
decoration: InputDecoration(
prefixIconConstraints: BoxConstraints.tight(Size(40, 24)),
prefixIcon: Padding(
padding: const EdgeInsets.only(
left: 8,
right: 8,
),
child: StreamSvgIcon.search(
color: Colors.black,
size: 24,
),
),
hintText: hintText,
hintStyle: TextStyle(
color: Colors.black.withOpacity(0.5),
fontSize: 14,
),
contentPadding: const EdgeInsets.all(0),
border: OutlineInputBorder(
borderSide: BorderSide.none,
borderRadius: BorderRadius.circular(24),
),
),
),
),
if (showCloseButton)
Material(
color: Colors.transparent,
child: IconButton(
padding: const EdgeInsets.all(0),
icon: StreamSvgIcon.close_small(
color: Colors.grey,
),
splashRadius: 24,
onPressed: () {
if (controller.text.isNotEmpty) {
Future.microtask(
() => [
controller.clear(),
onChanged(''),
],
);
}
},
),
),
],
),
);
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
name: example
description: A new Flutter project.
version: 1.0.66+68
version: 1.0.78+80
environment:
sdk: ">=2.2.2 <3.0.0"