add typing indicator widget in example messagelist

This commit is contained in:
Salvatore Giordano
2020-03-06 10:34:38 +01:00
parent 153455ae56
commit 60c2ccfc24
10 changed files with 151 additions and 120 deletions
+61 -61
View File
@@ -1,61 +1,61 @@
include: package:pedantic/analysis_options.yaml #include: package:pedantic/analysis_options.yaml
#
analyzer: #analyzer:
exclude: # exclude:
- lib/**/*.g.dart # - lib/**/*.g.dart
- example/* # - example/*
#
linter: #linter:
rules: # rules:
# these rules are documented on and in the same order as # # these rules are documented on and in the same order as
# the Dart Lint rules page to make maintenance easier # # the Dart Lint rules page to make maintenance easier
# https://github.com/dart-lang/linter/blob/master/example/all.yaml # # https://github.com/dart-lang/linter/blob/master/example/all.yaml
# - always_declare_return_types # # - always_declare_return_types
# - always_specify_types # # - always_specify_types
# - annotate_overrides # # - annotate_overrides
# - avoid_as # # - avoid_as
- avoid_empty_else # - avoid_empty_else
- avoid_init_to_null # - avoid_init_to_null
- avoid_return_types_on_setters # - avoid_return_types_on_setters
- avoid_web_libraries_in_flutter # - avoid_web_libraries_in_flutter
- await_only_futures # - await_only_futures
- camel_case_types # - camel_case_types
- cancel_subscriptions # - cancel_subscriptions
- close_sinks # - close_sinks
# - comment_references # we do not presume as to what people want to reference in their dartdocs # # - comment_references # we do not presume as to what people want to reference in their dartdocs
# - constant_identifier_names # https://github.com/dart-lang/linter/issues/204 # # - constant_identifier_names # https://github.com/dart-lang/linter/issues/204
- control_flow_in_finally # - control_flow_in_finally
- empty_constructor_bodies # - empty_constructor_bodies
- empty_statements # - empty_statements
- hash_and_equals # - hash_and_equals
- implementation_imports # - implementation_imports
# - invariant_booleans # # - invariant_booleans
# - iterable_contains_unrelated_type # # - iterable_contains_unrelated_type
- library_names # - library_names
# - library_prefixes # # - library_prefixes
# - list_remove_unrelated_type # # - list_remove_unrelated_type
# - literal_only_boolean_expressions # # - literal_only_boolean_expressions
- non_constant_identifier_names # - non_constant_identifier_names
# - one_member_abstracts # # - one_member_abstracts
# - only_throw_errors # # - only_throw_errors
# - overridden_fields # # - overridden_fields
# - package_api_docs ## - package_api_docs
- package_names # - package_names
- package_prefixed_library_names # - package_prefixed_library_names
- prefer_is_not_empty # - prefer_is_not_empty
# - prefer_mixin # https://github.com/dart-lang/language/issues/32 # # - prefer_mixin # https://github.com/dart-lang/language/issues/32
# - public_member_api_docs ## - public_member_api_docs
- slash_for_doc_comments # - slash_for_doc_comments
# - sort_constructors_first # # - sort_constructors_first
# - sort_unnamed_constructors_first # # - sort_unnamed_constructors_first
# - super_goes_last # no longer needed w/ Dart 2 # # - super_goes_last # no longer needed w/ Dart 2
- test_types_in_equals # - test_types_in_equals
- throw_in_finally # - throw_in_finally
# - type_annotate_public_apis # subset of always_specify_types # # - type_annotate_public_apis # subset of always_specify_types
- type_init_formals # - type_init_formals
# - unawaited_futures # # - unawaited_futures
- unnecessary_brace_in_string_interps # - unnecessary_brace_in_string_interps
- unnecessary_getters_setters # - unnecessary_getters_setters
- unnecessary_statements # - unnecessary_statements
- unrelated_type_equality_checks # - unrelated_type_equality_checks
- valid_regexps # - valid_regexps
+5 -6
View File
@@ -72,18 +72,17 @@ class ChannelListPage extends StatelessWidget {
} }
Widget _channelPreviewBuilder(BuildContext context, Channel channel) { Widget _channelPreviewBuilder(BuildContext context, Channel channel) {
final lastMessage = channel.state.messages.reversed final lastMessage = channel.state.messages.reversed.firstWhere(
.firstWhere((message) => message.type != "deleted"); (message) => message.type != "deleted",
orElse: () => null,
);
final subtitle = (lastMessage == null ? "nothing yet" : lastMessage.text); final subtitle = (lastMessage == null ? "nothing yet" : lastMessage.text);
final opacity = channel.state.unreadCount > .0 ? 1.0 : 0.5; final opacity = channel.state.unreadCount > .0 ? 1.0 : 0.5;
return ListTile( return ListTile(
leading: ChannelImage( leading: ChannelImage(),
channel: channel,
),
title: ChannelName( title: ChannelName(
channel: channel,
textStyle: textStyle:
StreamChatTheme.of(context).channelPreviewTheme.title.copyWith( StreamChatTheme.of(context).channelPreviewTheme.title.copyWith(
color: Colors.black.withOpacity(opacity), color: Colors.black.withOpacity(opacity),
+26 -9
View File
@@ -65,12 +65,28 @@ class ChannelPage extends StatelessWidget {
body: Column( body: Column(
children: <Widget>[ children: <Widget>[
Expanded( Expanded(
child: MessageListView( child: Stack(
threadBuilder: (_, parentMessage) { children: <Widget>[
return ThreadPage( MessageListView(
parent: parentMessage, threadBuilder: (_, parentMessage) {
); return ThreadPage(
}, parent: parentMessage,
);
},
),
Positioned.fill(
child: Align(
alignment: Alignment.bottomRight,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8.0,
vertical: 4,
),
child: TypingIndicator(),
),
),
),
],
), ),
), ),
MessageInput(), MessageInput(),
@@ -101,9 +117,10 @@ class ThreadPage extends StatelessWidget {
parentMessage: parent, parentMessage: parent,
), ),
), ),
MessageInput( if (parent.type != 'deleted')
parentMessage: parent, MessageInput(
), parentMessage: parent,
),
], ],
), ),
); );
+1 -2
View File
@@ -4,12 +4,11 @@ description: A new Flutter project.
version: 1.0.0+1 version: 1.0.0+1
environment: environment:
sdk: ">=2.1.0 <3.0.0" sdk: ">=2.2.2 <3.0.0"
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
cupertino_icons: ^0.1.2
stream_chat_flutter: stream_chat_flutter:
path: ../ path: ../
+3 -2
View File
@@ -78,7 +78,9 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget {
padding: const EdgeInsets.only(right: 10.0), padding: const EdgeInsets.only(right: 10.0),
child: Stack( child: Stack(
children: <Widget>[ children: <Widget>[
Center(child: ChannelImage(channel: channel)), Center(
child: ChannelImage(),
),
], ],
), ),
), ),
@@ -88,7 +90,6 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget {
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[ children: <Widget>[
ChannelName( ChannelName(
channel: channel,
textStyle: StreamChatTheme.of(context) textStyle: StreamChatTheme.of(context)
.channelTheme .channelTheme
.channelHeaderTheme .channelHeaderTheme
+2 -1
View File
@@ -45,7 +45,7 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart';
class ChannelImage extends StatelessWidget { class ChannelImage extends StatelessWidget {
const ChannelImage({ const ChannelImage({
Key key, Key key,
@required this.channel, this.channel,
this.size = 40, this.size = 40,
}) : super(key: key); }) : super(key: key);
@@ -57,6 +57,7 @@ class ChannelImage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final channel = this.channel ?? StreamChannel.of(context).channel;
return StreamBuilder<Map<String, dynamic>>( return StreamBuilder<Map<String, dynamic>>(
stream: channel.extraDataStream, stream: channel.extraDataStream,
initialData: channel.extraData, initialData: channel.extraData,
+4 -1
View File
@@ -1,13 +1,15 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat/stream_chat.dart';
import 'stream_channel.dart';
/// It shows the current [Channel] name using a [Text] widget. /// It shows the current [Channel] name using a [Text] widget.
/// ///
/// The widget uses a [StreamBuilder] to render the channel information image as soon as it updates. /// The widget uses a [StreamBuilder] to render the channel information image as soon as it updates.
class ChannelName extends StatelessWidget { class ChannelName extends StatelessWidget {
const ChannelName({ const ChannelName({
Key key, Key key,
@required this.channel, this.channel,
this.textStyle, this.textStyle,
}) : super(key: key); }) : super(key: key);
@@ -19,6 +21,7 @@ class ChannelName extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final channel = this.channel ?? StreamChannel.of(context).channel;
return StreamBuilder<Map<String, dynamic>>( return StreamBuilder<Map<String, dynamic>>(
stream: channel.extraDataStream, stream: channel.extraDataStream,
initialData: channel.extraData, initialData: channel.extraData,
+17 -26
View File
@@ -37,14 +37,11 @@ class ChannelPreview extends StatelessWidget {
onTap: () { onTap: () {
onTap(channel); onTap(channel);
}, },
leading: ChannelImage( leading: ChannelImage(),
channel: channel,
),
title: ChannelName( title: ChannelName(
textStyle: StreamChatTheme.of(context).channelPreviewTheme.title, textStyle: StreamChatTheme.of(context).channelPreviewTheme.title,
channel: channel,
), ),
subtitle: _buildSubtitle(), subtitle: _buildSubtitle(context),
trailing: Column( trailing: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end,
@@ -81,29 +78,23 @@ class ChannelPreview extends StatelessWidget {
); );
} }
Widget _buildSubtitle() { Widget _buildSubtitle(BuildContext context) {
return StreamBuilder<List<User>>( final opacity = channel.state.unreadCount > 0 ? 1.0 : 0.5;
stream: channel.state.typingEventsStream, return Align(
initialData: channel.state.typingEvents, alignment: Alignment.centerLeft,
builder: (context, snapshot) { child: TypingIndicator(
final typings = snapshot.data; channel: channel,
final opacity = channel.state.unreadCount > 0 ? 1.0 : 0.5; alternativeWidget: _buildLastMessage(context, opacity),
return typings.isNotEmpty style:
? TypingIndicator( StreamChatTheme.of(context).channelPreviewTheme.subtitle.copyWith(
typings: typings, color: StreamChatTheme.of(context)
style: StreamChatTheme.of(context)
.channelPreviewTheme .channelPreviewTheme
.subtitle .subtitle
.copyWith( .color
color: StreamChatTheme.of(context) .withOpacity(opacity),
.channelPreviewTheme ),
.subtitle ),
.color );
.withOpacity(opacity),
),
)
: _buildLastMessage(context, opacity);
});
} }
Widget _buildLastMessage(BuildContext context, double opacity) { Widget _buildLastMessage(BuildContext context, double opacity) {
+26 -6
View File
@@ -1,11 +1,13 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_flutter/src/stream_channel.dart';
/// Widget to show the current list of typing users /// Widget to show the current list of typing users
class TypingIndicator extends StatelessWidget { class TypingIndicator extends StatelessWidget {
const TypingIndicator({ const TypingIndicator({
Key key, Key key,
@required this.typings, this.channel,
this.alternativeWidget = const SizedBox(),
this.style, this.style,
}) : super(key: key); }) : super(key: key);
@@ -13,14 +15,32 @@ class TypingIndicator extends StatelessWidget {
final TextStyle style; final TextStyle style;
/// List of typing users /// List of typing users
final List<User> typings; final Channel channel;
/// Widget built when no typings is happening
final Widget alternativeWidget;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Text( final channelState =
'${typings.map((u) => u.name).join(',')} ${typings.length == 1 ? 'is' : 'are'} typing...', channel?.state ?? StreamChannel.of(context).channel.state;
maxLines: 1, return StreamBuilder<List<User>>(
style: style, initialData: channelState.typingEvents,
stream: channelState.typingEventsStream,
builder: (context, snapshot) {
return AnimatedSwitcher(
duration: Duration(milliseconds: 300),
child: snapshot.data.isNotEmpty
? Text(
'${snapshot.data.map((u) => u.name).join(',')} ${snapshot.data.length == 1 ? 'is' : 'are'} typing...',
maxLines: 1,
style: style,
)
: Container(
child: alternativeWidget,
),
);
},
); );
} }
} }
+6 -6
View File
@@ -4,18 +4,18 @@ description: Stream Chat official Flutter SDK. Build your own chat experience us
version: 0.1.8 version: 0.1.8
environment: environment:
sdk: ">=2.2.2 <3.0.0" sdk: ">=2.3.0 <3.0.0"
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
rxdart: ^0.23.1 rxdart: ^0.23.1
flutter_widgets: ^0.1.10 flutter_widgets: ^0.1.11
jiffy: ^3.0.0 jiffy: ^3.0.0
cached_network_image: ^2.0.0 cached_network_image: ^2.0.0
flutter_markdown: ^0.3.3 flutter_markdown: ^0.3.4
url_launcher: ^5.4.1 url_launcher: ^5.4.2
video_player: ^0.10.7 video_player: ^0.10.8+1
chewie: ^0.9.8+1 chewie: ^0.9.8+1
file_picker: ^1.4.3+2 file_picker: ^1.4.3+2
image_picker: ^0.6.3+4 image_picker: ^0.6.3+4
@@ -23,4 +23,4 @@ dependencies:
stream_chat: ^0.1.17 stream_chat: ^0.1.17
dev_dependencies: dev_dependencies:
pedantic: ^1.8.0+1 pedantic: ^1.9.0