Improve repo structure (#22)

* app updated

* readme updated

* rename base folder

* update chatty's readme

* move apps to packages dir

* update repo overview

Co-authored-by: diegoveloper <[email protected]>
This commit is contained in:
Neevash Ramdial (Nash)
2021-03-10 15:18:00 -04:00
committed by GitHub
co-authored by diegoveloper
parent bd6bb66279
commit 030c3eb428
290 changed files with 3804 additions and 4 deletions
+25
View File
@@ -0,0 +1,25 @@
import 'package:flutter/cupertino.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.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);
final Channel channel;
final double size;
@override
Widget build(BuildContext context) {
final avatarUrl = channel.extraData.containsKey('image') &&
(channel.extraData['image'] as String).isNotEmpty
? channel.extraData['image'] as String
: 'https://4.bp.blogspot.com/-Jx21kNqFSTU/UXemtqPhZCI/AAAAAAAAh74/BMGSzpU6F48/s1600/funny-cat-pictures-047-001.jpg';
return CupertinoCircleAvatar(
size: size,
url: avatarUrl,
);
}
}
@@ -0,0 +1,54 @@
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'
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);
return SliverList(
delegate: SliverChildBuilderDelegate(
(
BuildContext context,
int index,
) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4.0),
child: ChannelPreview(
channel: channels[index],
onTap: () {
Navigator.push(
context,
PageRouteBuilder(
pageBuilder: (_, __, ___) => StreamChannel(
channel: channels[index],
child: MessagePage(),
),
transitionsBuilder: (
_,
animation,
secondaryAnimation,
child,
) =>
SharedAxisTransition(
child: child,
animation: animation,
secondaryAnimation: secondaryAnimation,
transitionType: SharedAxisTransitionType.horizontal,
),
),
);
},
),
);
},
childCount: channels.length,
),
);
}
}
@@ -0,0 +1,25 @@
import 'package:flutter/cupertino.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart' show Channel;
class ChannelNameText extends StatelessWidget {
const ChannelNameText({
Key key,
@required this.channel,
this.size = 17,
}) : super(key: key);
final Channel channel;
final double size;
@override
Widget build(BuildContext context) {
return Text(
channel.extraData['name'] as String ?? 'No name',
style: TextStyle(
fontSize: size,
fontWeight: FontWeight.bold,
color: CupertinoColors.black,
),
);
}
}
@@ -0,0 +1,14 @@
import 'package:flutter/cupertino.dart';
class ChannelPageAppBar extends StatelessWidget {
const ChannelPageAppBar({
Key key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return CupertinoSliverNavigationBar(
largeTitle: Text('Messages'),
);
}
}
+119
View File
@@ -0,0 +1,119 @@
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 'utils.dart';
class ChannelPreview extends StatelessWidget {
final VoidCallback onTap;
final Channel channel;
const ChannelPreview({
Key key,
@required this.onTap,
@required this.channel,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final lastMessage =
channel.state.messages.isNotEmpty ? channel.state.messages.last : null;
final prefix = lastMessage?.attachments != null
? lastMessage?.attachments //TODO: ugly
?.map((e) {
if (e.type == 'image') {
return '📷 ';
} else if (e.type == 'video') {
return '🎬 ';
}
return null;
})
?.where((e) => e != null)
?.join(' ')
: '';
return GestureDetector(
onTap: onTap,
behavior: HitTestBehavior.opaque,
child: Container(
constraints: BoxConstraints.tightFor(
height: 90,
),
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8.0,
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding:
const EdgeInsets.symmetric(vertical: 16.0, horizontal: 8.0),
child: ChannelImage(
channel: channel,
size: 50,
),
),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: ChannelNameText(
channel: channel,
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Row(
children: [
Text(
isSameWeek(channel.lastMessageAt)
? formatDateSameWeek(channel.lastMessageAt)
: formatDate(channel.lastMessageAt),
style: TextStyle(
fontSize: 15,
color: CupertinoColors.systemGrey,
),
),
Icon(
CupertinoIcons.right_chevron,
color: CupertinoColors.systemGrey3,
),
],
),
)
],
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'$prefix${lastMessage?.text ?? ''}',
style: TextStyle(
fontWeight: FontWeight.normal,
color: CupertinoColors.systemGrey,
fontSize: 16,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
Divider(),
],
),
)
],
),
),
),
);
}
}
+84
View File
@@ -0,0 +1,84 @@
import 'package:flutter/cupertino.dart';
class ChatBubble extends CustomPainter {
final Color color;
final Alignment alignment;
ChatBubble({
@required this.color,
this.alignment,
});
final _radius = 10.0;
final _x = 10.0;
@override
void paint(Canvas canvas, Size size) {
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);
} 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);
}
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
+121
View File
@@ -0,0 +1,121 @@
import 'package:flutter/cupertino.dart';
import 'package:intl/date_symbol_data_local.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart'
show
Channel,
ChannelListController,
ChannelListCore,
ChannelsBloc,
LazyLoadScrollView,
Level,
PaginationParams,
SortOption,
StreamChatClient,
StreamChatCore,
User;
import 'package:imessage/channel_list_view.dart';
import 'package:imessage/channel_page_appbar.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
final client = StreamChatClient('b67pax5b2wdq', logLevel: Level.INFO); //
await client.connectUser(
User(
id: 'cool-shadow-7',
extraData: {
'image':
'https://getstream.io/random_png/?id=cool-shadow-7&amp;name=Cool+shadow',
},
),
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiY29vbC1zaGFkb3ctNyJ9.gkOlCRb1qgy4joHPaxFwPOdXcGvSPvp6QY0S4mpRkVo',
);
runApp(IMessage(client: client));
}
class IMessage extends StatelessWidget {
final StreamChatClient client;
IMessage({@required this.client});
@override
Widget build(BuildContext context) {
initializeDateFormatting('en_US', null);
return CupertinoApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: CupertinoThemeData(brightness: Brightness.light),
home: StreamChatCore(client: client, child: ChatLoader()),
);
}
}
class ChatLoader extends StatelessWidget {
ChatLoader({
Key key,
}) : super(key: key);
final channelListController = ChannelListController();
@override
Widget build(BuildContext context) {
final user = StreamChatCore.of(context).user;
return CupertinoPageScaffold(
child: ChannelsBloc(
child: ChannelListCore(
channelListController: channelListController,
filter: {
'members': {
r'$in': [user.id],
},
'type': {
r'$eq': 'messaging',
},
},
sort: [SortOption('last_message_at')],
pagination: PaginationParams(
limit: 20,
),
emptyBuilder: (BuildContext context) {
return Center(
child: Text('Looks like you are not in any channels'),
);
},
loadingBuilder: (BuildContext context) {
return Center(
child: SizedBox(
height: 100.0,
width: 100.0,
child: CupertinoActivityIndicator(),
),
);
},
errorBuilder: (BuildContext context, dynamic error) {
return Center(
child: Text(
'Oh no, something went wrong. Please check your config.'),
);
},
listBuilder: (
BuildContext context,
List<Channel> channels,
) =>
LazyLoadScrollView(
onEndOfPage: () async {
channelListController.paginateData();
},
child: CustomScrollView(
slivers: [
CupertinoSliverRefreshControl(onRefresh: () async {
channelListController.loadData();
}),
ChannelPageAppBar(),
SliverPadding(
sliver: ChannelListView(channels: channels),
padding: const EdgeInsets.only(top: 16),
)
],
),
))));
}
}
+26
View File
@@ -0,0 +1,26 @@
import 'package:flutter/cupertino.dart';
import 'package:imessage/utils.dart';
class MessageHeader extends StatelessWidget {
final String rawTimeStamp;
const MessageHeader({Key key, @required this.rawTimeStamp}) : super(key: key);
@override
Widget build(BuildContext context) {
final receivedAt = DateTime.parse(rawTimeStamp);
final textStyle = TextStyle(
color: CupertinoColors.systemGrey,
fontSize: 14,
);
return isSameWeek(receivedAt)
? Text(
formatDateSameWeek(receivedAt),
style: textStyle,
)
: Text(
formatDate(receivedAt),
style: textStyle,
);
}
}
+108
View File
@@ -0,0 +1,108 @@
import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:image_picker/image_picker.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart'
show Attachment, AttachmentFile, Message, MultipartFile, StreamChannel;
class MessageInput extends StatefulWidget {
const MessageInput({
Key key,
}) : super(key: key);
@override
_MessageInputState createState() => _MessageInputState();
}
class _MessageInputState extends State<MessageInput> {
final textController = TextEditingController();
File _image;
final picker = ImagePicker();
@override
void initState() {
super.initState();
textController.addListener(() {
setState(() {});
});
}
@override
Widget build(BuildContext context) {
return Align(
alignment: FractionalOffset.bottomCenter,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 32.0),
child: Row(
children: [
GestureDetector(
onTap: () async {
final pickedFile =
await picker.getImage(source: ImageSource.gallery);
final bytes = await File(pickedFile.path).readAsBytes();
final channel = StreamChannel.of(context).channel;
final message =
Message(text: textController.value.text, attachments: [
Attachment(
type: 'image',
file: AttachmentFile(bytes: bytes, path: pickedFile.path),
),
]);
await channel.sendMessage(message);
},
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Icon(
CupertinoIcons.camera_fill,
color: CupertinoColors.systemGrey,
size: 35,
),
),
),
Expanded(
child: CupertinoTextField(
controller: textController,
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
),
suffix: GestureDetector(
onTap: () async {
if (textController.value.text.isNotEmpty) {
await sendMessage(context, textController.value.text);
textController.clear();
}
},
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Icon(
CupertinoIcons.arrow_up_circle_fill,
color: CupertinoColors.activeGreen,
size: 35,
),
),
),
decoration: BoxDecoration(
border: Border.all(
color: CupertinoColors.systemGrey,
),
borderRadius: BorderRadius.all(Radius.circular(35))),
),
),
],
),
),
);
}
Future<void> sendMessage(BuildContext context, String input) async {
final streamChannel = StreamChannel.of(context);
await streamChannel.channel.sendMessage(Message(text: input.trim()));
}
}
@@ -0,0 +1,73 @@
import 'package:collection/collection.dart';
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'
show Message, StreamChatCore;
class MessageListView extends StatelessWidget {
const MessageListView({Key key, this.messages}) : super(key: key);
final List<Message> messages;
@override
Widget build(BuildContext context) {
final entries = groupBy(messages,
(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()
],
);
}
bool isReceived(Message message, BuildContext context) {
final currentUserId = StreamChatCore.of(context).user.id;
return message.user.id == currentUserId;
}
bool isSameDay(Message message) =>
message.createdAt.day == DateTime.now().day;
}
+64
View File
@@ -0,0 +1,64 @@
import 'package:flutter/cupertino.dart';
import 'package:imessage/message_list_view.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart'
show
LazyLoadScrollView,
MessageListController,
MessageListCore,
StreamChannel,
StreamChatCore;
import 'package:imessage/channel_image.dart';
import 'package:imessage/channel_name_text.dart';
class MessagePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final streamChannel = StreamChannel.of(context);
var messageListController = MessageListController();
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Column(
children: [
ChannelImage(
size: 25,
channel: streamChannel.channel,
),
ChannelNameText(
size: 16,
channel: streamChannel.channel,
),
],
),
), //ChannelHeader
child: StreamChatCore(
client: streamChannel.channel.client,
child: MessageListCore(
messageListController: messageListController,
loadingBuilder: (context) {
return Center(
child: CupertinoActivityIndicator(),
);
},
errorWidgetBuilder: (context, err) {
return Center(
child: Text('Error'),
);
},
emptyBuilder: (context) {
return Center(
child: Text('Nothing here...'),
);
},
messageListBuilder: (context, messages) => LazyLoadScrollView(
onStartOfPage: () async {
messageListController.paginateData();
},
child: MessageListView(
messages: messages,
),
))),
);
}
}
+148
View File
@@ -0,0 +1,148 @@
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, AttachmentUploadStateBuilder;
class MessageWidget extends StatelessWidget {
final Alignment alignment;
final Message message;
final Color color;
final Color messageColor;
const MessageWidget(
{Key key,
@required this.alignment,
@required this.message,
@required this.color,
@required this.messageColor})
: super(key: key);
@override
Widget build(BuildContext context) {
if (message.attachments?.isNotEmpty == true &&
message.attachments.first.type == 'image') {
return MessageImage(
color: color, message: message, messageColor: messageColor);
} else {
return MessageText(
alignment: alignment,
color: color,
message: message,
messageColor: messageColor);
}
}
}
class MessageImage extends StatelessWidget {
const MessageImage({
Key key,
@required this.color,
@required this.message,
@required this.messageColor,
}) : super(key: key);
final Color color;
final Message message;
final Color messageColor;
@override
Widget build(BuildContext context) {
if (message.text != null) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(20),
child: Container(
color: color,
child: Column(
children: [
if (message.attachments.first.file != null)
Image.memory(
message.attachments.first.file.bytes,
fit: BoxFit.cover,
)
else
CachedNetworkImage(
imageUrl: message.attachments.first.thumbUrl ??
message.attachments.first.imageUrl ??
message.attachments.first.assetUrl,
),
if (message.attachments.first?.title != null)
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(message.attachments.first.title,
style: TextStyle(color: messageColor)),
),
message.attachments.first.pretext != null
? Text(message.attachments.first.pretext)
: Container()
],
),
),
)
],
),
);
} else {
return ClipRRect(
borderRadius: BorderRadius.circular(20),
child: Container(
color: color,
child: CachedNetworkImage(
imageUrl: message.attachments.first.thumbUrl,
)),
);
}
}
}
class MessageText extends StatelessWidget {
const MessageText({
Key key,
@required this.alignment,
@required this.color,
@required this.message,
@required this.messageColor,
}) : super(key: key);
final Alignment alignment;
final Color color;
final Message message;
final Color messageColor;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Align(
alignment:
alignment, //Change this to Alignment.topRight or Alignment.topLeft
child: CustomPaint(
painter: ChatBubble(color: color, alignment: alignment),
child: Container(
margin: const EdgeInsets.fromLTRB(16.0, 8.0, 16.0, 8.0),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
constraints: BoxConstraints(
maxWidth: MediaQuery.of(context).size.width * 0.65),
child: Padding(
padding: const EdgeInsets.all(4.0),
child: Text(
message.text,
style: TextStyle(color: messageColor),
),
),
),
],
),
),
),
),
);
}
}
+70
View File
@@ -0,0 +1,70 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:intl/intl.dart';
import 'package:flutter/cupertino.dart';
String formatDate(DateTime date) {
final dateFormat = DateFormat.yMd().add_jm();
return dateFormat.format(date);
}
String formatDateSameWeek(DateTime date) {
DateFormat dateFormat;
if (date.day == DateTime.now().day) {
dateFormat = DateFormat('hh:mm a');
} else {
dateFormat = DateFormat('EEEE, hh:mm a');
}
return dateFormat.format(date);
}
String formatDateMessage(DateTime date) {
final dateFormat = DateFormat('EEE. MMM. d ' 'yy' ' hh:mm a');
return dateFormat.format(date);
}
bool isSameWeek(DateTime timestamp) =>
DateTime.now().difference(timestamp).inDays < 7;
class CupertinoCircleAvatar extends StatelessWidget {
final String url;
final double size;
const CupertinoCircleAvatar({Key key, this.url, this.size}) : super(key: key);
@override
Widget build(BuildContext context) {
return ClipRRect(
borderRadius: BorderRadius.circular(size / 2),
child: CachedNetworkImage(
imageUrl: url,
height: size,
width: size,
fit: BoxFit.cover,
errorWidget: (context, url, error) {
//TODO: this crash the app when getting 404 and in debug mode, see :https://github.com/Baseflow/flutter_cached_network_image/issues/504
return CachedNetworkImage(
imageUrl:
"https://4.bp.blogspot.com/-Jx21kNqFSTU/UXemtqPhZCI/AAAAAAAAh74/BMGSzpU6F48/s1600/funny-cat-pictures-047-001.jpg");
}),
);
}
}
class Divider extends StatelessWidget {
const Divider({
Key key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Expanded(
child: Align(
child: Container(
height: 1,
color: CupertinoColors.systemGrey5,
),
alignment: Alignment.bottomCenter,
),
);
}
}