Commit 5bd5df70 authored by Inomjon's avatar Inomjon

Chat ishlatib ko'rildi

parent cb19bef2
......@@ -5,6 +5,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:vmeeting/blocs/edit_profile_bloc/edit_profile_bloc.dart';
import 'package:vmeeting/blocs/user_sign_up_bloc/user_sign_up_bloc.dart';
import 'package:vmeeting/service/routes/routes_name.dart';
import 'package:vmeeting/srcchat/chat_dialog_screen.dart';
import 'package:vmeeting/views/main_view/main_page.dart';
import '../../blocs/user_login_bloc/user_log_in_bloc.dart';
import '../../src/controllers/enter_number_cont.dart';
......@@ -50,6 +51,9 @@ class MainNavigator extends StatelessWidget {
case MainRoutes.reset_password_page:
builder = (BuildContext _) => ResetPasswordPage(controller: controller);
break;
// case MainRoutes.chat_dialog:
// builder = (BuildContext _) => ChatDialogScreen(args![USER_ARG_NAME], args[DIALOG_ARG_NAME]);
// break;
case MainRoutes.user_signup_page:
builder = (BuildContext _) => MultiBlocProvider(
providers: [
......
......@@ -8,4 +8,5 @@ class MainRoutes {
static const String old_sign_in = "old_sign_in";
static const String edit_profile_page = "edit_profile_page";
static const String reset_password_page = "reset_password_page";
static const String chat_dialog = "chat_dialog";
}
\ No newline at end of file
import 'dart:async';
import 'package:connectycube_sdk/connectycube_sdk.dart';
class ChatManager {
static ChatManager? _instance;
ChatManager._();
static ChatManager get instance => _instance ??= ChatManager._();
StreamController<CubeMessage> sentMessagesController =
StreamController.broadcast();
Stream<CubeMessage> get sentMessagesStream {
return sentMessagesController.stream;
}
StreamController<MessageStatus> readMessagesController =
StreamController.broadcast();
Stream<MessageStatus> get readMessagesStream {
return readMessagesController.stream;
}
}
......@@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:vmeeting/src/constants/colors_const.dart';
import '../../app_models/app_users_model/app_users_model.dart';
import '../widgets/custom_loading/customloading.dart';
class AppUtils {
static Widget buttonLoader = SizedBox(
......@@ -36,5 +37,9 @@ class AppUtils {
}
static UserModel userModel = UserModel();
static CubeUser cubeUser = CubeUser();
static Widget buildLoading() {
return Center(child: CustomLoading());
}
}
import 'package:connectycube_sdk/connectycube_chat.dart';
import 'package:flutter/material.dart';
import 'package:vmeeting/srcchat/utils/consts.dart';
import 'package:vmeeting/srcchat/widgets/common.dart';
class AddOccupantScreen extends StatefulWidget {
final CubeUser _cubeUser;
@override
State<StatefulWidget> createState() {
return _AddOccupantScreenState(_cubeUser);
}
AddOccupantScreen(this._cubeUser);
}
class _AddOccupantScreenState extends State<AddOccupantScreen> {
static const String TAG = "_AddOccupantScreenState";
final CubeUser currentUser;
_AddOccupantScreenState(this.currentUser);
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () => _onBackPressed(context),
child: Scaffold(
appBar: AppBar(
automaticallyImplyLeading: true,
title: Text(
'Contacts',
),
),
body: BodyLayout(currentUser),
),
);
}
Future<bool> _onBackPressed(BuildContext context) {
Navigator.pop(context);
return Future.value(false);
}
}
class BodyLayout extends StatefulWidget {
final CubeUser currentUser;
BodyLayout(this.currentUser);
@override
State<StatefulWidget> createState() {
return _BodyLayoutState(currentUser);
}
}
class _BodyLayoutState extends State<BodyLayout> {
static const String TAG = "_BodyLayoutState";
final CubeUser currentUser;
List<CubeUser> userList = [];
Set<int> _selectedUsers = {};
var _isUsersContinues = false;
String? userToSearch;
String userMsg = " ";
_BodyLayoutState(this.currentUser);
_searchUser(value) {
log("searchUser _user= $value");
if (value != null)
setState(() {
userToSearch = value;
_isUsersContinues = true;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
padding: EdgeInsets.only(left: 16, right: 16, bottom: 16),
child: Column(
children: [
_buildTextFields(),
Container(
margin: EdgeInsets.only(left: 8),
child: Visibility(
maintainSize: false,
maintainAnimation: false,
maintainState: false,
visible: _isUsersContinues,
child: CircularProgressIndicator(
strokeWidth: 2,
),
),
),
Expanded(
child: _getUsersList(context),
),
],
)),
floatingActionButton: new Visibility(
visible: _selectedUsers.isNotEmpty,
child: FloatingActionButton(
heroTag: "Update dialog",
child: Icon(
Icons.check,
color: Colors.white,
),
backgroundColor: Colors.blue,
onPressed: () => _updateDialog(context, _selectedUsers.toList()),
),
),
);
}
Widget _buildTextFields() {
return new Container(
child: new Column(
children: <Widget>[
new Container(
child: new TextField(
autofocus: true,
textInputAction: TextInputAction.search,
decoration: new InputDecoration(labelText: 'Search users'),
onSubmitted: (value) {
_searchUser(value.trim());
}),
),
],
),
);
}
Widget _getUsersList(BuildContext context) {
clearValues() {
_isUsersContinues = false;
userToSearch = null;
userMsg = " ";
userList.clear();
}
if (_isUsersContinues) {
if (userToSearch != null && userToSearch!.isNotEmpty) {
getUsersByFullName(userToSearch!).then((users) {
log("getusers: $users", TAG);
setState(() {
clearValues();
userList.addAll(users!.items);
if (users.items.isEmpty) {
userMsg = "Couldn't find user";
}
});
}).catchError(
(onError) {
log("getusers catchError: $onError", TAG);
setState(() {
clearValues();
userMsg = "Couldn't find user";
});
},
);
}
}
if (userList.isEmpty)
return Center(
child: Text(
userMsg,
style: TextStyle(fontSize: 20),
),
);
else
return ListView.builder(
itemCount: userList.length,
itemBuilder: _getListItemTile,
);
}
Widget _getListItemTile(BuildContext context, int index) {
return Container(
child: TextButton(
child: Row(
children: <Widget>[
getUserAvatarWidget(userList[index], 30),
Flexible(
child: Container(
child: Column(
children: <Widget>[
Container(
child: Text(
'Name: ${userList[index].fullName}',
style: TextStyle(color: primaryColor),
),
alignment: Alignment.centerLeft,
margin: EdgeInsets.fromLTRB(10.0, 0.0, 0.0, 5.0),
),
],
),
margin: EdgeInsets.only(left: 20.0),
),
),
Container(
child: Checkbox(
value: _selectedUsers.contains(userList[index].id),
onChanged: ((checked) {
setState(() {
if (checked!) {
_selectedUsers.add(userList[index].id!);
} else {
_selectedUsers.remove(userList[index].id);
}
});
}),
),
),
],
),
onPressed: () {
setState(() {
if (_selectedUsers.contains(userList[index].id)) {
_selectedUsers.remove(userList[index].id);
} else {
_selectedUsers.add(userList[index].id!);
}
});
},
// color: greyColor2,
// padding: EdgeInsets.fromLTRB(25.0, 10.0, 25.0, 10.0),
// shape:
// RoundedRectangleBorder(borderRadius: BorderRadius.circular(10.0)),
),
margin: EdgeInsets.only(bottom: 10.0, left: 5.0, right: 5.0),
);
}
void _updateDialog(BuildContext context, List<int> users) async {
log("_updateDialog with users= $users");
Navigator.pop(context, users);
}
}
import 'dart:async';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:connectycube_sdk/connectycube_sdk.dart';
import 'utils/api_utils.dart';
import 'utils/consts.dart';
import 'widgets/common.dart';
class ChatDetailsScreen extends StatelessWidget {
final CubeUser _cubeUser;
final CubeDialog _cubeDialog;
ChatDetailsScreen(this._cubeUser, this._cubeDialog);
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () => _onBackPressed(context),
child: Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.close, color: Colors.white),
onPressed: () {
Navigator.of(context, rootNavigator: true).pop();
},
),
automaticallyImplyLeading: false,
title: Text(
_cubeDialog.type == CubeDialogType.PRIVATE
? "Contact details"
: "Group details",
),
centerTitle: false,
actions: <Widget>[
if (_cubeDialog.type != CubeDialogType.PRIVATE)
IconButton(
onPressed: () {
_exitDialog(context);
},
icon: Icon(
Icons.exit_to_app,
),
)
],
),
body: DetailScreen(_cubeUser, _cubeDialog),
),
);
}
Future<bool> _onBackPressed(BuildContext context) {
Navigator.pop(context);
return Future.value(false);
}
_exitDialog(BuildContext context) {
print('_exitDialog');
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('Leave Dialog'),
content: Text("Are you sure you want to leave this dialog?"),
actions: <Widget>[
TextButton(
child: Text('Cancel'),
onPressed: () {
Navigator.pop(context);
},
),
TextButton(
child: Text('Ok'),
onPressed: () {
deleteDialog(_cubeDialog.dialogId!).then((onValue) {
Fluttertoast.showToast(msg: 'Success');
Navigator.of(context, rootNavigator: true)
.pushNamedAndRemoveUntil(
'select_dialog',
(route) => false,
arguments: {USER_ARG_NAME: _cubeUser},
);
}).catchError((error) {
showDialogError(error, context);
});
},
),
],
);
},
);
}
}
class DetailScreen extends StatefulWidget {
static const String TAG = "DetailScreen";
final CubeUser _cubeUser;
final CubeDialog _cubeDialog;
DetailScreen(this._cubeUser, this._cubeDialog);
@override
State createState() => _cubeDialog.type == CubeDialogType.PRIVATE
? ContactScreenState(_cubeUser, _cubeDialog)
: GroupScreenState(_cubeUser, _cubeDialog);
}
abstract class ScreenState extends State<DetailScreen> {
final CubeUser _cubeUser;
CubeDialog _cubeDialog;
final Map<int, CubeUser> _occupants = Map();
var _isProgressContinues = false;
ScreenState(this._cubeUser, this._cubeDialog);
@override
void initState() {
super.initState();
if (_occupants.isEmpty) {
initUsers();
}
}
initUsers() async {
_isProgressContinues = true;
if (_cubeDialog.occupantsIds == null || _cubeDialog.occupantsIds!.isEmpty) {
setState(() {
_isProgressContinues = false;
});
return;
}
var result = await getUsersByIds(_cubeDialog.occupantsIds!.toSet());
_occupants.clear();
_occupants.addAll(result);
_occupants.remove(_cubeUser.id);
setState(() {
_isProgressContinues = false;
});
}
}
class ContactScreenState extends ScreenState {
CubeUser? contactUser;
initUser() {
contactUser = _occupants.values.isNotEmpty
? _occupants.values.first
: CubeUser(fullName: "Absent");
}
ContactScreenState(_cubeUser, _cubeDialog) : super(_cubeUser, _cubeDialog);
@override
Widget build(BuildContext context) {
initUser();
return Scaffold(
body: Container(
alignment: Alignment.center,
padding: EdgeInsets.all(60),
child: Column(
children: [
_buildAvatarFields(),
_buildTextFields(),
_buildButtons(),
Container(
margin: EdgeInsets.only(left: 8),
child: Visibility(
maintainSize: false,
maintainAnimation: false,
maintainState: false,
visible: _isProgressContinues,
child: CircularProgressIndicator(
strokeWidth: 2,
),
),
),
],
)),
);
}
Widget _buildAvatarFields() {
if (_isProgressContinues) {
return SizedBox.shrink();
}
return Stack(
children: <Widget>[getUserAvatarWidget(contactUser!, 50)],
);
}
Widget _buildTextFields() {
if (_isProgressContinues) {
return SizedBox.shrink();
}
return Container(
margin: EdgeInsets.all(50),
child: Column(
children: <Widget>[
Container(
padding: EdgeInsets.only(
right: 10, left: 10,
bottom: 3, // space between underline and text
),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: primaryColor, // Text colour here
width: 1.0, // Underline width
))),
child: Text(
contactUser!.fullName ??
contactUser!.login ??
contactUser!.email ??
'',
style: TextStyle(
color: primaryColor,
fontSize: 20, // Text colour here
),
),
)
],
),
);
}
Widget _buildButtons() {
if (_isProgressContinues) {
return SizedBox.shrink();
}
return new Container(
child: new Column(
children: <Widget>[
new ElevatedButton(
child: Text(
'Start dialog',
style: TextStyle(
color: Colors.white,
),
),
onPressed: () => Navigator.of(context, rootNavigator: true).pop(),
),
],
),
);
}
}
class GroupScreenState extends ScreenState {
final TextEditingController _nameFilter = new TextEditingController();
String? _photoUrl = "";
String _name = "";
Set<int?> _usersToRemove = {};
List<int>? _usersToAdd;
GroupScreenState(_cubeUser, _cubeDialog) : super(_cubeUser, _cubeDialog) {
_nameFilter.addListener(_nameListen);
_nameFilter.text = _cubeDialog.name;
clearFields();
}
void _nameListen() {
if (_nameFilter.text.isEmpty) {
_name = "";
} else {
_name = _nameFilter.text.trim();
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
child: Center(
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: 400),
child: Container(
alignment: Alignment.center,
padding: EdgeInsets.all(40),
child: Column(
children: [
_buildPhotoFields(),
_buildTextFields(),
_buildGroupFields(),
Container(
margin: EdgeInsets.only(left: 8),
child: Visibility(
maintainSize: false,
maintainAnimation: false,
maintainState: false,
visible: _isProgressContinues,
child: CircularProgressIndicator(
strokeWidth: 2,
),
),
),
],
)),
),
),
),
floatingActionButton: FloatingActionButton(
heroTag: "Update dialog",
child: Icon(
Icons.check,
color: Colors.white,
),
backgroundColor: Colors.blue,
onPressed: () => _updateDialog(),
),
);
}
Widget _buildPhotoFields() {
if (_isProgressContinues) {
return SizedBox.shrink();
}
Widget avatarCircle = getDialogAvatarWidget(_cubeDialog, 50);
return new Stack(
children: <Widget>[
InkWell(
splashColor: greyColor2,
borderRadius: BorderRadius.circular(45),
onTap: () => _chooseUserImage(),
child: avatarCircle,
),
new Positioned(
child: RawMaterialButton(
onPressed: () {
_chooseUserImage();
},
elevation: 2.0,
fillColor: Colors.white,
child: Icon(
Icons.mode_edit,
size: 20.0,
),
padding: EdgeInsets.all(5.0),
shape: CircleBorder(),
),
top: 55.0,
right: 35.0,
),
],
);
}
_chooseUserImage() async {
FilePickerResult? result = await FilePicker.platform.pickFiles(
type: FileType.image,
);
if (result == null) return;
var uploadImageFuture = getUploadingImageFuture(result);
uploadImageFuture.then((cubeFile) {
_photoUrl = cubeFile.getPublicUrl();
setState(() {
_cubeDialog.photo = _photoUrl;
});
}).catchError((error) {
_processUpdateError(error);
});
}
Widget _buildTextFields() {
if (_isProgressContinues) {
return SizedBox.shrink();
}
return Container(
padding: EdgeInsets.only(bottom: 20),
child: Column(
children: <Widget>[
Container(
child: TextField(
autofocus: true,
style: TextStyle(color: primaryColor, fontSize: 20.0),
controller: _nameFilter,
decoration: InputDecoration(labelText: 'Change group name'),
),
),
],
),
);
}
_buildGroupFields() {
if (_isProgressContinues) {
return SizedBox.shrink();
}
return Column(
children: <Widget>[
_addMemberBtn(),
_getUsersList(),
],
);
}
Widget _addMemberBtn() {
return Container(
padding: EdgeInsets.only(
bottom: 3, // space between underline and text
),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: Colors.green, // Text colour here
width: 2.0, // Underline width
))),
child: Row(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Text(
'Members:',
style: TextStyle(
color: primaryColor,
fontSize: 18, // Text colour here
),
),
Expanded(flex: 1, child: Container()),
IconButton(
onPressed: () {
_addOpponent();
},
icon: Icon(
Icons.person_add,
size: 26.0,
color: Colors.green,
),
),
Visibility(
visible: _usersToRemove.isNotEmpty,
child: IconButton(
onPressed: () {
_removeOpponent();
},
icon: Icon(
Icons.person_remove,
size: 26.0,
color: Colors.red,
),
),
)
],
),
);
}
Widget _getUsersList() {
if (_isProgressContinues) {
return SizedBox.shrink();
}
return ListView.separated(
padding: EdgeInsets.only(top: 8),
scrollDirection: Axis.vertical,
shrinkWrap: true,
primary: false,
itemCount: _occupants.length,
itemBuilder: _getListItemTile,
separatorBuilder: (context, index) {
return Divider(thickness: 2, indent: 20, endIndent: 20);
},
);
}
Widget _getListItemTile(BuildContext context, int index) {
final user = _occupants.values.elementAt(index);
Widget getUserAvatar() {
if (user.avatar != null && user.avatar!.isNotEmpty) {
return getUserAvatarWidget(user, 25);
} else {
return Material(
child: Icon(
Icons.account_circle,
size: 50.0,
color: greyColor,
),
borderRadius: BorderRadius.all(Radius.circular(25.0)),
clipBehavior: Clip.hardEdge,
);
}
}
return Container(
child: TextButton(
child: Row(
children: <Widget>[
getUserAvatar(),
Flexible(
child: Container(
child: Column(
children: <Widget>[
Container(
child: Text(
'${user.fullName}',
style: TextStyle(color: primaryColor),
),
alignment: Alignment.centerLeft,
margin: EdgeInsets.fromLTRB(10.0, 0.0, 0.0, 5.0),
),
],
),
margin: EdgeInsets.only(left: 20.0),
),
),
Container(
child: Checkbox(
value: _usersToRemove
.contains(_occupants.values.elementAt(index).id),
onChanged: ((checked) {
setState(() {
if (checked!) {
_usersToRemove.add(_occupants.values.elementAt(index).id);
} else {
_usersToRemove
.remove(_occupants.values.elementAt(index).id);
}
});
}),
),
),
],
),
onPressed: () {
log("user onPressed");
},
),
margin: EdgeInsets.only(bottom: 10.0),
);
}
void _processUpdateError(exception) {
log("_processUpdateUserError error $exception");
setState(() {
clearFields();
_isProgressContinues = false;
});
showDialogError(exception, context);
}
_addOpponent() async {
print('_addOpponent');
_usersToAdd = await Navigator.pushNamed(
context,
'search_users',
arguments: {
USER_ARG_NAME: _cubeUser,
},
);
if (_usersToAdd != null && _usersToAdd!.isNotEmpty) _updateDialog();
}
_removeOpponent() async {
print('_removeOpponent');
if (_usersToRemove.isNotEmpty) _updateDialog();
}
void _updateDialog() {
print('_updateDialog $_name');
if (_name.isEmpty &&
_photoUrl!.isEmpty &&
(_usersToAdd?.isEmpty ?? true) &&
(_usersToRemove.isEmpty)) {
Fluttertoast.showToast(msg: 'Nothing to save');
return;
}
Map<String, dynamic> params = {};
if (_name.isNotEmpty) params['name'] = _name;
if (_photoUrl!.isNotEmpty) params['photo'] = _photoUrl;
if (_usersToAdd?.isNotEmpty ?? false)
params['push_all'] = {'occupants_ids': List.of(_usersToAdd!)};
if (_usersToRemove.isNotEmpty)
params['pull_all'] = {'occupants_ids': List.of(_usersToRemove)};
setState(() {
_isProgressContinues = true;
});
updateDialog(_cubeDialog.dialogId!, params).then((dialog) {
_cubeDialog = dialog;
Fluttertoast.showToast(msg: 'Success');
setState(() {
if ((_usersToAdd?.isNotEmpty ?? false) || (_usersToRemove.isNotEmpty))
initUsers();
_isProgressContinues = false;
clearFields();
});
}).catchError((error) {
_processUpdateError(error);
});
}
clearFields() {
_name = '';
_photoUrl = '';
_usersToAdd = null;
_usersToRemove.clear();
}
}
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:connectycube_sdk/connectycube_sdk.dart';
import 'package:vmeeting/srcchat/select_dialog_screen.dart';
import 'chat_dialog_screen.dart';
const double DIALOGS_LIST_WIDTH = 300;
const double MIN_SCREEN_SIZE = 800;
const double DIVIDER_WIDTH = 1;
class ChatDialogResizableScreen extends StatelessWidget {
static const String TAG = "SelectDialogScreen";
final CubeUser currentUser;
final CubeDialog? selectedDialog;
ChatDialogResizableScreen(this.currentUser, this.selectedDialog);
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () => _onBackPressed(),
child: Scaffold(
body: BodyLayout(currentUser, selectedDialog),
),
);
}
Future<bool> _onBackPressed() {
return Future.value(true);
}
}
class BodyLayout extends StatefulWidget {
final CubeUser currentUser;
final CubeDialog? selectedDialog;
BodyLayout(this.currentUser, this.selectedDialog);
@override
State<StatefulWidget> createState() {
return _BodyLayoutState(currentUser, selectedDialog);
}
}
class _BodyLayoutState extends State<BodyLayout> {
static const String TAG = "_BodyLayoutState";
final CubeUser currentUser;
CubeDialog? selectedDialog;
_BodyLayoutState(this.currentUser, CubeDialog? selectedDialog) {
this.selectedDialog = selectedDialog;
}
@override
Widget build(BuildContext context) {
return Container(
height: MediaQuery.of(context).size.height,
child: Row(
children: [
if (isBigScreen || selectedDialog == null)
LayoutBuilder(builder: (context, constraint) {
var width = MediaQuery.of(context).size.width;
return SizedBox(
width: isBigScreen
? width / 4 <= DIALOGS_LIST_WIDTH
? DIALOGS_LIST_WIDTH
: width / 4
: width,
child: SelectDialogScreen(selectedDialog,
(selectedDialog) {
setState(() {
this.selectedDialog = null;
Future.delayed(Duration(milliseconds: 50), () {
setState(() {
this.selectedDialog = selectedDialog;
});
});
});
}),
);
}),
Visibility(
visible: isBigScreen,
child: const VerticalDivider(
width: DIVIDER_WIDTH,
),
),
getSelectedDialog()
],
),
);
}
Widget getSelectedDialog() {
if (selectedDialog != null) {
return Flexible(
child: Stack(
children: [
ChatDialogScreen(currentUser, selectedDialog!),
Align(
alignment: Alignment.topCenter,
child: SizedBox(
height: AppBar().preferredSize.height,
child: AppBar(
elevation: 0,
automaticallyImplyLeading: false,
leading: !isBigScreen && selectedDialog != null
? IconButton(
icon: const Icon(Icons.arrow_back, color: Colors.white),
onPressed: () {
Navigator.pop(context);
},
)
: null,
title: Text(
selectedDialog?.name ?? '',
),
centerTitle: false,
actions: <Widget>[
IconButton(
onPressed: () => showChatDetails(
context, currentUser, selectedDialog!),
icon: const Icon(
Icons.info_outline,
color: Colors.white,
),
),
],
),
),
),
],
),
);
} else if (isBigScreen) {
return Expanded(
child: Container(
margin: EdgeInsets.only(top: AppBar().preferredSize.height),
child: const Center(
child: Text(
'No dialog selected',
style: TextStyle(fontSize: 20),
),
),
),
);
} else {
return Container();
}
}
get isBigScreen => MediaQuery.of(context).size.width >= MIN_SCREEN_SIZE;
}
import 'dart:async';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:collection/collection.dart' show IterableExtension;
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:emoji_picker_flutter/emoji_picker_flutter.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:intl/intl.dart';
import 'package:universal_io/io.dart';
import 'package:connectycube_sdk/connectycube_sdk.dart';
import '../src/constants/colors_const.dart';
import '../src/utils/app_utils.dart';
import 'managers/chat_manager.dart';
import 'update_dialog_flow.dart';
import 'utils/api_utils.dart';
import 'utils/consts.dart';
import 'utils/platform_utils.dart' as platformUtils;
import 'widgets/common.dart';
import 'widgets/full_photo.dart';
import 'widgets/loading.dart';
class ChatDialogScreen extends StatelessWidget {
final CubeUser _cubeUser;
final CubeDialog _cubeDialog;
const ChatDialogScreen(this._cubeUser, this._cubeDialog, {super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: ColorConst.appGreenColor,
title: Text(
_cubeDialog.name != null ? _cubeDialog.name! : '',
),
centerTitle: false,
actions: <Widget>[
IconButton(
onPressed: () => showChatDetails(context, _cubeUser, _cubeDialog),
icon: Icon(
Icons.info_outline,
color: ColorConst.appBleckColor,
),
),
],
),
body: ChatScreen(_cubeUser, _cubeDialog),
);
}
}
class ChatScreen extends StatefulWidget {
static const String TAG = "_CreateChatScreenState";
final CubeUser _cubeUser;
final CubeDialog _cubeDialog;
ChatScreen(this._cubeUser, this._cubeDialog, {super.key});
@override
State createState() => ChatScreenState(_cubeUser, _cubeDialog);
}
class ChatScreenState extends State<ChatScreen> {
final CubeUser _cubeUser;
final CubeDialog _cubeDialog;
final Map<int?, CubeUser?> _occupants = {};
late bool isLoading;
late StreamSubscription<ConnectivityResult> connectivityStateSubscription;
String? imageUrl;
List<CubeMessage> listMessage = [];
Timer? typingTimer;
bool isTyping = false;
String userStatus = '';
static const int TYPING_TIMEOUT = 700;
static const int STOP_TYPING_TIMEOUT = 2000;
int _sendIsTypingTime = DateTime.now().millisecondsSinceEpoch;
Timer? _sendStopTypingTimer;
final TextEditingController textEditingController = TextEditingController();
final ScrollController listScrollController = ScrollController();
StreamSubscription<CubeMessage>? msgSubscription;
StreamSubscription<MessageStatus>? deliveredSubscription;
StreamSubscription<MessageStatus>? readSubscription;
StreamSubscription<TypingStatus>? typingSubscription;
StreamSubscription<MessageReaction>? reactionsSubscription;
final List<CubeMessage> _unreadMessages = [];
final List<CubeMessage> _unsentMessages = [];
static const int messagesPerPage = 50;
int lastPartSize = 0;
List<CubeMessage> oldMessages = [];
late FocusNode _editMessageFocusNode;
ChatScreenState(this._cubeUser, this._cubeDialog);
@override
void initState() {
super.initState();
_initCubeChat();
isLoading = false;
imageUrl = '';
listScrollController.addListener(onScrollChanged);
connectivityStateSubscription =
Connectivity().onConnectivityChanged.listen(onConnectivityChanged);
_editMessageFocusNode = createEditMessageFocusNode();
}
@override
void dispose() {
msgSubscription?.cancel();
deliveredSubscription?.cancel();
readSubscription?.cancel();
typingSubscription?.cancel();
reactionsSubscription?.cancel();
textEditingController.dispose();
connectivityStateSubscription.cancel();
super.dispose();
}
void openGallery() async {
FilePickerResult? result = await FilePicker.platform.pickFiles(
type: FileType.image,
);
if (result == null) return;
setState(() {
isLoading = true;
});
var uploadImageFuture = getUploadingImageFuture(result);
Uint8List imageData;
if (kIsWeb) {
imageData = result.files.single.bytes!;
} else {
imageData = File(result.files.single.path!).readAsBytesSync();
}
var decodedImage = await decodeImageFromList(imageData);
uploadImageFile(uploadImageFuture, decodedImage);
}
Future uploadImageFile(Future<CubeFile> uploadAction, imageData) async {
uploadAction.then((cubeFile) {
onSendChatAttachment(cubeFile, imageData);
}).catchError((ex) {
setState(() {
isLoading = false;
});
Fluttertoast.showToast(msg: 'This file is not an image');
});
}
void onReceiveMessage(CubeMessage message) {
log("onReceiveMessage message= $message");
if (message.dialogId != _cubeDialog.dialogId) return;
addMessageToListView(message);
}
void onDeliveredMessage(MessageStatus status) {
log("onDeliveredMessage message= $status");
updateReadDeliveredStatusMessage(status, false);
}
void onReadMessage(MessageStatus status) {
log("onReadMessage message= ${status.messageId}");
updateReadDeliveredStatusMessage(status, true);
}
void onReactionReceived(MessageReaction reaction) {
log("onReactionReceived message= ${reaction.messageId}");
_updateMessageReactions(reaction);
}
void onTypingMessage(TypingStatus status) {
log("TypingStatus message= ${status.userId}");
if (status.userId == _cubeUser.id ||
(status.dialogId != null && status.dialogId != _cubeDialog.dialogId))
return;
userStatus = _occupants[status.userId]?.fullName ??
_occupants[status.userId]?.login ??
_occupants[status.userId]?.email ??
'';
if (userStatus.isEmpty) return;
userStatus = "$userStatus is typing ...";
if (isTyping != true) {
setState(() {
isTyping = true;
});
}
startTypingTimer();
}
startTypingTimer() {
typingTimer?.cancel();
typingTimer = Timer(const Duration(milliseconds: 900), () {
setState(() {
isTyping = false;
});
});
}
void onSendChatMessage(String content) {
if (content.trim() != '') {
final message = createCubeMsg();
message.body = content.trim();
onSendMessage(message);
} else {
Fluttertoast.showToast(msg: 'Nothing to send');
}
}
void onSendChatAttachment(CubeFile cubeFile, imageData) async {
final attachment = CubeAttachment();
attachment.id = cubeFile.uid;
attachment.type = CubeAttachmentType.IMAGE_TYPE;
attachment.url = cubeFile.getPublicUrl();
attachment.height = imageData.height;
attachment.width = imageData.width;
final message = createCubeMsg();
message.body = "Attachment";
message.attachments = [attachment];
onSendMessage(message);
}
CubeMessage createCubeMsg() {
var message = CubeMessage();
message.dateSent = DateTime.now().millisecondsSinceEpoch ~/ 1000;
message.markable = true;
message.saveToHistory = true;
return message;
}
void onSendMessage(CubeMessage message) async {
log("onSendMessage message= $message");
textEditingController.clear();
await _cubeDialog.sendMessage(message);
message.senderId = _cubeUser.id;
addMessageToListView(message);
listScrollController.animateTo(0.0,
duration: const Duration(milliseconds: 300), curve: Curves.easeOut);
if (_cubeDialog.type == CubeDialogType.PRIVATE) {
ChatManager.instance.sentMessagesController
.add(message..dialogId = _cubeDialog.dialogId);
}
}
updateReadDeliveredStatusMessage(MessageStatus status, bool isRead) {
log('[updateReadDeliveredStatusMessage]');
setState(() {
CubeMessage? msg = listMessage
.firstWhereOrNull((msg) => msg.messageId == status.messageId);
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);
}
log('[updateReadDeliveredStatusMessage] status updated for $msg');
});
}
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>[
// List of messages
buildListMessage(),
//Typing content
buildTyping(),
// Input content
buildInput(),
],
),
// Loading
// buildLoading()
],
),
);
}
Widget buildItem(int index, CubeMessage message) {
markAsReadIfNeed() {
var isOpponentMsgRead =
message.readIds != null && message.readIds!.contains(_cubeUser.id);
if (message.senderId != _cubeUser.id && !isOpponentMsgRead) {
if (message.readIds == null) {
message.readIds = [_cubeUser.id!];
} else {
message.readIds!.add(_cubeUser.id!);
}
if (CubeChatConnection.instance.chatConnectionState ==
CubeChatConnectionState.Ready) {
_cubeDialog.readMessage(message);
} else {
_unreadMessages.add(message);
}
ChatManager.instance.readMessagesController.add(MessageStatus(
_cubeUser.id!, message.messageId!, _cubeDialog.dialogId!));
}
}
Widget getReadDeliveredWidget() {
log("[getReadDeliveredWidget]");
bool messageIsRead() {
log("[getReadDeliveredWidget] messageIsRead");
if (_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 != _cubeUser.id && _occupants.keys.contains(id));
}
bool messageIsDelivered() {
log("[getReadDeliveredWidget] messageIsDelivered");
if (_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 != _cubeUser.id && _occupants.keys.contains(id));
}
if (messageIsRead()) {
log("[getReadDeliveredWidget] if messageIsRead");
return getMessageStateWidget(MessageState.read);
} else if (messageIsDelivered()) {
log("[getReadDeliveredWidget] if messageIsDelivered");
return getMessageStateWidget(MessageState.delivered);
} else {
log("[getReadDeliveredWidget] sent");
return getMessageStateWidget(MessageState.sent);
}
}
Widget getDateWidget() {
return Text(
DateFormat('HH:mm').format(
DateTime.fromMillisecondsSinceEpoch(message.dateSent! * 1000)),
style: TextStyle(
color: greyColor, fontSize: 12.0, fontStyle: FontStyle.italic),
);
}
Widget getHeaderDateWidget() {
return Container(
alignment: Alignment.center,
margin: const EdgeInsets.all(10.0),
child: Text(
DateFormat('dd MMMM').format(
DateTime.fromMillisecondsSinceEpoch(message.dateSent! * 1000)),
style: TextStyle(
color: primaryColor, fontSize: 20.0, fontStyle: FontStyle.italic),
),
);
}
bool isHeaderView() {
int headerId = int.parse(DateFormat('ddMMyyyy').format(
DateTime.fromMillisecondsSinceEpoch(message.dateSent! * 1000)));
if (index >= listMessage.length - 1) {
return false;
}
var msgPrev = listMessage[index + 1];
int nextItemHeaderId = int.parse(DateFormat('ddMMyyyy').format(
DateTime.fromMillisecondsSinceEpoch(msgPrev.dateSent! * 1000)));
var result = headerId != nextItemHeaderId;
return result;
}
if (message.senderId == _cubeUser.id) {
// Right (own message)
return Column(
children: <Widget>[
isHeaderView() ? getHeaderDateWidget() : SizedBox.shrink(),
GestureDetector(
onLongPress: () => _reactOnMessage(message),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Padding(
padding: EdgeInsets.only(
bottom: isLastMessageRight(index) ? 20.0 : 10.0,
right: 4.0),
child: GestureDetector(
onTap: () => _reactOnMessage(message),
child: const Icon(Icons.add_reaction_outlined,
size: 16, color: Colors.grey),
),
),
message.attachments?.isNotEmpty ?? false
// Image
? Container(
margin: EdgeInsets.only(
bottom: isLastMessageRight(index) ? 20.0 : 10.0,
right: 10.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => FullPhoto(
url: message
.attachments!.first.url!)));
},
child: ClipRRect(
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(8.0),
topRight: Radius.circular(8.0)),
child: CachedNetworkImage(
placeholder: (context, url) => Container(
width: 200.0,
height: 200.0,
padding: const EdgeInsets.all(70.0),
decoration: BoxDecoration(
color: greyColor2,
borderRadius: const BorderRadius.all(
Radius.circular(8.0),
),
),
child: AppUtils.buildLoading(),
),
errorWidget: (context, url, error) =>
Material(
borderRadius: const BorderRadius.all(
Radius.circular(8.0),
),
clipBehavior: Clip.hardEdge,
child: Image.asset(
'images/img_not_available.jpeg',
width: 200.0,
height: 200.0,
fit: BoxFit.cover,
),
),
imageUrl: message.attachments!.first.url!,
width: 200.0,
height: 200.0,
fit: BoxFit.cover,
),
),
),
if (message.reactions != null &&
message.reactions!.total.isNotEmpty)
getReactionsWidget(message),
Row(
mainAxisSize: MainAxisSize.min,
children: [
getDateWidget(),
getReadDeliveredWidget(),
],
),
]),
)
: message.body != null && message.body!.isNotEmpty
// Text
? Flexible(
child: Container(
constraints: const BoxConstraints(maxWidth: 480),
padding:
const EdgeInsets.fromLTRB(15.0, 10.0, 15.0, 10.0),
decoration: BoxDecoration(
color: greyColor2,
borderRadius: BorderRadius.circular(8.0)),
margin: EdgeInsets.only(
bottom:
isLastMessageRight(index) ? 20.0 : 10.0,
right: 10.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
message.body!,
style: TextStyle(color: primaryColor),
),
if (message.reactions != null &&
message.reactions!.total.isNotEmpty)
getReactionsWidget(message),
Row(
mainAxisSize: MainAxisSize.min,
children: [
getDateWidget(),
getReadDeliveredWidget(),
],
),
]),
),
)
: Container(
padding: const EdgeInsets.fromLTRB(15.0, 10.0, 15.0, 10.0),
width: 200.0,
decoration: BoxDecoration(
color: greyColor2,
borderRadius: BorderRadius.circular(8.0)),
margin: EdgeInsets.only(
bottom: isLastMessageRight(index) ? 20.0 : 10.0,
right: 10.0),
child: Text(
"Empty",
style: TextStyle(color: primaryColor),
),
),
],
),
),
],
);
} else {
// Left (opponent message)
markAsReadIfNeed();
return Container(
margin: const EdgeInsets.only(bottom: 10.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
isHeaderView() ? getHeaderDateWidget() : SizedBox.shrink(),
GestureDetector(
onLongPress: () => _reactOnMessage(message),
child: Row(
children: <Widget>[
getUserAvatarWidget(_occupants[message.senderId], 30),
message.attachments?.isNotEmpty ?? false
? Container(
margin: EdgeInsets.only(left: 10.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => FullPhoto(
url: message
.attachments!.first.url!)));
},
child: ClipRRect(
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(8.0),
topRight: Radius.circular(8.0)),
child: CachedNetworkImage(
placeholder: (context, url) => Container(
width: 200.0,
height: 200.0,
padding: const EdgeInsets.all(70.0),
decoration: BoxDecoration(
color: greyColor2,
borderRadius: const BorderRadius.all(
Radius.circular(8.0),
),
),
child: AppUtils.buildLoading()
),
errorWidget: (context, url, error) =>
Material(
borderRadius: const BorderRadius.all(
Radius.circular(8.0),
),
clipBehavior: Clip.hardEdge,
child: Image.asset(
'images/img_not_available.jpeg',
width: 200.0,
height: 200.0,
fit: BoxFit.cover,
),
),
imageUrl: message.attachments!.first.url!,
width: 200.0,
height: 200.0,
fit: BoxFit.cover,
),
),
),
if (message.reactions != null &&
message.reactions!.total.isNotEmpty)
getReactionsWidget(message),
getDateWidget(),
]),
)
: message.body != null && message.body!.isNotEmpty
? Flexible(
child: Container(
constraints: const BoxConstraints(
minWidth: 0.0, maxWidth: 480),
padding: const EdgeInsets.fromLTRB(15.0, 10.0, 15.0, 10.0),
decoration: BoxDecoration(
color: primaryColor,
borderRadius: BorderRadius.circular(8.0)),
margin: const EdgeInsets.only(left: 10.0),
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
message.body!,
style: const TextStyle(color: Colors.white),
),
if (message.reactions != null &&
message.reactions!.total.isNotEmpty)
getReactionsWidget(message),
getDateWidget(),
]),
),
)
: Container(
padding: const EdgeInsets.fromLTRB(15.0, 10.0, 15.0, 10.0),
width: 200.0,
decoration: BoxDecoration(
color: greyColor2,
borderRadius: BorderRadius.circular(8.0)),
margin: EdgeInsets.only(
bottom:
isLastMessageRight(index) ? 20.0 : 10.0,
right: 10.0),
child: Text(
"Empty",
style: TextStyle(color: primaryColor),
),
),
Padding(
padding: const EdgeInsets.only(
// bottom: isLastMessageRight(index) ? 20.0 : 10.0,
left: 4.0),
child: GestureDetector(
onTap: () => _reactOnMessage(message),
child: Icon(Icons.add_reaction_outlined,
size: 16, color: primaryColor),
),
),
],
),
)
],
),
);
}
}
bool isLastMessageLeft(int index) {
if ((index > 0 && listMessage[index - 1].id == _cubeUser.id) ||
index == 0) {
return true;
} else {
return false;
}
}
bool isLastMessageRight(int index) {
if ((index > 0 && listMessage[index - 1].id != _cubeUser.id) ||
index == 0) {
return true;
} else {
return false;
}
}
Widget buildLoading() {
return Positioned(
child: isLoading ? AppUtils.buildLoading() : Container(),
);
}
Widget buildTyping() {
return Visibility(
visible: isTyping,
child: Container(
alignment: Alignment.centerLeft,
margin: const EdgeInsets.all(16.0),
child: Text(
userStatus,
style: TextStyle(color: primaryColor),
),
),
);
}
Widget buildInput() {
return Container(
width: double.infinity,
height: 50.0,
decoration: BoxDecoration(
border: Border(top: BorderSide(color: greyColor2, width: 0.5)),
color: Colors.white),
child: Row(
children: <Widget>[
// Button send image
Material(
color: Colors.white,
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 1.0),
child: IconButton(
icon: const Icon(Icons.image),
onPressed: () {
openGallery();
},
color: primaryColor,
),
),
),
// Edit text
Flexible(
child: TextField(
autofocus: platformUtils.isDesktop(),
focusNode: _editMessageFocusNode,
keyboardType: TextInputType.multiline,
maxLines: null,
style: TextStyle(color: primaryColor, fontSize: 15.0),
controller: textEditingController,
decoration: InputDecoration.collapsed(
hintText: 'Type your message...',
hintStyle: TextStyle(color: greyColor),
),
onChanged: (text) {
sendIsTypingStatus();
},
),
),
// Button send message
Material(
color: Colors.white,
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 8.0),
child: IconButton(
icon: const Icon(Icons.send),
onPressed: () => onSendChatMessage(textEditingController.text),
color: primaryColor,
),
),
),
],
),
);
}
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 Center(
child: AppUtils.buildLoading(),
);
} else {
listMessage = snapshot.data ?? [];
return getWidgetMessages(listMessage);
}
},
),
);
}
Future<List<CubeMessage>> getMessagesList() async {
if (listMessage.isNotEmpty) return Future.value(listMessage);
Completer<List<CubeMessage>> completer = Completer();
List<CubeMessage>? messages;
try {
await Future.wait<void>([
getMessagesByDate(0, false).then((loadedMessages) {
isLoading = false;
messages = loadedMessages;
}),
getAllUsersByIds(_cubeDialog.occupantsIds!.toSet()).then((result) =>
_occupants.addAll({ for (var item in result!.items) item.id : item }))
]);
completer.complete(messages);
} catch (error) {
completer.completeError(error);
}
return completer.future;
}
void onScrollChanged() {
if ((listScrollController.position.pixels ==
listScrollController.position.maxScrollExtent) &&
messagesPerPage >= lastPartSize) {
setState(() {
isLoading = true;
if (oldMessages.isNotEmpty) {
getMessagesBetweenDates(
oldMessages.first.dateSent ?? 0,
listMessage.last.dateSent ??
DateTime.now().millisecondsSinceEpoch ~/ 1000)
.then((newMessages) {
setState(() {
isLoading = false;
listMessage.addAll(newMessages);
if (newMessages.length < messagesPerPage) {
oldMessages.insertAll(0, listMessage);
listMessage = List.from(oldMessages);
oldMessages.clear();
}
});
});
} else {
getMessagesByDate(listMessage.last.dateSent ?? 0, false)
.then((messages) {
setState(() {
isLoading = false;
listMessage.addAll(messages);
});
});
}
});
}
}
Future<bool> onBackPress() {
return Navigator.pushNamedAndRemoveUntil<bool>(
context, 'select_dialog', (r) => false,
arguments: {USER_ARG_NAME: _cubeUser}).then((value) {
return true;
});
}
_initChatListeners() {
log("[_initChatListeners]");
msgSubscription = CubeChatConnection
.instance.chatMessagesManager!.chatMessagesStream
.listen(onReceiveMessage);
deliveredSubscription = CubeChatConnection
.instance.messagesStatusesManager!.deliveredStream
.listen(onDeliveredMessage);
readSubscription = CubeChatConnection
.instance.messagesStatusesManager!.readStream
.listen(onReadMessage);
typingSubscription = CubeChatConnection
.instance.typingStatusesManager!.isTypingStream
.listen(onTypingMessage);
reactionsSubscription = CubeChatConnection
.instance.messagesReactionsManager?.reactionsStream
.listen(onReactionReceived);
}
void _initCubeChat() {
log("_initCubeChat");
if (CubeChatConnection.instance.isAuthenticated()) {
log("[_initCubeChat] isAuthenticated");
_initChatListeners();
} else {
log("[_initCubeChat] not authenticated");
CubeChatConnection.instance.connectionStateStream.listen((state) {
log("[_initCubeChat] state $state");
if (CubeChatConnectionState.Ready == state) {
_initChatListeners();
if (_unreadMessages.isNotEmpty) {
_unreadMessages.forEach((cubeMessage) {
_cubeDialog.readMessage(cubeMessage);
});
_unreadMessages.clear();
}
if (_unsentMessages.isNotEmpty) {
_unsentMessages.forEach((cubeMessage) {
_cubeDialog.sendMessage(cubeMessage);
});
_unsentMessages.clear();
}
}
});
}
}
void sendIsTypingStatus() {
var currentTime = DateTime.now().millisecondsSinceEpoch;
var isTypingTimeout = currentTime - _sendIsTypingTime;
if (isTypingTimeout >= TYPING_TIMEOUT) {
_sendIsTypingTime = currentTime;
_cubeDialog.sendIsTypingStatus();
_startStopTypingStatus();
}
}
void _startStopTypingStatus() {
_sendStopTypingTimer?.cancel();
_sendStopTypingTimer =
Timer(Duration(milliseconds: STOP_TYPING_TIMEOUT), () {
_cubeDialog.sendStopTypingStatus();
});
}
Future<List<CubeMessage>> getMessagesByDate(int date, bool isLoadNew) async {
var params = GetMessagesParameters();
params.sorter = RequestSorter(SORT_DESC, '', 'date_sent');
params.limit = messagesPerPage;
params.filters = [
RequestFilter('', 'date_sent', isLoadNew || date == 0 ? 'gt' : 'lt', date)
];
return getMessages(_cubeDialog.dialogId!, params.getRequestParameters())
.then((result) {
lastPartSize = result!.items.length;
return result.items;
})
.whenComplete(() {})
.catchError((onError) {
return List<CubeMessage>.empty(growable: true);
});
}
Future<List<CubeMessage>> getMessagesBetweenDates(
int startDate, int endDate) async {
var params = GetMessagesParameters();
params.sorter = RequestSorter(SORT_DESC, '', 'date_sent');
params.limit = messagesPerPage;
params.filters = [
RequestFilter('', 'date_sent', 'gt', startDate),
RequestFilter('', 'date_sent', 'lt', endDate)
];
return getMessages(_cubeDialog.dialogId!, params.getRequestParameters())
.then((result) {
return result!.items;
});
}
void onConnectivityChanged(ConnectivityResult connectivityType) {
log("[ChatScreenState] connectivityType changed to '$connectivityType'");
if (connectivityType == ConnectivityResult.wifi ||
connectivityType == ConnectivityResult.mobile) {
setState(() {
isLoading = true;
});
getMessagesBetweenDates(listMessage.first.dateSent ?? 0,
DateTime.now().millisecondsSinceEpoch ~/ 1000)
.then((newMessages) {
setState(() {
if (newMessages.length == messagesPerPage) {
oldMessages = List.from(listMessage);
listMessage = newMessages;
} else {
listMessage.insertAll(0, newMessages);
}
});
}).whenComplete(() {
setState(() {
isLoading = false;
});
});
}
}
getReactionsWidget(CubeMessage message) {
if (message.reactions == null) return Container();
var isOwnMessage = message.senderId == _cubeUser.id;
return LayoutBuilder(builder: (context, constraints) {
var widgetWidth =
constraints.maxWidth == double.infinity ? 240 : constraints.maxWidth;
var maxColumns = (widgetWidth / 60).round();
if (message.reactions!.total.length < maxColumns) {
maxColumns = message.reactions!.total.length;
}
return SizedBox(
width: maxColumns * 56,
child: GridView.count(
primary: false,
crossAxisCount: maxColumns,
mainAxisSpacing: 4,
childAspectRatio: 2,
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
padding: const EdgeInsets.symmetric(vertical: 4),
children: <Widget>[
...message.reactions!.total.keys.map((reaction) {
return GestureDetector(
onTap: () => _performReaction(Emoji(reaction, ''), message),
child: Padding(
padding: EdgeInsets.only(
left: isOwnMessage ? 4 : 0,
right: isOwnMessage ? 0 : 4,
),
child: ClipRRect(
borderRadius: const BorderRadius.all(
Radius.circular(16),
),
child: Container(
padding: const EdgeInsets.symmetric(
vertical: 4, horizontal: 6),
color: message.reactions!.own.contains(reaction)
? Colors.green
: Colors.grey,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment:
MainAxisAlignment.spaceAround,
children: [
Text(reaction,
style: kIsWeb
? const TextStyle(
color: Colors.green,
fontFamily: 'NotoColorEmoji')
: null),
Text(
' ${message.reactions!.total[reaction].toString()}',
style: const TextStyle(
color: Colors.white,
)),
],
)),
)));
}).toList()
],
));
});
}
_reactOnMessage(CubeMessage message) {
showDialog<Emoji>(
context: context,
builder: (BuildContext context) {
return Dialog(
child: Container(
margin: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8.0)),
width: 400,
height: 400,
child: EmojiPicker(
config: const Config(
emojiTextStyle: kIsWeb
? TextStyle(
color: Colors.green, fontFamily: 'NotoColorEmoji')
: null,
iconColorSelected: Colors.green,
indicatorColor: Colors.green,
bgColor: Colors.white,
),
onEmojiSelected: (category, emoji) {
Navigator.pop(context, emoji);
},
)));
}).then((emoji) {
log("onEmojiSelected emoji: ${emoji?.emoji}");
if (emoji != null) {
_performReaction(emoji, message);
}
});
}
void _performReaction(Emoji emoji, CubeMessage message) {
if ((message.reactions?.own.isNotEmpty ?? false) &&
(message.reactions?.own.contains(emoji.emoji) ?? false)) {
removeMessageReaction(message.messageId!, emoji.emoji);
_updateMessageReactions(MessageReaction(
_cubeUser.id!, _cubeDialog.dialogId!, message.messageId!,
removeReaction: emoji.emoji));
} else {
addMessageReaction(message.messageId!, emoji.emoji);
_updateMessageReactions(MessageReaction(
_cubeUser.id!, _cubeDialog.dialogId!, message.messageId!,
addReaction: emoji.emoji));
}
}
void _updateMessageReactions(MessageReaction reaction) {
log('[_updateMessageReactions]');
setState(() {
CubeMessage? msg = listMessage
.firstWhereOrNull((msg) => msg.messageId == reaction.messageId);
if (msg == null) return;
if (msg.reactions == null) {
msg.reactions = CubeMessageReactions.fromJson({
'own': {if (reaction.userId == _cubeUser.id) reaction.addReaction},
'total': {reaction.addReaction: 1}
});
} else {
if (reaction.addReaction != null) {
if (reaction.userId != _cubeUser.id ||
!(msg.reactions?.own.contains(reaction.addReaction) ?? false)) {
if (reaction.userId == _cubeUser.id) {
msg.reactions!.own.add(reaction.addReaction!);
}
msg.reactions!.total[reaction.addReaction!] =
msg.reactions!.total[reaction.addReaction] == null
? 1
: msg.reactions!.total[reaction.addReaction]! + 1;
}
}
if (reaction.removeReaction != null) {
if (reaction.userId != _cubeUser.id ||
(msg.reactions?.own.contains(reaction.removeReaction) ?? false)) {
if (reaction.userId == _cubeUser.id) {
msg.reactions!.own.remove(reaction.removeReaction!);
}
msg.reactions!.total[reaction.removeReaction!] =
msg.reactions!.total[reaction.removeReaction] != null &&
msg.reactions!.total[reaction.removeReaction]! > 0
? msg.reactions!.total[reaction.removeReaction]! - 1
: 0;
}
msg.reactions!.total.removeWhere((key, value) => value == 0);
}
}
});
}
FocusNode createEditMessageFocusNode() {
return FocusNode(
onKey: (FocusNode node, RawKeyEvent evt) {
if (!evt.isShiftPressed && evt.logicalKey == LogicalKeyboardKey.enter) {
if (evt is RawKeyDownEvent) {
onSendChatMessage(textEditingController.text);
}
_editMessageFocusNode.requestFocus();
return KeyEventResult.handled;
} else if (evt.logicalKey == LogicalKeyboardKey.enter) {
if (evt is RawKeyDownEvent) {
setState(() {
textEditingController.text = textEditingController.text + '\n';
textEditingController.selection = TextSelection.collapsed(
offset: textEditingController.text.length);
});
}
_editMessageFocusNode.requestFocus();
return KeyEventResult.handled;
} else {
return KeyEventResult.ignored;
}
},
);
}
}
void showChatDetails(
BuildContext context, CubeUser cubeUser, CubeDialog cubeDialog) async {
log("_chatDetails= $cubeDialog");
platformUtils.showModal(
context: context, child: UpdateDialog(cubeUser, cubeDialog));
}
import 'package:connectycube_sdk/src/core/users/models/cube_user.dart';
import 'package:flutter/material.dart';
import 'package:vmeeting/srcchat/utils/consts.dart';
import 'new_dialog_screen.dart';
import 'new_group_dialog_screen.dart';
import 'utils/route_utils.dart';
class CreateDialog extends StatelessWidget {
final CubeUser currentUser;
CreateDialog(this.currentUser);
@override
Widget build(BuildContext context) {
return Navigator(
key: Navigation.createDialogNavigation,
initialRoute: 'search_users',
onGenerateRoute: (RouteSettings settings) {
Map<String, dynamic>? args =
settings.arguments as Map<String, dynamic>?;
Widget page;
switch (settings.name) {
case 'search_users':
page = CreateChatScreen(currentUser);
break;
case 'configure_group_dialog':
page = NewGroupDialogScreen(
args![USER_ARG_NAME],
args[DIALOG_ARG_NAME],
args[SELECTED_USERS_ARG_NAME],
);
break;
default:
page = CreateChatScreen(args![USER_ARG_NAME]);
break;
}
return PageRouteBuilder(
pageBuilder: (_, __, ___) => page,
);
});
}
}
import 'dart:convert';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:connectycube_sdk/connectycube_sdk.dart';
import '../firebase_options.dart';
import 'managers/push_notifications_manager.dart';
import 'phone_auth_flow.dart';
import 'utils/api_utils.dart';
import 'utils/consts.dart';
import 'utils/platform_utils.dart' as platformUtils;
import 'utils/pref_util.dart';
class LoginScreen extends StatelessWidget {
static const String TAG = "LoginScreen";
@override
Widget build(BuildContext context) {
return Scaffold(
// appBar: AppBar(automaticallyImplyLeading: false, title: Text('Chat')),
body: LoginPage(),
);
}
}
class LoginPage extends StatefulWidget {
@override
State<StatefulWidget> createState() => new LoginPageState();
}
// Used for controlling whether the user is loggin or creating an account
enum FormType { login, register }
class LoginPageState extends State<LoginPage> {
static const String TAG = "_LoginPageState";
final TextEditingController _loginFilter = new TextEditingController();
final TextEditingController _passwordFilter = new TextEditingController();
String _login = "";
String _password = "";
FormType _form = FormType
.login; // our default setting is to login, and we should switch to creating an account when the user chooses to
bool _isLoginContinues = false;
List<bool> loginEmailSelection = [true, false];
bool isEmailSelected = false;
LoginPageState() {
_loginFilter.addListener(_loginListen);
_passwordFilter.addListener(_passwordListen);
}
void _loginListen() {
if (_loginFilter.text.isEmpty) {
_login = "";
} else {
_login = _loginFilter.text;
}
}
void _passwordListen() {
if (_passwordFilter.text.isEmpty) {
_password = "";
} else {
_password = _passwordFilter.text;
}
}
// Swap in between our two forms, registering and logging in
void _formChange() async {
setState(() {
if (_form == FormType.register) {
_form = FormType.login;
} else {
_form = FormType.register;
}
});
}
@override
void initState() {
super.initState();
loginEmailSelection = [true, false];
isEmailSelected = loginEmailSelection[1];
}
@override
Widget build(BuildContext context) {
return new Scaffold(
body: Center(
child: SingleChildScrollView(
child: new Container(
padding: EdgeInsets.symmetric(horizontal: 16.0),
child: new Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[_buildLogoField(), _initLoginWidgets()],
),
),
),
),
);
}
Widget _buildLogoField() {
// return Image.asset('assets/images/splash.png');
return Container(
child: Align(
alignment: FractionalOffset.center,
child: Column(
children: [
ConstrainedBox(
constraints: BoxConstraints(maxWidth: 350),
child: Image.asset('assets/images/splash.png'),
),
Container(
margin: EdgeInsets.only(left: 8),
height: 18,
width: 18,
child: Visibility(
visible: _isLoginContinues,
child: CircularProgressIndicator(
strokeWidth: 2,
),
),
),
],
),
),
);
}
_initLoginWidgets() {
return FutureBuilder<Widget>(
future: getFilterChipsWidgets(),
builder: (BuildContext context, AsyncSnapshot<Widget> snapshot) {
if (snapshot.hasData) {
return snapshot.data!;
}
return SizedBox.shrink();
});
}
Future<Widget> getFilterChipsWidgets() async {
if (_isLoginContinues) return SizedBox.shrink();
SharedPrefs sharedPrefs = await SharedPrefs.instance.init();
var loginType = sharedPrefs.getLoginType();
var user = sharedPrefs.getUser();
if ((user != null && loginType == null) || loginType != null) {
_loginToCCWithSavedUser(context, loginType ?? LoginType.login);
return SizedBox.shrink();
} else
return new Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[_buildTextFields(), _buildButtons()],
);
}
Widget _buildTextFields() {
return ConstrainedBox(
constraints: BoxConstraints(maxWidth: 400),
child: Container(
child: Column(
children: <Widget>[
Align(
alignment: Alignment.centerRight,
child: ToggleButtons(
constraints: BoxConstraints(maxHeight: 38),
borderColor: Colors.green,
fillColor: Colors.green.shade400,
borderWidth: 1,
selectedBorderColor: Colors.green,
selectedColor: Colors.white,
borderRadius: BorderRadius.circular(28),
children: <Widget>[
Padding(
padding: const EdgeInsets.symmetric(
vertical: 4, horizontal: 6.0),
child: Text(
'By Login',
style: TextStyle(
color: isEmailSelected ? Colors.green : Colors.white),
),
),
Padding(
padding: const EdgeInsets.symmetric(
vertical: 4, horizontal: 6.0),
child: Text(
'By E-mail',
style: TextStyle(
color: isEmailSelected ? Colors.white : Colors.green),
),
),
],
onPressed: (int index) {
setState(() {
for (int i = 0; i < loginEmailSelection.length; i++) {
loginEmailSelection[i] = i == index;
}
isEmailSelected = loginEmailSelection[1];
});
},
isSelected: loginEmailSelection,
),
),
Container(
child: TextField(
keyboardType: isEmailSelected
? TextInputType.emailAddress
: TextInputType.text,
controller: _loginFilter,
decoration: InputDecoration(
labelText: isEmailSelected ? 'E-mail' : 'Login'),
),
),
Container(
child: TextField(
keyboardType: TextInputType.visiblePassword,
controller: _passwordFilter,
decoration: InputDecoration(labelText: 'Password'),
obscureText: true,
enableSuggestions: false,
autocorrect: false,
onSubmitted: (_) {
_form == FormType.login
? _loginPressed()
: _createAccountPressed();
},
),
)
],
),
),
);
}
Widget _buildButtons() {
return ConstrainedBox(
constraints: BoxConstraints(maxWidth: 400),
child: _form == FormType.login
? Container(
margin: EdgeInsets.only(top: 8),
child: Column(
children: <Widget>[
ElevatedButton(
child: new Text('Login'),
onPressed: _loginPressed,
),
TextButton(
child: new Text(
'Don\'t have an account? Tap here to register.'),
onPressed: _formChange,
),
...createCIPButtons(),
],
),
)
: Container(
margin: EdgeInsets.only(top: 8),
child: Column(
children: <Widget>[
ElevatedButton(
child: new Text('Create an Account'),
onPressed: _createAccountPressed,
),
TextButton(
child: new Text('Have an account? Click here to login.'),
onPressed: _formChange,
),
...createCIPButtons(),
],
),
),
);
}
List<Widget> createCIPButtons() {
return [
Visibility(
visible: platformUtils.isPhoneAuthSupported,
child: OutlinedButton.icon(
style: OutlinedButton.styleFrom(
minimumSize: Size(190, 36),
),
icon: Icon(
Icons.dialpad,
),
label: Text('By Phone number'),
onPressed: () {
platformUtils.showModal(
context: context, child: VerifyPhoneNumber());
},
),
),
SizedBox(
height: 6,
),
OutlinedButton.icon(
style: OutlinedButton.styleFrom(
foregroundColor: Colors.blue,
minimumSize: Size(190, 36),
),
icon: Icon(
Icons.facebook,
color: Colors.blue.shade700,
),
label: Text(
'By Facebook',
style: TextStyle(color: Colors.blue.shade700),
),
onPressed: () {
Fluttertoast.showToast(
msg: 'Coming soon', gravity: ToastGravity.BOTTOM);
},
),
];
}
void _loginPressed() {
print('login with $_login and $_password');
var userToLogin = CubeUser();
if (isEmailSelected) {
userToLogin.email = _login;
} else {
userToLogin.login = _login;
}
userToLogin.password = _password;
_loginToCC(context, userToLogin, saveUser: true);
}
void _createAccountPressed() {
print('create an user with $_login and $_password');
var userToSignUp = CubeUser();
if (isEmailSelected) {
userToSignUp.email = _login;
} else {
userToSignUp.login = _login;
}
userToSignUp.password = _password;
userToSignUp.fullName = _login;
_signInCC(context, userToSignUp);
}
_signInCC(BuildContext context, CubeUser user) async {
if (_isLoginContinues) return;
setState(() {
_isLoginContinues = true;
});
if (!CubeSessionManager.instance.isActiveSessionValid()) {
try {
await createSession();
} catch (error) {
_processLoginError(error);
}
}
signUp(user).then((newUser) {
print("signUp newUser $newUser");
user.id = newUser.id;
SharedPrefs.instance.saveNewUser(
user, isEmailSelected ? LoginType.email : LoginType.login);
signIn(user).then((result) {
PushNotificationsManager.instance.init();
_loginToCubeChat(context, user);
});
}).catchError((exception) {
_processLoginError(exception);
});
}
_loginToCC(BuildContext context, CubeUser user, {bool saveUser = false}) {
print("_loginToCC user: $user");
if (_isLoginContinues) return;
setState(() {
_isLoginContinues = true;
});
createSession(user).then((cubeSession) async {
print("createSession cubeSession: $cubeSession");
var tempUser = user;
user = cubeSession.user!..password = tempUser.password;
if (saveUser)
SharedPrefs.instance.init().then((sharedPrefs) {
sharedPrefs.saveNewUser(
user, isEmailSelected ? LoginType.email : LoginType.login);
});
PushNotificationsManager.instance.init();
_loginToCubeChat(context, user);
}).catchError((error) {
_processLoginError(error);
});
}
_loginToCCWithSavedUser(BuildContext context, LoginType loginType) async {
log("[_loginToCCWithSavedUser] user: $loginType");
if (_isLoginContinues) return;
setState(() {
_isLoginContinues = true;
});
Future<CubeUser>? signInFuture;
if (loginType == LoginType.phone) {
var phoneAuthToken =
await FirebaseAuth.instance.currentUser?.getIdToken();
if (phoneAuthToken == null) {
setState(() {
_isLoginContinues = false;
});
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('Error'),
content: Text(
'Your Phone authentication session was expired, please refresh it by second login using your phone number'),
actions: <Widget>[
TextButton(
child: Text("OK"),
onPressed: () => Navigator.of(context).pop(),
)
],
);
});
return;
}
signInFuture = createSession().then((cubeSession) {
return signInUsingFirebase(
DefaultFirebaseOptions.currentPlatform.projectId,
phoneAuthToken)
.then((cubeUser) {
return SharedPrefs.instance.init().then((sharedPrefs) {
sharedPrefs.saveNewUser(cubeUser, LoginType.phone);
return cubeUser
..password = CubeSessionManager.instance.activeSession?.token;
});
});
});
} else if (loginType == LoginType.login || loginType == LoginType.email) {
signInFuture = SharedPrefs.instance.init().then((sharedPrefs) {
var savedUser = sharedPrefs.getUser();
return createSession(savedUser).then((value) {
return savedUser!;
});
});
}
signInFuture?.then((cubeUser) {
PushNotificationsManager.instance.init();
_loginToCubeChat(context, cubeUser);
}).catchError((error) {
_processLoginError(error);
});
}
_loginToCubeChat(BuildContext context, CubeUser user) {
log("_loginToCubeChat user $user");
CubeChatConnectionSettings.instance.totalReconnections = 0;
CubeChatConnection.instance.login(user).then((cubeUser) {
_isLoginContinues = false;
_goDialogScreen(context, cubeUser);
}).catchError((error) {
_processLoginError(error);
});
}
void _processLoginError(exception) {
log("Login error $exception", TAG);
setState(() {
_isLoginContinues = false;
});
showDialogError(exception, context);
}
void _goDialogScreen(BuildContext context, CubeUser cubeUser) async {
log("_goDialogScreen");
FlutterLocalNotificationsPlugin()
.getNotificationAppLaunchDetails()
.then((details) {
log("getNotificationAppLaunchDetails");
String? payload = details!.notificationResponse?.payload;
log("getNotificationAppLaunchDetails, payload: $payload");
var dialogId;
if (payload == null) {
dialogId = SharedPrefs.instance.getSelectedDialogId();
log("getNotificationAppLaunchDetails, selectedDialogId: $dialogId");
} else {
Map<String, dynamic> payloadObject = jsonDecode(payload);
dialogId = payloadObject['dialog_id'];
}
if (dialogId != null && dialogId.isNotEmpty) {
getDialogs({'id': dialogId}).then((dialogs) {
if (dialogs?.items != null && dialogs!.items.isNotEmpty) {
CubeDialog dialog = dialogs.items.first;
navigateToNextScreen(cubeUser, dialog);
} else {
navigateToNextScreen(cubeUser, null);
}
}).catchError((onError) {
navigateToNextScreen(cubeUser, null);
});
} else {
navigateToNextScreen(cubeUser, null);
}
}).catchError((onError) {
log("getNotificationAppLaunchDetails ERROR");
navigateToNextScreen(cubeUser, null);
});
}
void navigateToNextScreen(CubeUser cubeUser, CubeDialog? dialog) {
SharedPrefs.instance.saveSelectedDialogId('');
Navigator.pushReplacementNamed(
context,
'select_dialog',
arguments: {USER_ARG_NAME: cubeUser, DIALOG_ARG_NAME: dialog},
);
if (dialog != null && !platformUtils.isDesktop()) {
Navigator.pushNamed(context, 'chat_dialog',
arguments: {USER_ARG_NAME: cubeUser, DIALOG_ARG_NAME: dialog});
}
}
}
import 'dart:async';
import 'package:connectycube_sdk/connectycube_sdk.dart';
class ChatManager {
static ChatManager? _instance;
ChatManager._();
static ChatManager get instance => _instance ??= ChatManager._();
StreamController<CubeMessage> sentMessagesController =
StreamController.broadcast();
Stream<CubeMessage> get sentMessagesStream {
return sentMessagesController.stream;
}
StreamController<MessageStatus> readMessagesController =
StreamController.broadcast();
Stream<MessageStatus> get readMessagesStream {
return readMessagesController.stream;
}
}
import 'dart:async';
import 'dart:convert';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:universal_io/io.dart';
import 'package:uuid/uuid.dart';
import 'package:connectycube_sdk/connectycube_sdk.dart';
import '../utils/consts.dart';
import '../utils/platform_utils.dart';
import '../utils/pref_util.dart';
class PushNotificationsManager {
static const TAG = "PushNotificationsManager";
static final PushNotificationsManager _instance =
PushNotificationsManager._internal();
late FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin;
PushNotificationsManager._internal() {
flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
}
BuildContext? applicationContext;
static PushNotificationsManager get instance => _instance;
Future<dynamic> Function(String? payload)? onNotificationClicked;
init() async {
log('[init]', TAG);
FirebaseMessaging firebaseMessaging = FirebaseMessaging.instance;
await firebaseMessaging.requestPermission(
alert: true, badge: true, sound: true);
const AndroidInitializationSettings initializationSettingsAndroid =
AndroidInitializationSettings('ic_launcher_foreground');
final DarwinInitializationSettings initializationSettingsIOS =
DarwinInitializationSettings(
requestSoundPermission: true,
requestBadgePermission: true,
requestAlertPermission: true,
onDidReceiveLocalNotification: onDidReceiveLocalNotification,
);
final InitializationSettings initializationSettings =
InitializationSettings(
android: initializationSettingsAndroid,
iOS: initializationSettingsIOS,
macOS: DarwinInitializationSettings());
await flutterLocalNotificationsPlugin.initialize(
initializationSettings,
onDidReceiveNotificationResponse:
(NotificationResponse notificationResponse) {
log('[onDidReceiveNotificationResponse] payload: ${notificationResponse.payload}',
TAG);
var data = notificationResponse.payload;
if (data != null) {
if (onNotificationClicked != null) {
onNotificationClicked?.call(data);
} else {
String? dialogId = jsonDecode(data)['dialog_id'];
SharedPrefs.instance.saveSelectedDialogId(dialogId ?? '');
}
}
},
onDidReceiveBackgroundNotificationResponse: notificationTapBackground,
);
String? token;
if (Platform.isAndroid || kIsWeb || Platform.isIOS || Platform.isMacOS) {
firebaseMessaging.getToken().then((token) {
log('[getToken] token: $token', TAG);
subscribe(token);
}).catchError((onError) {
log('[getToken] onError: $onError', TAG);
});
}
if (!isEmpty(token)) {
subscribe(token);
}
firebaseMessaging.onTokenRefresh.listen((newToken) {
subscribe(newToken);
});
FirebaseMessaging.onMessage.listen((remoteMessage) {
log('[onMessage] message: ${remoteMessage.data}', TAG);
showNotification(remoteMessage);
});
// TODO test after fix https://github.com/FirebaseExtended/flutterfire/issues/4898
FirebaseMessaging.onMessageOpenedApp.listen((remoteMessage) {
log('[onMessageOpenedApp] remoteMessage: $remoteMessage', TAG);
onNotificationClicked?.call(jsonEncode(remoteMessage.data));
});
}
subscribe(String? token) async {
log('[subscribe] token: $token', PushNotificationsManager.TAG);
SharedPrefs sharedPrefs = await SharedPrefs.instance.init();
if (sharedPrefs.getSubscriptionToken() == token) {
log('[subscribe] skip subscription for same token',
PushNotificationsManager.TAG);
return;
}
CreateSubscriptionParameters parameters = CreateSubscriptionParameters();
parameters.pushToken = token;
bool isProduction = kIsWeb ? true : bool.fromEnvironment('dart.vm.product');
parameters.environment =
isProduction ? CubeEnvironment.PRODUCTION : CubeEnvironment.DEVELOPMENT;
if (Platform.isAndroid || kIsWeb || Platform.isIOS || Platform.isMacOS) {
parameters.channel = NotificationsChannels.GCM;
parameters.platform = CubePlatform.ANDROID;
}
var deviceInfoPlugin = DeviceInfoPlugin();
var deviceId;
if (kIsWeb) {
var webBrowserInfo = await deviceInfoPlugin.webBrowserInfo;
deviceId = base64Encode(utf8.encode(webBrowserInfo.userAgent ?? ''));
} else if (Platform.isAndroid) {
var androidInfo = await deviceInfoPlugin.androidInfo;
deviceId = androidInfo.id;
} else if (Platform.isIOS) {
var iosInfo = await deviceInfoPlugin.iosInfo;
deviceId = iosInfo.identifierForVendor;
} else if (Platform.isMacOS) {
var macOsInfo = await deviceInfoPlugin.macOsInfo;
deviceId = macOsInfo.computerName;
}
parameters.udid = deviceId ?? Uuid().v4;
var packageInfo = await PackageInfo.fromPlatform();
parameters.bundleIdentifier = packageInfo.packageName;
createSubscription(parameters.getRequestParameters())
.then((cubeSubscription) {
log('[subscribe] subscription SUCCESS', PushNotificationsManager.TAG);
sharedPrefs.saveSubscriptionToken(token!);
cubeSubscription.forEach((subscription) {
if (subscription.clientIdentificationSequence == token) {
sharedPrefs.saveSubscriptionId(subscription.id!);
}
});
}).catchError((error) {
log('[subscribe] subscription ERROR: $error',
PushNotificationsManager.TAG);
});
}
Future<void> unsubscribe() {
return SharedPrefs.instance.init().then((sharedPrefs) {
int subscriptionId = sharedPrefs.getSubscriptionId();
if (subscriptionId != 0) {
return deleteSubscription(subscriptionId).then((voidResult) {
FirebaseMessaging.instance.deleteToken();
sharedPrefs.saveSubscriptionId(0);
});
}
return Future.value();
}).catchError((onError) {
log('[unsubscribe] ERROR: $onError', PushNotificationsManager.TAG);
});
}
Future<dynamic> onDidReceiveLocalNotification(
int id, String? title, String? body, String? payload) {
log('[onDidReceiveLocalNotification] id: $id , title: $title, body: $body, payload: $payload',
PushNotificationsManager.TAG);
return Future.value();
}
Future<dynamic> onSelectNotification(String? payload) {
log('[onSelectNotification] payload: $payload',
PushNotificationsManager.TAG);
onNotificationClicked?.call(payload);
return Future.value();
}
}
showNotification(RemoteMessage message) {
log('[showNotification] message: ${message.data}',
PushNotificationsManager.TAG);
Map<String, dynamic> data = message.data;
NotificationDetails buildNotificationDetails(
int? badge,
String threadIdentifier,
) {
final DarwinNotificationDetails darwinNotificationDetails =
DarwinNotificationDetails(
badgeNumber: badge,
threadIdentifier: threadIdentifier,
);
final AndroidNotificationDetails androidPlatformChannelSpecifics =
AndroidNotificationDetails(
'messages_channel_id',
'Chat messages',
channelDescription: 'Chat messages will be received here',
importance: Importance.max,
priority: Priority.high,
showWhen: true,
color: Colors.green,
);
return NotificationDetails(
android: androidPlatformChannelSpecifics,
iOS: darwinNotificationDetails,
macOS: darwinNotificationDetails);
}
var badge = int.tryParse(data['badge'].toString());
var threadId = data['ios_thread_id'] ?? data['dialog_id'] ?? 'ios_thread_id';
FlutterLocalNotificationsPlugin().show(
6543,
"Chat sample",
data['message'].toString(),
buildNotificationDetails(badge, threadId),
payload: jsonEncode(data),
);
}
@pragma('vm:entry-point')
Future<void> onBackgroundMessage(RemoteMessage message) async {
log('[onBackgroundMessage] message: ${message.data}',
PushNotificationsManager.TAG);
showNotification(message);
if(!Platform.isIOS) {
updateBadgeCount(int.tryParse(message.data['badge'].toString()));
}
return Future.value();
}
Future<dynamic> onNotificationSelected(String? payload, BuildContext? context) {
log('[onSelectNotification] payload: $payload', PushNotificationsManager.TAG);
if (context == null) return Future.value();
log('[onSelectNotification] context != null', PushNotificationsManager.TAG);
if (payload != null) {
return SharedPrefs.instance.init().then((sharedPrefs) {
CubeUser? user = sharedPrefs.getUser();
Map<String, dynamic> payloadObject = jsonDecode(payload);
String? dialogId = payloadObject['dialog_id'];
log("[onSelectNotification] dialog_id: $dialogId",
PushNotificationsManager.TAG);
getDialogs({'id': dialogId}).then((dialogs) {
if (dialogs?.items != null && dialogs!.items.isNotEmpty) {
CubeDialog dialog = dialogs.items.first;
Navigator.pushNamed(context, 'chat_dialog',
arguments: {USER_ARG_NAME: user, DIALOG_ARG_NAME: dialog});
}
});
});
} else {
return Future.value();
}
}
@pragma('vm:entry-point')
void notificationTapBackground(NotificationResponse notificationResponse) {
log('[notificationTapBackground] payload: ${notificationResponse.payload}');
}
import 'package:flutter/material.dart';
import 'package:connectycube_sdk/connectycube_chat.dart';
import 'package:vmeeting/src/constants/colors_const.dart';
import 'utils/api_utils.dart';
import 'utils/consts.dart';
import 'widgets/common.dart';
class CreateChatScreen extends StatelessWidget {
final CubeUser _cubeUser;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.close, color: ColorConst.appBleckColor),
onPressed: () {
Navigator.of(context, rootNavigator: false).pop();
},
),
automaticallyImplyLeading: false,
title: const Text(
'Search users...',
),
),
body: BodyLayout(_cubeUser),
);
}
CreateChatScreen(this._cubeUser);
}
class BodyLayout extends StatefulWidget {
final CubeUser currentUser;
BodyLayout(this.currentUser);
@override
State<StatefulWidget> createState() {
return _BodyLayoutState(currentUser);
}
}
class _BodyLayoutState extends State<BodyLayout> {
static const String TAG = "_BodyLayoutState";
final CubeUser currentUser;
List<CubeUser> userList = [];
Set<int> _selectedUsers = {};
var _isUsersContinues = false;
var _isPrivateDialog = true;
String? userToSearch;
String userMsg = " ";
_BodyLayoutState(this.currentUser);
_searchUser(value) {
log("searchUser _user= $value");
if (value != null)
setState(() {
userToSearch = value;
_isUsersContinues = true;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
padding: EdgeInsets.only(left: 16, right: 16, bottom: 16),
child: Column(
children: [
_buildTextFields(),
_buildDialogButton(),
Container(
margin: EdgeInsets.only(left: 8),
child: Visibility(
maintainSize: false,
maintainAnimation: false,
maintainState: false,
visible: _isUsersContinues,
child: CircularProgressIndicator(
strokeWidth: 2,
),
),
),
Expanded(
child: _getUsersList(context),
),
],
)),
floatingActionButton: new Visibility(
visible: !_isPrivateDialog,
child: FloatingActionButton(
heroTag: "New dialog",
child: Icon(
Icons.check,
color: Colors.white,
),
backgroundColor: Colors.blue,
onPressed: () => _createDialog(context, _selectedUsers, true),
),
),
);
}
Widget _buildTextFields() {
return new Container(
child: new Column(
children: <Widget>[
new Container(
child: new TextField(
autofocus: true,
textInputAction: TextInputAction.search,
decoration: new InputDecoration(labelText: 'Search users'),
onSubmitted: (value) {
_searchUser(value.trim());
}),
),
],
),
);
}
Widget _buildDialogButton() {
getIcon() {
if (_isPrivateDialog) {
return Icons.person;
} else {
return Icons.people;
}
}
getDescription() {
if (_isPrivateDialog) {
return "Create group chat";
} else {
return "Create private chat";
}
}
return new Container(
alignment: Alignment.centerLeft,
child: TextButton.icon(
icon: Icon(
getIcon(),
size: 25.0,
color: themeColor,
),
onPressed: () {
setState(() {
_isPrivateDialog = !_isPrivateDialog;
});
},
label: Text(getDescription()),
),
);
}
Widget _getUsersList(BuildContext context) {
clearValues() {
_isUsersContinues = false;
userToSearch = null;
userMsg = " ";
userList.clear();
}
if (_isUsersContinues) {
if (userToSearch != null && userToSearch!.isNotEmpty) {
getUsersByFullName(userToSearch!).then((users) {
log("getusers: $users", TAG);
setState(() {
clearValues();
userList.addAll(users!.items);
});
}).catchError((onError) {
log("getusers catchError: $onError", TAG);
setState(() {
clearValues();
userMsg = "Couldn't find user";
});
});
}
}
if (userList.isEmpty)
return FittedBox(
fit: BoxFit.contain,
child: Text(userMsg),
);
else
return ListView.builder(
itemCount: userList.length,
itemBuilder: _getListItemTile,
);
}
Widget _getListItemTile(BuildContext context, int index) {
getPrivateWidget() {
return Container(
child: TextButton(
child: Row(
children: <Widget>[
getUserAvatarWidget(userList[index], 30),
Flexible(
child: Container(
child: Column(
children: <Widget>[
Container(
child: Text(
'${userList[index].fullName}',
style: TextStyle(color: primaryColor),
),
alignment: Alignment.centerLeft,
margin: EdgeInsets.fromLTRB(10.0, 0.0, 0.0, 5.0),
),
],
),
margin: EdgeInsets.only(left: 20.0),
),
),
Container(
child: Icon(
Icons.arrow_forward,
size: 25.0,
color: themeColor,
),
),
],
),
onPressed: () {
_createDialog(context, {userList[index].id!}, false);
},
),
margin: EdgeInsets.only(bottom: 10.0, left: 5.0, right: 5.0),
);
}
getGroupWidget() {
return Container(
child: TextButton(
child: Row(
children: <Widget>[
getUserAvatarWidget(userList[index], 30),
Flexible(
child: Container(
child: Column(
children: <Widget>[
Container(
child: Text(
'${userList[index].fullName}',
style: TextStyle(color: primaryColor),
),
alignment: Alignment.centerLeft,
margin: EdgeInsets.fromLTRB(10.0, 0.0, 0.0, 5.0),
),
],
),
margin: EdgeInsets.only(left: 20.0),
),
),
Container(
child: Checkbox(
value: _selectedUsers.contains(userList[index].id),
onChanged: ((checked) {
setState(() {
if (checked!) {
_selectedUsers.add(userList[index].id!);
} else {
_selectedUsers.remove(userList[index].id);
}
});
}),
),
),
],
),
onPressed: () {
setState(() {
if (_selectedUsers.contains(userList[index].id)) {
_selectedUsers.remove(userList[index].id);
} else {
_selectedUsers.add(userList[index].id!);
}
});
},
),
margin: EdgeInsets.only(bottom: 10.0, left: 5.0, right: 5.0),
);
}
getItemWidget() {
if (_isPrivateDialog) {
return getPrivateWidget();
} else {
return getGroupWidget();
}
}
return getItemWidget();
}
void _createDialog(BuildContext context, Set<int> users, bool isGroup) async {
log("_createDialog with users= $users");
if (isGroup) {
CubeDialog newDialog = CubeDialog(CubeDialogType.GROUP, occupantsIds: users.toList());
List<CubeUser> usersToAdd = users.map((id) => userList.firstWhere((user) => user.id == id)).toList();
Navigator.of(context).pushNamed('configure_group_dialog', arguments: {
USER_ARG_NAME: currentUser,
DIALOG_ARG_NAME: newDialog,
SELECTED_USERS_ARG_NAME: usersToAdd,
});
} else {
CubeDialog newDialog = CubeDialog(CubeDialogType.PRIVATE, occupantsIds: users.toList());
createDialog(newDialog).then((createdDialog) {
Navigator.of(context, rootNavigator: true).pushNamedAndRemoveUntil('chat_dialog', (route) => false, arguments: {
USER_ARG_NAME: currentUser,
DIALOG_ARG_NAME: createdDialog
});
}).catchError((error) {
_processCreateDialogError(error);
});
}
}
void _processCreateDialogError(exception) {
log("Login error $exception", TAG);
showDialogError(exception, context);
}
@override
void initState() {
super.initState();
log("initState");
}
}
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:connectycube_sdk/connectycube_sdk.dart';
import 'utils/api_utils.dart';
import 'utils/consts.dart';
import 'widgets/common.dart';
class NewGroupDialogScreen extends StatelessWidget {
final CubeUser currentUser;
final CubeDialog _cubeDialog;
final List<CubeUser> users;
NewGroupDialogScreen(this.currentUser, this._cubeDialog, this.users);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
'Group configuration...',
),
centerTitle: true,
),
body: NewChatScreen(currentUser, _cubeDialog, users),
resizeToAvoidBottomInset: false);
}
}
class NewChatScreen extends StatefulWidget {
static const String TAG = "_CreateChatScreenState";
final CubeUser currentUser;
final CubeDialog _cubeDialog;
final List<CubeUser?> users;
NewChatScreen(this.currentUser, this._cubeDialog, this.users);
@override
State createState() => NewChatScreenState(currentUser, _cubeDialog, users);
}
class NewChatScreenState extends State<NewChatScreen> {
static const String TAG = "NewChatScreenState";
final CubeUser currentUser;
final CubeDialog _cubeDialog;
final List<CubeUser?> users;
final TextEditingController _nameFilter = new TextEditingController();
NewChatScreenState(this.currentUser, this._cubeDialog, this.users);
@override
void initState() {
super.initState();
_nameFilter.addListener(_nameListener);
}
void _nameListener() {
if (_nameFilter.text.length > 4) {
log("_createDialogImage text= ${_nameFilter.text.trim()}");
_cubeDialog.name = _nameFilter.text.trim();
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
padding: EdgeInsets.all(16),
child: Column(
children: [
_buildGroupFields(),
_buildDialogOccupants(),
],
)),
floatingActionButton: FloatingActionButton(
heroTag: "New dialog",
child: Icon(
Icons.check,
color: Colors.white,
),
backgroundColor: Colors.blue,
onPressed: () => _createDialog(),
),
resizeToAvoidBottomInset: false);
}
_buildGroupFields() {
getIcon() {
return getDialogAvatarWidget(_cubeDialog, 45,
placeholder: Icon(
Icons.add_a_photo,
size: 45.0,
color: blueColor,
));
}
return Column(
children: <Widget>[
Row(
children: <Widget>[
RawMaterialButton(
onPressed: () => _createDialogImage(),
elevation: 2.0,
fillColor: Colors.white,
child: getIcon(),
padding: EdgeInsets.all(0),
shape: CircleBorder(),
),
SizedBox(
width: 16,
),
Flexible(
child: TextField(
autofocus: true,
controller: _nameFilter,
decoration: InputDecoration(labelText: 'Group Name...'),
),
)
],
),
Container(
child: Text(
'Please provide a group name and an optional group icon',
style: TextStyle(color: primaryColor),
),
alignment: Alignment.centerLeft,
margin: EdgeInsets.all(16.0),
),
],
);
}
_createDialogImage() async {
FilePickerResult? result = await FilePicker.platform.pickFiles(
type: FileType.image,
);
if (result == null) return;
var uploadImageFuture = getUploadingImageFuture(result);
uploadImageFuture.then((cubeFile) {
var url = cubeFile.getPublicUrl();
log("_createDialogImage url= $url");
setState(() {
_cubeDialog.photo = url;
});
}).catchError((exception) {
_processDialogError(exception);
});
}
_buildDialogOccupants() {
_getListItemTile(BuildContext context, int index) {
return Container(
child: Column(
children: <Widget>[
getUserAvatarWidget(users[index]!, 25),
Container(
child: Column(
children: <Widget>[
Container(
child: Text(
users[index]!.fullName ??
users[index]!.login ??
users[index]!.email ??
'???',
style: TextStyle(color: primaryColor),
),
width: MediaQuery.of(context).size.width / 4,
alignment: Alignment.center,
margin: EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 5.0),
),
],
),
margin: EdgeInsets.fromLTRB(0.0, 10.0, 0.0, 10.0),
),
],
),
);
}
_getOccupants() {
return ListView.builder(
shrinkWrap: true,
padding: EdgeInsets.symmetric(vertical: 20.0),
scrollDirection: Axis.horizontal,
itemCount: _cubeDialog.occupantsIds!.length,
itemBuilder: _getListItemTile,
);
}
return Container(
child: Expanded(
child: _getOccupants(),
),
);
}
void _processDialogError(exception) {
log("error $exception", TAG);
showDialogError(exception, context);
}
_createDialog() {
log("_createDialog _cubeDialog= $_cubeDialog");
if (_cubeDialog.name == null || _cubeDialog.name!.length < 5) {
showDialogMsg("Enter more than 4 character", context);
} else {
createDialog(_cubeDialog).then((createdDialog) {
Navigator.of(context, rootNavigator: true).pushNamedAndRemoveUntil(
'chat_dialog', (route) => false, arguments: {
USER_ARG_NAME: currentUser,
DIALOG_ARG_NAME: createdDialog
});
}).catchError((exception) {
_processDialogError(exception);
});
}
}
}
import 'package:firebase_ui_auth/firebase_ui_auth.dart';
import 'package:flutter/material.dart';
import 'package:universal_io/io.dart';
import 'package:connectycube_sdk/connectycube_sdk.dart';
import 'utils/consts.dart';
import 'utils/pref_util.dart';
import 'utils/route_utils.dart';
const String PHONE_INPUT_ROUTE_NAME = 'PhoneInputScreen';
const String SMS_CODE_INPUT_ROUTE_NAME = 'SMSCodeInputScreen';
class VerifyPhoneNumber extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Navigator(
observers: [PhoneAuthRouteObserver(context)],
key: Navigation.verifyPhoneNavigation,
onGenerateRoute: (RouteSettings settings) {
return PageRouteBuilder(
reverseTransitionDuration:
Duration(milliseconds: Platform.isIOS ? 1000 : 300),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
const begin = Offset(0.0, 1.0);
const end = Offset.zero;
const curve = Curves.ease;
var tween =
Tween(begin: begin, end: end).chain(CurveTween(curve: curve));
return SlideTransition(
position: animation.drive(tween),
child: child,
);
},
settings: RouteSettings(name: PHONE_INPUT_ROUTE_NAME),
pageBuilder: (context, animation, secondaryAnimation) =>
PhoneInputScreen(
actions: [
SMSCodeRequestedAction((ctx1, action, flowKey, phoneNumber) {
Navigator.of(context).push(
MaterialPageRoute(
settings: RouteSettings(name: SMS_CODE_INPUT_ROUTE_NAME),
builder: (ctx2) => SMSCodeInputScreen(
flowKey: flowKey,
actions: [
AuthStateChangeAction<SignedIn>((ctx3, state) {
log('[AuthStateChangeAction] SignedIn');
state.user?.getIdToken().then((idToken) {
SharedPrefs.instance.saveLoginType(LoginType.phone);
Navigator.of(ctx3, rootNavigator: true)
.pushNamedAndRemoveUntil(
'login', (route) => false);
});
}),
AuthStateChangeAction<CredentialLinked>((ctx3, state) {
log('[AuthStateChangeAction] CredentialLinked');
state.user.getIdToken().then((idToken) {
SharedPrefs.instance.saveLoginType(LoginType.phone);
Navigator.of(ctx3, rootNavigator: true)
.pushNamedAndRemoveUntil(
'login', (route) => false);
});
}),
AuthStateChangeAction<Uninitialized>((ctx3, state) {
log('[AuthStateChangeAction] Uninitialized');
}),
AuthStateChangeAction<CredentialReceived>(
(ctx3, state) {
log('[AuthStateChangeAction] CredentialReceived');
}),
AuthStateChangeAction<AuthFailed>((ctx3, state) {
log('[AuthStateChangeAction] AuthFailed');
}),
AuthStateChangeAction<UserCreated>((ctx3, state) {
log('[AuthStateChangeAction] UserCreated');
state.credential.user?.getIdToken().then((idToken) {
SharedPrefs.instance.saveLoginType(LoginType.phone);
Navigator.of(ctx3, rootNavigator: true)
.pushNamedAndRemoveUntil(
'login', (route) => false);
});
}),
],
),
),
);
}),
],
),
);
},
);
}
}
class PhoneAuthRouteObserver extends RouteObserver {
final BuildContext context;
PhoneAuthRouteObserver(this.context);
@override
void didPop(Route route, Route? previousRoute) {
super.didPop(route, previousRoute);
if (route.settings.name == PHONE_INPUT_ROUTE_NAME) {
Navigator.of(context, rootNavigator: true).pop();
}
}
}
import 'dart:async';
import 'package:collection/collection.dart' show IterableExtension;
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:intl/intl.dart';
import 'package:connectycube_sdk/connectycube_sdk.dart';
import 'package:vmeeting/service/routes/routes_name.dart';
import '../src/constants/colors_const.dart';
import '../src/utils/app_utils.dart';
import 'chat_dialog_screen.dart';
import 'create_dialog_flow.dart';
import 'managers/chat_manager.dart';
import 'settings_screen.dart';
import 'utils/api_utils.dart';
import 'utils/consts.dart';
import 'utils/platform_utils.dart';
import 'widgets/common.dart';
class SelectDialogScreen extends StatelessWidget {
final Function(CubeDialog)? onDialogSelectedCallback;
final CubeDialog? selectedDialog;
SelectDialogScreen(this.selectedDialog, this.onDialogSelectedCallback);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
title: Text(
'Logged in as ${AppUtils.cubeUser.fullName ?? AppUtils.cubeUser.login ?? AppUtils.cubeUser.email}',
),
actions: <Widget>[
IconButton(
onPressed: () => _openSettings(context),
icon: Icon(
Icons.settings,
color: ColorConst.appBleckColor,
),
),
],
),
body: BodyLayout(AppUtils.cubeUser, selectedDialog, onDialogSelectedCallback),
);
}
_openSettings(BuildContext context) {
showModal(context: context, child: SettingsScreen(AppUtils.cubeUser));
}
}
class BodyLayout extends StatefulWidget {
final CubeUser currentUser;
final Function(CubeDialog)? onDialogSelectedCallback;
final CubeDialog? selectedDialog;
BodyLayout(this.currentUser, this.selectedDialog, this.onDialogSelectedCallback, {super.key});
@override
State<StatefulWidget> createState() {
return _BodyLayoutState(currentUser, selectedDialog, onDialogSelectedCallback);
}
}
class _BodyLayoutState extends State<BodyLayout> {
static const String TAG = "_BodyLayoutState";
final CubeUser currentUser;
List<ListItem<CubeDialog>> dialogList = [];
var _isDialogContinues = true;
StreamSubscription<CubeMessage>? msgSubscription;
StreamSubscription<MessageStatus>? msgDeliveringSubscription;
StreamSubscription<MessageStatus>? msgReadingSubscription;
StreamSubscription<MessageStatus>? msgLocalReadingSubscription;
StreamSubscription<CubeMessage>? msgSendingSubscription;
final ChatMessagesManager? chatMessagesManager =
CubeChatConnection.instance.chatMessagesManager;
Function(CubeDialog)? onDialogSelectedCallback;
CubeDialog? selectedDialog;
Map<String, Set<String>> unreadMessages = {};
_BodyLayoutState(
this.currentUser, this.selectedDialog, this.onDialogSelectedCallback);
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
padding: const EdgeInsets.only(top: 2),
child: Column(
children: [
Visibility(
visible: _isDialogContinues && dialogList.isEmpty,
child: Container(
margin: const EdgeInsets.all(40),
alignment: FractionalOffset.center,
child: AppUtils.buildLoading(),
),
),
Expanded(
child: _getDialogsList(context),
),
],
),
),
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 {
showModal(context: context, child: CreateDialog(currentUser));
}
void _processGetDialogError(exception) {
log("GetDialog error $exception", TAG);
setState(() {
_isDialogContinues = false;
});
showDialogError(exception, context);
}
Widget _getDialogsList(BuildContext context) {
if (_isDialogContinues) {
getDialogs().then((dialogs) {
_isDialogContinues = false;
log("getDialogs: $dialogs", TAG);
setState(() {
dialogList.clear();
dialogList.addAll(
dialogs?.items.map((dialog) => ListItem(dialog)).toList() ?? []);
});
}).catchError((exception) {
_processGetDialogError(exception);
});
}
if (_isDialogContinues && dialogList.isEmpty) {
return const SizedBox.shrink();
} else if (dialogList.isEmpty)
return const Center(
child: Text(
'No dialogs yet',
style: TextStyle(fontSize: 20),
),
);
else
return ListView.separated(
itemCount: dialogList.length,
itemBuilder: _getListItemTile,
separatorBuilder: (context, index) {
return Divider(
thickness: 1,
indent: 68,
height: 1,
);
},
);
}
Widget _getListItemTile(BuildContext context, int index) {
Widget getDialogIcon() {
var dialog = dialogList[index].data;
if (dialog.type == CubeDialogType.PRIVATE) {
return Icon(
Icons.person,
size: 40.0,
color: greyColor,
);
} else {
return Icon(
Icons.group,
size: 40.0,
color: greyColor,
);
}
}
getDialogAvatar() {
var dialog = dialogList[index].data;
return getDialogAvatarWidget(dialog, 25,
placeholder: getDialogIcon(), errorWidget: getDialogIcon());
}
return Container(
color: selectedDialog != null &&
selectedDialog!.dialogId == dialogList[index].data.dialogId
? const Color.fromARGB(100, 168, 228, 160)
: null,
padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 10),
child: GestureDetector(
behavior: HitTestBehavior.translucent,
child: Row(
children: <Widget>[
getDialogAvatar(),
Flexible(
child: Container(
margin: EdgeInsets.only(left: 8.0),
child: Column(
children: <Widget>[
Container(
alignment: Alignment.centerLeft,
child: Text(
'${dialogList[index].data.name ?? 'Unknown dialog'}',
style: TextStyle(
color: primaryColor,
fontWeight: FontWeight.bold,
fontSize: 16.0,
overflow: TextOverflow.ellipsis),
maxLines: 1,
),
),
Container(
alignment: Alignment.centerLeft,
child: Text(
dialogList[index].data.lastMessage ?? '',
style: TextStyle(
color: primaryColor,
overflow: TextOverflow.ellipsis),
maxLines: 2,
),
),
],
),
),
),
Visibility(
maintainAnimation: true,
maintainState: true,
visible: dialogList[index].isSelected,
child: IconButton(
iconSize: 25.0,
icon: Icon(
Icons.delete,
color: themeColor,
),
onPressed: () {
_deleteDialog(context, dialogList[index].data);
},
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Row(
children: [
getMessageStateWidget(
dialogList[index].data.lastMessageState),
Text(
DateFormat('MMM dd').format(dialogList[index].data.lastMessageDateSent != null ? DateTime.fromMillisecondsSinceEpoch(dialogList[index].data.lastMessageDateSent! * 1000) : dialogList[index].data.updatedAt!),
style: TextStyle(color: primaryColor),
),
],
),
if (dialogList[index].data.unreadMessageCount != null &&
dialogList[index].data.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(
dialogList[index].data.unreadMessageCount.toString(),
style: const TextStyle(color: Colors.white),
),
),
),
],
),
],
),
onLongPress: () {
setState(() {
dialogList[index].isSelected = !dialogList[index].isSelected;
});
},
onTap: () {
_selectDialog(context, dialogList[index].data);
},
),
);
}
void _deleteDialog(BuildContext context, CubeDialog dialog) async {
log("_deleteDialog= $dialog");
Fluttertoast.showToast(msg: 'Coming soon');
}
void _selectDialog(BuildContext context, CubeDialog dialog) async {
if (onDialogSelectedCallback != null) {
onDialogSelectedCallback?.call(dialog);
setState(() {
selectedDialog = dialog;
});
} else {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => ChatDialogScreen(currentUser, dialog)),
);
}
}
void refresh() {
setState(() {
_isDialogContinues = true;
});
}
@override
void initState() {
super.initState();
refreshBadgeCount();
msgSubscription =
chatMessagesManager!.chatMessagesStream.listen(onReceiveMessage);
msgDeliveringSubscription = CubeChatConnection
.instance.messagesStatusesManager?.deliveredStream
.listen(onMessageDelivered);
msgReadingSubscription = CubeChatConnection
.instance.messagesStatusesManager?.readStream
.listen(onMessageRead);
msgLocalReadingSubscription =
ChatManager.instance.readMessagesStream.listen(onMessageRead);
msgSendingSubscription =
ChatManager.instance.sentMessagesStream.listen(onReceiveMessage);
}
@override
void dispose() {
super.dispose();
log("dispose", TAG);
msgSubscription?.cancel();
msgDeliveringSubscription?.cancel();
msgReadingSubscription?.cancel();
msgLocalReadingSubscription?.cancel();
msgSendingSubscription?.cancel();
}
void onReceiveMessage(CubeMessage message) {
log("onReceiveMessage global message= $message");
updateDialog(message);
}
updateDialog(CubeMessage msg) {
refreshBadgeCount();
ListItem<CubeDialog>? dialogItem =
dialogList.firstWhereOrNull((dlg) => dlg.data.dialogId == msg.dialogId);
if (dialogItem == null) return;
setState(() {
dialogItem.data.lastMessage = msg.body;
dialogItem.data.lastMessageId = msg.messageId;
if (msg.senderId != currentUser.id) {
dialogItem.data.unreadMessageCount =
dialogItem.data.unreadMessageCount == null
? 1
: dialogItem.data.unreadMessageCount! + 1;
unreadMessages[msg.dialogId!] = <String>[
...unreadMessages[msg.dialogId] ?? [],
msg.messageId!
].toSet();
dialogItem.data.lastMessageState = null;
} else {
dialogItem.data.lastMessageState = MessageState.sent;
}
dialogItem.data.lastMessageDateSent = msg.dateSent;
dialogList.sort((a, b) {
DateTime dateA;
if (a.data.lastMessageDateSent != null) {
dateA = DateTime.fromMillisecondsSinceEpoch(
a.data.lastMessageDateSent! * 1000);
} else {
dateA = a.data.updatedAt!;
}
DateTime dateB;
if (b.data.lastMessageDateSent != null) {
dateB = DateTime.fromMillisecondsSinceEpoch(
b.data.lastMessageDateSent! * 1000);
} else {
dateB = b.data.updatedAt!;
}
if (dateA.isAfter(dateB)) {
return -1;
} else if (dateA.isBefore(dateB)) {
return 1;
} else {
return 0;
}
});
});
}
void onMessageDelivered(MessageStatus messageStatus) {
_updateLastMessageState(messageStatus, MessageState.delivered);
}
void onMessageRead(MessageStatus messageStatus) {
_updateLastMessageState(messageStatus, MessageState.read);
if (messageStatus.userId == currentUser.id &&
unreadMessages.containsKey(messageStatus.dialogId)) {
if (unreadMessages[messageStatus.dialogId]
?.remove(messageStatus.messageId) ??
false) {
setState(() {
var dialog = dialogList
.firstWhereOrNull(
(dlg) => dlg.data.dialogId == messageStatus.dialogId)
?.data;
if (dialog == null) return;
dialog.unreadMessageCount = dialog.unreadMessageCount == null ||
dialog.unreadMessageCount == 0
? 0
: dialog.unreadMessageCount! - 1;
});
}
}
}
void _updateLastMessageState(
MessageStatus messageStatus, MessageState state) {
var dialog = dialogList
.firstWhereOrNull((dlg) => dlg.data.dialogId == messageStatus.dialogId)
?.data;
if (dialog == null) return;
if (messageStatus.messageId == dialog.lastMessageId &&
messageStatus.userId != currentUser.id) {
if (dialog.lastMessageState != state) {
setState(() {
dialog.lastMessageState = state;
});
}
}
}
}
import 'package:file_picker/file_picker.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:connectycube_sdk/connectycube_sdk.dart';
import '../src/constants/colors_const.dart';
import '../src/utils/app_utils.dart';
import 'managers/push_notifications_manager.dart';
import 'utils/api_utils.dart';
import 'utils/consts.dart';
import 'utils/pref_util.dart';
import 'widgets/common.dart';
class SettingsScreen extends StatelessWidget {
final CubeUser currentUser;
SettingsScreen(this.currentUser);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.close, color: ColorConst.appBleckColor),
onPressed: () {
Navigator.of(context).pop();
},
),
automaticallyImplyLeading: false,
title: const Text(
'Settings',
style: TextStyle(fontWeight: FontWeight.bold),
),
centerTitle: false,
),
body: BodyLayout(currentUser),
resizeToAvoidBottomInset: false);
}
}
class BodyLayout extends StatefulWidget {
final CubeUser currentUser;
BodyLayout(this.currentUser);
@override
State<StatefulWidget> createState() {
return _BodyLayoutState(currentUser);
}
}
class _BodyLayoutState extends State<BodyLayout> {
static const String TAG = "_BodyLayoutState";
final CubeUser currentUser;
var _isUsersContinues = false;
String? _avatarUrl = "";
final TextEditingController _loginFilter = new TextEditingController();
final TextEditingController _nameFilter = new TextEditingController();
final TextEditingController _emailFilter = new TextEditingController();
String _login = "";
String _name = "";
String _email = "";
_BodyLayoutState(this.currentUser) {
_loginFilter.addListener(_loginListen);
_nameFilter.addListener(_nameListen);
_emailFilter.addListener(_emailListen);
_nameFilter.text = currentUser.fullName ?? '';
_loginFilter.text = currentUser.login ?? '';
_emailFilter.text = currentUser.email ?? '';
}
void _loginListen() {
if (_loginFilter.text.isEmpty) {
_login = "";
} else {
_login = _loginFilter.text.trim();
}
}
void _nameListen() {
if (_nameFilter.text.isEmpty) {
_name = "";
} else {
_name = _nameFilter.text.trim();
}
}
void _emailListen() {
if (_emailFilter.text.isEmpty) {
_email = "";
} else {
_email = _emailFilter.text.trim();
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
child: Center(
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: 400),
child: Container(
alignment: Alignment.center,
padding: EdgeInsets.all(60),
child: Column(
children: [
_buildAvatarFields(),
_buildTextFields(),
_buildButtons(),
Container(
margin: const EdgeInsets.only(left: 8),
child: Visibility(
maintainSize: false,
maintainAnimation: false,
maintainState: false,
visible: _isUsersContinues,
child: AppUtils.buttonLoader
),
),
],
),
),
),
),
),
);
}
Widget _buildAvatarFields() {
Widget avatarCircle = getUserAvatarWidget(currentUser, 50);
return Stack(
children: <Widget>[
InkWell(
splashColor: greyColor2,
borderRadius: BorderRadius.circular(45),
onTap: () => _chooseUserImage(),
child: avatarCircle,
),
Positioned(
top: 55.0,
right: 35.0,
child: RawMaterialButton(
onPressed: () {
_chooseUserImage();
},
elevation: 2.0,
fillColor: Colors.white,
padding: const EdgeInsets.all(5.0),
shape: const CircleBorder(),
child: const Icon(
Icons.mode_edit,
size: 20.0,
),
),
),
],
);
}
_chooseUserImage() async {
FilePickerResult? result = await FilePicker.platform.pickFiles(
type: FileType.image,
);
if (result == null) return;
var uploadImageFuture = getUploadingImageFuture(result);
uploadImageFuture.then((cubeFile) {
_avatarUrl = cubeFile.getPublicUrl();
setState(() {
currentUser.avatar = _avatarUrl;
});
}).catchError((exception) {
_processUpdateUserError(exception);
});
}
Widget _buildTextFields() {
return Column(
children: <Widget>[
TextField(
controller: _nameFilter,
decoration: const InputDecoration(labelText: 'Change name'),
),
TextField(
controller: _loginFilter,
decoration: const InputDecoration(labelText: 'Change login'),
),
TextField(
controller: _emailFilter,
decoration: const InputDecoration(labelText: 'Change e-mail'),
),
],
);
}
Widget _buildButtons() {
return Column(
children: <Widget>[
const SizedBox(
height: 6,
),
ElevatedButton(
style: OutlinedButton.styleFrom(
backgroundColor: ColorConst.appGreenColor,
minimumSize: const Size(120, 36),
),
onPressed: _updateUser,
child: const Text('Save'),
),
const SizedBox(
height: 6,
),
OutlinedButton.icon(
style: OutlinedButton.styleFrom(
backgroundColor: ColorConst.appMainColor,
minimumSize: const Size(160, 36),
),
icon: const Icon(
Icons.logout,
),
label: const Text('Logout'),
onPressed: _logout,
),
const SizedBox(
height: 6,
),
OutlinedButton.icon(
style: OutlinedButton.styleFrom(
foregroundColor: Colors.red.shade300,
minimumSize: const Size(160, 36),
),
icon: const Icon(
Icons.delete,
color: Colors.red,
),
label: const Text(
'Delete user',
style: TextStyle(color: Colors.red),
),
onPressed: _deleteUserPressed,
),
],
);
}
void _updateUser() {
print(
'_updateUser user with login: $_login, name: $_name, e-mail: $_email');
if (_login.isEmpty &&
_name.isEmpty &&
_avatarUrl!.isEmpty &&
_email.isEmpty) {
Fluttertoast.showToast(msg: 'Nothing to save');
return;
}
var userToUpdate = CubeUser()..id = currentUser.id;
if (_name.isNotEmpty) userToUpdate.fullName = _name;
if (_login.isNotEmpty) userToUpdate.login = _login;
if (_email.isNotEmpty) userToUpdate.email = _email;
if (_avatarUrl!.isNotEmpty) userToUpdate.avatar = _avatarUrl;
setState(() {
_isUsersContinues = true;
});
updateUser(userToUpdate).then((user) {
SharedPrefs.instance.updateUser(user);
Fluttertoast.showToast(msg: 'Success');
setState(() {
_isUsersContinues = false;
});
}).catchError((exception) {
_processUpdateUserError(exception);
});
}
void _logout() {
print('_logout $_login and $_name');
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text("Logout"),
content: const Text("Are you sure you want logout current user"),
actions: <Widget>[
TextButton(
child: const Text("CANCEL"),
onPressed: () {
Navigator.pop(context);
},
),
TextButton(
child: const Text("OK"),
onPressed: () {
signOut().then(
(voidValue) {
Navigator.pop(context); // cancel current Dialog
},
).catchError(
(onError) {
Navigator.pop(context); // cancel current Dialog
},
).whenComplete(() {
CubeChatConnection.instance.destroy();
PushNotificationsManager.instance.unsubscribe();
FirebaseAuth.instance.currentUser
?.unlink(PhoneAuthProvider.PROVIDER_ID);
SharedPrefs.instance.deleteUser();
Navigator.pop(context); // cancel current screen
_navigateToLoginScreen(context);
});
},
),
],
);
},
);
}
void _deleteUserPressed() {
print('_deleteUserPressed ${_login.isNotEmpty ? _login : _email}');
_userDelete();
}
void _userDelete() {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text("Delete user"),
content: Text("Are you sure you want to delete current user?"),
actions: <Widget>[
TextButton(
child: Text("CANCEL"),
onPressed: () {
Navigator.pop(context);
},
),
TextButton(
child: Text("OK"),
onPressed: () async {
CubeChatConnection.instance.destroy();
await SharedPrefs.instance.deleteUser();
deleteUser(currentUser.id!).then(
(voidValue) {
Navigator.pop(context); // cancel current Dialog
},
).catchError(
(onError) {
Navigator.pop(context); // cancel current Dialog
},
).whenComplete(() async {
await PushNotificationsManager.instance.unsubscribe();
Navigator.pop(context); // cancel current screen
_navigateToLoginScreen(context);
});
},
),
],
);
},
);
}
_navigateToLoginScreen(BuildContext context) {
Navigator.pushNamedAndRemoveUntil(context, 'login', (route) => false);
}
void _processUpdateUserError(exception) {
log("_processUpdateUserError error $exception", TAG);
setState(() {
_isUsersContinues = false;
});
showDialogError(exception, context);
}
}
import 'package:flutter/material.dart';
import 'package:connectycube_sdk/connectycube_sdk.dart';
import 'add_occupant_screen.dart';
import 'chat_details_screen.dart';
import 'utils/consts.dart';
import 'utils/route_utils.dart';
class UpdateDialog extends StatelessWidget {
final CubeUser currentUser;
final CubeDialog currentDialog;
UpdateDialog(this.currentUser, this.currentDialog);
@override
Widget build(BuildContext context) {
return Navigator(
key: Navigation.updateDialogNavigation,
initialRoute: 'dialog_info',
onGenerateRoute: (RouteSettings settings) {
Map<String, dynamic>? args =
settings.arguments as Map<String, dynamic>?;
MaterialPageRoute pageRout;
switch (settings.name) {
case 'dialog_info':
pageRout = MaterialPageRoute(
builder: (context) =>
ChatDetailsScreen(currentUser, currentDialog));
break;
case 'search_users':
pageRout = MaterialPageRoute<List<int>?>(
builder: (context) => AddOccupantScreen(
args![USER_ARG_NAME],
));
break;
default:
pageRout = MaterialPageRoute(
builder: (context) => ChatDetailsScreen(
args![USER_ARG_NAME],
args[DIALOG_ARG_NAME],
));
break;
}
return pageRout;
},
);
}
}
import 'dart:async';
import 'dart:collection';
import 'package:connectycube_sdk/connectycube_chat.dart';
import 'package:connectycube_sdk/connectycube_sdk.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:universal_io/io.dart';
import '../managers/push_notifications_manager.dart';
import 'platform_utils.dart';
void showDialogError(exception, context) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text("Error"),
content: Text("Something went wrong $exception"),
actions: <Widget>[
TextButton(
child: Text("OK"),
onPressed: () => Navigator.of(context).pop(),
)
],
);
});
}
void showDialogMsg(msg, context) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text("Alert"),
content: Text(msg),
actions: <Widget>[
TextButton(
child: Text("OK"),
onPressed: () => Navigator.of(context).pop(),
)
],
);
});
}
class ListItem<T> {
bool isSelected = false; //Selection property to highlight or not
T data; //Data of the user
ListItem(this.data); //Constructor to assign the data
}
Future<Map<int, CubeUser>> getUsersByIds(Set<int> ids) async {
Completer<Map<int, CubeUser>> completer = Completer();
Map<int, CubeUser> users = HashMap();
try {
var result = await (getAllUsersByIds(ids)
as FutureOr<PagedResult<CubeUser>>);
users.addAll(Map.fromIterable(result.items,
key: (item) => item.id, value: (item) => item));
} catch (ex) {
log("exception= $ex");
}
completer.complete(users);
return completer.future;
}
Future<CubeFile> getUploadingImageFuture(FilePickerResult result) async {
// there possible to upload the file as an array of bytes, but here showed two ways just as an example
if(kIsWeb){
return uploadRawFile(result.files.single.bytes!, result.files.single.name, isPublic: true, onProgress: (progress) {
log("uploadImageFile progress= $progress");
});
} else {
return uploadFile(File(result.files.single.path!), isPublic: true, onProgress: (progress) {
log("uploadImageFile progress= $progress");
});
}
}
refreshBadgeCount(){
getUnreadMessagesCount().then((result) {
updateBadgeCount(result['total']);
});
}
\ No newline at end of file
import 'package:firebase_auth/firebase_auth.dart';
import 'package:connectycube_sdk/connectycube_sdk.dart';
import '../../firebase_options.dart';
Future<CubeSession> createPhoneAuthSession() async {
var phoneAuthIdToken = await FirebaseAuth.instance.currentUser?.getIdToken();
if (phoneAuthIdToken == null) {
return createSession();
}
return createSession().then((cubeSession) {
return signInUsingFirebase(
DefaultFirebaseOptions.currentPlatform.projectId,
phoneAuthIdToken,
).then((_) {
return CubeSessionManager.instance.activeSession!;
});
});
}
const String APP_ID = "476";
const String AUTH_KEY = "PDZjPBzAO8WPfCp";
const String AUTH_SECRET = "6247kjxXCLRaua6";
import 'dart:ui';
final themeColor = Color(0xfff5a623);
final primaryColor = Color(0xff203152);
final greyColor = Color(0xffaeaeae);
final greyColor2 = Color(0xffE8E8E8);
final greyColor3 = Color(0xffeaeaea);
final blueColor = Color(0xff0080ff);
final String SORT_ASC = "asc";
final String SORT_DESC = "desc";
final String USER_ARG_NAME = "user";
final String DIALOG_ARG_NAME = "dialog";
final String SELECTED_USERS_ARG_NAME = "selected_users";
enum LoginType { login, email, phone, facebook }
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_app_badger/flutter_app_badger.dart';
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
import 'package:universal_io/io.dart';
bool isDesktop() {
return Platform.isLinux || Platform.isMacOS || Platform.isWindows;
}
void showModal({
required BuildContext context,
required Widget child,
double maxWidth = 600,
}) {
if (isDesktop()) {
showDialog(
context: context,
builder: (BuildContext cxt) {
return Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(8.0))),
child: RawKeyboardListener(
focusNode: FocusNode(onKey: (FocusNode node, RawKeyEvent evt) {
if (evt.logicalKey == LogicalKeyboardKey.escape) {
if (evt is RawKeyDownEvent) {
Navigator.pop(context);
return KeyEventResult.handled;
}
}
return KeyEventResult.ignored;
}),
child: ClipRRect(
borderRadius: BorderRadius.circular(8.0),
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: maxWidth),
child: child),
),
),
);
});
} else if (Platform.isAndroid) {
showMaterialModalBottomSheet(
context: context,
backgroundColor: Colors.transparent,
enableDrag: true,
expand: true,
builder: (context) {
return child;
});
} else if (Platform.isIOS) {
showCupertinoModalBottomSheet(
context: context,
backgroundColor: Colors.transparent,
enableDrag: true,
expand: true,
builder: (context) {
return child;
});
}
}
void updateBadgeCount(int? count) {
FlutterAppBadger.isAppBadgeSupported().then((isBadgesSupported) {
if (isBadgesSupported) {
if (count == null || count == 0) {
FlutterAppBadger.removeBadge();
} else {
FlutterAppBadger.updateBadgeCount(count);
}
}
});
}
bool get isPhoneAuthSupported => kIsWeb || !isDesktop();
import 'dart:async';
import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:connectycube_sdk/connectycube_chat.dart';
import 'consts.dart';
const String prefLoginType = "pref_login_type";
const String prefUserLogin = "pref_user_login";
const String prefUserEmail = "pref_user_email";
const String prefUserPhone = "pref_user_phone";
const String prefUserPsw = "pref_user_psw";
const String prefUserName = "pref_user_name";
const String prefUserId = "pref_user_id";
const String prefUserAvatar = "pref_user_avatar";
const String prefSubscriptionToken = "pref_subscription_token";
const String prefSubscriptionId = "pref_subscription_id";
const String prefSelectedDialogId = "pref_selected_dialog_id";
class SharedPrefs {
static final SharedPrefs _instance = SharedPrefs._internal();
late SharedPreferences prefs;
SharedPrefs._internal();
bool inited = false;
static SharedPrefs get instance => _instance;
Future<SharedPrefs> init() async {
Completer<SharedPrefs> completer = Completer();
if (inited) {
completer.complete(_instance);
} else {
prefs = await SharedPreferences.getInstance();
inited = true;
completer.complete(_instance);
}
return completer.future;
}
saveNewUser(CubeUser cubeUser, LoginType loginType) {
prefs.clear();
prefs.setString(prefLoginType, describeEnum(loginType));
if (cubeUser.login != null) prefs.setString(prefUserLogin, cubeUser.login!);
if (cubeUser.email != null) prefs.setString(prefUserEmail, cubeUser.email!);
if (cubeUser.phone != null) prefs.setString(prefUserPhone, cubeUser.phone!);
if (cubeUser.password != null)
prefs.setString(prefUserPsw, cubeUser.password!);
if (cubeUser.fullName != null)
prefs.setString(prefUserName, cubeUser.fullName!);
prefs.setInt(prefUserId, cubeUser.id!);
if (cubeUser.avatar != null)
prefs.setString(prefUserAvatar, cubeUser.avatar!);
}
updateUser(CubeUser cubeUser) {
if (cubeUser.password != null)
prefs.setString(prefUserPsw, cubeUser.password!);
if (cubeUser.login != null) prefs.setString(prefUserLogin, cubeUser.login!);
if (cubeUser.email != null) prefs.setString(prefUserEmail, cubeUser.email!);
if (cubeUser.phone != null) prefs.setString(prefUserPhone, cubeUser.phone!);
if (cubeUser.fullName != null)
prefs.setString(prefUserName, cubeUser.fullName!);
if (cubeUser.avatar != null)
prefs.setString(prefUserAvatar, cubeUser.avatar!);
}
CubeUser? getUser() {
if (prefs.getString(prefUserLogin) == null &&
prefs.getString(prefUserEmail) == null) return null;
var user = CubeUser();
user.login = prefs.getString(prefUserLogin);
user.email = prefs.getString(prefUserEmail);
user.phone = prefs.getString(prefUserPhone);
user.password = prefs.getString(prefUserPsw);
user.fullName = prefs.getString(prefUserName);
user.id = prefs.getInt(prefUserId);
user.avatar = prefs.getString(prefUserAvatar);
return user;
}
LoginType? getLoginType() {
var savedLoginType = prefs.getString(prefLoginType);
if (savedLoginType == null) return null;
var loginType = LoginType.values
.firstWhereOrNull((e) => describeEnum(e) == savedLoginType);
return loginType;
}
saveLoginType(LoginType loginType) {
prefs.setString(prefLoginType, describeEnum(loginType));
}
Future<bool> deleteUser() {
return prefs.clear();
}
saveSubscriptionToken(String token) {
prefs.setString(prefSubscriptionToken, token);
}
String getSubscriptionToken() {
return prefs.getString(prefSubscriptionToken) ?? "";
}
saveSubscriptionId(int id) {
prefs.setInt(prefSubscriptionId, id);
}
int getSubscriptionId() {
return prefs.getInt(prefSubscriptionId) ?? 0;
}
saveSelectedDialogId(String dialogId) {
prefs.setString(prefSelectedDialogId, dialogId);
}
String? getSelectedDialogId() {
return prefs.getString(prefSelectedDialogId);
}
}
import 'package:flutter/material.dart';
class Navigation {
static GlobalKey<NavigatorState> mainNavigation = GlobalKey();
static GlobalKey<NavigatorState> createDialogNavigation = GlobalKey();
static GlobalKey<NavigatorState> updateDialogNavigation = GlobalKey();
static GlobalKey<NavigatorState> verifyPhoneNavigation = GlobalKey();
}
import 'package:cached_network_image/cached_network_image.dart';
import 'package:connectycube_sdk/connectycube_chat.dart';
import 'package:flutter/material.dart';
import '../utils/consts.dart';
Widget getAvatarTextWidget(bool condition, String? text, {double? fontSize}) {
if (condition)
return SizedBox.shrink();
else
return Text(
isEmpty(text) ? '?' : text!,
style: TextStyle(fontSize: fontSize ?? 30, color: Colors.green),
);
}
Widget getUserAvatarWidget(CubeUser? cubeUser, double radius,
{Widget? placeholder, Widget? errorWidget}) {
return getAvatarWidget(cubeUser?.avatar, cubeUser?.fullName, radius,
placeholder: placeholder, errorWidget: errorWidget);
}
Widget getDialogAvatarWidget(CubeDialog? cubeDialog, double radius,
{Widget? placeholder, Widget? errorWidget}) {
return getAvatarWidget(cubeDialog?.photo, cubeDialog?.name, radius,
placeholder: placeholder, errorWidget: errorWidget);
}
Widget getAvatarWidget(String? imageUrl, String? name, double radius,
{Widget? placeholder, Widget? errorWidget}) {
return CircleAvatar(
backgroundColor: Color.fromARGB(20, 100, 100, 100),
radius: radius,
child: ClipRRect(
borderRadius: BorderRadius.circular(radius),
child: CachedNetworkImage(
imageUrl: imageUrl ?? '',
errorWidget: (context, url, error) {
return errorWidget ??
placeholder ??
Center(
child: Container(
child: getAvatarTextWidget(
false, name?.substring(0, 2).toUpperCase() ?? '?',
fontSize: radius)));
},
placeholder: (context, url) {
return placeholder ??
Center(
child: Container(
child: getAvatarTextWidget(
false, name?.substring(0, 2).toUpperCase() ?? '?',
fontSize: radius)));
},
fit: BoxFit.cover,
width: radius * 2,
height: radius * 2,
),
),
);
}
Widget getMessageStateWidget(MessageState? state) {
var result;
switch (state) {
case MessageState.read:
result = 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: greyColor,
),
Padding(
padding: EdgeInsets.only(left: 4),
child: Icon(
Icons.check_rounded,
size: 15.0,
color: greyColor,
),
)
]);
break;
case MessageState.sent:
result = Icon(
Icons.check_rounded,
size: 15.0,
color: greyColor,
);
break;
default:
result = SizedBox.shrink();
break;
}
return result;
}
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:photo_view/photo_view.dart';
class FullPhoto extends StatelessWidget {
final String url;
FullPhoto({Key? key, required this.url}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
'Full photo',
),
centerTitle: true,
),
body: FullPhotoScreen(url: url),
);
}
}
class FullPhotoScreen extends StatefulWidget {
final String url;
FullPhotoScreen({Key? key, required this.url}) : super(key: key);
@override
State createState() => FullPhotoScreenState(url: url);
}
class FullPhotoScreenState extends State<FullPhotoScreen> {
final String url;
FullPhotoScreenState({Key? key, required this.url});
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return Container(child: PhotoView(imageProvider: NetworkImage(url)));
}
}
import '../utils/consts.dart';
import 'package:flutter/material.dart';
class Loading extends StatelessWidget {
const Loading();
@override
Widget build(BuildContext context) {
return Container(
child: Center(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(themeColor),
),
),
color: Colors.white.withOpacity(0.8),
);
}
}
......@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:vmeeting/blocs/home_page_bloc/home_page_bloc.dart';
import 'package:vmeeting/src/controllers/enter_number_cont.dart';
import 'package:vmeeting/src/utils/app_utils.dart';
import 'package:vmeeting/views/home_view/home_page.dart';
import 'package:vmeeting/views/profile_view/profile_page.dart';
import 'package:vmeeting/views/users_view/users_page.dart';
......@@ -11,6 +12,7 @@ import '../../src/constants/colors_const.dart';
import '../../src/utils/pref_util.dart';
import '../../src/widgets/appbar_animation_widgets/icon_painters/category_icon_painter.dart';
import '../../src/widgets/appbar_animation_widgets/sliver_scaffold.dart';
import '../../srcchat/select_dialog_screen.dart';
import '../drower_view/drower_menue.dart';
class MainPage extends StatefulWidget {
......@@ -32,7 +34,7 @@ class _MainPageState extends State<MainPage> {
],
child: HomePage(controller: widget.controller),
),
AllUsersViewPage(controller: widget.controller),
SelectDialogScreen(null, null),
ProfilePage(controller: widget.controller)
];
......@@ -73,7 +75,7 @@ class _MainPageState extends State<MainPage> {
onTap: OnTapped,
items: const <Widget>[
Icon(Icons.home),
Icon(Icons.search),
Icon(Icons.chat),
Icon(Icons.person)
],
),
......
......@@ -6,11 +6,19 @@
#include "generated_plugin_registrant.h"
#include <desktop_webview_auth/desktop_webview_auth_plugin.h>
#include <emoji_picker_flutter/emoji_picker_flutter_plugin.h>
#include <file_selector_linux/file_selector_plugin.h>
#include <flutter_webrtc/flutter_web_r_t_c_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) desktop_webview_auth_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "DesktopWebviewAuthPlugin");
desktop_webview_auth_plugin_register_with_registrar(desktop_webview_auth_registrar);
g_autoptr(FlPluginRegistrar) emoji_picker_flutter_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "EmojiPickerFlutterPlugin");
emoji_picker_flutter_plugin_register_with_registrar(emoji_picker_flutter_registrar);
g_autoptr(FlPluginRegistrar) file_selector_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin");
file_selector_plugin_register_with_registrar(file_selector_linux_registrar);
......
......@@ -3,6 +3,8 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
desktop_webview_auth
emoji_picker_flutter
file_selector_linux
flutter_webrtc
url_launcher_linux
......
......@@ -5,22 +5,38 @@
import FlutterMacOS
import Foundation
import connectivity_plus
import desktop_webview_auth
import device_info_plus
import emoji_picker_flutter
import file_selector_macos
import firebase_auth
import firebase_core
import firebase_messaging
import flutter_app_badger
import flutter_local_notifications
import flutter_webrtc
import package_info_plus
import path_provider_foundation
import shared_preferences_foundation
import sqflite
import url_launcher_macos
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
ConnectivityPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlugin"))
DesktopWebviewAuthPlugin.register(with: registry.registrar(forPlugin: "DesktopWebviewAuthPlugin"))
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
EmojiPickerFlutterPlugin.register(with: registry.registrar(forPlugin: "EmojiPickerFlutterPlugin"))
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
FLTFirebaseAuthPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAuthPlugin"))
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
FLTFirebaseMessagingPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseMessagingPlugin"))
FlutterAppBadgerPlugin.register(with: registry.registrar(forPlugin: "FlutterAppBadgerPlugin"))
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
FlutterWebRTCPlugin.register(with: registry.registrar(forPlugin: "FlutterWebRTCPlugin"))
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
}
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
_flutterfire_internals:
dependency: transitive
description:
name: _flutterfire_internals
sha256: "5dadadeecceac19d6a63c9d2e037bb8df58ddd4aedb94e8a056af2f39ee50f9d"
url: "https://pub.dev"
source: hosted
version: "1.3.11"
archive:
dependency: transitive
description:
......@@ -9,6 +17,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.4.6"
args:
dependency: transitive
description:
name: args
sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596
url: "https://pub.dev"
source: hosted
version: "2.4.2"
async:
dependency: transitive
description:
......@@ -33,6 +49,30 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.1"
cached_network_image:
dependency: "direct main"
description:
name: cached_network_image
sha256: f98972704692ba679db144261172a8e20feb145636c617af0eb4022132a6797f
url: "https://pub.dev"
source: hosted
version: "3.3.0"
cached_network_image_platform_interface:
dependency: transitive
description:
name: cached_network_image_platform_interface
sha256: "56aa42a7a01e3c9db8456d9f3f999931f1e05535b5a424271e9a38cabf066613"
url: "https://pub.dev"
source: hosted
version: "3.0.0"
cached_network_image_web:
dependency: transitive
description:
name: cached_network_image_web
sha256: "759b9a9f8f6ccbb66c185df805fac107f05730b1dab9c64626d1008cca532257"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
characters:
dependency: transitive
description:
......@@ -58,13 +98,29 @@ packages:
source: hosted
version: "1.1.1"
collection:
dependency: transitive
dependency: "direct main"
description:
name: collection
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
url: "https://pub.dev"
source: hosted
version: "1.18.0"
connectivity_plus:
dependency: "direct main"
description:
name: connectivity_plus
sha256: "77a180d6938f78ca7d2382d2240eb626c0f6a735d0bfdce227d8ffb80f95c48b"
url: "https://pub.dev"
source: hosted
version: "4.0.2"
connectivity_plus_platform_interface:
dependency: transitive
description:
name: connectivity_plus_platform_interface
sha256: cf1d1c28f4416f8c654d7dc3cd638ec586076255d407cef3ddbdaf178272a71a
url: "https://pub.dev"
source: hosted
version: "1.2.4"
connectycube_flutter_call_kit:
dependency: "direct main"
description:
......@@ -153,6 +209,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.1.3"
dbus:
dependency: transitive
description:
name: dbus
sha256: "365c771ac3b0e58845f39ec6deebc76e3276aa9922b0cc60840712094d9047ac"
url: "https://pub.dev"
source: hosted
version: "0.7.10"
desktop_webview_auth:
dependency: transitive
description:
name: desktop_webview_auth
sha256: "328533e6b8f5ad72b890a49d1d1df0fc827b319b0da30c7612bdb284ebe448d2"
url: "https://pub.dev"
source: hosted
version: "0.0.14"
device_info_plus:
dependency: "direct main"
description:
......@@ -169,6 +241,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "7.0.0"
email_validator:
dependency: transitive
description:
name: email_validator
sha256: e9a90f27ab2b915a27d7f9c2a7ddda5dd752d6942616ee83529b686fc086221b
url: "https://pub.dev"
source: hosted
version: "2.1.17"
emoji_picker_flutter:
dependency: "direct main"
description:
name: emoji_picker_flutter
sha256: "009c51efc763d5a6ba05a5628b8b2184c327cd117d66ea9c3e7edf2ff269c423"
url: "https://pub.dev"
source: hosted
version: "1.6.3"
equatable:
dependency: "direct main"
description:
......@@ -201,6 +289,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "7.0.0"
file_picker:
dependency: "direct main"
description:
name: file_picker
sha256: be325344c1f3070354a1d84a231a1ba75ea85d413774ec4bdf444c023342e030
url: "https://pub.dev"
source: hosted
version: "5.5.0"
file_selector_linux:
dependency: transitive
description:
......@@ -233,6 +329,30 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.9.3+1"
firebase_auth:
dependency: "direct main"
description:
name: firebase_auth
sha256: "738c4225bf8e766750423abcaeab1dc45f1bdb8975e2b32b69d561350b124685"
url: "https://pub.dev"
source: hosted
version: "4.12.1"
firebase_auth_platform_interface:
dependency: transitive
description:
name: firebase_auth_platform_interface
sha256: "40f759021591e3ce5741e981ae7488903dd31cab59aa19a663a8255511d58f95"
url: "https://pub.dev"
source: hosted
version: "7.0.3"
firebase_auth_web:
dependency: transitive
description:
name: firebase_auth_web
sha256: e068327d62503c32e2b3fbcc27991f6d8a687e2fdb60e9ad865835208b2a29ed
url: "https://pub.dev"
source: hosted
version: "5.8.6"
firebase_core:
dependency: "direct main"
description:
......@@ -257,11 +377,91 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.8.1"
firebase_dynamic_links:
dependency: transitive
description:
name: firebase_dynamic_links
sha256: "3d7d8f922f91949b4ea4648fe278b7397c50f81557db7101c24bb52896972565"
url: "https://pub.dev"
source: hosted
version: "5.4.3"
firebase_dynamic_links_platform_interface:
dependency: transitive
description:
name: firebase_dynamic_links_platform_interface
sha256: "7a924375c503830007d182d9b70fe9bc8e9b2ed72cda3ddf74a964ba55179919"
url: "https://pub.dev"
source: hosted
version: "0.2.6+11"
firebase_messaging:
dependency: "direct main"
description:
name: firebase_messaging
sha256: "53952a6f7860c44429bec80719c411e0ff77ce6cf31fade1515c7bdd87abe4a1"
url: "https://pub.dev"
source: hosted
version: "14.7.3"
firebase_messaging_platform_interface:
dependency: transitive
description:
name: firebase_messaging_platform_interface
sha256: "543390d1c76aaf3fa563de223d1732a5def5a5efe31428e43a44e9a47efc5ed3"
url: "https://pub.dev"
source: hosted
version: "4.5.12"
firebase_messaging_web:
dependency: transitive
description:
name: firebase_messaging_web
sha256: ecfe4e851652dc5f40f1e42efcac0c2bc6bf5ef08faca936c94e1472c247d222
url: "https://pub.dev"
source: hosted
version: "3.5.12"
firebase_ui_auth:
dependency: "direct main"
description:
name: firebase_ui_auth
sha256: c3ec2926f4e0e56974e628e0ace10273e5615a4ae62d281e6f3fd68012406224
url: "https://pub.dev"
source: hosted
version: "1.10.0"
firebase_ui_localizations:
dependency: transitive
description:
name: firebase_ui_localizations
sha256: "3f2548115b5f33fabc5cc47bee2f1d1d11a049381fbd64a8ecdd04fda6564319"
url: "https://pub.dev"
source: hosted
version: "1.8.0"
firebase_ui_oauth:
dependency: transitive
description:
name: firebase_ui_oauth
sha256: "3fe9ea5f870ec26fda06f72e79ca0693310b746f18815df88dfb510c653f8606"
url: "https://pub.dev"
source: hosted
version: "1.4.14"
firebase_ui_shared:
dependency: transitive
description:
name: firebase_ui_shared
sha256: f1d07c130a39104d32fba1dab274b7bcb13be2bf4e652624a4ccabb58f9781f1
url: "https://pub.dev"
source: hosted
version: "1.4.1"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_app_badger:
dependency: "direct main"
description:
name: flutter_app_badger
sha256: "64d4a279bab862ed28850431b9b446b9820aaae0bf363322d51077419f930fa8"
url: "https://pub.dev"
source: hosted
version: "1.5.0"
flutter_background:
dependency: "direct main"
description:
......@@ -278,6 +478,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "8.1.3"
flutter_cache_manager:
dependency: transitive
description:
name: flutter_cache_manager
sha256: "8207f27539deb83732fdda03e259349046a39a4c767269285f449ade355d54ba"
url: "https://pub.dev"
source: hosted
version: "3.3.1"
flutter_lints:
dependency: "direct dev"
description:
......@@ -286,6 +494,35 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.3"
flutter_local_notifications:
dependency: "direct main"
description:
name: flutter_local_notifications
sha256: "53c332ecee8e4d723269c1c2d0cdf7cbbff0a66cc0554d230a6f38cae81760d1"
url: "https://pub.dev"
source: hosted
version: "14.1.4"
flutter_local_notifications_linux:
dependency: transitive
description:
name: flutter_local_notifications_linux
sha256: "33f741ef47b5f63cc7f78fe75eeeac7e19f171ff3c3df054d84c1e38bedb6a03"
url: "https://pub.dev"
source: hosted
version: "4.0.0+1"
flutter_local_notifications_platform_interface:
dependency: transitive
description:
name: flutter_local_notifications_platform_interface
sha256: "7cf643d6d5022f3baed0be777b0662cce5919c0a7b86e700299f22dc4ae660ef"
url: "https://pub.dev"
source: hosted
version: "7.0.0+1"
flutter_localizations:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
flutter_plugin_android_lifecycle:
dependency: transitive
description:
......@@ -302,6 +539,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "6.2.0"
flutter_svg:
dependency: transitive
description:
name: flutter_svg
sha256: d39e7f95621fc84376bc0f7d504f05c3a41488c562f4a8ad410569127507402c
url: "https://pub.dev"
source: hosted
version: "2.0.9"
flutter_test:
dependency: "direct dev"
description: flutter
......@@ -320,6 +565,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.9.45"
fluttertoast:
dependency: "direct main"
description:
name: fluttertoast
sha256: dfdde255317af381bfc1c486ed968d5a43a2ded9c931e87cbecd88767d6a71c1
url: "https://pub.dev"
source: hosted
version: "8.2.4"
google_fonts:
dependency: "direct main"
description:
......@@ -448,6 +701,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.1"
logging:
dependency: "direct main"
description:
name: logging
sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340"
url: "https://pub.dev"
source: hosted
version: "1.2.0"
lottie:
dependency: "direct main"
description:
......@@ -488,6 +749,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.4"
modal_bottom_sheet:
dependency: "direct main"
description:
name: modal_bottom_sheet
sha256: "3bba63c62d35c931bce7f8ae23a47f9a05836d8cb3c11122ada64e0b2f3d718f"
url: "https://pub.dev"
source: hosted
version: "3.0.0-pre"
nested:
dependency: transitive
description:
......@@ -496,6 +765,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.0"
nm:
dependency: transitive
description:
name: nm
sha256: "2c9aae4127bdc8993206464fcc063611e0e36e72018696cd9631023a31b24254"
url: "https://pub.dev"
source: hosted
version: "0.5.0"
objectid:
dependency: transitive
description:
......@@ -504,6 +781,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.0"
octo_image:
dependency: transitive
description:
name: octo_image
sha256: "45b40f99622f11901238e18d48f5f12ea36426d8eced9f4cbf58479c7aa2430d"
url: "https://pub.dev"
source: hosted
version: "2.0.0"
os_detect:
dependency: transitive
description:
......@@ -536,8 +821,16 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.8.3"
path_provider:
path_parsing:
dependency: transitive
description:
name: path_parsing
sha256: e3e67b1629e6f7e8100b367d3db6ba6af4b1f0bb80f64db18ef1fbabd2fa9ccf
url: "https://pub.dev"
source: hosted
version: "1.0.1"
path_provider:
dependency: "direct main"
description:
name: path_provider
sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa
......@@ -632,6 +925,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "5.4.0"
photo_view:
dependency: "direct main"
description:
name: photo_view
sha256: "8036802a00bae2a78fc197af8a158e3e2f7b500561ed23b4c458107685e645bb"
url: "https://pub.dev"
source: hosted
version: "0.14.0"
platform:
dependency: transitive
description:
......@@ -688,8 +989,16 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.2.1"
shared_preferences:
rxdart:
dependency: transitive
description:
name: rxdart
sha256: "0c7c0cedd93788d996e33041ffecda924cc54389199cde4e6a34b440f50044cb"
url: "https://pub.dev"
source: hosted
version: "0.27.7"
shared_preferences:
dependency: "direct main"
description:
name: shared_preferences
sha256: "81429e4481e1ccfb51ede496e916348668fd0921627779233bd24cc3ff6abd02"
......@@ -757,6 +1066,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.10.0"
sqflite:
dependency: transitive
description:
name: sqflite
sha256: "591f1602816e9c31377d5f008c2d9ef7b8aca8941c3f89cc5fd9d84da0c38a9a"
url: "https://pub.dev"
source: hosted
version: "2.3.0"
sqflite_common:
dependency: transitive
description:
name: sqflite_common
sha256: bb4738f15b23352822f4c42a531677e5c6f522e079461fd240ead29d8d8a54a6
url: "https://pub.dev"
source: hosted
version: "2.5.0+2"
stack_trace:
dependency: transitive
description:
......@@ -805,6 +1130,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.6.1"
timezone:
dependency: transitive
description:
name: timezone
sha256: "1cfd8ddc2d1cfd836bc93e67b9be88c3adaeca6f40a00ca999104c30693cdca0"
url: "https://pub.dev"
source: hosted
version: "0.9.2"
tuple:
dependency: transitive
description:
......@@ -918,13 +1251,37 @@ packages:
source: hosted
version: "3.1.0"
uuid:
dependency: transitive
dependency: "direct main"
description:
name: uuid
sha256: "648e103079f7c64a36dc7d39369cabb358d377078a051d6ae2ad3aa539519313"
url: "https://pub.dev"
source: hosted
version: "3.0.7"
vector_graphics:
dependency: transitive
description:
name: vector_graphics
sha256: "0f0c746dd2d6254a0057218ff980fc7f5670fd0fcf5e4db38a490d31eed4ad43"
url: "https://pub.dev"
source: hosted
version: "1.1.9+1"
vector_graphics_codec:
dependency: transitive
description:
name: vector_graphics_codec
sha256: "0edf6d630d1bfd5589114138ed8fada3234deacc37966bec033d3047c29248b7"
url: "https://pub.dev"
source: hosted
version: "1.1.9+1"
vector_graphics_compiler:
dependency: transitive
description:
name: vector_graphics_compiler
sha256: d24333727332d9bd20990f1483af4e09abdb9b1fc7c3db940b56ab5c42790c26
url: "https://pub.dev"
source: hosted
version: "1.1.9+1"
vector_math:
dependency: transitive
description:
......
......@@ -52,6 +52,23 @@ dependencies:
lottie: ^2.4.0
curved_navigation_bar: ^1.0.3
image_picker: ^1.0.4
shared_preferences: ^2.0.12
file_picker: ^5.2.11
fluttertoast: ^8.0.8
cached_network_image: ^3.2.0
photo_view: ^0.14.0
emoji_picker_flutter: ^1.5.4
firebase_messaging: ^14.6.3
flutter_local_notifications: ^14.1.1
connectivity_plus: ^4.0.0
logging: ^1.0.2
collection: ^1.17.1
path_provider: ^2.0.8
uuid: ^3.0.6
modal_bottom_sheet: ^3.0.0-pre
firebase_ui_auth: ^1.4.3
firebase_auth: ^4.6.3
flutter_app_badger: ^1.5.0
dev_dependencies:
flutter_test:
......
......@@ -6,15 +6,27 @@
#include "generated_plugin_registrant.h"
#include <connectivity_plus/connectivity_plus_windows_plugin.h>
#include <desktop_webview_auth/desktop_webview_auth_plugin.h>
#include <emoji_picker_flutter/emoji_picker_flutter_plugin_c_api.h>
#include <file_selector_windows/file_selector_windows.h>
#include <firebase_auth/firebase_auth_plugin_c_api.h>
#include <firebase_core/firebase_core_plugin_c_api.h>
#include <flutter_webrtc/flutter_web_r_t_c_plugin.h>
#include <permission_handler_windows/permission_handler_windows_plugin.h>
#include <url_launcher_windows/url_launcher_windows.h>
void RegisterPlugins(flutter::PluginRegistry* registry) {
ConnectivityPlusWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin"));
DesktopWebviewAuthPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("DesktopWebviewAuthPlugin"));
EmojiPickerFlutterPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("EmojiPickerFlutterPluginCApi"));
FileSelectorWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FileSelectorWindows"));
FirebaseAuthPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FirebaseAuthPluginCApi"));
FirebaseCorePluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FirebaseCorePluginCApi"));
FlutterWebRTCPluginRegisterWithRegistrar(
......
......@@ -3,7 +3,11 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
connectivity_plus
desktop_webview_auth
emoji_picker_flutter
file_selector_windows
firebase_auth
firebase_core
flutter_webrtc
permission_handler_windows
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment