Skip to content

Instantly share code, notes, and snippets.

@TasyaShark
Created May 13, 2024 11:55
Show Gist options
  • Save TasyaShark/880ef102cf11be8196959e4893a09f8e to your computer and use it in GitHub Desktop.
Save TasyaShark/880ef102cf11be8196959e4893a09f8e to your computer and use it in GitHub Desktop.
main.dart
import 'dart:async';
import 'package:connectycube_sdk/connectycube_sdk.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
const String appId = "476";
const String authKey = "PDZjPBzAO8WPfCp";
const String authSecret = "6247kjxXCLRaua6";
const String userArgName = 'user';
const String dialogArgName = 'dialog';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(const App());
}
class App extends StatefulWidget {
const App({super.key});
@override
State<StatefulWidget> createState() {
return _AppState();
}
}
class _AppState extends State<App> {
AppLifecycleState? appState;
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.green),
),
home: const LoginScreen(),
onGenerateRoute: (settings) {
String? name = settings.name;
Map<String, dynamic>? args =
settings.arguments as Map<String, dynamic>?;
MaterialPageRoute pageRout;
switch (name) {
case 'chat_dialog':
pageRout = MaterialPageRoute(
builder: (context) =>
ChatScreen(args![userArgName], args[dialogArgName]));
break;
case 'select_dialog':
pageRout = MaterialPageRoute<bool>(
builder: (context) => ChatsListScreen(args![userArgName]));
break;
case 'login':
pageRout =
MaterialPageRoute(builder: (context) => const LoginScreen());
break;
default:
pageRout =
MaterialPageRoute(builder: (context) => const LoginScreen());
break;
}
return pageRout;
},
);
}
@override
void initState() {
super.initState();
init(appId, authKey, authSecret);
}
}
class LoginScreen extends StatefulWidget {
const LoginScreen({super.key});
@override
State<StatefulWidget> createState() => LoginScreenState();
}
class LoginScreenState extends State<LoginScreen> {
final TextEditingController _loginEditingController = TextEditingController();
final TextEditingController _passwordEditingController =
TextEditingController();
bool _isLoginContinues = false;
@override
Widget build(BuildContext context) {
if (_isLoginContinues) {
return Scaffold(
body: Center(
child: Container(
margin: const EdgeInsets.only(left: 8),
height: 18,
width: 18,
child: const CircularProgressIndicator(
strokeWidth: 2,
),
),
),
);
}
return Scaffold(
body: Center(
child: SingleChildScrollView(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 400),
child: Column(
children: <Widget>[
TextField(
keyboardType: TextInputType.text,
controller: _loginEditingController,
decoration: const InputDecoration(labelText: 'Login'),
),
TextField(
keyboardType: TextInputType.visiblePassword,
controller: _passwordEditingController,
decoration: const InputDecoration(labelText: 'Password'),
obscureText: true,
enableSuggestions: false,
autocorrect: false,
onSubmitted: (_) {
_login();
},
),
Container(
margin: const EdgeInsets.only(top: 8),
child: ElevatedButton(
onPressed: _login,
child: const Text('Login'),
),
)
],
),
)),
),
),
);
}
void _login() {
var userToLogin = CubeUser()
..login = _loginEditingController.text
..password = _passwordEditingController.text;
setState(() {
_isLoginContinues = true;
});
createSession(userToLogin).then((cubeSession) async {
print("createSession cubeSession: $cubeSession");
CubeChatConnection.instance
.login(userToLogin..id = cubeSession.user!.id)
.then((cubeUser) {
setState(() {
_isLoginContinues = false;
});
Navigator.pushReplacementNamed(
context,
'select_dialog',
arguments: {userArgName: cubeUser},
);
}).catchError((error) {
// process creating Chat session error
print("loginChat error: $error");
setState(() {
_isLoginContinues = false;
});
});
}).catchError((error) {
// process creating API session error
print("createSession error: $error");
setState(() {
_isLoginContinues = false;
});
});
}
}
class ChatsListScreen extends StatelessWidget {
final CubeUser currentUser;
const ChatsListScreen(this.currentUser, {super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
title: Text(
'Logged in as ${currentUser.fullName ?? currentUser.login}',
),
),
body: ChatsListBody(currentUser),
);
}
}
class ChatsListBody extends StatefulWidget {
final CubeUser currentUser;
const ChatsListBody(this.currentUser, {super.key});
@override
State<StatefulWidget> createState() {
return _ChatsListBodyState();
}
}
class _ChatsListBodyState extends State<ChatsListBody> {
List<CubeDialog> dialogList = [];
bool _isDialogsLoafingContinues = true;
@override
void initState() {
super.initState();
getDialogs().then((pagedResult) {
if (mounted) {
setState(() {
dialogList = pagedResult?.items ?? [];
});
}
}).whenComplete(() {
setState(() {
_isDialogsLoafingContinues = false;
});
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
padding: const EdgeInsets.only(top: 2),
child: Column(
children: [
Visibility(
visible: _isDialogsLoafingContinues,
child: Container(
margin: const EdgeInsets.all(40),
alignment: FractionalOffset.center,
child: const CircularProgressIndicator(
strokeWidth: 2,
),
),
),
Visibility(
visible: dialogList.isEmpty,
child: Container(
margin: const EdgeInsets.all(40),
alignment: FractionalOffset.center,
child: const Text(
'No dialogs yet',
style: TextStyle(fontSize: 20),
),
),
),
Visibility(
visible: dialogList.isNotEmpty,
child: Flexible(
child: ListView.separated(
itemCount: dialogList.length,
itemBuilder: (BuildContext context, int index) {
return getDialogItemTile(dialogList[index]);
},
separatorBuilder: (context, index) {
return const Divider(
thickness: 1,
indent: 68,
height: 1,
);
},
),
),
),
],
),
),
floatingActionButton: FloatingActionButton(
heroTag: "New dialog",
backgroundColor: Colors.blue,
onPressed: () => _createNewDialog(context),
child: const Icon(
Icons.add_comment,
color: Colors.white,
),
),
);
}
void _createNewDialog(BuildContext context) async {
showDialog<int>(
context: context,
barrierDismissible: false,
builder: (context) {
var userIdEditingController = TextEditingController();
return AlertDialog(
title: const Text('Type participant\'s ID'),
content: TextField(
keyboardType: TextInputType.number,
controller: userIdEditingController,
decoration: const InputDecoration(labelText: 'Participant ID'),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, null),
child: const Text('Cancel'),
),
TextButton(
onPressed: () => Navigator.pop(
context, int.tryParse(userIdEditingController.text)),
child: const Text('Ok'),
),
],
);
},
).then((participantId) {
if (participantId != null) {
var newDialog =
CubeDialog(CubeDialogType.PRIVATE, occupantsIds: [participantId]);
createDialog(newDialog).then((createdDialog) {
Navigator.pushNamed(context, 'chat_dialog', arguments: {
userArgName: widget.currentUser,
dialogArgName: createdDialog
});
});
}
});
}
Widget getDialogItemTile(CubeDialog dialog) {
Widget getDialogIcon() {
return Icon(
dialog.type == CubeDialogType.PRIVATE ? Icons.person : Icons.group,
size: 40.0,
color: Colors.grey,
);
}
Widget getAvatarWidget(String? imageUrl, String? name, double radius) {
return CircleAvatar(
backgroundColor: const Color.fromARGB(20, 100, 100, 100),
radius: radius,
child: ClipRRect(
borderRadius: BorderRadius.circular(radius),
child: Image.network(
imageUrl ?? '',
fit: BoxFit.cover,
width: radius * 2,
height: radius * 2,
loadingBuilder: (context, child, loadingProgress) {
return getDialogIcon();
},
errorBuilder:
(BuildContext context, Object error, StackTrace? stackTrace) {
return getDialogIcon();
},
),
),
);
}
Widget getDialogAvatar() {
return getAvatarWidget(dialog.photo, dialog.name, 25);
}
Widget getMessageStateWidget(MessageState? state) {
Widget result;
switch (state) {
case MessageState.read:
result = const Stack(children: <Widget>[
Icon(
Icons.check_rounded,
size: 15.0,
color: Colors.green,
),
Padding(
padding: EdgeInsets.only(
left: 4,
),
child: Icon(
Icons.check_rounded,
size: 15.0,
color: Colors.green,
),
)
]);
break;
case MessageState.delivered:
result = const Stack(children: <Widget>[
Icon(
Icons.check_rounded,
size: 15.0,
color: Colors.grey,
),
Padding(
padding: EdgeInsets.only(left: 4),
child: Icon(
Icons.check_rounded,
size: 15.0,
color: Colors.grey,
),
)
]);
break;
case MessageState.sent:
result = const Icon(
Icons.check_rounded,
size: 15.0,
color: Colors.grey,
);
break;
default:
result = const SizedBox.shrink();
break;
}
return result;
}
return Container(
padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 10),
child: GestureDetector(
behavior: HitTestBehavior.translucent,
child: Row(
children: <Widget>[
getDialogAvatar(),
Flexible(
child: Container(
margin: const EdgeInsets.only(left: 8.0),
child: Column(
children: <Widget>[
Container(
alignment: Alignment.centerLeft,
child: Text(
dialog.name ?? 'Unknown name',
style: TextStyle(
color: Colors.blueGrey.shade800,
fontWeight: FontWeight.bold,
fontSize: 16.0,
overflow: TextOverflow.ellipsis),
maxLines: 1,
),
),
Container(
alignment: Alignment.centerLeft,
child: Text(
dialog.lastMessage ?? '',
style: TextStyle(
color: Colors.blueGrey.shade800,
overflow: TextOverflow.ellipsis),
maxLines: 2,
),
),
],
),
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Row(
children: [
getMessageStateWidget(dialog.lastMessageState),
Text(
DateFormat('MMM dd').format(
dialog.lastMessageDateSent != null
? DateTime.fromMillisecondsSinceEpoch(
dialog.lastMessageDateSent! * 1000)
: dialog.updatedAt!),
style: TextStyle(color: Colors.blueGrey.shade800),
),
],
),
if (dialog.unreadMessageCount != null &&
dialog.unreadMessageCount != 0)
Padding(
padding: const EdgeInsets.only(top: 4),
child: Container(
padding: const EdgeInsets.symmetric(
vertical: 2, horizontal: 6),
decoration: BoxDecoration(
color: Colors.green,
borderRadius: BorderRadius.circular(10.0)),
child: Text(
dialog.unreadMessageCount.toString(),
style: const TextStyle(color: Colors.white),
),
),
),
],
),
],
),
onTap: () {
Navigator.pushNamed(context, 'chat_dialog', arguments: {
userArgName: widget.currentUser,
dialogArgName: dialog,
});
},
),
);
}
}
class ChatScreen extends StatelessWidget {
final CubeUser _cubeUser;
final CubeDialog _cubeDialog;
const ChatScreen(this._cubeUser, this._cubeDialog, {super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(_cubeDialog.name ?? ''),
),
body: ChatScreenBody(_cubeUser, _cubeDialog),
);
}
}
class ChatScreenBody extends StatefulWidget {
final CubeUser currentUser;
final CubeDialog cubeDialog;
const ChatScreenBody(this.currentUser, this.cubeDialog, {super.key});
@override
State createState() => ChatScreenBodyState();
}
class ChatScreenBodyState extends State<ChatScreenBody> {
final Map<int?, CubeUser?> _occupants = {};
late bool isLoading;
List<CubeMessage> listMessage = [];
String userStatus = '';
final TextEditingController textEditingController = TextEditingController();
final ScrollController listScrollController = ScrollController();
StreamSubscription<CubeMessage>? msgSubscription;
StreamSubscription<MessageStatus>? deliveredSubscription;
StreamSubscription<MessageStatus>? readSubscription;
@override
void initState() {
super.initState();
_initCubeChat();
isLoading = false;
}
@override
void dispose() {
msgSubscription?.cancel();
deliveredSubscription?.cancel();
readSubscription?.cancel();
textEditingController.dispose();
super.dispose();
}
void onReceiveMessage(CubeMessage message) {
if (message.dialogId != widget.cubeDialog.dialogId) return;
addMessageToListView(message);
}
void onDeliveredMessage(MessageStatus status) {
updateReadDeliveredStatusMessage(status, false);
}
void onReadMessage(MessageStatus status) {
updateReadDeliveredStatusMessage(status, true);
}
void onSendChatMessage(String content) {
if (content.trim() != '') {
final message = createCubeMsg();
message.body = content.trim();
onSendMessage(message);
} else {
print('Nothing to send');
}
}
CubeMessage createCubeMsg() {
var message = CubeMessage();
message.dateSent = DateTime.now().millisecondsSinceEpoch ~/ 1000;
message.markable = true;
message.saveToHistory = true;
return message;
}
void onSendMessage(CubeMessage message) async {
textEditingController.clear();
await widget.cubeDialog.sendMessage(message);
message.senderId = widget.currentUser.id;
addMessageToListView(message);
listScrollController.animateTo(0.0,
duration: const Duration(milliseconds: 300), curve: Curves.easeOut);
}
updateReadDeliveredStatusMessage(MessageStatus status, bool isRead) {
setState(() {
CubeMessage? msg = listMessage
.where((msg) => msg.messageId == status.messageId)
.firstOrNull;
if (msg == null) return;
if (isRead) {
msg.readIds == null
? msg.readIds = [status.userId]
: msg.readIds?.add(status.userId);
} else {
msg.deliveredIds == null
? msg.deliveredIds = [status.userId]
: msg.deliveredIds?.add(status.userId);
}
});
}
addMessageToListView(CubeMessage message) {
setState(() {
isLoading = false;
int existMessageIndex = listMessage.indexWhere((cubeMessage) {
return cubeMessage.messageId == message.messageId;
});
if (existMessageIndex != -1) {
listMessage[existMessageIndex] = message;
} else {
listMessage.insert(0, message);
}
});
}
@override
Widget build(BuildContext context) {
return SafeArea(
child: Stack(
children: <Widget>[
Column(
children: <Widget>[
buildListMessage(),
buildInput(),
],
),
buildLoading(),
],
),
);
}
Widget buildItem(int index, CubeMessage message) {
markAsReadIfNeed() {
var isOpponentMsgRead = message.readIds != null &&
message.readIds!.contains(widget.currentUser.id);
if (message.senderId != widget.currentUser.id && !isOpponentMsgRead) {
if (message.readIds == null) {
message.readIds = [widget.currentUser.id!];
} else {
message.readIds!.add(widget.currentUser.id!);
}
if (CubeChatConnection.instance.chatConnectionState ==
CubeChatConnectionState.Ready) {
widget.cubeDialog.readMessage(message);
}
}
}
Widget getReadDeliveredWidget() {
bool messageIsRead() {
if (widget.cubeDialog.type == CubeDialogType.PRIVATE) {
return message.readIds != null &&
(message.recipientId == null ||
message.readIds!.contains(message.recipientId));
}
return message.readIds != null &&
message.readIds!.any((int id) =>
id != widget.currentUser.id && _occupants.keys.contains(id));
}
bool messageIsDelivered() {
if (widget.cubeDialog.type == CubeDialogType.PRIVATE) {
return message.deliveredIds != null &&
(message.recipientId == null ||
message.deliveredIds!.contains(message.recipientId));
}
return message.deliveredIds != null &&
message.deliveredIds!.any((int id) =>
id != widget.currentUser.id && _occupants.keys.contains(id));
}
if (messageIsRead()) {
return getMessageStateWidget(MessageState.read);
} else if (messageIsDelivered()) {
return getMessageStateWidget(MessageState.delivered);
} else {
return getMessageStateWidget(MessageState.sent);
}
}
Widget getDateWidget() {
return Text(
DateFormat('HH:mm').format(
DateTime.fromMillisecondsSinceEpoch(message.dateSent! * 1000)),
style: const TextStyle(
color: Colors.grey, fontSize: 12.0, fontStyle: FontStyle.italic),
);
}
Widget getMessageBubble(bool isOwnMessage) {
if (!isOwnMessage) {
markAsReadIfNeed();
}
return Column(
key: Key('${message.messageId}'),
children: <Widget>[
Row(
mainAxisAlignment:
isOwnMessage ? MainAxisAlignment.end : MainAxisAlignment.start,
children: <Widget>[
Flexible(
child: Container(
constraints: const BoxConstraints(maxWidth: 480),
padding: const EdgeInsets.fromLTRB(8.0, 4.0, 8.0, 4.0),
decoration: BoxDecoration(
color: isOwnMessage
? Colors.grey.shade200
: Colors.blueGrey.shade800,
borderRadius: BorderRadius.circular(8.0)),
margin: EdgeInsets.only(
bottom: isOwnMessage ? 20.0 : 10.0,
right: isOwnMessage ? 10.0 : 0,
left: isOwnMessage ? 0 : 10.0),
child: Column(
crossAxisAlignment: isOwnMessage
? CrossAxisAlignment.end
: CrossAxisAlignment.start,
children: [
Text(
message.body!,
style: TextStyle(
color: isOwnMessage
? Colors.blueGrey.shade800
: Colors.grey.shade200),
),
Row(
mainAxisSize: MainAxisSize.min,
children: [
getDateWidget(),
if (isOwnMessage) getReadDeliveredWidget(),
],
),
]),
),
)
],
),
],
);
}
return getMessageBubble(message.senderId == widget.currentUser.id);
}
Widget buildLoading() {
return Positioned(
child: isLoading
? Container(
color: Colors.white.withOpacity(0.8),
child: const Center(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(Colors.green),
),
))
: const SizedBox.shrink());
}
Widget buildInput() {
return Container(
width: double.infinity,
height: 50.0,
decoration: BoxDecoration(
border: Border(
top: BorderSide(color: Colors.blueGrey.shade800, width: 0.5)),
color: Colors.white),
child: Row(
children: <Widget>[
Flexible(
child: TextField(
autofocus: true,
keyboardType: TextInputType.multiline,
maxLines: null,
style: TextStyle(color: Colors.blueGrey.shade800, fontSize: 15.0),
controller: textEditingController,
decoration: const InputDecoration.collapsed(
hintText: 'Type your message...',
hintStyle: TextStyle(color: Colors.grey),
),
),
),
// Button send message
Material(
color: Colors.white,
child: Container(
margin: const EdgeInsets.only(left: 4, right: 2.0),
child: IconButton(
icon: const Icon(Icons.send),
onPressed: () => onSendChatMessage(textEditingController.text),
color: Colors.blueGrey.shade800,
),
),
),
],
),
);
}
Widget buildListMessage() {
getWidgetMessages(listMessage) {
return ListView.builder(
padding: const EdgeInsets.all(10.0),
itemBuilder: (context, index) => buildItem(index, listMessage[index]),
itemCount: listMessage.length,
reverse: true,
controller: listScrollController,
);
}
return Flexible(
child: StreamBuilder<List<CubeMessage>>(
stream: getMessagesList().asStream(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const Center(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(Colors.green)));
} else {
listMessage = snapshot.data ?? [];
return getWidgetMessages(listMessage);
}
},
),
);
}
Future<List<CubeMessage>> getMessagesList() {
if (listMessage.isNotEmpty) return Future.value(listMessage);
var params = GetMessagesParameters();
params.sorter = RequestSorter('desc', '', 'date_sent');
return getMessages(
widget.cubeDialog.dialogId!, params.getRequestParameters())
.then((getMessagesResult) {
return getAllUsersByIds(widget.cubeDialog.occupantsIds!.toSet())
.then((getUsersResult) {
_occupants
.addAll({for (var item in getUsersResult!.items) item.id: item});
return getMessagesResult?.items ?? [];
});
});
}
_initCubeChat() {
msgSubscription = CubeChatConnection
.instance.chatMessagesManager!.chatMessagesStream
.listen(onReceiveMessage);
deliveredSubscription = CubeChatConnection
.instance.messagesStatusesManager!.deliveredStream
.listen(onDeliveredMessage);
readSubscription = CubeChatConnection
.instance.messagesStatusesManager!.readStream
.listen(onReadMessage);
}
}
Widget getMessageStateWidget(MessageState? state) {
Widget result;
switch (state) {
case MessageState.read:
result = const Stack(children: <Widget>[
Icon(
Icons.check_rounded,
size: 15.0,
color: Colors.green,
),
Padding(
padding: EdgeInsets.only(
left: 4,
),
child: Icon(
Icons.check_rounded,
size: 15.0,
color: Colors.green,
),
)
]);
break;
case MessageState.delivered:
result = Stack(children: <Widget>[
Icon(
Icons.check_rounded,
size: 15.0,
color: Colors.grey.shade700,
),
Padding(
padding: const EdgeInsets.only(left: 4),
child: Icon(
Icons.check_rounded,
size: 15.0,
color: Colors.grey.shade700,
),
)
]);
break;
case MessageState.sent:
result = Icon(
Icons.check_rounded,
size: 15.0,
color: Colors.grey.shade700,
);
break;
default:
result = const SizedBox.shrink();
break;
}
return result;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment