diff --git a/packages/imessage/analysis_options.yaml b/packages/imessage/analysis_options.yaml index 6f995b8..61b6c4d 100644 --- a/packages/imessage/analysis_options.yaml +++ b/packages/imessage/analysis_options.yaml @@ -1,60 +1,29 @@ -include: package:pedantic/analysis_options.yaml +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. -analyzer: - exclude: - - lib/**/*.g.dart - - example/* - - test/* +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. rules: - # these rules are documented on and in the same order as - # the Dart Lint rules page to make maintenance easier - # https://github.com/dart-lang/linter/blob/master/example/all.yaml - # - always_declare_return_types - # - always_specify_types - # - annotate_overrides - # - avoid_as - - avoid_empty_else - - avoid_init_to_null - - avoid_return_types_on_setters - - avoid_web_libraries_in_flutter - - await_only_futures - - camel_case_types - - cancel_subscriptions - - close_sinks - # - 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 - - control_flow_in_finally - - empty_constructor_bodies - - empty_statements - - hash_and_equals - - implementation_imports - # - invariant_booleans - # - iterable_contains_unrelated_type - - library_names - # - library_prefixes - # - list_remove_unrelated_type - # - literal_only_boolean_expressions - - non_constant_identifier_names - # - one_member_abstracts - # - only_throw_errors - # - overridden_fields - - package_names - - package_prefixed_library_names - - prefer_is_not_empty - # - prefer_mixin # https://github.com/dart-lang/language/issues/32 - - slash_for_doc_comments - # - sort_constructors_first - # - sort_unnamed_constructors_first - # - super_goes_last # no longer needed w/ Dart 2 - - test_types_in_equals - - throw_in_finally - # - type_annotate_public_apis # subset of always_specify_types - - type_init_formals - # - unawaited_futures - - unnecessary_brace_in_string_interps - - unnecessary_getters_setters - - unnecessary_statements - - unrelated_type_equality_checks - - valid_regexps \ No newline at end of file + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/packages/imessage/lib/channel_image.dart b/packages/imessage/lib/channel_image.dart index d7b6343..f6cabed 100644 --- a/packages/imessage/lib/channel_image.dart +++ b/packages/imessage/lib/channel_image.dart @@ -1,11 +1,15 @@ import 'package:flutter/cupertino.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart' show Channel; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart' + show Channel; import 'package:imessage/utils.dart'; class ChannelImage extends StatelessWidget { - const ChannelImage({Key? key, required this.channel, required this.size}) - : super(key: key); + const ChannelImage({ + Key? key, + required this.channel, + required this.size, + }) : super(key: key); final Channel channel; final double size; diff --git a/packages/imessage/lib/channel_list_view.dart b/packages/imessage/lib/channel_list_view.dart index 9421b6d..609c0a3 100644 --- a/packages/imessage/lib/channel_list_view.dart +++ b/packages/imessage/lib/channel_list_view.dart @@ -2,12 +2,13 @@ import 'package:flutter/cupertino.dart'; import 'package:imessage/channel_preview.dart'; import 'package:imessage/message_page.dart'; import 'package:animations/animations.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart' +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart' show Channel, StreamChannel; class ChannelListView extends StatelessWidget { const ChannelListView({Key? key, required this.channels}) : super(key: key); final List channels; + @override Widget build(BuildContext context) { channels.removeWhere((channel) => channel.lastMessageAt == null); @@ -27,7 +28,7 @@ class ChannelListView extends StatelessWidget { PageRouteBuilder( pageBuilder: (_, __, ___) => StreamChannel( channel: channels[index], - child: MessagePage(), + child: const MessagePage(), ), transitionsBuilder: ( _, diff --git a/packages/imessage/lib/channel_name_text.dart b/packages/imessage/lib/channel_name_text.dart index 169bb9d..f8abe10 100644 --- a/packages/imessage/lib/channel_name_text.dart +++ b/packages/imessage/lib/channel_name_text.dart @@ -1,15 +1,18 @@ import 'package:flutter/cupertino.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart' show Channel; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart' + show Channel; class ChannelNameText extends StatelessWidget { const ChannelNameText({ Key? key, required this.channel, this.size = 17, + this.fontWeight, }) : super(key: key); final Channel channel; final double size; + final FontWeight? fontWeight; @override Widget build(BuildContext context) { @@ -17,7 +20,7 @@ class ChannelNameText extends StatelessWidget { channel.extraData['name'] as String? ?? 'No name', style: TextStyle( fontSize: size, - fontWeight: FontWeight.bold, + fontWeight: fontWeight, color: CupertinoColors.black, ), ); diff --git a/packages/imessage/lib/channel_page_appbar.dart b/packages/imessage/lib/channel_page_appbar.dart index 5f44995..26ed344 100644 --- a/packages/imessage/lib/channel_page_appbar.dart +++ b/packages/imessage/lib/channel_page_appbar.dart @@ -7,8 +7,11 @@ class ChannelPageAppBar extends StatelessWidget { @override Widget build(BuildContext context) { - return CupertinoSliverNavigationBar( - largeTitle: Text('Messages'), + return const CupertinoSliverNavigationBar( + largeTitle: Text( + 'Messages', + style: TextStyle(letterSpacing: -1.3), + ), ); } } diff --git a/packages/imessage/lib/channel_preview.dart b/packages/imessage/lib/channel_preview.dart index 98a16c4..37ea87d 100644 --- a/packages/imessage/lib/channel_preview.dart +++ b/packages/imessage/lib/channel_preview.dart @@ -2,7 +2,8 @@ import 'package:flutter/cupertino.dart'; import 'package:imessage/channel_image.dart'; import 'package:imessage/channel_name_text.dart'; import 'package:imessage/utils.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart' show Channel; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart' + show Channel; import 'utils.dart'; @@ -23,7 +24,7 @@ class ChannelPreview extends StatelessWidget { : null; final prefix = lastMessage?.attachments != null - ? lastMessage?.attachments //TODO: ugly + ? lastMessage?.attachments .map((e) { if (e.type == 'image') { return '📷 '; @@ -35,13 +36,12 @@ class ChannelPreview extends StatelessWidget { .where((e) => e != null) .join(' ') : ''; + return GestureDetector( onTap: onTap, behavior: HitTestBehavior.opaque, - child: Container( - constraints: BoxConstraints.tightFor( - height: 90, - ), + child: SizedBox( + height: 70, child: Padding( padding: const EdgeInsets.symmetric( horizontal: 8.0, @@ -50,11 +50,10 @@ class ChannelPreview extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( - padding: - const EdgeInsets.symmetric(vertical: 16.0, horizontal: 8.0), + padding: const EdgeInsets.only(left: 16.0, right: 8.0), child: ChannelImage( channel: channel, - size: 50, + size: 46, ), ), Expanded( @@ -67,27 +66,29 @@ class ChannelPreview extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: ChannelNameText( - channel: channel, - ), + padding: const EdgeInsets.symmetric(horizontal: 4.0), + child: ChannelNameText(channel: channel), ), Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0), + padding: const EdgeInsets.symmetric(horizontal: 4.0), child: Row( children: [ Text( isSameWeek(channel.lastMessageAt!) ? formatDateSameWeek(channel.lastMessageAt!) : formatDate(channel.lastMessageAt!), - style: TextStyle( - fontSize: 15, + style: const TextStyle( + fontSize: 13, color: CupertinoColors.systemGrey, ), ), - Icon( - CupertinoIcons.right_chevron, - color: CupertinoColors.systemGrey3, + const Padding( + padding: EdgeInsets.only(left: 4.0), + child: Icon( + CupertinoIcons.chevron_right, + size: 16, + color: CupertinoColors.systemGrey3, + ), ), ], ), @@ -95,19 +96,19 @@ class ChannelPreview extends StatelessWidget { ], ), Padding( - padding: const EdgeInsets.all(8.0), + padding: const EdgeInsets.all(4.0), child: Text( '$prefix${lastMessage?.text ?? ''}', - style: TextStyle( + style: const TextStyle( fontWeight: FontWeight.normal, color: CupertinoColors.systemGrey, - fontSize: 16, + fontSize: 14, ), maxLines: 2, overflow: TextOverflow.ellipsis, ), ), - Divider(), + const Divider(), ], ), ) diff --git a/packages/imessage/lib/cutom_painter.dart b/packages/imessage/lib/cutom_painter.dart index 0ed6213..6fc6537 100644 --- a/packages/imessage/lib/cutom_painter.dart +++ b/packages/imessage/lib/cutom_painter.dart @@ -3,82 +3,109 @@ import 'package:flutter/cupertino.dart'; class ChatBubble extends CustomPainter { final Color color; final Alignment? alignment; + final bool hasTail; ChatBubble({ required this.color, + required this.hasTail, this.alignment, - }); + }) : paintFill = Paint() + ..color = color + ..style = PaintingStyle.fill; - final _radius = 10.0; - final _x = 10.0; + final Paint paintFill; @override void paint(Canvas canvas, Size size) { + var path = Path(); + const cornerSize = 18.0; + const buffer = 6.0; + const innerTailWidth = 7.0; + const innerTailHeight = 4.0; if (alignment == Alignment.topRight) { - canvas.drawRRect( - RRect.fromLTRBAndCorners( - 0, - 0, - size.width - 8, - size.height, - bottomLeft: Radius.circular(_radius), - topRight: Radius.circular(_radius), - topLeft: Radius.circular(_radius), - ), - Paint() - ..color = color - ..style = PaintingStyle.fill); - var path = Path(); - path.moveTo(size.width - _x, size.height - 20); - path.lineTo(size.width - _x, size.height); - path.lineTo(size.width, size.height); - canvas.clipPath(path); - canvas.drawRRect( - RRect.fromLTRBAndCorners( - size.width - _x, - 0.0, - size.width, - size.height, - topRight: Radius.circular(_radius), - ), - Paint() - ..color = color - ..style = PaintingStyle.fill); + path.moveTo(0, cornerSize); + path.lineTo(0, size.height - cornerSize); + path.arcToPoint(Offset(cornerSize, size.height), + radius: const Radius.circular(cornerSize), clockwise: false); + + if (hasTail) { + path.lineTo(size.width - cornerSize - innerTailWidth, size.height); + path.arcToPoint( + Offset(size.width - buffer - innerTailWidth, + size.height - innerTailHeight), + radius: const Radius.circular(cornerSize), + clockwise: false); + + path.arcToPoint(Offset(size.width, size.height), + radius: const Radius.circular(buffer * 2), clockwise: false); + + path.arcToPoint( + Offset(size.width - buffer, size.height - buffer - innerTailHeight), + radius: const Radius.circular(buffer + innerTailHeight), + clockwise: true); + } else { + path.lineTo(size.width - cornerSize, size.height); + path.arcToPoint(Offset(size.width - buffer, size.height - cornerSize), + radius: const Radius.circular(cornerSize), clockwise: false); + } + + path.lineTo(size.width - buffer, cornerSize); + + path.arcToPoint(Offset(size.width - cornerSize - buffer, 0), + radius: const Radius.circular(cornerSize), clockwise: false); + + path.lineTo(cornerSize, 0); + + path.arcToPoint(const Offset(0, cornerSize), + radius: const Radius.circular(cornerSize), clockwise: false); } else { - canvas.drawRRect( - RRect.fromLTRBAndCorners( - _x, - 0, - size.width, - size.height, - bottomRight: Radius.circular(_radius), - topRight: Radius.circular(_radius), - topLeft: Radius.circular(_radius), - ), - Paint() - ..color = color - ..style = PaintingStyle.fill); - var path = Path(); - path.moveTo(0, size.height); - path.lineTo(_x, size.height); - path.lineTo(_x, size.height - 20); - canvas.clipPath(path); - canvas.drawRRect( - RRect.fromLTRBAndCorners( - 0, - 0.0, - _x, - size.height, - topRight: Radius.circular(_radius), - ), - Paint() - ..color = color - ..style = PaintingStyle.fill); + path.moveTo(size.width, cornerSize); + path.arcToPoint(Offset(size.width - cornerSize, 0), + radius: const Radius.circular(cornerSize), clockwise: false); + + path.lineTo(cornerSize + buffer, 0); + + path.arcToPoint(const Offset(buffer, cornerSize), + radius: const Radius.circular(cornerSize), clockwise: false); + + if (hasTail) { + path.lineTo(buffer, size.height - buffer - innerTailHeight); + + path.arcToPoint(Offset(0, size.height), + radius: const Radius.circular(buffer + innerTailHeight), + clockwise: true); + + path.arcToPoint( + Offset(buffer + innerTailWidth, size.height - innerTailHeight), + radius: const Radius.circular(buffer * 2), + clockwise: false); + + path.arcToPoint(Offset(cornerSize + innerTailWidth, size.height), + radius: const Radius.circular(cornerSize), clockwise: false); + } else { + path.lineTo(buffer, size.height - cornerSize); + path.arcToPoint(Offset(buffer + cornerSize, size.height), + radius: const Radius.circular(cornerSize), clockwise: false); + } + + path.lineTo(size.width - cornerSize, size.height); + path.arcToPoint(Offset(size.width, size.height - cornerSize), + radius: const Radius.circular(cornerSize), clockwise: false); } + canvas.drawPath( + path, + paintFill, + ); } @override - bool shouldRepaint(CustomPainter oldDelegate) { - return true; + bool shouldRepaint(ChatBubble oldDelegate) { + if (color != oldDelegate.color || + alignment != oldDelegate.alignment || + hasTail != oldDelegate.hasTail) { + return true; + } else { + return false; + } } } diff --git a/packages/imessage/lib/main.dart b/packages/imessage/lib/main.dart index 7763692..43e1a91 100644 --- a/packages/imessage/lib/main.dart +++ b/packages/imessage/lib/main.dart @@ -1,19 +1,20 @@ import 'package:flutter/cupertino.dart'; import 'package:intl/date_symbol_data_local.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart' - hide ChannelListView; import 'package:imessage/channel_list_view.dart'; import 'package:imessage/channel_page_appbar.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; Future main() async { WidgetsFlutterBinding.ensureInitialized(); final client = StreamChatClient('b67pax5b2wdq', logLevel: Level.INFO); // + + // For demonstration purposes. Fixed user and token. await client.connectUser( User( id: 'cool-shadow-7', - extraData: { + extraData: const { 'image': 'https://getstream.io/random_png/?id=cool-shadow-7&name=Cool+shadow', }, @@ -25,9 +26,9 @@ Future main() async { } class IMessage extends StatelessWidget { - final StreamChatClient client; + const IMessage({Key? key, required this.client}) : super(key: key); - IMessage({required this.client}); + final StreamChatClient client; @override Widget build(BuildContext context) { @@ -35,7 +36,7 @@ class IMessage extends StatelessWidget { return CupertinoApp( title: 'Flutter Demo', debugShowCheckedModeBanner: false, - theme: CupertinoThemeData(brightness: Brightness.light), + theme: const CupertinoThemeData(brightness: Brightness.light), home: StreamChatCore(client: client, child: ChatLoader()), ); } @@ -59,15 +60,15 @@ class ChatLoader extends StatelessWidget { Filter.in_('members', [user.id]), Filter.equal('type', 'messaging'), ]), - sort: [SortOption('last_message_at')], + sort: const [SortOption('last_message_at')], limit: 20, emptyBuilder: (BuildContext context) { - return Center( + return const Center( child: Text('Looks like you are not in any channels'), ); }, loadingBuilder: (BuildContext context) { - return Center( + return const Center( child: SizedBox( height: 100.0, width: 100.0, @@ -76,7 +77,7 @@ class ChatLoader extends StatelessWidget { ); }, errorBuilder: (BuildContext context, dynamic error) { - return Center( + return const Center( child: Text( 'Oh no, something went wrong. Please check your config.'), ); @@ -94,7 +95,7 @@ class ChatLoader extends StatelessWidget { CupertinoSliverRefreshControl(onRefresh: () async { return channelListController.loadData!(); }), - ChannelPageAppBar(), + const ChannelPageAppBar(), SliverPadding( sliver: ChannelListView(channels: channels), padding: const EdgeInsets.only(top: 16), diff --git a/packages/imessage/lib/message_header.dart b/packages/imessage/lib/message_header.dart index e8ee94b..f156078 100644 --- a/packages/imessage/lib/message_header.dart +++ b/packages/imessage/lib/message_header.dart @@ -9,9 +9,10 @@ class MessageHeader extends StatelessWidget { @override Widget build(BuildContext context) { final receivedAt = DateTime.parse(rawTimeStamp); - final textStyle = TextStyle( + const textStyle = TextStyle( color: CupertinoColors.systemGrey, - fontSize: 14, + fontWeight: FontWeight.w400, + fontSize: 11, ); return isSameWeek(receivedAt) ? Text( diff --git a/packages/imessage/lib/message_input.dart b/packages/imessage/lib/message_input.dart index 0e47240..5f107b0 100644 --- a/packages/imessage/lib/message_input.dart +++ b/packages/imessage/lib/message_input.dart @@ -2,7 +2,7 @@ import 'dart:io'; import 'package:flutter/cupertino.dart'; import 'package:image_picker/image_picker.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart' +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart' show Attachment, AttachmentFile, Message, StreamChannel; class MessageInput extends StatefulWidget { @@ -59,28 +59,27 @@ class _MessageInputState extends State { ); await channel.sendMessage(message); }, - child: Padding( - padding: const EdgeInsets.all(8.0), + child: const Padding( + padding: EdgeInsets.all(8.0), child: Icon( CupertinoIcons.camera_fill, color: CupertinoColors.systemGrey, - size: 35, + size: 32, ), ), ), Expanded( child: CupertinoTextField( controller: textController, + maxLines: 10, + minLines: 1, onSubmitted: (input) async { await sendMessage(context, input); }, - placeholder: 'Text Message', - prefix: Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - '', - ), //trick to add padding around placeholder iMessage text - ), + placeholder: 'iMessage', + padding: + const EdgeInsets.symmetric(vertical: 8, horizontal: 12), + suffixMode: OverlayVisibilityMode.editing, suffix: GestureDetector( onTap: () async { if (textController.value.text.isNotEmpty) { @@ -88,20 +87,16 @@ class _MessageInputState extends State { textController.clear(); } }, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Icon( - CupertinoIcons.arrow_up_circle_fill, - color: CupertinoColors.activeGreen, - size: 35, - ), + child: const Icon( + CupertinoIcons.arrow_up_circle_fill, + color: CupertinoColors.activeBlue, + size: 35, ), ), decoration: BoxDecoration( - border: Border.all( - color: CupertinoColors.systemGrey, - ), - borderRadius: BorderRadius.all(Radius.circular(35))), + border: Border.all(color: CupertinoColors.systemGrey), + borderRadius: const BorderRadius.all(Radius.circular(35)), + ), ), ), ], diff --git a/packages/imessage/lib/message_list_view.dart b/packages/imessage/lib/message_list_view.dart index 9efa5b5..7c63d41 100644 --- a/packages/imessage/lib/message_list_view.dart +++ b/packages/imessage/lib/message_list_view.dart @@ -3,7 +3,7 @@ import 'package:flutter/cupertino.dart'; import 'package:imessage/message_header.dart'; import 'package:imessage/message_input.dart'; import 'package:imessage/message_widget.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart' +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart' show Message, StreamChatCore; class MessageListView extends StatelessWidget { @@ -16,50 +16,68 @@ class MessageListView extends StatelessWidget { (Message message) => message.createdAt.toString().substring(0, 10)) .entries .toList(); - return Column( - children: [ - Expanded( - child: SizedBox( - height: MediaQuery.of(context).size.height * 0.9, - child: Align( - alignment: FractionalOffset.topCenter, - child: ListView.builder( - reverse: true, - itemCount: entries.length, - itemBuilder: (context, index) { - return Column( - children: [ - Padding( - padding: - const EdgeInsets.fromLTRB(8.0, 24.0, 8.0, 8.0), - child: MessageHeader( - rawTimeStamp: entries[index].key), //date - ), - ...entries[index] - .value //messages - .map((message) { - return MessageWidget( - alignment: isReceived(message, context) - ? Alignment.centerLeft - : Alignment.topRight, - color: isReceived(message, context) - ? CupertinoColors.systemGrey5 - : CupertinoColors.systemBlue, - messageColor: isReceived(message, context) - ? CupertinoColors.black - : CupertinoColors.white, - message: message, - ); - }) - .toList() - .reversed, - ], - ); - }), - )), - ), - MessageInput() - ], + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: Column( + children: [ + Expanded( + child: SizedBox( + height: MediaQuery.of(context).size.height * 0.9, + child: Align( + alignment: FractionalOffset.topCenter, + child: ListView.builder( + reverse: true, + itemCount: entries.length, + itemBuilder: (context, index) { + return Column( + children: [ + Padding( + padding: const EdgeInsets.fromLTRB( + 8.0, 24.0, 8.0, 8.0), + child: MessageHeader( + rawTimeStamp: entries[index].key), //date + ), + ...entries[index] + .value //messages + .asMap() + .entries + .map( + (entry) { + final message = entry.value; + final isFinalMessage = 0 == entry.key; + final received = + isReceived(message, context); + return (received) + ? MessageWidget( + alignment: Alignment.topRight, + margin: const EdgeInsets.fromLTRB( + 8.0, 4.0, 16.0, 4.0), + color: const Color(0xFF3CABFA), + messageColor: CupertinoColors.white, + message: message, + hasTail: isFinalMessage, + ) + : MessageWidget( + alignment: Alignment.centerLeft, + margin: const EdgeInsets.fromLTRB( + 16.0, 4.0, 8.0, 4.0), + color: CupertinoColors.systemGrey5, + messageColor: CupertinoColors.black, + message: message, + hasTail: isFinalMessage, + ); + }, + ) + .toList() + .reversed, + ], + ); + }), + )), + ), + const MessageInput() + ], + ), ); } diff --git a/packages/imessage/lib/message_page.dart b/packages/imessage/lib/message_page.dart index 08a20e0..23d15df 100644 --- a/packages/imessage/lib/message_page.dart +++ b/packages/imessage/lib/message_page.dart @@ -1,6 +1,7 @@ import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; import 'package:imessage/message_list_view.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart' +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart' show LazyLoadScrollView, MessageListController, @@ -12,6 +13,8 @@ import 'package:imessage/channel_image.dart'; import 'package:imessage/channel_name_text.dart'; class MessagePage extends StatelessWidget { + const MessagePage({Key? key}) : super(key: key); + @override Widget build(BuildContext context) { final streamChannel = StreamChannel.of(context); @@ -22,43 +25,46 @@ class MessagePage extends StatelessWidget { middle: Column( children: [ ChannelImage( - size: 25, + size: 32, channel: streamChannel.channel, ), ChannelNameText( - size: 16, channel: streamChannel.channel, + size: 10, + fontWeight: FontWeight.w300, ), ], ), - ), //ChannelHeader + ), child: StreamChatCore( - client: streamChannel.channel.client, - child: MessageListCore( - messageListController: messageListController, - loadingBuilder: (context) { - return Center( - child: CupertinoActivityIndicator(), - ); - }, - errorBuilder: (context, err) { - return Center( - child: Text('Error'), - ); - }, - emptyBuilder: (context) { - return Center( - child: Text('Nothing here...'), - ); - }, - messageListBuilder: (context, messages) => LazyLoadScrollView( - onStartOfPage: () async { - await messageListController.paginateData!(); - }, - child: MessageListView( - messages: messages, - ), - ))), + client: streamChannel.channel.client, + child: MessageListCore( + messageListController: messageListController, + loadingBuilder: (context) { + return const Center( + child: CupertinoActivityIndicator(), + ); + }, + errorBuilder: (context, err) { + return const Center( + child: Text('Error'), + ); + }, + emptyBuilder: (context) { + return const Center( + child: Text('Nothing here...'), + ); + }, + messageListBuilder: (context, messages) => LazyLoadScrollView( + onStartOfPage: () async { + await messageListController.paginateData!(); + }, + child: MessageListView( + messages: messages, + ), + ), + ), + ), ); } } diff --git a/packages/imessage/lib/message_widget.dart b/packages/imessage/lib/message_widget.dart index 8fa6638..d0ea9a4 100644 --- a/packages/imessage/lib/message_widget.dart +++ b/packages/imessage/lib/message_widget.dart @@ -1,21 +1,26 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/cupertino.dart'; import 'package:imessage/cutom_painter.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart' show Message; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart' + show Message; class MessageWidget extends StatelessWidget { final Alignment alignment; + final EdgeInsets margin; final Message message; final Color color; final Color messageColor; + final bool hasTail; - const MessageWidget( - {Key? key, - required this.alignment, - required this.message, - required this.color, - required this.messageColor}) - : super(key: key); + const MessageWidget({ + Key? key, + required this.alignment, + required this.margin, + required this.message, + required this.color, + required this.messageColor, + this.hasTail = false, + }) : super(key: key); @override Widget build(BuildContext context) { @@ -25,10 +30,13 @@ class MessageWidget extends StatelessWidget { color: color, message: message, messageColor: messageColor); } else { return MessageText( - alignment: alignment, - color: color, - message: message, - messageColor: messageColor); + alignment: alignment, + margin: margin, + color: color, + message: message, + messageColor: messageColor, + hasTail: hasTail, + ); } } } @@ -102,35 +110,45 @@ class MessageText extends StatelessWidget { const MessageText({ Key? key, required this.alignment, + required this.margin, required this.color, required this.message, required this.messageColor, + required this.hasTail, }) : super(key: key); final Alignment alignment; final Color color; final Message message; final Color messageColor; + final EdgeInsets margin; + final bool hasTail; @override Widget build(BuildContext context) { + if (message.text?.isEmpty ?? true) { + return const SizedBox.shrink(); + } return Padding( - padding: const EdgeInsets.all(8.0), + padding: const EdgeInsets.only(top: 1), child: Align( alignment: alignment, //Change this to Alignment.topRight or Alignment.topLeft child: CustomPaint( - painter: ChatBubble(color: color, alignment: alignment), + painter: + ChatBubble(color: color, alignment: alignment, hasTail: hasTail), child: Container( - margin: const EdgeInsets.fromLTRB(16.0, 8.0, 16.0, 8.0), + margin: margin, child: Row( mainAxisSize: MainAxisSize.min, children: [ - Container( + ConstrainedBox( constraints: BoxConstraints( - maxWidth: MediaQuery.of(context).size.width * 0.65), + maxWidth: MediaQuery.of(context).size.width * 0.65, + ), child: Padding( - padding: const EdgeInsets.all(4.0), + padding: const EdgeInsets.symmetric( + horizontal: 6.0, vertical: 4), child: Text( message.text!, style: TextStyle(color: messageColor), diff --git a/packages/imessage/lib/utils.dart b/packages/imessage/lib/utils.dart index 6487b35..e8d1d56 100644 --- a/packages/imessage/lib/utils.dart +++ b/packages/imessage/lib/utils.dart @@ -3,7 +3,7 @@ import 'package:intl/intl.dart'; import 'package:flutter/cupertino.dart'; String formatDate(DateTime date) { - final dateFormat = DateFormat.yMd().add_jm(); + final dateFormat = DateFormat.yMd(); return dateFormat.format(date); } @@ -12,7 +12,7 @@ String formatDateSameWeek(DateTime date) { if (date.day == DateTime.now().day) { dateFormat = DateFormat('hh:mm a'); } else { - dateFormat = DateFormat('EEEE, hh:mm a'); + dateFormat = DateFormat('EEEE'); } return dateFormat.format(date); } diff --git a/packages/imessage/pubspec.yaml b/packages/imessage/pubspec.yaml index c2605f6..4d98602 100644 --- a/packages/imessage/pubspec.yaml +++ b/packages/imessage/pubspec.yaml @@ -24,20 +24,20 @@ dependencies: flutter: sdk: flutter intl: ^0.17.0 - stream_chat_flutter: ^3.0.0 + stream_chat_flutter_core: ^3.0.0 animations: ^2.0.1 collection: ^1.15.0 cached_network_image: ^3.1.0 - - # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.3 + image_picker: ^0.8.4+4 dev_dependencies: flutter_test: sdk: flutter - pedantic: ^1.11.1 + flutter_lints: ^1.0.0 + # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/packages/imessage/test/widget_test.dart b/packages/imessage/test/widget_test.dart deleted file mode 100644 index af87e7c..0000000 --- a/packages/imessage/test/widget_test.dart +++ /dev/null @@ -1,30 +0,0 @@ -// This is a basic Flutter widget test. -// -// To perform an interaction with a widget in your test, use the WidgetTester -// utility that Flutter provides. For example, you can send tap and scroll -// gestures. You can also use WidgetTester to find child widgets in the widget -// tree, read text, and verify that the values of widget properties are correct. - -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -import 'package:imessage/main.dart'; - -void main() { - testWidgets('Counter increments smoke test', (WidgetTester tester) async { - // Build our app and trigger a frame. - await tester.pumpWidget(IMessage()); - - // Verify that our counter starts at 0. - expect(find.text('0'), findsOneWidget); - expect(find.text('1'), findsNothing); - - // Tap the '+' icon and trigger a frame. - await tester.tap(find.byIcon(Icons.add)); - await tester.pump(); - - // Verify that our counter has incremented. - expect(find.text('0'), findsNothing); - expect(find.text('1'), findsOneWidget); - }); -}