diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index a08a18f6..1e7527eb 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -842,4 +842,4 @@ /* End XCConfigurationList section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; -} \ No newline at end of file +} diff --git a/example/ios/Runner/Info.plist b/example/ios/Runner/Info.plist index 53d6d58a..7e63f46d 100644 --- a/example/ios/Runner/Info.plist +++ b/example/ios/Runner/Info.plist @@ -58,8 +58,6 @@ remote-notification - UIUserInterfaceStyle - Light UIViewControllerBasedStatusBarAppearance diff --git a/example/lib/main.dart b/example/lib/main.dart index 8d436614..ca3bedd2 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -59,7 +59,10 @@ class MyApp extends StatelessWidget { debugShowCheckedModeBanner: false, theme: ThemeData.light(), darkTheme: ThemeData.dark(), - themeMode: ThemeMode.system, + themeMode: + WidgetsBinding.instance.window.platformBrightness == Brightness.light + ? ThemeMode.light + : ThemeMode.dark, onGenerateRoute: AppRoutes.generateRoute, initialRoute: client.state.user == null ? Routes.CHOOSE_USER : Routes.HOME, diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 911d26d2..4f76c695 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -1,6 +1,6 @@ name: example description: A new Flutter project. -version: 1.0.105+108 +version: 1.0.106+109 environment: sdk: ">=2.2.2 <3.0.0" diff --git a/lib/src/message_list_view.dart b/lib/src/message_list_view.dart index a85beb0a..40680991 100644 --- a/lib/src/message_list_view.dart +++ b/lib/src/message_list_view.dart @@ -173,10 +173,10 @@ class _MessageListViewState extends State { bool _showScrollToBottom = false; ItemPositionsListener _itemPositionListener; int _messageListLength; + StreamChannelState streamChannel; int get _initialIndex { if (widget.initialScrollIndex != null) return widget.initialScrollIndex; - final streamChannel = StreamChannel.of(context); if (streamChannel.initialMessageId != null) { final messages = streamChannel.channel.state.messages; final totalMessages = messages.length; @@ -196,11 +196,10 @@ class _MessageListViewState extends State { } bool _isInitialMessage(String id) { - final streamChannel = StreamChannel.of(context); return streamChannel.initialMessageId == id; } - bool get _upToDate => StreamChannel.of(context).channel.state.isUpToDate; + bool get _upToDate => streamChannel.channel.state.isUpToDate; bool _topPaginationActive = false; bool _bottomPaginationActive = false; @@ -216,270 +215,259 @@ class _MessageListViewState extends State { @override Widget build(BuildContext context) { - final streamChannel = StreamChannel.of(context); - final messagesStream = widget.parentMessage != null ? streamChannel.channel.state.threadsStream .where((threads) => threads.containsKey(widget.parentMessage.id)) .map((threads) => threads[widget.parentMessage.id]) : streamChannel.channel.state?.messagesStream; - return WillPopScope( - onWillPop: () async { - if (!_upToDate) { - await streamChannel.reloadChannel(); - } - return true; - }, - child: StreamBuilder>( - stream: messagesStream?.map((messages) => messages - ?.where((e) => - !e.isDeleted || - (e.isDeleted && - e.user.id == streamChannel.channel.client.state.user.id)) - ?.toList()), - builder: (context, snapshot) { - if (!snapshot.hasData) { + return StreamBuilder>( + stream: messagesStream?.map((messages) => messages + ?.where((e) => + !e.isDeleted || + (e.isDeleted && + e.user.id == streamChannel.channel.client.state.user.id)) + ?.toList()), + builder: (context, snapshot) { + if (!snapshot.hasData) { + return Center( + child: const CircularProgressIndicator(), + ); + } + + final messageList = snapshot.data?.reversed?.toList() ?? []; + if (messageList.isEmpty) { + if (_upToDate) { return Center( - child: const CircularProgressIndicator(), + child: Text( + 'No chats here yet...', + style: StreamChatTheme.of(context) + .textTheme + .footnote + .copyWith( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(.5)), + ), ); } + } else { + messages = messageList; + } - 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)), - ), - ); - } - } else { - messages = messageList; - } + final newMessagesListLength = messages.length; - 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 (_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; } + } else if (!_topPaginationActive && _upToDate) { + // Reset the index in-case we send any new message + initialIndex = 0; + initialAlignment = 0; } + } - _messageListLength = newMessagesListLength; + _messageListLength = newMessagesListLength; - return Stack( - alignment: Alignment.center, - children: [ - 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 Stack( + alignment: Alignment.center, + children: [ + LazyLoadScrollView( + onStartOfPage: () async { + _inBetweenList = false; + if (!_upToDate) { + _topPaginationActive = false; + _bottomPaginationActive = true; return _paginateData( streamChannel, - QueryDirection.top, + QueryDirection.bottom, ); - }, - onInBetweenOfPage: () { - _inBetweenList = true; - }, - child: ScrollablePositionedList.builder( - 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 + - (widget.parentMessage != null ? 1 : 0), - itemBuilder: (context, i) { - if (i == messages.length + 2) { - if (widget.parentMessageBuilder != null) { - return widget.parentMessageBuilder( - context, - widget.parentMessage, - ); - } else { - return Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - buildParentMessage(widget.parentMessage), - Container( - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [ - StreamChatTheme.of(context) - .colorTheme - .whiteSmoke, - StreamChatTheme.of(context) - .colorTheme - .whiteSnow, - ], - ), - ), - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - '${widget.parentMessage.replyCount} ${widget.parentMessage.replyCount == 1 ? 'Reply' : 'Replies'}', - textAlign: TextAlign.center, - style: StreamChatTheme.of(context) - .channelTheme - .channelHeaderTheme - .lastMessageAt, - ), - ), - ), - ], - ); - } - } - if (i == messages.length + 1) { - return _buildLoadingIndicator( - streamChannel, - QueryDirection.top, - ); - } - if (i == 0) { - return _buildLoadingIndicator( - streamChannel, - QueryDirection.bottom, - ); - } - final message = messages[i - 1]; - final nextMessage = (i - 1) > 0 ? messages[i - 2] : null; - - Widget messageWidget; - - if (i == 1) { - messageWidget = _buildBottomMessage( + } + }, + onEndOfPage: () async { + _inBetweenList = false; + _topPaginationActive = true; + _bottomPaginationActive = false; + return _paginateData( + streamChannel, + QueryDirection.top, + ); + }, + onInBetweenOfPage: () { + _inBetweenList = true; + }, + child: ScrollablePositionedList.builder( + 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 + + (widget.parentMessage != null ? 1 : 0), + itemBuilder: (context, i) { + if (i == messages.length + 2) { + if (widget.parentMessageBuilder != null) { + return widget.parentMessageBuilder( context, - message, - messages, - streamChannel, - ); - } else if (i == messages.length - 1) { - messageWidget = _buildTopMessage( - context, - message, - messages, - streamChannel, + widget.parentMessage, ); } else { - if (widget.messageBuilder != null) { - messageWidget = Builder( - key: ValueKey('MESSAGE-${message.id}'), - builder: (_) => widget.messageBuilder( - context, - MessageDetails( - context, - message, - messages, - i, - ), - messages), - ); - } else { - messageWidget = buildMessage(message, messages, i); - } - } - - if (nextMessage != null && - !Jiffy(message.createdAt.toLocal()).isSame( - nextMessage.createdAt.toLocal(), Units.DAY)) { return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - messageWidget, - Padding( - padding: - const EdgeInsets.symmetric(vertical: 12.0), - child: widget.dateDividerBuilder != null - ? widget.dateDividerBuilder( - nextMessage.createdAt.toLocal()) - : DateDivider( - dateTime: nextMessage.createdAt.toLocal(), - ), + buildParentMessage(widget.parentMessage), + Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + StreamChatTheme.of(context) + .colorTheme + .whiteSmoke, + StreamChatTheme.of(context) + .colorTheme + .whiteSnow, + ], + ), + ), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + '${widget.parentMessage.replyCount} ${widget.parentMessage.replyCount == 1 ? 'Reply' : 'Replies'}', + textAlign: TextAlign.center, + style: StreamChatTheme.of(context) + .channelTheme + .channelHeaderTheme + .lastMessageAt, + ), + ), ), ], ); } + } + if (i == messages.length + 1) { + return _buildLoadingIndicator( + streamChannel, + QueryDirection.top, + ); + } + if (i == 0) { + return _buildLoadingIndicator( + streamChannel, + QueryDirection.bottom, + ); + } + final message = messages[i - 1]; + final nextMessage = (i - 1) > 0 ? messages[i - 2] : null; - return messageWidget; - }, - ), + 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('MESSAGE-${message.id}'), + builder: (_) => widget.messageBuilder( + context, + MessageDetails( + context, + message, + messages, + i, + ), + messages), + ); + } else { + messageWidget = buildMessage(message, messages, i); + } + } + + if (nextMessage != null && + !Jiffy(message.createdAt.toLocal()).isSame( + nextMessage.createdAt.toLocal(), Units.DAY)) { + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + messageWidget, + Padding( + padding: const EdgeInsets.symmetric(vertical: 12.0), + child: widget.dateDividerBuilder != null + ? widget.dateDividerBuilder( + nextMessage.createdAt.toLocal()) + : DateDivider( + dateTime: nextMessage.createdAt.toLocal(), + ), + ), + ], + ); + } + + return messageWidget; + }, ), - if (widget.showScrollToBottom) _buildScrollToBottom(), - Positioned( - top: 20.0, - child: ValueListenableBuilder>( - valueListenable: _itemPositionListener.itemPositions, - builder: (context, values, _) { - final items = _itemPositionListener.itemPositions?.value; - if (items.isEmpty || messages.isEmpty) { - return SizedBox(); - } + ), + if (widget.showScrollToBottom) _buildScrollToBottom(), + Positioned( + top: 20.0, + child: ValueListenableBuilder>( + valueListenable: _itemPositionListener.itemPositions, + builder: (context, values, _) { + final items = _itemPositionListener.itemPositions?.value; + if (items.isEmpty || messages.isEmpty) { + return SizedBox(); + } - var index = _getTopElement(values).index; + var index = _getTopElement(values).index; - if (index > messages.length) { - return SizedBox(); - } + if (index > messages.length) { + return SizedBox(); + } - if (index == messages.length) { - index = max(index - 1, 0); - } + 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(), - ); - }, - ), + return widget.dateDividerBuilder != null + ? widget.dateDividerBuilder( + messages[index].createdAt.toLocal(), + ) + : DateDivider( + dateTime: messages[index].createdAt.toLocal(), + ); + }, ), - ], - ); - }), - ); + ), + ], + ); + }); } Future _paginateData( @@ -499,7 +487,6 @@ class _MessageListViewState extends State { } Widget _buildScrollToBottom() { - final streamChannel = StreamChannel.of(context); return StreamBuilder>( stream: Rx.combineLatest2( streamChannel.channel.state.isUpToDateStream, @@ -744,7 +731,7 @@ class _MessageListViewState extends State { final isNextUser = index - 2 >= 0 && message.user.id == messages[index - 2]?.user?.id; - final channel = StreamChannel.of(context).channel; + final channel = streamChannel.channel; final readList = channel.state?.read ?.where((element) => element.user.id != userId) ?.where((read) => @@ -836,7 +823,7 @@ class _MessageListViewState extends State { _itemPositionListener = widget.itemPositionListener ?? ItemPositionsListener.create(); - final streamChannel = StreamChannel.of(context); + streamChannel = StreamChannel.of(context); initialIndex = _initialIndex; initialAlignment = _initialAlignment; @@ -879,16 +866,13 @@ class _MessageListViewState extends State { context, MaterialPageRoute(builder: (_) { return StreamBuilder( - stream: StreamChannel.of(context) - .channel - .state - .messagesStream - .map((messages) => + stream: streamChannel.channel.state.messagesStream.map( + (messages) => messages.firstWhere((m) => m.id == message.id)), initialData: message, builder: (_, snapshot) { return StreamChannel( - channel: StreamChannel.of(context).channel, + channel: streamChannel.channel, child: widget.threadBuilder(context, snapshot.data), ); }); @@ -900,6 +884,9 @@ class _MessageListViewState extends State { @override void dispose() { + if (!_upToDate) { + streamChannel.reloadChannel(); + } _messageNewListener?.cancel(); super.dispose(); }