feat: update dependencies, ui and linting
This commit is contained in:
@@ -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
|
||||
# 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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<Channel> 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: (
|
||||
_,
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -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),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<void> 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<void> 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),
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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<MessageInput> {
|
||||
);
|
||||
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<MessageInput> {
|
||||
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)),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -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()
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user