Commit 8dd94647 authored by Inomjon's avatar Inomjon

update chiqarish

parent 52d9223a
......@@ -10,6 +10,8 @@ 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';
import '../../src/login_screen.dart';
import '../../src/utils/app_utils.dart';
import '../../srcchat/settings_screen.dart';
import '../../views/edit_profile_view/edit_profile_page.dart';
import '../../views/reset_password_view/reset_password_page.dart';
import '../../views/signin_view/signin_page.dart';
......@@ -51,9 +53,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.setting_screen:
builder = (BuildContext _) => SettingsScreen(AppUtils.cubeUser);
break;
case MainRoutes.user_signup_page:
builder = (BuildContext _) => MultiBlocProvider(
providers: [
......
......@@ -8,5 +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";
static const String setting_screen = "setting_screen";
}
\ No newline at end of file
......@@ -39,6 +39,7 @@ class UserRepository {
isChaking = false;
controller.inputElevatedButton.add(false);
errorMessage(context," ConnectyCube 11");
SharedPrefs.deleteUserData();
});
}
return isChaking;
......@@ -68,6 +69,7 @@ class UserRepository {
})
.catchError((error){
controller.inputElevatedButton.add(false);
SharedPrefs.deleteUserData();
errorMessage(context, " ConnectyCube 33");
print(error);
});
......
import 'dart:ui';
import 'package:flutter/material.dart';
import '../../../srcchat/settings_screen.dart';
import '../../../srcchat/utils/platform_utils.dart';
import '../../constants/colors_const.dart';
import '../../utils/app_utils.dart';
import '../textfiled_widgets/auth_text_fild.dart';
import 'icon_painters/notification_icon_painter.dart';
......@@ -14,7 +17,12 @@ const double _appBarExpandedHeight1 = 50 + _searchBarHeight; // 154
class SliverScaffold extends StatefulWidget {
final Widget appBarIcon;
final int pageIndex;
const SliverScaffold({Key? key, required this.body,required this.appBarIcon, required this.pageIndex}) : super(key: key);
const SliverScaffold(
{Key? key,
required this.body,
required this.appBarIcon,
required this.pageIndex})
: super(key: key);
final Widget body;
......@@ -55,18 +63,21 @@ class _SliverScaffoldState extends State<SliverScaffold> {
headerSliverBuilder: (_, __) => [
SliverAppBar(
leading: Row(
children: [
SizedBox(width: actionSpacing),
widget.appBarIcon
],
children: [SizedBox(width: actionSpacing), widget.appBarIcon],
),
leadingWidth: 74,
centerTitle: true,
actions: [
IconButton(
widget.pageIndex == 0
? IconButton(
onPressed: () {},
splashRadius: 24,
icon: NotificationIconPainter.getCustomPaint(iconStrokeWidth),
)
: IconButton(
onPressed: () => _openSettings(context),
splashRadius: 24,
icon: Icon(Icons.settings, color: ColorConst.appBleckColor),
),
SizedBox(width: actionSpacing),
],
......@@ -117,6 +128,10 @@ class _SliverScaffoldState extends State<SliverScaffold> {
);
}
_openSettings(BuildContext context) {
showModal(context: context, child: SettingsScreen(AppUtils.cubeUser,));
}
_scrollListener() {
setState(() {
currentExtent = _scrollController.offset;
......
......@@ -16,11 +16,8 @@ class CreateDialog extends StatelessWidget {
key: Navigation.createDialogNavigation,
initialRoute: 'search_users',
onGenerateRoute: (RouteSettings settings) {
Map<String, dynamic>? args =
settings.arguments as Map<String, dynamic>?;
Map<String, dynamic>? args = settings.arguments as Map<String, dynamic>?;
Widget page;
switch (settings.name) {
case 'search_users':
page = CreateChatScreen(currentUser);
......
......@@ -3,9 +3,12 @@ import 'package:flutter/material.dart';
import 'package:connectycube_sdk/connectycube_chat.dart';
import 'package:vmeeting/src/constants/colors_const.dart';
import 'chat_dialog_resizable_screen.dart';
import 'chat_dialog_screen.dart';
import 'utils/api_utils.dart';
import 'utils/consts.dart';
import 'widgets/common.dart';
import 'package:vmeeting/srcchat//utils/platform_utils.dart' as platformUtils;
class CreateChatScreen extends StatelessWidget {
final CubeUser _cubeUser;
......@@ -29,13 +32,13 @@ class CreateChatScreen extends StatelessWidget {
);
}
CreateChatScreen(this._cubeUser);
const CreateChatScreen(this._cubeUser, {super.key});
}
class BodyLayout extends StatefulWidget {
final CubeUser currentUser;
BodyLayout(this.currentUser);
const BodyLayout(this.currentUser, {super.key});
@override
State<StatefulWidget> createState() {
......@@ -48,7 +51,7 @@ class _BodyLayoutState extends State<BodyLayout> {
final CubeUser currentUser;
List<CubeUser> userList = [];
Set<int> _selectedUsers = {};
final Set<int> _selectedUsers = {};
var _isUsersContinues = false;
var _isPrivateDialog = true;
String? userToSearch;
......@@ -58,30 +61,31 @@ class _BodyLayoutState extends State<BodyLayout> {
_searchUser(value) {
log("searchUser _user= $value");
if (value != null)
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),
padding: const EdgeInsets.only(left: 16, right: 16, bottom: 16),
child: Column(
children: [
_buildTextFields(),
_buildDialogButton(),
Container(
margin: EdgeInsets.only(left: 8),
margin: const EdgeInsets.only(left: 8),
child: Visibility(
maintainSize: false,
maintainAnimation: false,
maintainState: false,
visible: _isUsersContinues,
child: CircularProgressIndicator(
child: const CircularProgressIndicator(
strokeWidth: 2,
),
),
......@@ -91,36 +95,32 @@ class _BodyLayoutState extends State<BodyLayout> {
),
],
)),
floatingActionButton: new Visibility(
floatingActionButton: Visibility(
visible: !_isPrivateDialog,
child: FloatingActionButton(
heroTag: "New dialog",
child: Icon(
backgroundColor: Colors.blue,
onPressed: () => _createDialog(context, _selectedUsers, true),
child: const Icon(
Icons.check,
color: Colors.white,
),
backgroundColor: Colors.blue,
onPressed: () => _createDialog(context, _selectedUsers, true),
),
),
);
}
Widget _buildTextFields() {
return new Container(
child: new Column(
return Column(
children: <Widget>[
new Container(
child: new TextField(
TextField(
autofocus: true,
textInputAction: TextInputAction.search,
decoration: new InputDecoration(labelText: 'Search users'),
decoration: const InputDecoration(labelText: 'Search users'),
onSubmitted: (value) {
_searchUser(value.trim());
}),
),
],
),
);
}
......@@ -141,7 +141,7 @@ class _BodyLayoutState extends State<BodyLayout> {
}
}
return new Container(
return Container(
alignment: Alignment.centerLeft,
child: TextButton.icon(
icon: Icon(
......@@ -184,84 +184,83 @@ class _BodyLayoutState extends State<BodyLayout> {
});
}
}
if (userList.isEmpty)
if (userList.isEmpty) {
return FittedBox(
fit: BoxFit.contain,
child: Text(userMsg),
);
else
} else {
return ListView.builder(
itemCount: userList.length,
itemBuilder: _getListItemTile,
);
}
}
Widget _getListItemTile(BuildContext context, int index) {
getPrivateWidget() {
return Container(
margin: const EdgeInsets.only(bottom: 10.0, left: 5.0, right: 5.0),
child: TextButton(
child: Row(
children: <Widget>[
getUserAvatarWidget(userList[index], 30),
Flexible(
child: Container(
margin: const EdgeInsets.only(left: 20.0),
child: Column(
children: <Widget>[
Container(
alignment: Alignment.centerLeft,
margin: const EdgeInsets.fromLTRB(10.0, 0.0, 0.0, 5.0),
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(
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(
margin: const EdgeInsets.only(bottom: 10.0, left: 5.0, right: 5.0),
child: TextButton(
child: Row(
children: <Widget>[
getUserAvatarWidget(userList[index], 30),
Flexible(
child: Container(
margin: const EdgeInsets.only(left: 20.0),
child: Column(
children: <Widget>[
Container(
alignment: Alignment.centerLeft,
margin: const EdgeInsets.fromLTRB(10.0, 0.0, 0.0, 5.0),
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(
Checkbox(
value: _selectedUsers.contains(userList[index].id),
onChanged: ((checked) {
setState(() {
......@@ -273,7 +272,6 @@ class _BodyLayoutState extends State<BodyLayout> {
});
}),
),
),
],
),
onPressed: () {
......@@ -286,7 +284,6 @@ class _BodyLayoutState extends State<BodyLayout> {
});
},
),
margin: EdgeInsets.only(bottom: 10.0, left: 5.0, right: 5.0),
);
}
......@@ -302,7 +299,6 @@ class _BodyLayoutState extends State<BodyLayout> {
}
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();
......@@ -314,10 +310,15 @@ class _BodyLayoutState extends State<BodyLayout> {
} 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
});
platformUtils.isDesktop()
? Navigator.push(
context,
MaterialPageRoute(builder: (context) => ChatDialogResizableScreen(currentUser, createdDialog)),
)
: Navigator.push(
context,
MaterialPageRoute(builder: (context) => ChatDialogScreen(currentUser, createdDialog)),
);
}).catchError((error) {
_processCreateDialogError(error);
});
......
......@@ -5,13 +5,11 @@ import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:intl/intl.dart';
import 'package:connectycube_sdk/connectycube_sdk.dart';
import '../src/constants/colors_const.dart';
import '../src/utils/app_utils.dart';
import 'chat_dialog_resizable_screen.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';
......@@ -22,35 +20,15 @@ class SelectDialogScreen extends StatelessWidget {
final Function(CubeDialog)? onDialogSelectedCallback;
final CubeDialog? selectedDialog;
SelectDialogScreen(this.selectedDialog, this.onDialogSelectedCallback);
const SelectDialogScreen(this.selectedDialog, this.onDialogSelectedCallback, {super.key});
@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 {
......@@ -58,7 +36,7 @@ class BodyLayout extends StatefulWidget {
final Function(CubeDialog)? onDialogSelectedCallback;
final CubeDialog? selectedDialog;
BodyLayout(this.currentUser, this.selectedDialog, this.onDialogSelectedCallback, {super.key});
const BodyLayout(this.currentUser, this.selectedDialog, this.onDialogSelectedCallback, {super.key});
@override
State<StatefulWidget> createState() {
......@@ -137,11 +115,9 @@ class _BodyLayoutState extends State<BodyLayout> {
if (_isDialogContinues) {
getDialogs().then((dialogs) {
_isDialogContinues = false;
log("getDialogs: $dialogs", TAG);
setState(() {
dialogList.clear();
dialogList.addAll(
dialogs?.items.map((dialog) => ListItem(dialog)).toList() ?? []);
dialogList.addAll(dialogs?.items.map((dialog) => ListItem(dialog)).toList() ?? []);
});
}).catchError((exception) {
_processGetDialogError(exception);
......@@ -191,8 +167,7 @@ class _BodyLayoutState extends State<BodyLayout> {
getDialogAvatar() {
var dialog = dialogList[index].data;
return getDialogAvatarWidget(dialog, 25,
placeholder: getDialogIcon(), errorWidget: getDialogIcon());
return getDialogAvatarWidget(dialog, 25, placeholder: getDialogIcon(), errorWidget: getDialogIcon());
}
return Container(
......@@ -213,8 +188,7 @@ class _BodyLayoutState extends State<BodyLayout> {
children: <Widget>[
Container(
alignment: Alignment.centerLeft,
child: Text(
'${dialogList[index].data.name ?? 'Unknown dialog'}',
child: Text(dialogList[index].data.name ?? 'Unknown dialog',
style: TextStyle(
color: primaryColor,
fontWeight: FontWeight.bold,
......
......@@ -4,9 +4,14 @@ import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:connectycube_sdk/connectycube_sdk.dart';
import 'package:vmeeting/service/routes/routes_name.dart';
import 'package:vmeeting/src/extension/context_extensions.dart';
import '../src/constants/colors_const.dart';
import '../src/controllers/enter_number_cont.dart';
import '../src/utils/app_utils.dart';
import '../src/widgets/image_avatar.dart';
import '../src/widgets/textfiled_widgets/auth_text_fild.dart';
import 'managers/push_notifications_manager.dart';
import 'utils/api_utils.dart';
import 'utils/consts.dart';
......@@ -57,9 +62,12 @@ class _BodyLayoutState extends State<BodyLayout> {
final CubeUser currentUser;
var _isUsersContinues = false;
String? _avatarUrl = "";
final TextEditingController _loginFilter = new TextEditingController();
final TextEditingController _nameFilter = new TextEditingController();
final TextEditingController _emailFilter = new TextEditingController();
final TextEditingController _loginFilter = TextEditingController();
final TextEditingController _nameFilter = TextEditingController();
final _nameFocusNode = FocusNode();
final _loginFocusNode = FocusNode();
final _emileFocusNode = FocusNode();
final TextEditingController _emailFilter = TextEditingController();
String _login = "";
String _name = "";
String _email = "";
......@@ -103,14 +111,16 @@ class _BodyLayoutState extends State<BodyLayout> {
body: SingleChildScrollView(
child: Center(
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: 400),
constraints: const BoxConstraints(maxWidth: 400),
child: Container(
alignment: Alignment.center,
padding: EdgeInsets.all(60),
padding: const EdgeInsets.all(8),
child: Column(
children: [
_buildAvatarFields(),
const SizedBox(height: 30),
_buildTextFields(),
const SizedBox(height: 50),
_buildButtons(),
Container(
margin: const EdgeInsets.only(left: 8),
......@@ -130,21 +140,25 @@ class _BodyLayoutState extends State<BodyLayout> {
),
);
}
Widget buildImage(String? imagaAvatar) {
return Center(
child: AvatarImage(
radius: context.h * 0.08,
imagePath: getPrivateUrlForUid(imagaAvatar),
));
}
Widget _buildAvatarFields() {
Widget avatarCircle = getUserAvatarWidget(currentUser, 50);
return Stack(
children: <Widget>[
InkWell(
splashColor: greyColor2,
borderRadius: BorderRadius.circular(45),
onTap: () => _chooseUserImage(),
child: avatarCircle,
child: buildImage(AppUtils.userModel.avatar ?? ""),
),
Positioned(
top: 55.0,
right: 35.0,
top: 75.0,
right: 85.0,
child: RawMaterialButton(
onPressed: () {
_chooseUserImage();
......@@ -185,17 +199,34 @@ class _BodyLayoutState extends State<BodyLayout> {
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'),
AuthTextFild(
lableName: "Change name",
borderRadius: 10,
elevation: 1,
color: Colors.grey.withOpacity(0.2),
type: TextInputType.emailAddress,
focusNode: _nameFocusNode,
textEditingController: _nameFilter,
),
const SizedBox(height: 10),
AuthTextFild(
lableName: "Change login",
borderRadius: 10,
elevation: 1,
color: Colors.grey.withOpacity(0.2),
type: TextInputType.emailAddress,
focusNode: _loginFocusNode,
textEditingController: _loginFilter,
),
const SizedBox(height: 10),
AuthTextFild(
lableName: "Change e-mail",
borderRadius: 10,
elevation: 1,
color: Colors.grey.withOpacity(0.2),
type: TextInputType.emailAddress,
focusNode: _emileFocusNode,
textEditingController: _emailFilter,
),
],
);
......@@ -218,20 +249,6 @@ class _BodyLayoutState extends State<BodyLayout> {
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,
......@@ -252,17 +269,11 @@ class _BodyLayoutState extends State<BodyLayout> {
}
void _updateUser() {
print(
'_updateUser user with login: $_login, name: $_name, e-mail: $_email');
if (_login.isEmpty &&
_name.isEmpty &&
_avatarUrl!.isEmpty &&
_email.isEmpty) {
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;
......@@ -271,7 +282,7 @@ class _BodyLayoutState extends State<BodyLayout> {
_isUsersContinues = true;
});
updateUser(userToUpdate).then((user) {
SharedPrefs.instance.updateUser(user);
// SharedPrefs.instance.updateUser(user);
Fluttertoast.showToast(msg: 'Success');
setState(() {
_isUsersContinues = false;
......@@ -310,8 +321,7 @@ class _BodyLayoutState extends State<BodyLayout> {
).whenComplete(() {
CubeChatConnection.instance.destroy();
PushNotificationsManager.instance.unsubscribe();
FirebaseAuth.instance.currentUser
?.unlink(PhoneAuthProvider.PROVIDER_ID);
FirebaseAuth.instance.currentUser?.unlink(PhoneAuthProvider.PROVIDER_ID);
SharedPrefs.instance.deleteUser();
Navigator.pop(context); // cancel current screen
_navigateToLoginScreen(context);
......@@ -325,7 +335,6 @@ class _BodyLayoutState extends State<BodyLayout> {
}
void _deleteUserPressed() {
print('_deleteUserPressed ${_login.isNotEmpty ? _login : _email}');
_userDelete();
}
......@@ -334,24 +343,22 @@ class _BodyLayoutState extends State<BodyLayout> {
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text("Delete user"),
content: Text("Are you sure you want to delete current user?"),
title: const Text("Delete user"),
content: const Text("Are you sure you want to delete current user?"),
actions: <Widget>[
TextButton(
child: Text("CANCEL"),
child: const Text("CANCEL"),
onPressed: () {
Navigator.pop(context);
},
),
TextButton(
child: Text("OK"),
child: const Text("OK"),
onPressed: () async {
CubeChatConnection.instance.destroy();
await SharedPrefs.instance.deleteUser();
deleteUser(currentUser.id!).then(
(voidValue) {
Navigator.pop(context); // cancel current Dialog
Navigator.pushReplacementNamed(context, MainRoutes.sign_in_page);
},
).catchError(
(onError) {
......@@ -359,7 +366,6 @@ class _BodyLayoutState extends State<BodyLayout> {
},
).whenComplete(() async {
await PushNotificationsManager.instance.unsubscribe();
Navigator.pop(context); // cancel current screen
_navigateToLoginScreen(context);
});
},
......@@ -371,7 +377,8 @@ class _BodyLayoutState extends State<BodyLayout> {
}
_navigateToLoginScreen(BuildContext context) {
Navigator.pushNamedAndRemoveUntil(context, 'login', (route) => false);
SharedPrefs.instance.deleteUser();
Navigator.pushReplacementNamed(context, MainRoutes.sign_in_page);
}
void _processUpdateUserError(exception) {
......
......@@ -65,7 +65,7 @@ Widget getMessageStateWidget(MessageState? state) {
switch (state) {
case MessageState.read:
result = Stack(children: <Widget>[
result = const Stack(children: <Widget>[
Icon(
Icons.check_rounded,
size: 15.0,
......
......@@ -82,6 +82,17 @@ class _DrawerMenueState extends State<DrawerMenue> {
Navigator.pushNamed(context, MainRoutes.reset_password_page);
},
),
ListTile(
leading: Icon(Icons.settings, color: ColorConst.appBleckColor),
title: const SmallText(text: "Settings",size: 16),
trailing: Icon(Icons.navigate_next,
color: ColorConst.appBleckColor,
size: 30,
),
onTap: () async {
Navigator.pushNamed(context, MainRoutes.setting_screen);
},
),
const Spacer(),
ListTile(
leading: Icon(Icons.login, color: ColorConst.appRedColor),
......
......@@ -34,7 +34,7 @@ class _MainPageState extends State<MainPage> {
],
child: HomePage(controller: widget.controller),
),
SelectDialogScreen(null, null),
SelectDialogScreen(null,null),
ProfilePage(controller: widget.controller)
];
......
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