feat: update dependencies, ui and linting

This commit is contained in:
Gordon Hayes
2021-11-15 12:19:31 +01:00
parent 575fa4863b
commit f9df3647a0
16 changed files with 332 additions and 315 deletions
+25 -56
View File
@@ -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: # The following line activates a set of recommended lints for Flutter apps,
exclude: # packages, and plugins designed to encourage good coding practices.
- lib/**/*.g.dart include: package:flutter_lints/flutter.yaml
- example/*
- test/*
linter: 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: rules:
# these rules are documented on and in the same order as # avoid_print: false # Uncomment to disable the `avoid_print` rule
# the Dart Lint rules page to make maintenance easier # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# https://github.com/dart-lang/linter/blob/master/example/all.yaml
# - always_declare_return_types # Additional information about this file can be found at
# - always_specify_types # https://dart.dev/guides/language/analysis-options
# - 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
+7 -3
View File
@@ -1,11 +1,15 @@
import 'package:flutter/cupertino.dart'; 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'; import 'package:imessage/utils.dart';
class ChannelImage extends StatelessWidget { class ChannelImage extends StatelessWidget {
const ChannelImage({Key? key, required this.channel, required this.size}) const ChannelImage({
: super(key: key); Key? key,
required this.channel,
required this.size,
}) : super(key: key);
final Channel channel; final Channel channel;
final double size; final double size;
+3 -2
View File
@@ -2,12 +2,13 @@ import 'package:flutter/cupertino.dart';
import 'package:imessage/channel_preview.dart'; import 'package:imessage/channel_preview.dart';
import 'package:imessage/message_page.dart'; import 'package:imessage/message_page.dart';
import 'package:animations/animations.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; show Channel, StreamChannel;
class ChannelListView extends StatelessWidget { class ChannelListView extends StatelessWidget {
const ChannelListView({Key? key, required this.channels}) : super(key: key); const ChannelListView({Key? key, required this.channels}) : super(key: key);
final List<Channel> channels; final List<Channel> channels;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
channels.removeWhere((channel) => channel.lastMessageAt == null); channels.removeWhere((channel) => channel.lastMessageAt == null);
@@ -27,7 +28,7 @@ class ChannelListView extends StatelessWidget {
PageRouteBuilder( PageRouteBuilder(
pageBuilder: (_, __, ___) => StreamChannel( pageBuilder: (_, __, ___) => StreamChannel(
channel: channels[index], channel: channels[index],
child: MessagePage(), child: const MessagePage(),
), ),
transitionsBuilder: ( transitionsBuilder: (
_, _,
+5 -2
View File
@@ -1,15 +1,18 @@
import 'package:flutter/cupertino.dart'; 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 { class ChannelNameText extends StatelessWidget {
const ChannelNameText({ const ChannelNameText({
Key? key, Key? key,
required this.channel, required this.channel,
this.size = 17, this.size = 17,
this.fontWeight,
}) : super(key: key); }) : super(key: key);
final Channel channel; final Channel channel;
final double size; final double size;
final FontWeight? fontWeight;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -17,7 +20,7 @@ class ChannelNameText extends StatelessWidget {
channel.extraData['name'] as String? ?? 'No name', channel.extraData['name'] as String? ?? 'No name',
style: TextStyle( style: TextStyle(
fontSize: size, fontSize: size,
fontWeight: FontWeight.bold, fontWeight: fontWeight,
color: CupertinoColors.black, color: CupertinoColors.black,
), ),
); );
@@ -7,8 +7,11 @@ class ChannelPageAppBar extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return CupertinoSliverNavigationBar( return const CupertinoSliverNavigationBar(
largeTitle: Text('Messages'), largeTitle: Text(
'Messages',
style: TextStyle(letterSpacing: -1.3),
),
); );
} }
} }
+24 -23
View File
@@ -2,7 +2,8 @@ import 'package:flutter/cupertino.dart';
import 'package:imessage/channel_image.dart'; import 'package:imessage/channel_image.dart';
import 'package:imessage/channel_name_text.dart'; import 'package:imessage/channel_name_text.dart';
import 'package:imessage/utils.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'; import 'utils.dart';
@@ -23,7 +24,7 @@ class ChannelPreview extends StatelessWidget {
: null; : null;
final prefix = lastMessage?.attachments != null final prefix = lastMessage?.attachments != null
? lastMessage?.attachments //TODO: ugly ? lastMessage?.attachments
.map((e) { .map((e) {
if (e.type == 'image') { if (e.type == 'image') {
return '📷 '; return '📷 ';
@@ -35,13 +36,12 @@ class ChannelPreview extends StatelessWidget {
.where((e) => e != null) .where((e) => e != null)
.join(' ') .join(' ')
: ''; : '';
return GestureDetector( return GestureDetector(
onTap: onTap, onTap: onTap,
behavior: HitTestBehavior.opaque, behavior: HitTestBehavior.opaque,
child: Container( child: SizedBox(
constraints: BoxConstraints.tightFor( height: 70,
height: 90,
),
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 8.0, horizontal: 8.0,
@@ -50,11 +50,10 @@ class ChannelPreview extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Padding( Padding(
padding: padding: const EdgeInsets.only(left: 16.0, right: 8.0),
const EdgeInsets.symmetric(vertical: 16.0, horizontal: 8.0),
child: ChannelImage( child: ChannelImage(
channel: channel, channel: channel,
size: 50, size: 46,
), ),
), ),
Expanded( Expanded(
@@ -67,27 +66,29 @@ class ChannelPreview extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0), padding: const EdgeInsets.symmetric(horizontal: 4.0),
child: ChannelNameText( child: ChannelNameText(channel: channel),
channel: channel,
),
), ),
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0), padding: const EdgeInsets.symmetric(horizontal: 4.0),
child: Row( child: Row(
children: [ children: [
Text( Text(
isSameWeek(channel.lastMessageAt!) isSameWeek(channel.lastMessageAt!)
? formatDateSameWeek(channel.lastMessageAt!) ? formatDateSameWeek(channel.lastMessageAt!)
: formatDate(channel.lastMessageAt!), : formatDate(channel.lastMessageAt!),
style: TextStyle( style: const TextStyle(
fontSize: 15, fontSize: 13,
color: CupertinoColors.systemGrey, color: CupertinoColors.systemGrey,
), ),
), ),
Icon( const Padding(
CupertinoIcons.right_chevron, padding: EdgeInsets.only(left: 4.0),
color: CupertinoColors.systemGrey3, child: Icon(
CupertinoIcons.chevron_right,
size: 16,
color: CupertinoColors.systemGrey3,
),
), ),
], ],
), ),
@@ -95,19 +96,19 @@ class ChannelPreview extends StatelessWidget {
], ],
), ),
Padding( Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(4.0),
child: Text( child: Text(
'$prefix${lastMessage?.text ?? ''}', '$prefix${lastMessage?.text ?? ''}',
style: TextStyle( style: const TextStyle(
fontWeight: FontWeight.normal, fontWeight: FontWeight.normal,
color: CupertinoColors.systemGrey, color: CupertinoColors.systemGrey,
fontSize: 16, fontSize: 14,
), ),
maxLines: 2, maxLines: 2,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
), ),
Divider(), const Divider(),
], ],
), ),
) )
+90 -63
View File
@@ -3,82 +3,109 @@ import 'package:flutter/cupertino.dart';
class ChatBubble extends CustomPainter { class ChatBubble extends CustomPainter {
final Color color; final Color color;
final Alignment? alignment; final Alignment? alignment;
final bool hasTail;
ChatBubble({ ChatBubble({
required this.color, required this.color,
required this.hasTail,
this.alignment, this.alignment,
}); }) : paintFill = Paint()
..color = color
..style = PaintingStyle.fill;
final _radius = 10.0; final Paint paintFill;
final _x = 10.0;
@override @override
void paint(Canvas canvas, Size size) { 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) { if (alignment == Alignment.topRight) {
canvas.drawRRect( path.moveTo(0, cornerSize);
RRect.fromLTRBAndCorners( path.lineTo(0, size.height - cornerSize);
0, path.arcToPoint(Offset(cornerSize, size.height),
0, radius: const Radius.circular(cornerSize), clockwise: false);
size.width - 8,
size.height, if (hasTail) {
bottomLeft: Radius.circular(_radius), path.lineTo(size.width - cornerSize - innerTailWidth, size.height);
topRight: Radius.circular(_radius), path.arcToPoint(
topLeft: Radius.circular(_radius), Offset(size.width - buffer - innerTailWidth,
), size.height - innerTailHeight),
Paint() radius: const Radius.circular(cornerSize),
..color = color clockwise: false);
..style = PaintingStyle.fill);
var path = Path(); path.arcToPoint(Offset(size.width, size.height),
path.moveTo(size.width - _x, size.height - 20); radius: const Radius.circular(buffer * 2), clockwise: false);
path.lineTo(size.width - _x, size.height);
path.lineTo(size.width, size.height); path.arcToPoint(
canvas.clipPath(path); Offset(size.width - buffer, size.height - buffer - innerTailHeight),
canvas.drawRRect( radius: const Radius.circular(buffer + innerTailHeight),
RRect.fromLTRBAndCorners( clockwise: true);
size.width - _x, } else {
0.0, path.lineTo(size.width - cornerSize, size.height);
size.width, path.arcToPoint(Offset(size.width - buffer, size.height - cornerSize),
size.height, radius: const Radius.circular(cornerSize), clockwise: false);
topRight: Radius.circular(_radius), }
),
Paint() path.lineTo(size.width - buffer, cornerSize);
..color = color
..style = PaintingStyle.fill); 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 { } else {
canvas.drawRRect( path.moveTo(size.width, cornerSize);
RRect.fromLTRBAndCorners( path.arcToPoint(Offset(size.width - cornerSize, 0),
_x, radius: const Radius.circular(cornerSize), clockwise: false);
0,
size.width, path.lineTo(cornerSize + buffer, 0);
size.height,
bottomRight: Radius.circular(_radius), path.arcToPoint(const Offset(buffer, cornerSize),
topRight: Radius.circular(_radius), radius: const Radius.circular(cornerSize), clockwise: false);
topLeft: Radius.circular(_radius),
), if (hasTail) {
Paint() path.lineTo(buffer, size.height - buffer - innerTailHeight);
..color = color
..style = PaintingStyle.fill); path.arcToPoint(Offset(0, size.height),
var path = Path(); radius: const Radius.circular(buffer + innerTailHeight),
path.moveTo(0, size.height); clockwise: true);
path.lineTo(_x, size.height);
path.lineTo(_x, size.height - 20); path.arcToPoint(
canvas.clipPath(path); Offset(buffer + innerTailWidth, size.height - innerTailHeight),
canvas.drawRRect( radius: const Radius.circular(buffer * 2),
RRect.fromLTRBAndCorners( clockwise: false);
0,
0.0, path.arcToPoint(Offset(cornerSize + innerTailWidth, size.height),
_x, radius: const Radius.circular(cornerSize), clockwise: false);
size.height, } else {
topRight: Radius.circular(_radius), path.lineTo(buffer, size.height - cornerSize);
), path.arcToPoint(Offset(buffer + cornerSize, size.height),
Paint() radius: const Radius.circular(cornerSize), clockwise: false);
..color = color }
..style = PaintingStyle.fill);
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 @override
bool shouldRepaint(CustomPainter oldDelegate) { bool shouldRepaint(ChatBubble oldDelegate) {
return true; if (color != oldDelegate.color ||
alignment != oldDelegate.alignment ||
hasTail != oldDelegate.hasTail) {
return true;
} else {
return false;
}
} }
} }
+12 -11
View File
@@ -1,19 +1,20 @@
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:intl/date_symbol_data_local.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_list_view.dart';
import 'package:imessage/channel_page_appbar.dart'; import 'package:imessage/channel_page_appbar.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
Future<void> main() async { Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
final client = StreamChatClient('b67pax5b2wdq', logLevel: Level.INFO); // final client = StreamChatClient('b67pax5b2wdq', logLevel: Level.INFO); //
// For demonstration purposes. Fixed user and token.
await client.connectUser( await client.connectUser(
User( User(
id: 'cool-shadow-7', id: 'cool-shadow-7',
extraData: { extraData: const {
'image': 'image':
'https://getstream.io/random_png/?id=cool-shadow-7&amp;name=Cool+shadow', 'https://getstream.io/random_png/?id=cool-shadow-7&amp;name=Cool+shadow',
}, },
@@ -25,9 +26,9 @@ Future<void> main() async {
} }
class IMessage extends StatelessWidget { 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -35,7 +36,7 @@ class IMessage extends StatelessWidget {
return CupertinoApp( return CupertinoApp(
title: 'Flutter Demo', title: 'Flutter Demo',
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
theme: CupertinoThemeData(brightness: Brightness.light), theme: const CupertinoThemeData(brightness: Brightness.light),
home: StreamChatCore(client: client, child: ChatLoader()), home: StreamChatCore(client: client, child: ChatLoader()),
); );
} }
@@ -59,15 +60,15 @@ class ChatLoader extends StatelessWidget {
Filter.in_('members', [user.id]), Filter.in_('members', [user.id]),
Filter.equal('type', 'messaging'), Filter.equal('type', 'messaging'),
]), ]),
sort: [SortOption('last_message_at')], sort: const [SortOption('last_message_at')],
limit: 20, limit: 20,
emptyBuilder: (BuildContext context) { emptyBuilder: (BuildContext context) {
return Center( return const Center(
child: Text('Looks like you are not in any channels'), child: Text('Looks like you are not in any channels'),
); );
}, },
loadingBuilder: (BuildContext context) { loadingBuilder: (BuildContext context) {
return Center( return const Center(
child: SizedBox( child: SizedBox(
height: 100.0, height: 100.0,
width: 100.0, width: 100.0,
@@ -76,7 +77,7 @@ class ChatLoader extends StatelessWidget {
); );
}, },
errorBuilder: (BuildContext context, dynamic error) { errorBuilder: (BuildContext context, dynamic error) {
return Center( return const Center(
child: Text( child: Text(
'Oh no, something went wrong. Please check your config.'), 'Oh no, something went wrong. Please check your config.'),
); );
@@ -94,7 +95,7 @@ class ChatLoader extends StatelessWidget {
CupertinoSliverRefreshControl(onRefresh: () async { CupertinoSliverRefreshControl(onRefresh: () async {
return channelListController.loadData!(); return channelListController.loadData!();
}), }),
ChannelPageAppBar(), const ChannelPageAppBar(),
SliverPadding( SliverPadding(
sliver: ChannelListView(channels: channels), sliver: ChannelListView(channels: channels),
padding: const EdgeInsets.only(top: 16), padding: const EdgeInsets.only(top: 16),
+3 -2
View File
@@ -9,9 +9,10 @@ class MessageHeader extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final receivedAt = DateTime.parse(rawTimeStamp); final receivedAt = DateTime.parse(rawTimeStamp);
final textStyle = TextStyle( const textStyle = TextStyle(
color: CupertinoColors.systemGrey, color: CupertinoColors.systemGrey,
fontSize: 14, fontWeight: FontWeight.w400,
fontSize: 11,
); );
return isSameWeek(receivedAt) return isSameWeek(receivedAt)
? Text( ? Text(
+17 -22
View File
@@ -2,7 +2,7 @@ import 'dart:io';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:image_picker/image_picker.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; show Attachment, AttachmentFile, Message, StreamChannel;
class MessageInput extends StatefulWidget { class MessageInput extends StatefulWidget {
@@ -59,28 +59,27 @@ class _MessageInputState extends State<MessageInput> {
); );
await channel.sendMessage(message); await channel.sendMessage(message);
}, },
child: Padding( child: const Padding(
padding: const EdgeInsets.all(8.0), padding: EdgeInsets.all(8.0),
child: Icon( child: Icon(
CupertinoIcons.camera_fill, CupertinoIcons.camera_fill,
color: CupertinoColors.systemGrey, color: CupertinoColors.systemGrey,
size: 35, size: 32,
), ),
), ),
), ),
Expanded( Expanded(
child: CupertinoTextField( child: CupertinoTextField(
controller: textController, controller: textController,
maxLines: 10,
minLines: 1,
onSubmitted: (input) async { onSubmitted: (input) async {
await sendMessage(context, input); await sendMessage(context, input);
}, },
placeholder: 'Text Message', placeholder: 'iMessage',
prefix: Padding( padding:
padding: const EdgeInsets.all(8.0), const EdgeInsets.symmetric(vertical: 8, horizontal: 12),
child: Text( suffixMode: OverlayVisibilityMode.editing,
'',
), //trick to add padding around placeholder iMessage text
),
suffix: GestureDetector( suffix: GestureDetector(
onTap: () async { onTap: () async {
if (textController.value.text.isNotEmpty) { if (textController.value.text.isNotEmpty) {
@@ -88,20 +87,16 @@ class _MessageInputState extends State<MessageInput> {
textController.clear(); textController.clear();
} }
}, },
child: Padding( child: const Icon(
padding: const EdgeInsets.all(8.0), CupertinoIcons.arrow_up_circle_fill,
child: Icon( color: CupertinoColors.activeBlue,
CupertinoIcons.arrow_up_circle_fill, size: 35,
color: CupertinoColors.activeGreen,
size: 35,
),
), ),
), ),
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border.all( border: Border.all(color: CupertinoColors.systemGrey),
color: CupertinoColors.systemGrey, borderRadius: const BorderRadius.all(Radius.circular(35)),
), ),
borderRadius: BorderRadius.all(Radius.circular(35))),
), ),
), ),
], ],
+63 -45
View File
@@ -3,7 +3,7 @@ import 'package:flutter/cupertino.dart';
import 'package:imessage/message_header.dart'; import 'package:imessage/message_header.dart';
import 'package:imessage/message_input.dart'; import 'package:imessage/message_input.dart';
import 'package:imessage/message_widget.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; show Message, StreamChatCore;
class MessageListView extends StatelessWidget { class MessageListView extends StatelessWidget {
@@ -16,50 +16,68 @@ class MessageListView extends StatelessWidget {
(Message message) => message.createdAt.toString().substring(0, 10)) (Message message) => message.createdAt.toString().substring(0, 10))
.entries .entries
.toList(); .toList();
return Column( return Padding(
children: [ padding: const EdgeInsets.symmetric(horizontal: 8.0),
Expanded( child: Column(
child: SizedBox( children: [
height: MediaQuery.of(context).size.height * 0.9, Expanded(
child: Align( child: SizedBox(
alignment: FractionalOffset.topCenter, height: MediaQuery.of(context).size.height * 0.9,
child: ListView.builder( child: Align(
reverse: true, alignment: FractionalOffset.topCenter,
itemCount: entries.length, child: ListView.builder(
itemBuilder: (context, index) { reverse: true,
return Column( itemCount: entries.length,
children: [ itemBuilder: (context, index) {
Padding( return Column(
padding: children: [
const EdgeInsets.fromLTRB(8.0, 24.0, 8.0, 8.0), Padding(
child: MessageHeader( padding: const EdgeInsets.fromLTRB(
rawTimeStamp: entries[index].key), //date 8.0, 24.0, 8.0, 8.0),
), child: MessageHeader(
...entries[index] rawTimeStamp: entries[index].key), //date
.value //messages ),
.map((message) { ...entries[index]
return MessageWidget( .value //messages
alignment: isReceived(message, context) .asMap()
? Alignment.centerLeft .entries
: Alignment.topRight, .map(
color: isReceived(message, context) (entry) {
? CupertinoColors.systemGrey5 final message = entry.value;
: CupertinoColors.systemBlue, final isFinalMessage = 0 == entry.key;
messageColor: isReceived(message, context) final received =
? CupertinoColors.black isReceived(message, context);
: CupertinoColors.white, return (received)
message: message, ? MessageWidget(
); alignment: Alignment.topRight,
}) margin: const EdgeInsets.fromLTRB(
.toList() 8.0, 4.0, 16.0, 4.0),
.reversed, color: const Color(0xFF3CABFA),
], messageColor: CupertinoColors.white,
); message: message,
}), hasTail: isFinalMessage,
)), )
), : MessageWidget(
MessageInput() 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()
],
),
); );
} }
+36 -30
View File
@@ -1,6 +1,7 @@
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:imessage/message_list_view.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 show
LazyLoadScrollView, LazyLoadScrollView,
MessageListController, MessageListController,
@@ -12,6 +13,8 @@ import 'package:imessage/channel_image.dart';
import 'package:imessage/channel_name_text.dart'; import 'package:imessage/channel_name_text.dart';
class MessagePage extends StatelessWidget { class MessagePage extends StatelessWidget {
const MessagePage({Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final streamChannel = StreamChannel.of(context); final streamChannel = StreamChannel.of(context);
@@ -22,43 +25,46 @@ class MessagePage extends StatelessWidget {
middle: Column( middle: Column(
children: [ children: [
ChannelImage( ChannelImage(
size: 25, size: 32,
channel: streamChannel.channel, channel: streamChannel.channel,
), ),
ChannelNameText( ChannelNameText(
size: 16,
channel: streamChannel.channel, channel: streamChannel.channel,
size: 10,
fontWeight: FontWeight.w300,
), ),
], ],
), ),
), //ChannelHeader ),
child: StreamChatCore( child: StreamChatCore(
client: streamChannel.channel.client, client: streamChannel.channel.client,
child: MessageListCore( child: MessageListCore(
messageListController: messageListController, messageListController: messageListController,
loadingBuilder: (context) { loadingBuilder: (context) {
return Center( return const Center(
child: CupertinoActivityIndicator(), child: CupertinoActivityIndicator(),
); );
}, },
errorBuilder: (context, err) { errorBuilder: (context, err) {
return Center( return const Center(
child: Text('Error'), child: Text('Error'),
); );
}, },
emptyBuilder: (context) { emptyBuilder: (context) {
return Center( return const Center(
child: Text('Nothing here...'), child: Text('Nothing here...'),
); );
}, },
messageListBuilder: (context, messages) => LazyLoadScrollView( messageListBuilder: (context, messages) => LazyLoadScrollView(
onStartOfPage: () async { onStartOfPage: () async {
await messageListController.paginateData!(); await messageListController.paginateData!();
}, },
child: MessageListView( child: MessageListView(
messages: messages, messages: messages,
), ),
))), ),
),
),
); );
} }
} }
+36 -18
View File
@@ -1,21 +1,26 @@
import 'package:cached_network_image/cached_network_image.dart'; import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:imessage/cutom_painter.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 { class MessageWidget extends StatelessWidget {
final Alignment alignment; final Alignment alignment;
final EdgeInsets margin;
final Message message; final Message message;
final Color color; final Color color;
final Color messageColor; final Color messageColor;
final bool hasTail;
const MessageWidget( const MessageWidget({
{Key? key, Key? key,
required this.alignment, required this.alignment,
required this.message, required this.margin,
required this.color, required this.message,
required this.messageColor}) required this.color,
: super(key: key); required this.messageColor,
this.hasTail = false,
}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -25,10 +30,13 @@ class MessageWidget extends StatelessWidget {
color: color, message: message, messageColor: messageColor); color: color, message: message, messageColor: messageColor);
} else { } else {
return MessageText( return MessageText(
alignment: alignment, alignment: alignment,
color: color, margin: margin,
message: message, color: color,
messageColor: messageColor); message: message,
messageColor: messageColor,
hasTail: hasTail,
);
} }
} }
} }
@@ -102,35 +110,45 @@ class MessageText extends StatelessWidget {
const MessageText({ const MessageText({
Key? key, Key? key,
required this.alignment, required this.alignment,
required this.margin,
required this.color, required this.color,
required this.message, required this.message,
required this.messageColor, required this.messageColor,
required this.hasTail,
}) : super(key: key); }) : super(key: key);
final Alignment alignment; final Alignment alignment;
final Color color; final Color color;
final Message message; final Message message;
final Color messageColor; final Color messageColor;
final EdgeInsets margin;
final bool hasTail;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (message.text?.isEmpty ?? true) {
return const SizedBox.shrink();
}
return Padding( return Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.only(top: 1),
child: Align( child: Align(
alignment: alignment:
alignment, //Change this to Alignment.topRight or Alignment.topLeft alignment, //Change this to Alignment.topRight or Alignment.topLeft
child: CustomPaint( child: CustomPaint(
painter: ChatBubble(color: color, alignment: alignment), painter:
ChatBubble(color: color, alignment: alignment, hasTail: hasTail),
child: Container( child: Container(
margin: const EdgeInsets.fromLTRB(16.0, 8.0, 16.0, 8.0), margin: margin,
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Container( ConstrainedBox(
constraints: BoxConstraints( constraints: BoxConstraints(
maxWidth: MediaQuery.of(context).size.width * 0.65), maxWidth: MediaQuery.of(context).size.width * 0.65,
),
child: Padding( child: Padding(
padding: const EdgeInsets.all(4.0), padding: const EdgeInsets.symmetric(
horizontal: 6.0, vertical: 4),
child: Text( child: Text(
message.text!, message.text!,
style: TextStyle(color: messageColor), style: TextStyle(color: messageColor),
+2 -2
View File
@@ -3,7 +3,7 @@ import 'package:intl/intl.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
String formatDate(DateTime date) { String formatDate(DateTime date) {
final dateFormat = DateFormat.yMd().add_jm(); final dateFormat = DateFormat.yMd();
return dateFormat.format(date); return dateFormat.format(date);
} }
@@ -12,7 +12,7 @@ String formatDateSameWeek(DateTime date) {
if (date.day == DateTime.now().day) { if (date.day == DateTime.now().day) {
dateFormat = DateFormat('hh:mm a'); dateFormat = DateFormat('hh:mm a');
} else { } else {
dateFormat = DateFormat('EEEE, hh:mm a'); dateFormat = DateFormat('EEEE');
} }
return dateFormat.format(date); return dateFormat.format(date);
} }
+4 -4
View File
@@ -24,20 +24,20 @@ dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
intl: ^0.17.0 intl: ^0.17.0
stream_chat_flutter: ^3.0.0 stream_chat_flutter_core: ^3.0.0
animations: ^2.0.1 animations: ^2.0.1
collection: ^1.15.0 collection: ^1.15.0
cached_network_image: ^3.1.0 cached_network_image: ^3.1.0
# The following adds the Cupertino Icons font to your application. # The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons. # Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.3 cupertino_icons: ^1.0.3
image_picker: ^0.8.4+4
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
pedantic: ^1.11.1 flutter_lints: ^1.0.0
# For information on the generic Dart part of this file, see the # For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec # following page: https://dart.dev/tools/pub/pubspec
-30
View File
@@ -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);
});
}