Merge branches 'feature/new-ui' and 'llc-independence' of https://github.com/GetStream/stream-chat-flutter into llc-independence

 Conflicts:
	packages/flutter_widgets/pubspec.yaml
This commit is contained in:
Sahil Kumar
2021-01-28 19:54:02 +05:30
125 changed files with 4500 additions and 1380 deletions
+1
View File
@@ -1,6 +1,7 @@
.DS_Store
.atom/
.idea/
.vscode/
.packages
.pub/
@@ -1,3 +1,4 @@
org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true
android.enableJetifier=true
android.enableR8=true
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip
+1 -1
View File
@@ -156,7 +156,7 @@ class _MessageViewState extends State<MessageView> {
/// Convenience method for scrolling the list view when a new message is sent.
void _updateList() {
_scrollController.animateTo(
_scrollController.position.maxScrollExtent,
0,
duration: const Duration(milliseconds: 200),
curve: Curves.easeOut,
);
+4
View File
@@ -1,3 +1,7 @@
## 1.0.0-rc
- Integrated new-ui, split package functionality,
## 0.2.20+2
- Added `shouldAddChannel` to ChannelsBloc in order to check if a channel has to be added to the list when a new message arrives
+3 -7
View File
@@ -60,13 +60,9 @@ If it seems related to the [flutter file picker plugin](https://github.com/migue
## Docs
### Business logic components
We provide 3 Widgets dedicated to business logic and state management:
- [StreamChat](https://pub.dev/documentation/stream_chat_flutter/latest/stream_chat_flutter/StreamChat-class.html)
- [StreamChannel](https://pub.dev/documentation/stream_chat_flutter/latest/stream_chat_flutter/StreamChannel-class.html)
- [ChannelsBloc](https://pub.dev/documentation/stream_chat_flutter/0.2.0-alpha+2/stream_chat_flutter/ChannelsBloc-class.html)
This package provides UI components required for integrating Stream Chat into your application.
Alternatively, you may use the core package (stream_chat_flutter_core) which allows more customisation and provides business logic but no UI components.
If you require the maximum amount of control over the API, please use the low level client package: stream_chat.
### UI Components
@@ -11,52 +11,91 @@ import 'routes/routes.dart';
const kStreamApiKey = 'STREAM_API_KEY';
const kStreamUserId = 'STREAM_USER_ID';
const kStreamToken = 'STREAM_TOKEN';
const kDefaultStreamApiKey = 'uj7qrdbfrzvg';
const kDefaultStreamApiKey = 'kv7mcsxr24p8';
class ChooseUserPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final users = <String, User>{
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoidmlzaGFsIn0.lCz-idDgaZ-xszjnuB_hTfeIOhTFmJtTB2fEjhwrcCI':
User(
id: 'vishal',
extraData: {
'name': 'Vishal',
},
),
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.1AvvK2TvEsfcxAlfOK2iUMFACucnk6N-Q8f5bbfnjCM':
User(
id: 'super-band-9',
extraData: {
'name': 'John Doe',
},
),
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoic2FsdmF0b3JlIn0.uQ-pz8e1_C-Df2rIFpkkvWf7g4M0aW2Zkr7pHVhmU1Y':
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoic2FsdmF0b3JlIn0.pgiJz7sIc7iP29BHKFwe3nLm5-OaR_1l2P-SlgiC9a8':
User(
id: 'salvatore',
extraData: {
'name': 'Salvatore',
'name': 'Salvatore Giordano',
'image':
'https://ca.slack-edge.com/T02RM6X6B-USKK9FFRT-30c415e207a9-512',
},
),
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoidG9tbWFzbyJ9.feLvMytd5_3-eETKrRdwtO3jcA0HxD8woFUbFU7gQCg':
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoic2FoaWwifQ.WnIUoB5gR2kcAsFhiDvkiD6zdHXZ-VSU2aQWWkhsvfo':
User(
id: 'sahil',
extraData: {
'name': 'Sahil Kumar',
'image':
'https://ca.slack-edge.com/T02RM6X6B-U01EYU51M89-bbc152b40321-512',
},
),
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiYmVuIn0.nAz2sNFGQwY7rl2Og2z3TGHUsdpnN53tOsUglJFvLmg':
User(
id: 'ben',
extraData: {
'name': 'Ben Golden',
'image':
'https://ca.slack-edge.com/T02RM6X6B-U01AXAF23MG-f57403a3cb0d-512',
},
),
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoidGhpZXJyeSJ9.lEq6TrZtHzjoNtf7HHRufUPyGo_pa8vg4_XhEBp4ckY':
User(
id: 'thierry',
extraData: {
'name': 'Thierry Schellenbach',
'image':
'https://ca.slack-edge.com/T02RM6X6B-U02RM6X6D-g28a1278a98e-512',
},
),
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoidG9tbWFzbyJ9.GLSI0ESshERMo2WjUpysD709NEtn1zmGimUN2an7g9o':
User(
id: 'tommaso',
extraData: {
'name': 'Tommaso',
'name': 'Tommaso Barbugli',
'image':
'https://ca.slack-edge.com/T02RM6X6B-U02U7SJP4-0f65a5997877-512',
},
),
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiamFhcCJ9.VLkLs6KH-jRRkPvhYMa4_lqN0R6WiK7JhJVmYvxcKz0':
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiZGV2ZW4ifQ.z3zI4PqJnNhc-1o-VKcmb6BnnQ0oxFNCRHwEulHqcWc':
User(
id: 'jaap',
id: 'deven',
extraData: {
'name': 'Jaap',
'name': 'Deven Joshi',
'image':
'https://ca.slack-edge.com/T02RM6X6B-U01AM7ELPTL-8a60da32704c-512',
},
),
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoibmVldmFzaCJ9.3EdHegTxibrz3A9cTiKmpEyawwcCVB8FXnoFzr4eKvw':
User(
id: 'neevash',
extraData: {
'name': 'Neevash Ramdial',
'image':
'https://ca.slack-edge.com/T02RM6X6B-U01DZ046DS8-b00d321d2880-512',
},
),
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoicWF0ZXN0MSJ9.fnelU7HcP7QoEEsCGteNlF1fppofzNlrnpDQuIgeKCU':
User(
id: 'qatest1',
extraData: {
'name': 'QA test 1',
},
),
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoicWF0ZXN0MiJ9.vSCqAEbs2WVmMWsOsa7065Fsjq-rsTih6qsHPynl7XM':
User(
id: 'qatest2',
extraData: {
'name': 'QA test 2',
},
),
};
users.updateAll(
(_, value) => value..extraData['image'] = getRandomPicUrl(value));
return Scaffold(
backgroundColor: StreamChatTheme.of(context).colorTheme.whiteSnow,
body: SafeArea(
@@ -135,287 +135,284 @@ class _NewChatScreenState extends State<NewChatScreen> {
centerTitle: true,
),
body: ValueListenableBuilder<ConnectionStatus>(
valueListenable: StreamChat.of(context).client.wsConnectionStatus,
builder: (context, status, _) {
String statusString = '';
bool showStatus = true;
valueListenable: StreamChat.of(context).client.wsConnectionStatus,
builder: (context, status, _) {
String statusString = '';
bool showStatus = true;
switch (status) {
case ConnectionStatus.connected:
statusString = 'Connected';
showStatus = false;
break;
case ConnectionStatus.connecting:
statusString = 'Reconnecting...';
break;
case ConnectionStatus.disconnected:
statusString = 'Disconnected';
break;
}
return InfoTile(
showMessage: showStatus,
tileAnchor: Alignment.topCenter,
childAnchor: Alignment.topCenter,
message: statusString,
child: StreamChannel(
showLoading: false,
channel: channel,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ChipsInputTextField<User>(
key: _chipInputTextFieldStateKey,
controller: _controller,
focusNode: _searchFocusNode,
chipBuilder: (context, user) {
return GestureDetector(
onTap: () {
_chipInputTextFieldState.removeItem(user);
_searchFocusNode.requestFocus();
},
child: Stack(
alignment: AlignmentDirectional.centerStart,
switch (status) {
case ConnectionStatus.connected:
statusString = 'Connected';
showStatus = false;
break;
case ConnectionStatus.connecting:
statusString = 'Reconnecting...';
break;
case ConnectionStatus.disconnected:
statusString = 'Disconnected';
break;
}
return InfoTile(
showMessage: showStatus,
tileAnchor: Alignment.topCenter,
childAnchor: Alignment.topCenter,
message: statusString,
child: StreamChannel(
showLoading: false,
channel: channel,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ChipsInputTextField<User>(
key: _chipInputTextFieldStateKey,
controller: _controller,
focusNode: _searchFocusNode,
chipBuilder: (context, user) {
return GestureDetector(
onTap: () {
_chipInputTextFieldState.removeItem(user);
_searchFocusNode.requestFocus();
},
child: Stack(
alignment: AlignmentDirectional.centerStart,
children: [
Container(
decoration: BoxDecoration(
color: StreamChatTheme.of(context)
.colorTheme
.greyGainsboro,
borderRadius: BorderRadius.circular(12),
),
padding: const EdgeInsets.only(left: 24),
child: Padding(
padding: const EdgeInsets.fromLTRB(8, 4, 12, 4),
child: Text(
user.name,
maxLines: 1,
style: TextStyle(
color: StreamChatTheme.of(context)
.colorTheme
.black,
),
),
),
),
Container(
foregroundDecoration: BoxDecoration(
color: StreamChatTheme.of(context)
.colorTheme
.overlay,
shape: BoxShape.circle,
),
child: UserAvatar(
showOnlineStatus: false,
user: user,
constraints: BoxConstraints.tightFor(
height: 24,
width: 24,
),
),
),
StreamSvgIcon.close(),
],
),
);
},
onChipAdded: (user) {
setState(() => _selectedUsers.add(user));
},
onChipRemoved: (user) {
setState(() => _selectedUsers.remove(user));
},
),
if (!_isSearchActive && !_selectedUsers.isNotEmpty)
Container(
child: InkWell(
onTap: () {
Navigator.pushNamed(
context,
Routes.NEW_GROUP_CHAT,
);
},
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Row(
children: [
Container(
decoration: BoxDecoration(
color: StreamChatTheme.of(context)
.colorTheme
.greyGainsboro,
borderRadius: BorderRadius.circular(12),
),
padding: const EdgeInsets.only(left: 24),
child: Padding(
padding:
const EdgeInsets.fromLTRB(8, 4, 12, 4),
child: Text(
user.name,
maxLines: 1,
style: TextStyle(
color: StreamChatTheme.of(context)
.colorTheme
.black,
),
StreamNeumorphicButton(
child: Center(
child: StreamSvgIcon.contacts(
color: StreamChatTheme.of(context)
.colorTheme
.accentBlue,
size: 24,
),
),
),
Container(
foregroundDecoration: BoxDecoration(
color: StreamChatTheme.of(context)
.colorTheme
.overlay,
shape: BoxShape.circle,
),
child: UserAvatar(
showOnlineStatus: false,
user: user,
constraints: BoxConstraints.tightFor(
height: 24,
width: 24,
),
),
SizedBox(width: 8),
Text(
'Create a Group',
style: StreamChatTheme.of(context)
.textTheme
.bodyBold,
),
StreamSvgIcon.close(),
],
),
);
},
onChipAdded: (user) {
setState(() => _selectedUsers.add(user));
},
onChipRemoved: (user) {
setState(() => _selectedUsers.remove(user));
},
),
),
),
if (!_isSearchActive && !_selectedUsers.isNotEmpty)
Container(
child: InkWell(
onTap: () {
Navigator.pushNamed(
context,
Routes.NEW_GROUP_CHAT,
);
},
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Row(
children: [
StreamNeumorphicButton(
child: Center(
child: StreamSvgIcon.contacts(
color: StreamChatTheme.of(context)
.colorTheme
.accentBlue,
size: 24,
),
),
),
SizedBox(width: 8),
Text(
'Create a Group',
style: StreamChatTheme.of(context)
.textTheme
.bodyBold,
),
],
),
),
),
if (_showUserList)
Container(
width: double.maxFinite,
decoration: BoxDecoration(
gradient:
StreamChatTheme.of(context).colorTheme.bgGradient,
),
if (_showUserList)
Container(
width: double.maxFinite,
decoration: BoxDecoration(
gradient:
StreamChatTheme.of(context).colorTheme.bgGradient,
),
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 8,
horizontal: 8,
),
child: Text(
_isSearchActive
? "Matches for \"$_userNameQuery\""
: 'On the platform',
style: StreamChatTheme.of(context)
.textTheme
.footnote
.copyWith(
color: StreamChatTheme.of(context)
.colorTheme
.black
.withOpacity(.5))),
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 8,
horizontal: 8,
),
child: Text(
_isSearchActive
? "Matches for \"$_userNameQuery\""
: 'On the platform',
style: StreamChatTheme.of(context)
.textTheme
.footnote
.copyWith(
color: StreamChatTheme.of(context)
.colorTheme
.black
.withOpacity(.5))),
),
Expanded(
child: _showUserList
? 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);
}
},
pagination: PaginationParams(
limit: 25,
),
filter: {
if (_userNameQuery.isNotEmpty)
'name': {
r'$autocomplete': _userNameQuery,
},
'id': {
r'$ne': StreamChat.of(context).user.id,
),
Expanded(
child: _showUserList
? 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);
}
},
pagination: PaginationParams(
limit: 25,
),
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,
),
},
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...',
style: StreamChatTheme.of(
context)
.textTheme
.footnote
.copyWith(
color: StreamChatTheme
.of(context)
.colorTheme
.black
.withOpacity(
.5)),
),
],
),
),
Text(
'No user matches these keywords...',
style: StreamChatTheme.of(
context)
.textTheme
.footnote
.copyWith(
color: StreamChatTheme
.of(context)
.colorTheme
.black
.withOpacity(.5)),
),
],
),
),
);
},
);
},
),
),
);
},
);
},
),
)
: FutureBuilder<bool>(
future: channel.initialized,
builder: (context, snapshot) {
if (snapshot.data == true) {
return MessageListView();
}
return Center(
child: Text(
'No chats here yet...',
style: TextStyle(
fontSize: 12,
color: StreamChatTheme.of(context)
.colorTheme
.black
.withOpacity(.5),
),
),
);
},
),
),
MessageInput(
focusNode: _messageInputFocusNode,
preMessageSending: (message) async {
await channel.watch();
return message;
},
onMessageSent: (m) {
Navigator.pushNamedAndRemoveUntil(
context,
Routes.CHANNEL_PAGE,
ModalRoute.withName(Routes.HOME),
arguments: ChannelPageArgs(channel: channel),
);
},
),
],
),
)
: FutureBuilder<bool>(
future: channel.initialized,
builder: (context, snapshot) {
if (snapshot.data == true) {
return MessageListView();
}
return Center(
child: Text(
'No chats here yet...',
style: TextStyle(
fontSize: 12,
color: StreamChatTheme.of(context)
.colorTheme
.black
.withOpacity(.5),
),
),
);
},
),
),
MessageInput(
focusNode: _messageInputFocusNode,
preMessageSending: (message) async {
await channel.watch();
return message;
},
onMessageSent: (m) {
Navigator.pushNamedAndRemoveUntil(
context,
Routes.CHANNEL_PAGE,
ModalRoute.withName(Routes.HOME),
arguments: ChannelPageArgs(channel: channel),
);
},
),
],
),
);
}),
),
);
},
),
);
}
}
@@ -1,7 +1,7 @@
name: example
description: A new Flutter project.
publish_to: 'none'
version: 1.1.3+6
version: 1.2.0
environment:
sdk: ">=2.2.2 <3.0.0"
@@ -1,8 +1,7 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import '../stream_chat_flutter.dart';
class AttachmentError extends StatelessWidget {
@@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'stream_chat_theme.dart';
import 'utils.dart';
@@ -1,5 +1,4 @@
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/lazy_load_scroll_view.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
class ChannelFileDisplayScreen extends StatefulWidget {
@@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/back_button.dart';
import 'package:stream_chat_flutter/src/channel_info.dart';
import 'package:stream_chat_flutter/src/channel_name.dart';
@@ -9,7 +9,6 @@ import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
import '../stream_chat_flutter.dart';
import './channel_name.dart';
import 'channel_image.dart';
import 'stream_channel.dart';
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_header.png)
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_header_paint.png)
@@ -1,6 +1,6 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/group_image.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
@@ -2,7 +2,7 @@ import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/stream_neumorphic_button.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
@@ -5,16 +5,13 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
import 'package:shimmer/shimmer.dart';
import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_flutter/src/channels_bloc.dart';
import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
import 'package:stream_chat_flutter/src/utils.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import '../stream_chat_flutter.dart';
import 'channel_bottom_sheet.dart';
import 'channel_preview.dart';
import 'stream_channel.dart';
import 'stream_chat.dart';
/// Callback called when tapping on a channel
typedef ChannelTapCallback = void Function(Channel, Widget);
@@ -158,168 +155,163 @@ class _ChannelListViewState extends State<ChannelListView>
with WidgetsBindingObserver {
final ScrollController _scrollController = ScrollController();
final SlidableController _slideController = SlidableController();
final ChannelListController _channelListController = ChannelListController();
@override
Widget build(BuildContext context) {
final channelsBloc = ChannelsBloc.of(context);
var child = ChannelListCore(
channelListController: _channelListController,
listBuilder: (context, list) {
return _buildListView(list);
},
emptyBuilder: (BuildContext context) {
return _buildEmptyWidget();
},
errorBuilder: (Error error) {
return _buildErrorWidget(context);
},
loadingBuilder: (BuildContext context) {
return _buildLoadingWidget();
},
pagination: widget.pagination,
options: widget.options,
sort: widget.sort,
filter: widget.filter,
);
if (!widget.pullToRefresh) {
return _buildListView(channelsBloc);
return child;
} else {
return RefreshIndicator(
onRefresh: () async {
_channelListController.loadData();
},
child: child,
);
}
}
Widget _buildListView(
List<Channel> channels,
) {
var child;
if (channels.isNotEmpty) {
if (widget.crossAxisCount > 1) {
child = GridView.builder(
padding: widget.padding,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: widget.crossAxisCount),
itemCount: channels.length,
physics: AlwaysScrollableScrollPhysics(),
controller: _scrollController,
itemBuilder: (context, index) {
return _gridItemBuilder(context, index, channels);
},
);
} else {
child = ListView.separated(
padding: widget.padding,
physics: AlwaysScrollableScrollPhysics(),
itemCount:
channels.isNotEmpty ? channels.length + 1 : channels.length,
separatorBuilder: (_, index) {
if (widget.separatorBuilder != null) {
return widget.separatorBuilder(context, index);
}
return _separatorBuilder(context, index);
},
itemBuilder: (context, index) {
return _listItemBuilder(context, index, channels);
},
);
}
}
return RefreshIndicator(
onRefresh: () async {
return channelsBloc.queryChannels(
filter: widget.filter,
sortOptions: widget.sort,
paginationParams: widget.pagination,
options: widget.options,
);
},
child: _buildListView(channelsBloc),
return AnimatedSwitcher(
child: child,
duration: Duration(milliseconds: 500),
);
}
StreamBuilder<List<Channel>> _buildListView(
ChannelsBlocState channelsBlocState,
) {
return StreamBuilder<List<Channel>>(
stream: channelsBlocState.channelsStream,
builder: (context, snapshot) {
var child;
if (snapshot.hasError) {
child = _buildErrorWidget(
snapshot,
context,
channelsBlocState,
);
} else if (!snapshot.hasData) {
child = _buildLoadingWidget();
} else {
final channels = snapshot.data;
Widget _buildEmptyWidget() {
if (widget.emptyBuilder != null) {
return widget.emptyBuilder(context);
}
if (channels.isEmpty && widget.emptyBuilder != null) {
child = widget.emptyBuilder(context);
}
if (channels.isEmpty && widget.emptyBuilder == null) {
child = LayoutBuilder(
builder: (context, viewportConstraints) {
return SingleChildScrollView(
physics: AlwaysScrollableScrollPhysics(),
child: Stack(
children: [
ConstrainedBox(
constraints: BoxConstraints(
minHeight: viewportConstraints.maxHeight,
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: StreamSvgIcon.message(
size: 136,
color: StreamChatTheme.of(context)
.colorTheme
.greyGainsboro,
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'Lets start chatting!',
style: StreamChatTheme.of(context)
.textTheme
.headline,
),
),
Padding(
padding: const EdgeInsets.symmetric(
vertical: 8.0,
horizontal: 52,
),
child: Text(
'How about sending your first message to a friend?',
textAlign: TextAlign.center,
style: StreamChatTheme.of(context)
.textTheme
.body
.copyWith(
color: StreamChatTheme.of(context)
.colorTheme
.grey,
),
),
),
],
),
return LayoutBuilder(
builder: (context, viewportConstraints) {
return SingleChildScrollView(
physics: AlwaysScrollableScrollPhysics(),
child: Stack(
children: [
ConstrainedBox(
constraints: BoxConstraints(
minHeight: viewportConstraints.maxHeight,
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: StreamSvgIcon.message(
size: 136,
color: StreamChatTheme.of(context)
.colorTheme
.greyGainsboro,
),
if (widget.onStartChatPressed != null)
Positioned(
right: 0,
left: 0,
bottom: 32,
child: Center(
child: FlatButton(
onPressed: widget.onStartChatPressed,
child: Text(
'Start a chat',
style: StreamChatTheme.of(context)
.textTheme
.bodyBold
.copyWith(
color: StreamChatTheme.of(context)
.colorTheme
.accentBlue,
),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'Lets start chatting!',
style: StreamChatTheme.of(context).textTheme.headline,
),
),
Padding(
padding: const EdgeInsets.symmetric(
vertical: 8.0,
horizontal: 52,
),
child: Text(
'How about sending your first message to a friend?',
textAlign: TextAlign.center,
style: StreamChatTheme.of(context)
.textTheme
.body
.copyWith(
color:
StreamChatTheme.of(context).colorTheme.grey,
),
),
),
],
),
),
],
),
),
if (widget.onStartChatPressed != null)
Positioned(
right: 0,
left: 0,
bottom: 32,
child: Center(
child: FlatButton(
onPressed: widget.onStartChatPressed,
child: Text(
'Start a chat',
style: StreamChatTheme.of(context)
.textTheme
.bodyBold
.copyWith(
color: StreamChatTheme.of(context)
.colorTheme
.accentBlue,
),
),
),
),
);
},
);
}
if (channels.isNotEmpty) {
if (widget.crossAxisCount > 1) {
child = GridView.builder(
padding: widget.padding,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: widget.crossAxisCount),
itemCount: channels.length,
physics: AlwaysScrollableScrollPhysics(),
controller: _scrollController,
itemBuilder: (context, index) {
return _gridItemBuilder(context, index, channels);
},
);
} else {
child = ListView.separated(
padding: widget.padding,
physics: AlwaysScrollableScrollPhysics(),
itemCount:
channels.isNotEmpty ? channels.length + 1 : channels.length,
separatorBuilder: (_, index) {
if (widget.separatorBuilder != null) {
return widget.separatorBuilder(context, index);
}
return _separatorBuilder(context, index);
},
itemBuilder: (context, index) {
return _listItemBuilder(context, index, channels);
},
);
}
}
}
return AnimatedSwitcher(
child: child,
duration: Duration(milliseconds: 500),
),
],
),
);
},
);
@@ -443,27 +435,8 @@ class _ChannelListViewState extends State<ChannelListView>
}
Widget _buildErrorWidget(
AsyncSnapshot<List<Channel>> snapshot,
BuildContext context,
ChannelsBlocState channelsBlocState,
) {
if (snapshot.error is Error) {
print((snapshot.error as Error).stackTrace);
}
if (widget.errorBuilder != null) {
return widget.errorBuilder(snapshot.error);
}
var message = snapshot.error.toString();
if (snapshot.error is DioError) {
final dioError = snapshot.error as DioError;
if (dioError.type == DioErrorType.RESPONSE) {
message = dioError.message;
} else {
message = 'Check your connection and retry';
}
}
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
@@ -484,20 +457,9 @@ class _ChannelListViewState extends State<ChannelListView>
),
style: Theme.of(context).textTheme.headline6,
),
Padding(
padding: const EdgeInsets.only(
top: 16.0,
),
child: Text(message),
),
FlatButton(
onPressed: () {
channelsBlocState.queryChannels(
filter: widget.filter,
sortOptions: widget.sort,
paginationParams: widget.pagination,
options: widget.options,
);
_channelListController.loadData();
},
child: Text('Retry'),
),
@@ -714,14 +676,7 @@ class _ChannelListViewState extends State<ChannelListView>
if (_scrollController.position.maxScrollExtent ==
_scrollController.offset &&
_scrollController.offset != 0) {
channelsProvider.queryChannels(
filter: widget.filter,
sortOptions: widget.sort,
paginationParams: widget.pagination.copyWith(
offset: channelsProvider.channels?.length ?? 0,
),
options: widget.options,
);
_channelListController.paginateData();
}
}
@@ -759,12 +714,7 @@ class _ChannelListViewState extends State<ChannelListView>
EventType.channelVisible,
)
.listen((event) {
channelsBloc.queryChannels(
filter: widget.filter,
sortOptions: widget.sort,
paginationParams: widget.pagination,
options: widget.options,
);
_channelListController.loadData();
});
}
@@ -777,13 +727,7 @@ class _ChannelListViewState extends State<ChannelListView>
widget.pagination?.toJson()?.toString() !=
oldWidget.pagination?.toJson()?.toString() ||
widget.options?.toString() != oldWidget.options?.toString()) {
final channelsBloc = ChannelsBloc.of(context);
channelsBloc.queryChannels(
filter: widget.filter,
sortOptions: widget.sort,
paginationParams: widget.pagination,
options: widget.options,
);
_channelListController.loadData();
}
}
@@ -1,5 +1,4 @@
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/lazy_load_scroll_view.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:video_player/video_player.dart';
@@ -1,9 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import '../stream_chat_flutter.dart';
import 'stream_channel.dart';
/// It shows the current [Channel] name using a [Text] widget.
///
@@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:jiffy/jiffy.dart';
import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
import '../stream_chat_flutter.dart';
@@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
class ChannelUnreadIndicator extends StatelessWidget {
@@ -3,7 +3,7 @@ import 'dart:io';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
import 'package:stream_chat_flutter/src/utils.dart';
@@ -6,7 +6,6 @@ import 'package:photo_view/photo_view.dart';
import 'package:stream_chat_flutter/src/image_footer.dart';
import 'package:stream_chat_flutter/src/image_header.dart';
import 'package:video_player/video_player.dart';
import 'stream_channel.dart';
import '../stream_chat_flutter.dart';
@@ -7,7 +7,7 @@ import 'package:dio/dio.dart';
import 'package:esys_flutter_share/esys_flutter_share.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
@@ -1,6 +1,6 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/full_screen_media.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
@@ -1,9 +1,8 @@
import 'package:flutter/material.dart';
import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
import 'image_actions_modal.dart';
import 'stream_channel.dart';
class ImageHeader extends StatelessWidget implements PreferredSizeWidget {
/// True if this header shows the leading back button
@@ -4,7 +4,6 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:photo_manager/photo_manager.dart';
import 'package:stream_chat_flutter/src/lazy_load_scroll_view.dart';
import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
import '../stream_chat_flutter.dart';
@@ -4,9 +4,8 @@ import 'dart:ui';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/reaction_picker.dart';
import 'package:stream_chat_flutter/src/stream_channel.dart';
import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
import 'package:stream_chat_flutter/src/utils.dart';
@@ -13,7 +13,7 @@ import 'package:http_parser/http_parser.dart' as httpParser;
import 'package:image_picker/image_picker.dart';
import 'package:mime/mime.dart';
import 'package:photo_manager/photo_manager.dart';
import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/compress_video_service.dart';
import 'package:stream_chat_flutter/src/media_list_view.dart';
import 'package:stream_chat_flutter/src/message_list_view.dart';
@@ -26,7 +26,6 @@ import 'package:video_compress/video_compress.dart';
import '../stream_chat_flutter.dart';
import 'extension.dart';
import 'quoted_message_widget.dart';
import 'stream_channel.dart';
typedef FileUploader = Future<String> Function(PlatformFile, Channel);
typedef AttachmentThumbnailBuilder = Widget Function(
@@ -6,9 +6,8 @@ import 'package:flutter/material.dart';
import 'package:jiffy/jiffy.dart';
import 'package:rxdart/rxdart.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/info_tile.dart';
import 'package:stream_chat_flutter/src/lazy_load_scroll_view.dart';
import 'package:stream_chat_flutter/src/message_widget.dart';
import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
import 'package:stream_chat_flutter/src/system_message.dart';
@@ -16,7 +15,6 @@ import 'package:visibility_detector/visibility_detector.dart';
import '../stream_chat_flutter.dart';
import 'date_divider.dart';
import 'stream_channel.dart';
import 'swipeable.dart';
import 'extension.dart';
@@ -238,309 +236,300 @@ class _MessageListViewState extends State<MessageListView> {
bool _inBetweenList = false;
MessageListController _messageListController = MessageListController();
@override
Widget build(BuildContext context) {
final messagesStream = _isThreadConversation
? streamChannel.channel.state.threadsStream
.where((threads) => threads.containsKey(widget.parentMessage.id))
.map((threads) => threads[widget.parentMessage.id])
: streamChannel.channel.state?.messagesStream;
return MessageListCore(
loadingBuilder: (context) {
return Center(
child: const CircularProgressIndicator(),
);
},
emptyBuilder: (context) {
return Center(
child: Text(
'No chats here yet...',
style: StreamChatTheme.of(context).textTheme.footnote.copyWith(
color: StreamChatTheme.of(context)
.colorTheme
.black
.withOpacity(.5)),
),
);
},
messageListBuilder: (context, list) {
return _buildListView(list);
},
messageListController: _messageListController,
parentMessage: widget.parentMessage,
showScrollToBottom: widget.showScrollToBottom,
errorWidgetBuilder: (BuildContext context, Object error) {
return Center(
child: Text(
'Something went wrong',
style: StreamChatTheme.of(context).textTheme.footnote.copyWith(
color: StreamChatTheme.of(context)
.colorTheme
.black
.withOpacity(.5)),
),
);
},
);
}
return StreamBuilder<List<Message>>(
stream: messagesStream?.map((messages) => messages
?.where((e) =>
(!e.isDeleted && e.shadowed != true) ||
(e.isDeleted &&
e.user.id == streamChannel.channel.client.state.user.id))
?.toList()),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: const CircularProgressIndicator(),
);
Widget _buildListView(List<Message> data) {
messages = data;
final newMessagesListLength = messages.length;
if (_messageListLength != null) {
if (_bottomPaginationActive || (_inBetweenList && _upToDate)) {
if (_itemPositionListener.itemPositions.value?.isNotEmpty == true) {
final first = _itemPositionListener.itemPositions.value.first;
final diff = newMessagesListLength - _messageListLength;
if (diff > 0) {
initialIndex = first.index + diff;
initialAlignment = first.itemLeadingEdge;
}
}
} else if (!_topPaginationActive && _upToDate) {
// Reset the index in-case we send any new message
initialIndex = 0;
initialAlignment = 0;
}
}
final messageList = snapshot.data?.reversed?.toList() ?? [];
if (messageList.isEmpty) {
if (_upToDate) {
return Center(
child: Text(
'No chats here yet...',
style: StreamChatTheme.of(context)
.textTheme
.footnote
.copyWith(
color: StreamChatTheme.of(context)
.colorTheme
.black
.withOpacity(.5)),
_messageListLength = newMessagesListLength;
final _client = StreamChat.of(context).client;
return Stack(
alignment: Alignment.center,
children: [
ValueListenableBuilder<ConnectionStatus>(
valueListenable: _client.wsConnectionStatus,
builder: (context, status, _) {
String statusString = '';
bool showStatus = true;
switch (status) {
case ConnectionStatus.connected:
statusString = 'Connected';
showStatus = false;
break;
case ConnectionStatus.connecting:
statusString = 'Reconnecting...';
break;
case ConnectionStatus.disconnected:
statusString = 'Disconnected';
break;
}
return InfoTile(
showMessage:
widget.showConnectionStateTile ? showStatus : false,
tileAnchor: Alignment.topCenter,
childAnchor: Alignment.topCenter,
message: statusString,
child: LazyLoadScrollView(
onStartOfPage: () async {
_inBetweenList = false;
if (!_upToDate) {
_topPaginationActive = false;
_bottomPaginationActive = true;
return _paginateData(
streamChannel,
QueryDirection.bottom,
);
}
},
onEndOfPage: () async {
_inBetweenList = false;
_topPaginationActive = true;
_bottomPaginationActive = false;
return _paginateData(
streamChannel,
QueryDirection.top,
);
},
onInBetweenOfPage: () {
_inBetweenList = true;
},
child: ScrollablePositionedList.separated(
key: ValueKey(initialIndex + initialAlignment),
itemPositionsListener: _itemPositionListener,
addAutomaticKeepAlives: true,
initialScrollIndex: initialIndex ?? 0,
initialAlignment: initialAlignment ?? 0,
physics: widget.scrollPhysics,
itemScrollController: _scrollController,
reverse: true,
itemCount:
messages.length + 2 + (_isThreadConversation ? 1 : 0),
separatorBuilder: (context, i) {
if (i == messages.length) return Offstage();
if (i == 0) return SizedBox(height: 30);
if (i == messages.length + 1) {
final replyCount = widget.parentMessage.replyCount;
return Container(
decoration: BoxDecoration(
gradient: StreamChatTheme.of(context)
.colorTheme
.bgGradient,
),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'$replyCount ${replyCount == 1 ? 'Reply' : 'Replies'}',
textAlign: TextAlign.center,
style: StreamChatTheme.of(context)
.channelTheme
.channelHeaderTheme
.lastMessageAt,
),
),
);
}
final message = messages[i];
final nextMessage = messages[i - 1];
if (!Jiffy(message.createdAt.toLocal()).isSame(
nextMessage.createdAt.toLocal(),
Units.DAY,
)) {
final divider = widget.dateDividerBuilder != null
? widget.dateDividerBuilder(
nextMessage.createdAt.toLocal(),
)
: DateDivider(
dateTime: nextMessage.createdAt.toLocal(),
);
return Padding(
padding: const EdgeInsets.symmetric(vertical: 12.0),
child: divider,
);
}
final timeDiff =
Jiffy(nextMessage.createdAt.toLocal()).diff(
message.createdAt.toLocal(),
Units.MINUTE,
);
final isNextUserSame =
message.user.id == nextMessage.user?.id;
final isThread = message.replyCount > 0;
final isDeleted = message.isDeleted;
if (timeDiff >= 1 ||
!isNextUserSame ||
isThread ||
isDeleted) {
return SizedBox(height: 8);
}
return SizedBox(height: 2);
},
itemBuilder: (context, i) {
if (i == messages.length + 2) {
if (widget.parentMessageBuilder != null) {
return widget.parentMessageBuilder(
context,
widget.parentMessage,
);
} else {
return buildParentMessage(widget.parentMessage);
}
}
if (i == messages.length + 1) {
return _buildLoadingIndicator(
streamChannel,
QueryDirection.top,
);
}
if (i == 0) {
return _buildLoadingIndicator(
streamChannel,
QueryDirection.bottom,
);
}
final message = messages[i - 1];
Widget messageWidget;
if (i == 1) {
messageWidget = _buildBottomMessage(
context,
message,
messages,
streamChannel,
);
} else if (i == messages.length - 1) {
messageWidget = _buildTopMessage(
context,
message,
messages,
streamChannel,
);
} else {
if (widget.messageBuilder != null) {
messageWidget = Builder(
key: ValueKey<String>('MESSAGE-${message.id}'),
builder: (context) => widget.messageBuilder(
context,
MessageDetails(
context,
message,
messages,
i,
),
messages),
);
} else {
messageWidget = buildMessage(message, messages, i);
}
}
return messageWidget;
},
),
),
);
}
} else {
messages = messageList;
}
final newMessagesListLength = messages.length;
if (_messageListLength != null) {
if (_bottomPaginationActive || (_inBetweenList && _upToDate)) {
if (_itemPositionListener.itemPositions.value?.isNotEmpty ==
true) {
final first = _itemPositionListener.itemPositions.value.first;
final diff = newMessagesListLength - _messageListLength;
if (diff > 0) {
initialIndex = first.index + diff;
initialAlignment = first.itemLeadingEdge;
}
}),
if (widget.showScrollToBottom) _buildScrollToBottom(),
Positioned(
top: 20.0,
child: ValueListenableBuilder<Iterable<ItemPosition>>(
valueListenable: _itemPositionListener.itemPositions,
builder: (context, values, _) {
final items = _itemPositionListener.itemPositions?.value;
if (items.isEmpty || messages.isEmpty) {
return SizedBox();
}
} else if (!_topPaginationActive && _upToDate) {
// Reset the index in-case we send any new message
initialIndex = 0;
initialAlignment = 0;
}
}
_messageListLength = newMessagesListLength;
final _client = StreamChat.of(context).client;
var index = _getTopElement(values).index;
return Stack(
alignment: Alignment.center,
children: [
ValueListenableBuilder<ConnectionStatus>(
valueListenable: _client.wsConnectionStatus,
builder: (context, status, _) {
String statusString = '';
bool showStatus = true;
if (index > messages.length) {
return SizedBox();
}
switch (status) {
case ConnectionStatus.connected:
statusString = 'Connected';
showStatus = false;
break;
case ConnectionStatus.connecting:
statusString = 'Reconnecting...';
break;
case ConnectionStatus.disconnected:
statusString = 'Disconnected';
break;
}
if (index == messages.length) {
index = max(index - 1, 0);
}
return InfoTile(
showMessage:
widget.showConnectionStateTile ? showStatus : false,
tileAnchor: Alignment.topCenter,
childAnchor: Alignment.topCenter,
message: statusString,
child: LazyLoadScrollView(
onStartOfPage: () async {
_inBetweenList = false;
if (!_upToDate) {
_topPaginationActive = false;
_bottomPaginationActive = true;
return _paginateData(
streamChannel,
QueryDirection.bottom,
);
}
},
onEndOfPage: () async {
_inBetweenList = false;
_topPaginationActive = true;
_bottomPaginationActive = false;
return _paginateData(
streamChannel,
QueryDirection.top,
);
},
onInBetweenOfPage: () {
_inBetweenList = true;
},
child: ScrollablePositionedList.separated(
key: ValueKey(initialIndex + initialAlignment),
itemPositionsListener: _itemPositionListener,
addAutomaticKeepAlives: true,
initialScrollIndex: initialIndex ?? 0,
initialAlignment: initialAlignment ?? 0,
physics: widget.scrollPhysics,
itemScrollController: _scrollController,
reverse: true,
itemCount: messages.length +
2 +
(_isThreadConversation ? 1 : 0),
separatorBuilder: (context, i) {
if (i == messages.length) return Offstage();
if (i == 0) return SizedBox(height: 30);
if (i == messages.length + 1) {
final replyCount =
widget.parentMessage.replyCount;
return Container(
decoration: BoxDecoration(
gradient: StreamChatTheme.of(context)
.colorTheme
.bgGradient,
),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'$replyCount ${replyCount == 1 ? 'Reply' : 'Replies'}',
textAlign: TextAlign.center,
style: StreamChatTheme.of(context)
.channelTheme
.channelHeaderTheme
.lastMessageAt,
),
),
);
}
final message = messages[i];
final nextMessage = messages[i - 1];
if (!Jiffy(message.createdAt.toLocal()).isSame(
nextMessage.createdAt.toLocal(),
Units.DAY,
)) {
final divider = widget.dateDividerBuilder != null
? widget.dateDividerBuilder(
nextMessage.createdAt.toLocal(),
)
: DateDivider(
dateTime: nextMessage.createdAt.toLocal(),
);
return Padding(
padding:
const EdgeInsets.symmetric(vertical: 12.0),
child: divider,
);
}
final timeDiff =
Jiffy(nextMessage.createdAt.toLocal()).diff(
message.createdAt.toLocal(),
Units.MINUTE,
);
final isNextUserSame =
message.user.id == nextMessage.user?.id;
final isThread = message.replyCount > 0;
final isDeleted = message.isDeleted;
if (timeDiff >= 1 ||
!isNextUserSame ||
isThread ||
isDeleted) {
return SizedBox(height: 8);
}
return SizedBox(height: 2);
},
itemBuilder: (context, i) {
if (i == messages.length + 2) {
if (widget.parentMessageBuilder != null) {
return widget.parentMessageBuilder(
context,
widget.parentMessage,
);
} else {
return buildParentMessage(widget.parentMessage);
}
}
if (i == messages.length + 1) {
return _buildLoadingIndicator(
streamChannel,
QueryDirection.top,
);
}
if (i == 0) {
return _buildLoadingIndicator(
streamChannel,
QueryDirection.bottom,
);
}
final message = messages[i - 1];
Widget messageWidget;
if (i == 1) {
messageWidget = _buildBottomMessage(
context,
message,
messages,
streamChannel,
);
} else if (i == messages.length - 1) {
messageWidget = _buildTopMessage(
context,
message,
messages,
streamChannel,
);
} else {
if (widget.messageBuilder != null) {
messageWidget = Builder(
key:
ValueKey<String>('MESSAGE-${message.id}'),
builder: (context) => widget.messageBuilder(
context,
MessageDetails(
context,
message,
messages,
i,
),
messages),
);
} else {
messageWidget =
buildMessage(message, messages, i);
}
}
return messageWidget;
},
),
),
return widget.dateDividerBuilder != null
? widget.dateDividerBuilder(
messages[index].createdAt.toLocal(),
)
: DateDivider(
dateTime: messages[index].createdAt.toLocal(),
);
}),
if (widget.showScrollToBottom) _buildScrollToBottom(),
Positioned(
top: 20.0,
child: ValueListenableBuilder<Iterable<ItemPosition>>(
valueListenable: _itemPositionListener.itemPositions,
builder: (context, values, _) {
final items = _itemPositionListener.itemPositions?.value;
if (items.isEmpty || messages.isEmpty) {
return SizedBox();
}
var index = _getTopElement(values).index;
if (index > messages.length) {
return SizedBox();
}
if (index == messages.length) {
index = max(index - 1, 0);
}
return widget.dateDividerBuilder != null
? widget.dateDividerBuilder(
messages[index].createdAt.toLocal(),
)
: DateDivider(
dateTime: messages[index].createdAt.toLocal(),
);
},
),
),
],
);
});
},
),
),
],
);
}
Future<void> _paginateData(
StreamChannelState channel, QueryDirection direction) {
if (!_isThreadConversation) {
return channel.queryMessages(direction: direction);
} else {
return channel.getReplies(widget.parentMessage.id);
}
return _messageListController.paginateData(direction: direction);
}
ItemPosition _getTopElement(Iterable<ItemPosition> values) {
@@ -1,7 +1,7 @@
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/reaction_bubble.dart';
import 'package:stream_chat_flutter/src/reaction_picker.dart';
import 'package:stream_chat_flutter/src/stream_chat.dart';
@@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:jiffy/jiffy.dart';
import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
/// It shows the current [Message] preview.
@@ -1,13 +1,9 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/info_tile.dart';
import 'package:stream_chat_flutter/src/message_search_item.dart';
import '../stream_chat_flutter.dart';
import 'lazy_load_scroll_view.dart';
import 'message_search_bloc.dart';
/// Callback called when tapping on a user
typedef MessageSearchItemTapCallback = void Function(GetMessageResponse);
@@ -120,23 +116,115 @@ class MessageSearchListView extends StatefulWidget {
}
class _MessageSearchListViewState extends State<MessageSearchListView> {
@override
void initState() {
super.initState();
final messageSearchBloc = MessageSearchBloc.of(context);
messageSearchBloc.search(
filter: widget.filters,
sort: widget.sortOptions,
query: widget.messageQuery,
pagination: widget.paginationParams,
messageFilter: widget.messageFilters,
);
}
MessageSearchListController _messageSearchListController =
MessageSearchListController();
@override
Widget build(BuildContext context) {
final messageSearchBloc = MessageSearchBloc.of(context);
return _buildListView(messageSearchBloc);
return MessageSearchListCore(
filters: widget.filters,
sortOptions: widget.sortOptions,
messageQuery: widget.messageQuery,
paginationParams: widget.paginationParams,
messageFilters: widget.messageFilters,
messageSearchListController: _messageSearchListController,
emptyBuilder: (context) {
if (widget.emptyBuilder != null) {
return widget.emptyBuilder(context, widget.messageQuery);
}
return LayoutBuilder(
builder: (context, viewportConstraints) {
return SingleChildScrollView(
physics: AlwaysScrollableScrollPhysics(),
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: viewportConstraints.maxHeight,
),
child: Center(
child: Text('There are no messages currently'),
),
),
);
},
);
},
errorBuilder: (error) {
if (error is Error) {
print((error).stackTrace);
}
if (widget.errorBuilder != null) {
return widget.errorBuilder(error);
}
var message = error.toString();
if (error is DioError) {
final dioError = error as DioError;
if (dioError.type == DioErrorType.RESPONSE) {
message = dioError.message;
} else {
message = 'Check your connection and retry';
}
}
return InfoTile(
showMessage: widget.showErrorTile,
tileAnchor: Alignment.topCenter,
childAnchor: Alignment.topCenter,
message: 'An error occurred.',
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text.rich(
TextSpan(
children: [
WidgetSpan(
child: Padding(
padding: const EdgeInsets.only(right: 2.0),
child: Icon(Icons.error_outline),
),
),
TextSpan(text: 'Error loading messages'),
],
),
style: Theme.of(context).textTheme.headline6,
),
Padding(
padding: const EdgeInsets.only(top: 16.0),
child: Text(message),
),
RaisedButton(
onPressed: () {
_messageSearchListController.loadData();
},
child: Text('Retry'),
),
],
),
),
);
},
loadingBuilder: (context) {
return LayoutBuilder(
builder: (context, viewportConstraints) {
return SingleChildScrollView(
physics: AlwaysScrollableScrollPhysics(),
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: viewportConstraints.maxHeight,
),
child: Center(
child: CircularProgressIndicator(),
),
),
);
},
);
},
childBuilder: (list) {
return _buildListView(list);
},
);
}
Widget _separatorBuilder(BuildContext context, int index) {
@@ -157,8 +245,9 @@ class _MessageSearchListViewState extends State<MessageSearchListView> {
);
}
Widget _buildQueryProgressIndicator(
context, MessageSearchBlocState messageSearchBloc) {
Widget _buildQueryProgressIndicator(context) {
MessageSearchBlocState messageSearchBloc = MessageSearchBloc.of(context);
return StreamBuilder<bool>(
stream: messageSearchBloc.queryMessagesLoading,
initialData: false,
@@ -187,204 +276,66 @@ class _MessageSearchListViewState extends State<MessageSearchListView> {
});
}
Widget _buildListView(MessageSearchBlocState messageSearchBloc) {
return StreamBuilder<List<GetMessageResponse>>(
stream: messageSearchBloc.messagesStream,
builder: (context, snapshot) {
if (snapshot.hasError) {
if (snapshot.error is Error) {
print((snapshot.error as Error).stackTrace);
}
Widget _buildListView(List<GetMessageResponse> data) {
final items = data;
if (widget.errorBuilder != null) {
return widget.errorBuilder(snapshot.error);
}
var message = snapshot.error.toString();
if (snapshot.error is DioError) {
final dioError = snapshot.error as DioError;
if (dioError.type == DioErrorType.RESPONSE) {
message = dioError.message;
} else {
message = 'Check your connection and retry';
}
}
return InfoTile(
showMessage: widget.showErrorTile,
tileAnchor: Alignment.topCenter,
childAnchor: Alignment.topCenter,
message: 'An error occurred.',
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text.rich(
TextSpan(
children: [
WidgetSpan(
child: Padding(
padding: const EdgeInsets.only(right: 2.0),
child: Icon(Icons.error_outline),
),
),
TextSpan(text: 'Error loading messages'),
],
),
style: Theme.of(context).textTheme.headline6,
),
Padding(
padding: const EdgeInsets.only(top: 16.0),
child: Text(message),
),
RaisedButton(
onPressed: () {
messageSearchBloc.search(
filter: widget.filters,
sort: widget.sortOptions,
query: widget.messageQuery,
pagination: widget.paginationParams,
messageFilter: widget.messageFilters,
);
},
child: Text('Retry'),
),
],
),
),
);
Widget child = ListView.separated(
physics: AlwaysScrollableScrollPhysics(),
itemCount: items.isNotEmpty ? items.length + 1 : items.length,
separatorBuilder: (_, index) {
if (widget.separatorBuilder != null) {
return widget.separatorBuilder(context, index);
}
if (!snapshot.hasData) {
return LayoutBuilder(
builder: (context, viewportConstraints) {
return SingleChildScrollView(
physics: AlwaysScrollableScrollPhysics(),
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: viewportConstraints.maxHeight,
),
child: Center(
child: CircularProgressIndicator(),
),
),
);
},
);
return _separatorBuilder(context, index);
},
itemBuilder: (context, index) {
if (index < items.length) {
return _listItemBuilder(context, items[index]);
}
final items = snapshot.data;
if (items.isEmpty) {
if (widget.emptyBuilder != null) {
return widget.emptyBuilder(context, widget.messageQuery);
}
return LayoutBuilder(
builder: (context, viewportConstraints) {
return SingleChildScrollView(
physics: AlwaysScrollableScrollPhysics(),
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: viewportConstraints.maxHeight,
),
child: Center(
child: Text('There are no messages currently'),
),
),
);
},
);
}
Widget child = ListView.separated(
physics: AlwaysScrollableScrollPhysics(),
itemCount: items.isNotEmpty ? items.length + 1 : items.length,
separatorBuilder: (_, index) {
if (widget.separatorBuilder != null) {
return widget.separatorBuilder(context, index);
}
return _separatorBuilder(context, index);
},
itemBuilder: (context, index) {
if (index < items.length) {
return _listItemBuilder(context, items[index]);
}
return _buildQueryProgressIndicator(context, messageSearchBloc);
},
);
if (widget.pullToRefresh) {
child = RefreshIndicator(
onRefresh: () async => messageSearchBloc.search(
filter: widget.filters,
sort: widget.sortOptions,
query: widget.messageQuery,
pagination: widget.paginationParams,
messageFilter: widget.messageFilters,
),
child: child,
);
}
child = LazyLoadScrollView(
onEndOfPage: () => messageSearchBloc.loadMore(
filter: widget.filters,
sort: widget.sortOptions,
pagination: widget.paginationParams.copyWith(
offset: messageSearchBloc.messageResponses?.length ?? 0,
),
query: widget.messageQuery,
messageFilter: widget.messageFilters,
),
child: child,
);
if (widget.showResultCount) {
child = Column(
children: [
Container(
width: double.maxFinite,
decoration: BoxDecoration(
gradient: StreamChatTheme.of(context).colorTheme.bgGradient,
),
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 8,
horizontal: 8,
),
child: Text(
'${items.length} results',
style: TextStyle(
color: StreamChatTheme.of(context).colorTheme.grey,
),
),
),
),
Expanded(child: child),
],
);
}
return child;
return _buildQueryProgressIndicator(context);
},
);
}
@override
void didUpdateWidget(MessageSearchListView oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.filters?.toString() != oldWidget.filters?.toString() ||
jsonEncode(widget.sortOptions) != jsonEncode(oldWidget.sortOptions) ||
widget.paginationParams?.toJson()?.toString() !=
oldWidget.paginationParams?.toJson()?.toString() ||
widget.messageQuery?.toString() != oldWidget.messageQuery?.toString() ||
widget.messageFilters?.toString() !=
oldWidget.messageFilters?.toString()) {
final messageSearchBloc = MessageSearchBloc.of(context);
messageSearchBloc.search(
filter: widget.filters,
sort: widget.sortOptions,
query: widget.messageQuery,
pagination: widget.paginationParams,
messageFilter: widget.messageFilters,
if (widget.pullToRefresh) {
child = RefreshIndicator(
onRefresh: () async {
_messageSearchListController.loadData();
},
child: child,
);
}
child = LazyLoadScrollView(
onEndOfPage: () async {
return _messageSearchListController.paginateData();
},
child: child,
);
if (widget.showResultCount) {
child = Column(
children: [
Container(
width: double.maxFinite,
decoration: BoxDecoration(
gradient: StreamChatTheme.of(context).colorTheme.bgGradient,
),
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 8,
horizontal: 8,
),
child: Text(
'${items.length} results',
style: TextStyle(
color: StreamChatTheme.of(context).colorTheme.grey,
),
),
),
),
Expanded(child: child),
],
);
}
return child;
}
}
@@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'stream_chat_theme.dart';
import 'utils.dart';
@@ -2,7 +2,7 @@ import 'dart:math';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:video_player/video_player.dart';
import 'attachment_error.dart';
@@ -4,8 +4,9 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_app_badger/flutter_app_badger.dart';
import 'package:flutter_portal/flutter_portal.dart';
import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
/// Widget used to provide information about the chat to the widget tree
///
@@ -61,9 +62,8 @@ class StreamChat extends StatefulWidget {
}
/// The current state of the StreamChat widget
class StreamChatState extends State<StreamChat> with WidgetsBindingObserver {
class StreamChatState extends State<StreamChat> {
Client get client => widget.client;
Timer _disconnectTimer;
@override
Widget build(BuildContext context) {
@@ -81,7 +81,10 @@ class StreamChatState extends State<StreamChat> with WidgetsBindingObserver {
accentColor: streamTheme.colorTheme.accentBlue,
scaffoldBackgroundColor: streamTheme.colorTheme.white,
),
child: widget.child,
child: StreamChatCore(
child: widget.child,
client: client,
),
);
},
),
@@ -106,7 +109,6 @@ class StreamChatState extends State<StreamChat> with WidgetsBindingObserver {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
client.state?.totalUnreadCountStream?.listen((count) {
if (count > 0) {
FlutterAppBadger.updateBadgeCount(count);
@@ -115,67 +117,4 @@ class StreamChatState extends State<StreamChat> with WidgetsBindingObserver {
}
});
}
StreamSubscription _newMessageSubscription;
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (client.state?.user != null) {
if (state == AppLifecycleState.paused) {
if (client.showLocalNotification != null) {
_newMessageSubscription = client
.on(EventType.messageNew)
.where((e) => e.user?.id != user.id)
.where((e) => e.message.silent != true)
.where((e) => e.message.shadowed != true)
.listen((event) async {
final channel = client.channel(
event.channelType,
id: event.channelId,
);
client.showLocalNotification(
event.message,
ChannelModel(
id: channel.id,
createdAt: channel.createdAt,
extraData: channel.extraData,
type: channel.type,
memberCount: channel.memberCount,
frozen: channel.frozen,
cid: channel.cid,
deletedAt: channel.deletedAt,
config: channel.config,
createdBy: channel.createdBy,
updatedAt: channel.updatedAt,
lastMessageAt: channel.lastMessageAt,
),
);
});
_disconnectTimer = Timer(client.backgroundKeepAlive, () {
client.disconnect();
});
} else {
client.disconnect();
}
} else if (state == AppLifecycleState.resumed) {
_newMessageSubscription?.cancel();
if (_disconnectTimer?.isActive == true) {
_disconnectTimer.cancel();
} else {
if (client.wsConnectionStatus.value ==
ConnectionStatus.disconnected) {
NotificationService.handleIosMessageQueue(client);
client.connect();
}
}
}
}
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
}
@@ -1,6 +1,6 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/channel_header.dart';
import 'package:stream_chat_flutter/src/channel_preview.dart';
import 'package:stream_chat_flutter/src/message_input.dart';
@@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
@@ -1,7 +1,6 @@
import 'package:flutter/material.dart';
import 'package:lottie/lottie.dart';
import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_flutter/src/stream_channel.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
/// Widget to show the current list of typing users
class TypingIndicator extends StatelessWidget {
@@ -1,6 +1,6 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import '../stream_chat_flutter.dart';
@@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:jiffy/jiffy.dart';
import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
import 'package:stream_chat_flutter/src/user_list_view.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
@@ -1,9 +1,5 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_flutter/src/lazy_load_scroll_view.dart';
import 'package:stream_chat_flutter/src/users_bloc.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'user_item.dart';
@@ -142,209 +138,168 @@ class _UserListViewState extends State<UserListView>
with WidgetsBindingObserver {
bool get _isListView => widget.crossAxisCount == 1;
@override
void initState() {
super.initState();
final usersBloc = UsersBloc.of(context);
usersBloc.queryUsers(
filter: widget.filter,
sort: widget.sort,
pagination: widget.pagination,
options: widget.options,
);
}
UserListController _userListController = UserListController();
@override
Widget build(BuildContext context) {
final usersBloc = UsersBloc.of(context);
if (!widget.pullToRefresh) {
return _buildListView(usersBloc);
}
return RefreshIndicator(
onRefresh: () async {
return usersBloc.queryUsers(
filter: widget.filter,
sort: widget.sort,
options: widget.options,
pagination: widget.pagination,
var child = UserListCore(
errorBuilder: (err) {
return _buildError(err);
},
emptyBuilder: (context) {
return _buildEmpty();
},
loadingBuilder: (context) {
return LayoutBuilder(
builder: (context, viewportConstraints) {
return SingleChildScrollView(
physics: AlwaysScrollableScrollPhysics(),
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: viewportConstraints.maxHeight,
),
child: Center(
child: CircularProgressIndicator(),
),
),
);
},
);
},
child: _buildListView(usersBloc),
listBuilder: (context, list) {
return _buildListView(list);
},
pagination: widget.pagination,
options: widget.options,
sort: widget.sort,
filter: widget.filter,
groupAlphabetically: widget.groupAlphabetically,
userListController: _userListController,
);
if (!widget.pullToRefresh) {
return child;
} else {
return RefreshIndicator(
onRefresh: () async {
_userListController.loadData();
},
child: child,
);
}
}
bool get isListAlreadySorted =>
widget.sort?.any((e) => e.field == 'name' && e.direction == 1) ?? false;
Stream<List<ListItem>> _buildUserStream(
UsersBlocState usersBlocState,
) {
return usersBlocState.usersStream.map(
(users) {
if (widget.groupAlphabetically) {
var temp = users;
if (!isListAlreadySorted) {
temp = users..sort((curr, next) => curr.name.compareTo(next.name));
}
final groupedUsers = <String, List<User>>{};
for (var e in temp) {
final alphabet = e.name[0]?.toUpperCase();
groupedUsers[alphabet] = [...groupedUsers[alphabet] ?? [], e];
}
final items = <ListItem>[];
for (var key in groupedUsers.keys) {
items.add(ListHeaderItem(key));
items.addAll(groupedUsers[key].map((e) => ListUserItem(e)));
}
return items;
}
return users.map((e) => ListUserItem(e)).toList();
Widget _buildError(Error error) {
print((error).stackTrace);
if (widget.errorBuilder != null) {
return widget.errorBuilder(error);
}
var message = error.toString();
if (error is DioError) {
final dioError = error as DioError;
if (dioError.type == DioErrorType.RESPONSE) {
message = dioError.message;
} else {
message = 'Check your connection and retry';
}
}
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text.rich(
TextSpan(
children: [
WidgetSpan(
child: Padding(
padding: const EdgeInsets.only(
right: 2.0,
),
child: Icon(Icons.error_outline),
),
),
TextSpan(text: 'Error loading channels'),
],
),
style: Theme.of(context).textTheme.headline6,
),
Padding(
padding: const EdgeInsets.only(
top: 16.0,
),
child: Text(message),
),
FlatButton(
onPressed: () {
_userListController.loadData();
},
child: Text('Retry'),
),
],
),
);
}
Widget _buildEmpty() {
if (widget.emptyBuilder != null) {
return widget.emptyBuilder(context);
}
return LayoutBuilder(
builder: (context, viewportConstraints) {
return SingleChildScrollView(
physics: AlwaysScrollableScrollPhysics(),
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: viewportConstraints.maxHeight,
),
child: Center(
child: Text('There are no users currently'),
),
),
);
},
);
}
StreamBuilder<List<ListItem>> _buildListView(
UsersBlocState usersBlocState,
Widget _buildListView(
List<ListItem> items,
) {
return StreamBuilder(
stream: _buildUserStream(usersBlocState),
builder: (context, snapshot) {
if (snapshot.hasError) {
if (snapshot.error is Error) {
print((snapshot.error as Error).stackTrace);
}
if (widget.errorBuilder != null) {
return widget.errorBuilder(snapshot.error);
}
var message = snapshot.error.toString();
if (snapshot.error is DioError) {
final dioError = snapshot.error as DioError;
if (dioError.type == DioErrorType.RESPONSE) {
message = dioError.message;
} else {
message = 'Check your connection and retry';
}
}
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text.rich(
TextSpan(
children: [
WidgetSpan(
child: Padding(
padding: const EdgeInsets.only(
right: 2.0,
),
child: Icon(Icons.error_outline),
),
),
TextSpan(text: 'Error loading channels'),
],
),
style: Theme.of(context).textTheme.headline6,
),
Padding(
padding: const EdgeInsets.only(
top: 16.0,
),
child: Text(message),
),
FlatButton(
onPressed: () {
usersBlocState.queryUsers(
filter: widget.filter,
sort: widget.sort,
pagination: widget.pagination,
options: widget.options,
);
},
child: Text('Retry'),
),
],
final child = _isListView
? ListView.separated(
physics: AlwaysScrollableScrollPhysics(),
itemCount: items.isNotEmpty ? items.length + 1 : items.length,
separatorBuilder: (_, index) {
if (widget.separatorBuilder != null) {
return widget.separatorBuilder(context, index);
}
return _separatorBuilder(context, index);
},
itemBuilder: (context, index) {
return _listItemBuilder(context, index, items);
},
)
: GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: widget.crossAxisCount,
),
);
}
if (!snapshot.hasData) {
return LayoutBuilder(
builder: (context, viewportConstraints) {
return SingleChildScrollView(
physics: AlwaysScrollableScrollPhysics(),
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: viewportConstraints.maxHeight,
),
child: Center(
child: CircularProgressIndicator(),
),
),
);
itemCount: items.isNotEmpty ? items.length + 1 : items.length,
physics: AlwaysScrollableScrollPhysics(),
itemBuilder: (context, index) {
return _gridItemBuilder(context, index, items);
},
);
}
final items = snapshot.data;
if (items.isEmpty && widget.emptyBuilder != null) {
return widget.emptyBuilder(context);
}
if (items.isEmpty && widget.emptyBuilder == null) {
return LayoutBuilder(
builder: (context, viewportConstraints) {
return SingleChildScrollView(
physics: AlwaysScrollableScrollPhysics(),
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: viewportConstraints.maxHeight,
),
child: Center(
child: Text('There are no users currently'),
),
),
);
},
);
}
final child = _isListView
? ListView.separated(
physics: AlwaysScrollableScrollPhysics(),
itemCount: items.isNotEmpty ? items.length + 1 : items.length,
separatorBuilder: (_, index) {
if (widget.separatorBuilder != null) {
return widget.separatorBuilder(context, index);
}
return _separatorBuilder(context, index);
},
itemBuilder: (context, index) {
return _listItemBuilder(context, index, items);
},
)
: GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: widget.crossAxisCount,
),
itemCount: items.isNotEmpty ? items.length + 1 : items.length,
physics: AlwaysScrollableScrollPhysics(),
itemBuilder: (context, index) {
return _gridItemBuilder(context, index, items);
},
);
return LazyLoadScrollView(
onEndOfPage: () async {
return _listenUserPagination(usersBlocState);
},
child: child,
);
return LazyLoadScrollView(
onEndOfPage: () async {
return _userListController.paginateData();
},
child: child,
);
}
@@ -483,72 +438,4 @@ class _UserListViewState extends State<UserListView>
color: StreamChatTheme.of(context).colorTheme.greyWhisper,
);
}
void _listenUserPagination(UsersBlocState usersProvider) {
usersProvider.queryUsers(
filter: widget.filter,
sort: widget.sort,
pagination: widget.pagination.copyWith(
offset: usersProvider.users?.length ?? 0,
),
options: widget.options,
);
}
@override
void didUpdateWidget(UserListView oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.filter?.toString() != oldWidget.filter?.toString() ||
jsonEncode(widget.sort) != jsonEncode(oldWidget.sort) ||
widget.pagination?.toJson()?.toString() !=
oldWidget.pagination?.toJson()?.toString() ||
widget.options?.toString() != oldWidget.options?.toString()) {
final usersBloc = UsersBloc.of(context);
usersBloc.queryUsers(
filter: widget.filter,
sort: widget.sort,
pagination: widget.pagination,
options: widget.options,
);
}
}
}
abstract class ListItem {
String get key {
if (this is ListHeaderItem) {
final header = (this as ListHeaderItem).heading;
return 'HEADER-$header';
}
if (this is ListUserItem) {
final user = (this as ListUserItem).user;
return 'USER-${user.id}';
}
return null;
}
Widget when({
@required Widget Function(String heading) headerItem,
@required Widget Function(User user) userItem,
}) {
if (this is ListHeaderItem) {
return headerItem((this as ListHeaderItem).heading);
}
if (this is ListUserItem) {
return userItem((this as ListUserItem).user);
}
return SizedBox();
}
}
class ListHeaderItem extends ListItem {
final String heading;
ListHeaderItem(this.heading);
}
class ListUserItem extends ListItem {
final User user;
ListUserItem(this.user);
}
@@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/user_avatar.dart';
class UserReactionDisplay extends StatelessWidget {
+1 -1
View File
@@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:url_launcher/url_launcher.dart';
import '../stream_chat_flutter.dart';
@@ -1,5 +1,3 @@
export 'package:stream_chat/stream_chat.dart';
export 'src/back_button.dart';
export 'src/channel_header.dart';
export 'src/channel_image.dart';
@@ -7,7 +5,6 @@ export 'src/channel_list_header.dart';
export 'src/channel_list_view.dart';
export 'src/channel_name.dart';
export 'src/channel_preview.dart';
export 'src/channels_bloc.dart';
export 'src/date_divider.dart';
export 'src/deleted_message.dart';
export 'src/file_attachment.dart';
@@ -22,8 +19,6 @@ export 'src/message_text.dart';
export 'src/message_widget.dart';
export 'src/reaction_picker.dart';
export 'src/sending_indicator.dart';
export 'src/stream_channel.dart';
export 'src/stream_chat.dart';
export 'src/stream_chat_theme.dart';
export 'src/stream_neumorphic_button.dart';
export 'src/stream_svg_icon.dart';
@@ -35,11 +30,8 @@ export 'src/user_item.dart';
export 'src/user_item.dart';
export 'src/user_list_view.dart';
export 'src/user_list_view.dart';
export 'src/users_bloc.dart';
export 'src/users_bloc.dart';
export 'src/utils.dart';
export 'src/video_attachment.dart';
export 'src/message_search_bloc.dart';
export 'src/message_search_item.dart';
export 'src/message_search_list_view.dart';
export 'src/unread_indicator.dart';
@@ -47,3 +39,5 @@ export 'src/option_list_tile.dart';
export 'src/channel_file_display_screen.dart';
export 'src/channel_media_display_screen.dart';
export 'src/info_tile.dart';
export 'src/stream_chat.dart';
export 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
+4 -1
View File
@@ -1,7 +1,7 @@
name: stream_chat_flutter
homepage: https://github.com/GetStream/stream-chat-flutter
description: Stream Chat official Flutter SDK. Build your own chat experience using Dart and Flutter.
version: 0.2.13+1
version: 1.0.0-rc
repository: https://github.com/GetStream/stream-chat-flutter
issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues
@@ -11,6 +11,9 @@ environment:
dependencies:
flutter:
sdk: flutter
# TODO: Change over to main dependency when deployed
stream_chat_flutter_core:
path: ../stream_chat_flutter_core
flutter_app_badger: ^1.1.2
photo_view: ^0.10.3
rxdart: ^0.24.1
+1 -1
View File
@@ -1,5 +1,5 @@
import 'package:mockito/mockito.dart';
import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
class MockClient extends Mock implements Client {}
@@ -0,0 +1,74 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
build/
# Android related
**/android/**/gradle-wrapper.jar
**/android/.gradle
**/android/captures/
**/android/gradlew
**/android/gradlew.bat
**/android/local.properties
**/android/**/GeneratedPluginRegistrant.java
# iOS/XCode related
**/ios/**/*.mode1v3
**/ios/**/*.mode2v3
**/ios/**/*.moved-aside
**/ios/**/*.pbxuser
**/ios/**/*.perspectivev3
**/ios/**/*sync/
**/ios/**/.sconsign.dblite
**/ios/**/.tags*
**/ios/**/.vagrant/
**/ios/**/DerivedData/
**/ios/**/Icon?
**/ios/**/Pods/
**/ios/**/.symlinks/
**/ios/**/profile
**/ios/**/xcuserdata
**/ios/.generated/
**/ios/Flutter/App.framework
**/ios/Flutter/Flutter.framework
**/ios/Flutter/Flutter.podspec
**/ios/Flutter/Generated.xcconfig
**/ios/Flutter/app.flx
**/ios/Flutter/app.zip
**/ios/Flutter/flutter_assets/
**/ios/Flutter/flutter_export_environment.sh
**/ios/ServiceDefinitions.json
**/ios/Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!**/ios/**/default.mode1v3
!**/ios/**/default.mode2v3
!**/ios/**/default.pbxuser
!**/ios/**/default.perspectivev3
@@ -0,0 +1,10 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: 78910062997c3a836feee883712c241a5fd22983
channel: stable
project_type: package
@@ -0,0 +1,3 @@
## 1.0.0-rc
* First release
+219
View File
@@ -0,0 +1,219 @@
SOURCE CODE LICENSE AGREEMENT
IMPORTANT - READ THIS CAREFULLY BEFORE DOWNLOADING, INSTALLING, USING OR
ELECTRONICALLY ACCESSING THIS PROPRIETARY PRODUCT.
THIS IS A LEGAL AGREEMENT BETWEEN STREAM.IO, INC. (“STREAM.IO”) AND THE
BUSINESS ENTITY OR PERSON FOR WHOM YOU (“YOU”) ARE ACTING (“CUSTOMER”) AS THE
LICENSEE OF THE PROPRIETARY SOFTWARE INTO WHICH THIS AGREEMENT HAS BEEN
INCLUDED (THE “AGREEMENT”). YOU AGREE THAT YOU ARE THE CUSTOMER, OR YOU ARE AN
EMPLOYEE OR AGENT OF CUSTOMER AND ARE ENTERING INTO THIS AGREEMENT FOR LICENSE
OF THE SOFTWARE BY CUSTOMER FOR CUSTOMERS BUSINESS PURPOSES AS DESCRIBED IN
AND IN ACCORDANCE WITH THIS AGREEMENT. YOU HEREBY AGREE THAT YOU ENTER INTO
THIS AGREEMENT ON BEHALF OF CUSTOMER AND THAT YOU HAVE THE AUTHORITY TO BIND
CUSTOMER TO THIS AGREEMENT.
STREAM.IO IS WILLING TO LICENSE THE SOFTWARE TO CUSTOMER ONLY ON THE FOLLOWING
CONDITIONS: (1) YOU ARE A CURRENT CUSTOMER OF STREAM.IO; (2) YOU ARE NOT A
COMPETITOR OF STREAM.IO; AND (3) THAT YOU ACCEPT ALL THE TERMS IN THIS
AGREEMENT. BY DOWNLOADING, INSTALLING, CONFIGURING, ACCESSING OR OTHERWISE
USING THE SOFTWARE, INCLUDING ANY UPDATES, UPGRADES, OR NEWER VERSIONS, YOU
REPRESENT, WARRANT AND ACKNOWLEDGE THAT (A) CUSTOMER IS A CURRENT CUSTOMER OF
STREAM.IO; (B) CUSTOMER IS NOT A COMPETITOR OF STREAM.IO; AND THAT (C) YOU HAVE
READ THIS AGREEMENT, UNDERSTAND THIS AGREEMENT, AND THAT CUSTOMER AGREES TO BE
BOUND BY ALL THE TERMS OF THIS AGREEMENT.
IF YOU DO NOT AGREE TO ALL THE TERMS AND CONDITIONS OF THIS AGREEMENT,
STREAM.IO IS UNWILLING TO LICENSE THE SOFTWARE TO CUSTOMER, AND THEREFORE, DO
NOT COMPLETE THE DOWNLOAD PROCESS, ACCESS OR OTHERWISE USE THE SOFTWARE, AND
CUSTOMER SHOULD IMMEDIATELY RETURN THE SOFTWARE AND CEASE ANY USE OF THE
SOFTWARE.
1. SOFTWARE. The Stream.io software accompanying this Agreement, may include
Source Code, Executable Object Code, associated media, printed materials and
documentation (collectively, the “Software”). The Software also includes any
updates or upgrades to or new versions of the original Software, if and when
made available to you by Stream.io. “Source Code” means computer programming
code in human readable form that is not suitable for machine execution without
the intervening steps of interpretation or compilation. “Executable Object
Code" means the computer programming code in any other form than Source Code
that is not readily perceivable by humans and suitable for machine execution
without the intervening steps of interpretation or compilation. “Site” means a
Customer location controlled by Customer. “Authorized User” means any employee
or contractor of Customer working at the Site, who has signed a written
confidentiality agreement with Customer or is otherwise bound in writing by
confidentiality and use obligations at least as restrictive as those imposed
under this Agreement.
2. LICENSE GRANT. Subject to the terms and conditions of this Agreement, in
consideration for the representations, warranties, and covenants made by
Customer in this Agreement, Stream.io grants to Customer, during the term of
this Agreement, a personal, non-exclusive, non-transferable, non-sublicensable
license to:
a. install and use Software Source Code on password protected computers at a Site,
restricted to Authorized Users;
b. create derivative works, improvements (whether or not patentable), extensions
and other modifications to the Software Source Code (“Modifications”) to build
unique scalable newsfeeds, activity streams, and in-app messaging via Streams
application program interface (“API”);
c. compile the Software Source Code to create Executable Object Code versions of
the Software Source Code and Modifications to build such newsfeeds, activity
streams, and in-app messaging via the API;
d. install, execute and use such Executable Object Code versions solely for
Customers internal business use (including development of websites through
which data generated by Stream services will be streamed (“Apps”));
e. use and distribute such Executable Object Code as part of Customers Apps; and
f. make electronic copies of the Software and Modifications as required for backup
or archival purposes.
3. RESTRICTIONS. Customer is responsible for all activities that occur in
connection with the Software. Customer will not, and will not attempt to: (a)
sublicense or transfer the Software or any Source Code related to the Software
or any of Customers rights under this Agreement, except as otherwise provided
in this Agreement, (b) use the Software Source Code for the benefit of a third
party or to operate a service; (c) allow any third party to access or use the
Software Source Code; (d) sublicense or distribute the Software Source Code or
any Modifications in Source Code or other derivative works based on any part of
the Software Source Code; (e) use the Software in any manner that competes with
Stream.io or its business; or (e) otherwise use the Software in any manner that
exceeds the scope of use permitted in this Agreement. Customer shall use the
Software in compliance with any accompanying documentation any laws applicable
to Customer.
4. OPEN SOURCE. Customer and its Authorized Users shall not use any software or
software components that are open source in conjunction with the Software
Source Code or any Modifications in Source Code or in any way that could
subject the Software to any open source licenses.
5. CONTRACTORS. Under the rights granted to Customer under this Agreement,
Customer may permit its employees, contractors, and agencies of Customer to
become Authorized Users to exercise the rights to the Software granted to
Customer in accordance with this Agreement solely on behalf of Customer to
provide services to Customer; provided that Customer shall be liable for the
acts and omissions of all Authorized Users to the extent any of such acts or
omissions, if performed by Customer, would constitute a breach of, or otherwise
give rise to liability to Customer under, this Agreement. Customer shall not
and shall not permit any Authorized User to use the Software except as
expressly permitted in this Agreement.
6. COMPETITIVE PRODUCT DEVELOPMENT. Customer shall not use the Software in any way
to engage in the development of products or services which could be reasonably
construed to provide a complete or partial functional or commercial alternative
to Stream.ios products or services (a “Competitive Product”). Customer shall
ensure that there is no direct or indirect use of, or sharing of, Software
source code, or other information based upon or derived from the Software to
develop such products or services. Without derogating from the generality of
the foregoing, development of Competitive Products shall include having direct
or indirect access to, supervising, consulting or assisting in the development
of, or producing any specifications, documentation, object code or source code
for, all or part of a Competitive Product.
7. LIMITATION ON MODIFICATIONS. Notwithstanding any provision in this Agreement,
Modifications may only be created and used by Customer as permitted by this
Agreement and Modification Source Code may not be distributed to third parties.
Customer will not assert against Stream.io, its affiliates, or their customers,
direct or indirect, agents and contractors, in any way, any patent rights that
Customer may obtain relating to any Modifications for Stream.io, its
affiliates, or their customers, direct or indirect, agents and contractors
manufacture, use, import, offer for sale or sale of any Stream.io products or
services.
8. DELIVERY AND ACCEPTANCE. The Software will be delivered electronically pursuant
to Stream.io standard download procedures. The Software is deemed accepted upon
delivery.
9. IMPLEMENTATION AND SUPPORT. Stream.io has no obligation under this Agreement to
provide any support or consultation concerning the Software.
10. TERM AND TERMINATION. The term of this Agreement begins when the Software is
downloaded or accessed and shall continue until terminated. Either party may
terminate this Agreement upon written notice. This Agreement shall
automatically terminate if Customer is or becomes a competitor of Stream.io or
makes or sells any Competitive Products. Upon termination of this Agreement for
any reason, (a) all rights granted to Customer in this Agreement immediately
cease to exist, (b) Customer must promptly discontinue all use of the Software
and return to Stream.io or destroy all copies of the Software in Customers
possession or control. Any continued use of the Software by Customer or attempt
by Customer to exercise any rights under this Agreement after this Agreement
has terminated shall be considered copyright infringement and subject Customer
to applicable remedies for copyright infringement. Sections 2, 5, 6, 8 and 9
shall survive expiration or termination of this Agreement for any reason.
11. OWNERSHIP. As between the parties, the Software and all worldwide intellectual
property rights and proprietary rights relating thereto or embodied therein,
are the exclusive property of Stream.io and its suppliers. Stream.io and its
suppliers reserve all rights in and to the Software not expressly granted to
Customer in this Agreement, and no other licenses or rights are granted by
implication, estoppel or otherwise.
12. WARRANTY DISCLAIMER. USE OF THIS SOFTWARE IS ENTIRELY AT YOURS AND CUSTOMERS
OWN RISK. THE SOFTWARE IS PROVIDED “AS IS” WITHOUT ANY WARRANTY OF ANY KIND
WHATSOEVER. STREAM.IO DOES NOT MAKE, AND HEREBY DISCLAIMS, ANY WARRANTY OF ANY
KIND, WHETHER EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING WITHOUT
LIMITATION, THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE, TITLE, NON-INFRINGEMENT OF THIRD-PARTY RIGHTS, RESULTS, EFFORTS,
QUALITY OR QUIET ENJOYMENT. STREAM.IO DOES NOT WARRANT THAT THE SOFTWARE IS
ERROR-FREE, WILL FUNCTION WITHOUT INTERRUPTION, WILL MEET ANY SPECIFIC NEED
THAT CUSTOMER HAS, THAT ALL DEFECTS WILL BE CORRECTED OR THAT IT IS
SUFFICIENTLY DOCUMENTED TO BE USABLE BY CUSTOMER. TO THE EXTENT THAT STREAM.IO
MAY NOT DISCLAIM ANY WARRANTY AS A MATTER OF APPLICABLE LAW, THE SCOPE AND
DURATION OF SUCH WARRANTY WILL BE THE MINIMUM PERMITTED UNDER SUCH LAW.
CUSTOMER ACKNOWLEDGES THAT IT HAS RELIED ON NO WARRANTIES OTHER THAN THE
EXPRESS WARRANTIES IN THIS AGREEMENT.
13. LIMITATION OF LIABILITY. TO THE FULLEST EXTENT PERMISSIBLE BY LAW, STREAM.IOS
TOTAL LIABILITY FOR ALL DAMAGES ARISING OUT OF OR RELATED TO THE SOFTWARE OR
THIS AGREEMENT, WHETHER IN CONTRACT, TORT (INCLUDING NEGLIGENCE) OR OTHERWISE,
SHALL NOT EXCEED $100. IN NO EVENT WILL STREAM.IO BE LIABLE FOR ANY INDIRECT,
CONSEQUENTIAL, EXEMPLARY, PUNITIVE, SPECIAL OR INCIDENTAL DAMAGES OF ANY KIND
WHATSOEVER, INCLUDING ANY LOST DATA AND LOST PROFITS, ARISING FROM OR RELATING
TO THE SOFTWARE EVEN IF STREAM.IO HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES. CUSTOMER ACKNOWLEDGES THAT THIS PROVISION REFLECTS THE AGREED UPON
ALLOCATION OF RISK FOR THIS AGREEMENT AND THAT STREAM.IO WOULD NOT ENTER INTO
THIS AGREEMENT WITHOUT THESE LIMITATIONS ON ITS LIABILITY.
14. General. Customer may not assign or transfer this Agreement, by operation of
law or otherwise, or any of its rights under this Agreement (including the
license rights granted to Customer) to any third party without Stream.ios
prior written consent, which consent will not be unreasonably withheld or
delayed. Stream.io may assign this Agreement, without consent, including, but
limited to, affiliate or any successor to all or substantially all its business
or assets to which this Agreement relates, whether by merger, sale of assets,
sale of stock, reorganization or otherwise. Any attempted assignment or
transfer in violation of the foregoing will be null and void. Stream.io shall
not be liable hereunder by reason of any failure or delay in the performance of
its obligations hereunder for any cause which is beyond the reasonable control.
All notices, consents, and approvals under this Agreement must be delivered in
writing by courier, by electronic mail, or by certified or registered mail,
(postage prepaid and return receipt requested) to the other party at the
address set forth in the customer agreement between Stream.io and Customer and
will be effective upon receipt or when delivery is refused. This Agreement will
be governed by and interpreted in accordance with the laws of the State of
Colorado, without reference to its choice of laws rules. The United Nations
Convention on Contracts for the International Sale of Goods does not apply to
this Agreement. Any action or proceeding arising from or relating to this
Agreement shall be brought in a federal or state court in Denver, Colorado, and
each party irrevocably submits to the jurisdiction and venue of any such court
in any such action or proceeding. All waivers must be in writing. Any waiver or
failure to enforce any provision of this Agreement on one occasion will not be
deemed a waiver of any other provision or of such provision on any other
occasion. If any provision of this Agreement is unenforceable, such provision
will be changed and interpreted to accomplish the objectives of such provision
to the greatest extent possible under applicable law and the remaining
provisions will continue in full force and effect. Customer shall not violate
any applicable law, rule or regulation, including those regarding the export of
technical data. The headings of Sections of this Agreement are for convenience
and are not to be used in interpreting this Agreement. As used in this
Agreement, the word “including” means “including but not limited to.” This
Agreement (including all exhibits and attachments) constitutes the entire
agreement between the parties regarding the subject hereof and supersedes all
prior or contemporaneous agreements, understandings and communication, whether
written or oral. This Agreement may be amended only by a written document
signed by both parties. The terms of any purchase order or similar document
submitted by Customer to Stream.io will have no effect.
@@ -0,0 +1,90 @@
# Official Flutter SDK Core for [Stream Chat](https://getstream.io/chat/)
<p align="center">
<a href="https://getstream.io/tutorials/ios-chat/"><img src="https://i.imgur.com/L4Mj8S2.png" alt="Flutter Chat" width="60%" /></a>
</p>
> The official Flutter core components for Stream Chat, a service for
> building chat applications.
[![Pub](https://img.shields.io/pub/v/stream_chat_flutter.svg)](https://pub.dartlang.org/packages/stream_chat_flutter)
![](https://img.shields.io/badge/platform-flutter%20%7C%20flutter%20web-ff69b4.svg?style=flat-square)
[![Gitter](https://badges.gitter.im/GetStream/stream-chat-flutter.svg)](https://gitter.im/GetStream/stream-chat-flutter?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
![CI](https://github.com/GetStream/stream-chat-flutter/workflows/CI/badge.svg?branch=master)
<img align="right" src="https://getstream.imgix.net/images/ios-chat-tutorial/iphone_chat_art@1x.png?auto=format,enhance" width="50%" />
**Quick Links**
- [Register](https://getstream.io/chat/trial/) to get an API key for Stream Chat
- [Flutter Chat Tutorial](https://getstream.io/chat/flutter/tutorial/)
- [Chat UI Kit](https://getstream.io/chat/ui-kit/)
## Flutter Chat Tutorial
The best place to start is the [Flutter Chat Tutorial](https://getstream.io/chat/flutter/tutorial/).
It teaches you how to use this SDK and also shows how to make frequently required changes.
## Example App
This repo includes a fully functional example app with setup instructions.
The example is available under the [example](https://github.com/GetStream/stream-chat-flutter-core/tree/master/example) folder.
## Add dependency
Add this to your package's pubspec.yaml file, use the latest version [![Pub](https://img.shields.io/pub/v/stream_chat_flutter.svg)](https://pub.dartlang.org/packages/stream_chat_flutter)
```yaml
dependencies:
stream_chat_flutter_core: ^latest_version
```
You should then run `flutter packages get`
This package requires no custom setup on any platform since it does not depend on any platform-specific dependency
## Docs
This package provides business logic to fetch common things required for integrating Stream Chat into your application.
The core package allows more customisation and hence provides business logic but no UI components.
Please use the stream_chat_flutter package for the full fledged suite of UI components or stream_chat for the low-level client.
The package primarily contains three types of classes:
1) Business Logic Components
2) Core Components
3) Core Controllers
### Business Logic Components
These components allow you to have the maximum and lower-level control of the queries being executed.
The BLoCs we provide are:
1) ChannelsBloc
2) MessageSearchBloc
3) UsersBloc
### Core Components
Core components usually are an easy way to fetch data associated with Stream Chat which are decoupled from UI and often expose UI builders.
Data fetching can be controlled with the controllers of the respective core components.
1) ChannelListCore (Fetch a list of channels)
2) MessageListCore (Fetch a list of messages from a channel)
3) MessageSearchListCore (Fetch a list of search messages)
4) UserListCore (Fetch a list of users)
5) StreamChatCore (This is different from the other core components - it is a version of StreamChat decoupled from theme and initialisations.)
### Core Controllers
Core Controllers are supplied to respective CoreList widgets which allows reloading and pagination of data whenever needed.
1) ChannelListController
2) MessageListController
3) MessageSearchListController
4) ChannelListController
## Contributing
We welcome code changes that improve this library or fix a problem,
please make sure to follow all best practices and add tests if applicable before submitting a Pull Request on Github.
We are pleased to merge your code into the official repository.
Make sure to sign our [Contributor License Agreement (CLA)](https://docs.google.com/forms/d/e/1FAIpQLScFKsKkAJI7mhCr7K9rEIOpqIDThrWxuvxnwUq2XkHyG154vQ/viewform) first.
See our license file for more details.
@@ -0,0 +1,41 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
/build/
# Web related
lib/generated_plugin_registrant.dart
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
@@ -0,0 +1,10 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: 78910062997c3a836feee883712c241a5fd22983
channel: stable
project_type: app
@@ -0,0 +1,16 @@
# example
Example app for testing stream_chat_flutter_core
## Getting Started
This project is a starting point for a Flutter application.
A few resources to get you started if this is your first Flutter project:
- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)
For help getting started with Flutter, view our
[online documentation](https://flutter.dev/docs), which offers tutorials,
samples, guidance on mobile development, and a full API reference.
@@ -0,0 +1,11 @@
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
# Remember to never publicly share your keystore.
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
key.properties
@@ -0,0 +1,54 @@
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
apply plugin: 'com.android.application'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion 29
lintOptions {
disable 'InvalidPackage'
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "example.example"
minSdkVersion 16
targetSdkVersion 29
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
}
}
}
flutter {
source '../..'
}
@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="example.example">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
@@ -0,0 +1,47 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="example.example">
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
calls FlutterMain.startInitialization(this); in its onCreate method.
In most cases you can leave this as-is, but you if you want to provide
additional functionality it is fine to subclass or reimplement
FlutterApplication and put your custom class here. -->
<application
android:name="io.flutter.app.FlutterApplication"
android:label="example"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<!-- Displays an Android View that continues showing the launch screen
Drawable until Flutter paints its first frame, then this splash
screen fades out. A splash screen is useful to avoid any visual
gap between the end of Android's launch screen and the painting of
Flutter's first frame. -->
<meta-data
android:name="io.flutter.embedding.android.SplashScreenDrawable"
android:resource="@drawable/launch_background"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
</manifest>
@@ -0,0 +1,5 @@
package example.example;
import io.flutter.embedding.android.FlutterActivity;
public class MainActivity extends FlutterActivity {}
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>
Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">@android:color/white</item>
</style>
</resources>
@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="example.example">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
@@ -0,0 +1,29 @@
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.0'
}
}
allprojects {
repositories {
google()
jcenter()
}
}
rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(':app')
}
task clean(type: Delete) {
delete rootProject.buildDir
}
@@ -0,0 +1,4 @@
org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true
android.enableJetifier=true
android.enableR8=true
@@ -0,0 +1,6 @@
#Fri Jun 23 08:50:38 CEST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip
@@ -0,0 +1,11 @@
include ':app'
def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
def properties = new Properties()
assert localPropertiesFile.exists()
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
@@ -0,0 +1,32 @@
*.mode1v3
*.mode2v3
*.moved-aside
*.pbxuser
*.perspectivev3
**/*sync/
.sconsign.dblite
.tags*
**/.vagrant/
**/DerivedData/
Icon?
**/Pods/
**/.symlinks/
profile
xcuserdata
**/.generated/
Flutter/App.framework
Flutter/Flutter.framework
Flutter/Flutter.podspec
Flutter/Generated.xcconfig
Flutter/app.flx
Flutter/app.zip
Flutter/flutter_assets/
Flutter/flutter_export_environment.sh
ServiceDefinitions.json
Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!default.mode1v3
!default.mode2v3
!default.pbxuser
!default.perspectivev3
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>8.0</string>
</dict>
</plist>
@@ -0,0 +1,2 @@
#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"
@@ -0,0 +1,2 @@
#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"
@@ -0,0 +1,495 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
);
name = Flutter;
sourceTree = "<group>";
};
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
);
sourceTree = "<group>";
};
97C146EF1CF9000F007C117D /* Products */ = {
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
);
name = Products;
sourceTree = "<group>";
};
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
);
path = Runner;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
97C146ED1CF9000F007C117D /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
);
buildRules = (
);
dependencies = (
);
name = Runner;
productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1020;
ORGANIZATIONNAME = "";
TargetAttributes = {
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
LastSwiftMigration = 1100;
};
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
97C146ED1CF9000F007C117D /* Runner */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
97C146EC1CF9000F007C117D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Thin Binary";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Run Script";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C146FB1CF9000F007C117D /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C147001CF9000F007C117D /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
249021D3217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Profile;
};
249021D4217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = example.example;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Profile;
};
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
97C147061CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = example.example;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
};
97C147071CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = example.example;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147031CF9000F007C117D /* Debug */,
97C147041CF9000F007C117D /* Release */,
249021D3217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147061CF9000F007C117D /* Debug */,
97C147071CF9000F007C117D /* Release */,
249021D4217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
</Workspace>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>
@@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1020"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
</Workspace>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>
@@ -0,0 +1,13 @@
import UIKit
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
@@ -0,0 +1,122 @@
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "[email protected]",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "[email protected]",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "[email protected]",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "[email protected]",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "[email protected]",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "[email protected]",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "[email protected]",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "[email protected]",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "[email protected]",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "[email protected]",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "[email protected]",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "[email protected]",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "[email protected]",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "[email protected]",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "[email protected]",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "[email protected]",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "[email protected]",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "[email protected]",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "[email protected]",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 564 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Some files were not shown because too many files have changed in this diff Show More