mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-05-10 23:47:11 +00:00
group added
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
import 'dart:convert';
|
||||
|
||||
class Group {
|
||||
final String? groupName;
|
||||
final String? groupDescription;
|
||||
final List<dynamic> members;
|
||||
|
||||
Group({this.groupName, this.groupDescription, this.members = const []});
|
||||
|
||||
factory Group.fromJson(Map<String, dynamic> json) {
|
||||
return Group(
|
||||
groupName: json['gName'],
|
||||
groupDescription: json['desc'],
|
||||
members: json['contacts'],
|
||||
);
|
||||
}
|
||||
|
||||
static Map<String, dynamic> toJson(Group group) {
|
||||
return {
|
||||
'gName': group.groupName,
|
||||
'desc': group.groupDescription,
|
||||
'contacts': group.members,
|
||||
};
|
||||
}
|
||||
|
||||
static String encode(List<Group> groups) => json.encode(
|
||||
groups
|
||||
.map<Map<String, dynamic>>((group) => Group.toJson(group))
|
||||
.toList(),
|
||||
);
|
||||
|
||||
static List<Group> decode(String? groups) =>
|
||||
(json.decode(groups!) as List<dynamic>)
|
||||
.map<Group>((item) => Group.fromJson(item))
|
||||
.toList();
|
||||
}
|
||||
@@ -2,7 +2,9 @@ import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:simplex_chat/constants.dart';
|
||||
import 'package:simplex_chat/model/group.dart';
|
||||
import 'package:simplex_chat/widgets/custom_text_field.dart';
|
||||
|
||||
class AddGroupView extends StatefulWidget {
|
||||
@@ -13,11 +15,15 @@ class AddGroupView extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _AddGroupViewState extends State<AddGroupView> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
|
||||
final _displayNameController = TextEditingController();
|
||||
final _descController = TextEditingController();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_displayNameController.dispose();
|
||||
_descController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -27,57 +33,79 @@ class _AddGroupViewState extends State<AddGroupView> {
|
||||
onTap: () => FocusScope.of(context).unfocus(),
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
leading: BackButton(
|
||||
onPressed: () => Navigator.of(context).pop(false),
|
||||
),
|
||||
title: const Text('New Group'),
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
const SizedBox(height: 10.0),
|
||||
const Center(
|
||||
child: GroupDP(),
|
||||
),
|
||||
const SizedBox(height: 25.0),
|
||||
const Text('Group Name', style: kSmallHeadingStyle),
|
||||
const SizedBox(height: 10.0),
|
||||
CustomTextField(
|
||||
textEditingController: _displayNameController,
|
||||
textInputType: TextInputType.name,
|
||||
hintText: 'e.g College friends',
|
||||
validatorFtn: (value) {
|
||||
if (value!.isEmpty) {
|
||||
return 'Group name cannot be empty!';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 10.0),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.person_add),
|
||||
title: const Text('Add a member'),
|
||||
onTap: () {},
|
||||
),
|
||||
const Divider(height: 30.0),
|
||||
const ListTile(
|
||||
leading: CircleAvatar(
|
||||
backgroundImage: AssetImage('assets/dp.png'),
|
||||
body: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
const SizedBox(height: 10.0),
|
||||
const Center(
|
||||
child: GroupDP(),
|
||||
),
|
||||
title: Text('You'),
|
||||
trailing: Text(
|
||||
'Owner',
|
||||
style: TextStyle(color: Colors.grey, fontSize: 12.0),
|
||||
)),
|
||||
],
|
||||
const SizedBox(height: 25.0),
|
||||
const Text('Group Name', style: kSmallHeadingStyle),
|
||||
const SizedBox(height: 10.0),
|
||||
CustomTextField(
|
||||
textEditingController: _displayNameController,
|
||||
textInputType: TextInputType.name,
|
||||
hintText: 'e.g College friends',
|
||||
validatorFtn: (value) {
|
||||
if (value!.isEmpty) {
|
||||
return 'Group name cannot be empty!';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 10.0),
|
||||
const Text('Group Description', style: kSmallHeadingStyle),
|
||||
const SizedBox(height: 10.0),
|
||||
CustomTextField(
|
||||
textEditingController: _descController,
|
||||
textInputType: TextInputType.text,
|
||||
hintText: 'e.g Friends from UK',
|
||||
),
|
||||
const SizedBox(height: 10.0),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.person_add),
|
||||
title: const Text('Add a member'),
|
||||
onTap: () {},
|
||||
),
|
||||
const Divider(height: 30.0),
|
||||
const ListTile(
|
||||
leading: CircleAvatar(
|
||||
backgroundImage: AssetImage('assets/dp.png'),
|
||||
),
|
||||
title: Text('You'),
|
||||
trailing: Text(
|
||||
'Owner',
|
||||
style: TextStyle(color: Colors.grey, fontSize: 12.0),
|
||||
)),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
floatingActionButton: Visibility(
|
||||
visible: MediaQuery.of(context).viewInsets.bottom == 0,
|
||||
child: FloatingActionButton(
|
||||
heroTag: 'setup',
|
||||
onPressed: () {
|
||||
FocusScope.of(context).unfocus();
|
||||
Navigator.pop(context);
|
||||
onPressed: () async {
|
||||
if (_formKey.currentState!.validate()) {
|
||||
FocusScope.of(context).unfocus();
|
||||
_addNewGroup(_displayNameController.text.trim(),
|
||||
_descController.text.trim());
|
||||
_descController.clear();
|
||||
_displayNameController.clear();
|
||||
Navigator.of(context).pop(true);
|
||||
}
|
||||
},
|
||||
child: const Icon(Icons.check),
|
||||
),
|
||||
@@ -85,6 +113,33 @@ class _AddGroupViewState extends State<AddGroupView> {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _addNewGroup(String name, String desc) async {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
List<Group> _localList = [];
|
||||
_localList = List.from(Group.decode(prefs.getString('groups')));
|
||||
|
||||
List<Group> _groups = [
|
||||
Group(
|
||||
groupName: name,
|
||||
groupDescription: desc,
|
||||
members: <String>[],
|
||||
),
|
||||
];
|
||||
_groups = _localList + _groups;
|
||||
|
||||
final String _newGroups = Group.encode(_groups);
|
||||
|
||||
await prefs.setString('groups', _newGroups);
|
||||
|
||||
var snackBar = SnackBar(
|
||||
backgroundColor: Colors.green,
|
||||
content: Text('$name added!'),
|
||||
);
|
||||
ScaffoldMessenger.of(context)
|
||||
..hideCurrentSnackBar()
|
||||
..showSnackBar(snackBar);
|
||||
}
|
||||
}
|
||||
|
||||
class GroupDP extends StatefulWidget {
|
||||
|
||||
@@ -0,0 +1,335 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:simplex_chat/animations/bottom_animation.dart';
|
||||
import 'package:simplex_chat/app_routes.dart';
|
||||
import 'package:simplex_chat/constants.dart';
|
||||
import 'package:simplex_chat/model/group.dart';
|
||||
import 'package:simplex_chat/views/conversation/conversation_view.dart';
|
||||
|
||||
class GroupView extends StatefulWidget {
|
||||
const GroupView({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<GroupView> createState() => _GroupViewState();
|
||||
}
|
||||
|
||||
class _GroupViewState extends State<GroupView> {
|
||||
bool? _eraseMedia = false;
|
||||
final List<String> _options = [
|
||||
'Add group',
|
||||
'Scan invitation',
|
||||
];
|
||||
|
||||
List<Group> _groupList = [];
|
||||
|
||||
// delete a group
|
||||
void _deleteContact(Group group) async {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
setState(() {
|
||||
_groupList.remove(group);
|
||||
});
|
||||
await prefs.setString('groups', Group.encode(_groupList));
|
||||
var snackBar = SnackBar(
|
||||
backgroundColor: Colors.red,
|
||||
content: Text('${group.groupName} deleted!'),
|
||||
);
|
||||
ScaffoldMessenger.of(context)
|
||||
..hideCurrentSnackBar()
|
||||
..showSnackBar(snackBar);
|
||||
}
|
||||
|
||||
// getting data from local storage FOR NOW!!
|
||||
void _getGroups() async {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
final String? _groups = prefs.getString('groups');
|
||||
setState(() {
|
||||
_groupList = List.from(Group.decode(_groups));
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_getGroups();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 20.0),
|
||||
child: Column(
|
||||
children: [
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: GestureDetector(
|
||||
onTap: _addNewGroups,
|
||||
child: SvgPicture.asset(
|
||||
'assets/logo.svg',
|
||||
height: 40.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 15.0),
|
||||
Row(
|
||||
children: const [
|
||||
Icon(Icons.group, color: kPrimaryColor),
|
||||
SizedBox(width: 8.0),
|
||||
Text(
|
||||
'Groups',
|
||||
style: kHeadingStyle,
|
||||
)
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 5.0),
|
||||
_groupList.isEmpty
|
||||
? SizedBox(
|
||||
height: MediaQuery.of(context).size.height * 0.7,
|
||||
child: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: const [
|
||||
Text(
|
||||
"You don't have any groups yet!",
|
||||
style: kMediumHeadingStyle,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
SizedBox(height: 8.0),
|
||||
Text(
|
||||
'Click the icon below to add a contact',
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
: ListView(
|
||||
shrinkWrap: true,
|
||||
children: List.generate(
|
||||
_groupList.length,
|
||||
(index) => WidgetAnimator(
|
||||
child: ListTile(
|
||||
leading: const CircleAvatar(
|
||||
backgroundImage: AssetImage('assets/dp.png'),
|
||||
),
|
||||
title: Text(_groupList[index].groupName!),
|
||||
subtitle: Text(_groupList[index].groupDescription!),
|
||||
trailing: Text(
|
||||
'Members: ${_groupList[index].members.length}',
|
||||
style: const TextStyle(
|
||||
fontSize: 11, color: Colors.grey),
|
||||
),
|
||||
onLongPress: () =>
|
||||
_conversationOptions(_groupList[index]),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
floatingActionButton: PopupMenuButton(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(5.0),
|
||||
),
|
||||
offset: const Offset(-10, -120),
|
||||
onSelected: (value) async {
|
||||
if (value == _options[0]) {
|
||||
var value = await Navigator.pushNamed(context, AppRoutes.addGroup);
|
||||
if (value == true) {
|
||||
_getGroups();
|
||||
}
|
||||
} else {
|
||||
await Navigator.pushNamed(context, AppRoutes.scanInvitation);
|
||||
}
|
||||
},
|
||||
itemBuilder: (context) => _options
|
||||
.map(
|
||||
(opt) => PopupMenuItem(
|
||||
value: opt,
|
||||
child: Text(opt),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
child: const FloatingActionButton(
|
||||
heroTag: 'group',
|
||||
onPressed: null,
|
||||
child: Icon(
|
||||
Icons.group_add,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _conversationOptions(Group group) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
_deleteConversation(group);
|
||||
},
|
||||
child: const Text(
|
||||
'Delete Group',
|
||||
style: TextStyle(color: Colors.red),
|
||||
)),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
_disconnect();
|
||||
},
|
||||
child: const Text('Leave Group')),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _deleteConversation(Group group) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => StatefulBuilder(
|
||||
builder: (context, setState) => AlertDialog(
|
||||
title: const Text('Are you Sure?'),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Text('All group history will be deleted from your device!'),
|
||||
const SizedBox(height: 15.0),
|
||||
Row(
|
||||
children: [
|
||||
Checkbox(
|
||||
value: _eraseMedia,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_eraseMedia = value;
|
||||
});
|
||||
}),
|
||||
const Text('Erase files & Media')
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
InkWell(
|
||||
onTap: () {
|
||||
_deleteContact(group);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(8.0),
|
||||
child: Icon(Icons.check, color: Colors.green),
|
||||
),
|
||||
),
|
||||
InkWell(
|
||||
onTap: () => Navigator.pop(context),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(8.0),
|
||||
child: Icon(Icons.cancel_outlined, color: Colors.red),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _disconnect() {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => StatefulBuilder(
|
||||
builder: (context, setState) => AlertDialog(
|
||||
title: const Text('Are you Sure?'),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Text(
|
||||
'Leaving a group will erase all the data from your device and you will no longer be able to contact again!'),
|
||||
const SizedBox(height: 15.0),
|
||||
Row(
|
||||
children: [
|
||||
Checkbox(
|
||||
value: _eraseMedia,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_eraseMedia = value;
|
||||
});
|
||||
}),
|
||||
const Text('Erase files & Media')
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
InkWell(
|
||||
onTap: () => Navigator.pop(context),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(8.0),
|
||||
child: Icon(Icons.check, color: Colors.green),
|
||||
),
|
||||
),
|
||||
InkWell(
|
||||
onTap: () => Navigator.pop(context),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(8.0),
|
||||
child: Icon(Icons.cancel_outlined, color: Colors.red),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// dummy ftn for loading new contacts
|
||||
void _addNewGroups() async {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
List<Group> _localList = [];
|
||||
_localList = List.from(Group.decode(prefs.getString('groups')));
|
||||
|
||||
List<Group> _groups = [
|
||||
Group(
|
||||
groupName: 'Family group',
|
||||
groupDescription: 'Some description here',
|
||||
members: <String>[
|
||||
'Hamza',
|
||||
'Alice',
|
||||
'John',
|
||||
'Bob',
|
||||
],
|
||||
),
|
||||
Group(
|
||||
groupName: 'Friends',
|
||||
groupDescription: 'Miss you all',
|
||||
members: <String>[
|
||||
'Alice',
|
||||
'John',
|
||||
'Bob',
|
||||
],
|
||||
),
|
||||
];
|
||||
_groups = _localList + _groups;
|
||||
|
||||
// dummy ftn for filling the list
|
||||
final String _newGroups = Group.encode(_groups);
|
||||
|
||||
await prefs.setString('groups', _newGroups);
|
||||
|
||||
_getGroups();
|
||||
|
||||
const snackBar = SnackBar(
|
||||
backgroundColor: Colors.green,
|
||||
content: Text('New Groups loaded!'),
|
||||
);
|
||||
ScaffoldMessenger.of(context)
|
||||
..hideCurrentSnackBar()
|
||||
..showSnackBar(snackBar);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user