Sample application.
This commit is contained in:
3
lib/config.dart
Normal file
3
lib/config.dart
Normal file
@@ -0,0 +1,3 @@
|
||||
String AuthEndpoint = 'http://192.168.1.15:8016/authclientuser';
|
||||
String BaseEndpoint = 'user-graphql-apikey';
|
||||
String DataEndpoint = 'http://192.168.1.15:8016/user-graphql-apikey';
|
||||
157
lib/contactCreationPage.dart
Normal file
157
lib/contactCreationPage.dart
Normal file
@@ -0,0 +1,157 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'dart:io';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'contactService.dart';
|
||||
import 'model/contactCreation.dart';
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
class ContactCreationPage extends StatefulWidget{
|
||||
late ContactService contactService;
|
||||
ContactCreationPage({Key? key, required this.contactService}) : super(key: key);
|
||||
|
||||
@override
|
||||
_ContatcCreationPageState createState() => _ContatcCreationPageState();
|
||||
}
|
||||
|
||||
class _ContatcCreationPageState extends State<ContactCreationPage> {
|
||||
final TextEditingController _nameController = TextEditingController();
|
||||
final TextEditingController _emailController = TextEditingController();
|
||||
final TextEditingController _phoneController = TextEditingController();
|
||||
final TextEditingController _streetController = TextEditingController();
|
||||
final TextEditingController _street2Controller = TextEditingController();
|
||||
final TextEditingController _cityController = TextEditingController();
|
||||
final ImagePicker _picker = ImagePicker();
|
||||
String? _image;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Create Contact'),
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child:Padding(
|
||||
padding: EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
TextFormField(
|
||||
controller: _nameController,
|
||||
decoration: InputDecoration(labelText: 'Name'),
|
||||
),
|
||||
TextFormField(
|
||||
controller: _emailController,
|
||||
decoration: InputDecoration(labelText: 'Email'),
|
||||
),
|
||||
TextFormField(
|
||||
controller: _phoneController,
|
||||
decoration: InputDecoration(labelText: 'Phone'),
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
Text(
|
||||
'Address:',
|
||||
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
|
||||
),
|
||||
TextFormField(
|
||||
controller: _streetController,
|
||||
decoration: InputDecoration(labelText: 'Street'),
|
||||
),
|
||||
TextFormField(
|
||||
controller: _street2Controller,
|
||||
decoration: InputDecoration(labelText: 'Street2'),
|
||||
),
|
||||
TextFormField(
|
||||
controller: _cityController,
|
||||
decoration: InputDecoration(labelText: 'City'),
|
||||
),
|
||||
FloatingActionButton(
|
||||
onPressed: pickImage,
|
||||
tooltip: 'Pick Image',
|
||||
child: const Icon(Icons.add_a_photo),
|
||||
),
|
||||
Container(
|
||||
child: getImage(),
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
_saveContact(context);
|
||||
},
|
||||
child: Text('Save Contact'),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
getImage() {
|
||||
if (_image != null){
|
||||
Uint8List imageBytes = base64.decode(_image.toString());
|
||||
ImageProvider imageProvider = MemoryImage(imageBytes);
|
||||
return Image(
|
||||
image: imageProvider,
|
||||
height: 150,
|
||||
width: 150,
|
||||
);
|
||||
}
|
||||
else{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future pickImage() async {
|
||||
final XFile? image = await _picker.pickImage(source: ImageSource.gallery);
|
||||
final path = image?.path;
|
||||
if (path != null){
|
||||
if (kIsWeb){
|
||||
final response = await fetchWebImage(path);
|
||||
final imageValue = base64Encode(response);
|
||||
setState(() {
|
||||
_image = imageValue;
|
||||
});
|
||||
}
|
||||
else {
|
||||
final response = await File(path).readAsBytes();
|
||||
final imageValue = base64Encode(response);
|
||||
setState(() {
|
||||
_image = imageValue;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<int>> fetchWebImage(path) async {
|
||||
final response = await http.get(
|
||||
Uri.parse(path),
|
||||
);
|
||||
return response.bodyBytes;
|
||||
}
|
||||
|
||||
void _saveContact(BuildContext context) async {
|
||||
final name = _nameController.text;
|
||||
final email = _emailController.text;
|
||||
final phone = _phoneController.text;
|
||||
final street = _streetController.text;
|
||||
final street2 = _street2Controller.text;
|
||||
final city = _cityController.text;
|
||||
final image1920 = _image;
|
||||
|
||||
if (name.isNotEmpty && email.isNotEmpty) {
|
||||
final newContact = ContactCreation(
|
||||
name: name, email: email, phone: phone, street: street, street2: street2, city: city, image1920: image1920);
|
||||
final newCreatedContacts = await widget.contactService.contactCreateRequest(contact: newContact);
|
||||
Navigator.pop(context, newCreatedContacts); // Return the new contact to the previous page
|
||||
} else {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Please enter name and email')),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
210
lib/contactDetailPage.dart
Normal file
210
lib/contactDetailPage.dart
Normal file
@@ -0,0 +1,210 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'secureStorage.dart';
|
||||
import 'homepage.dart';
|
||||
import 'contactService.dart';
|
||||
import 'model/contactDetail.dart';
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
class ContatcDetailPage extends StatefulWidget {
|
||||
late ContactDetail contact ;
|
||||
late ContactService contactService;
|
||||
ContatcDetailPage({Key? key, required this.contact, required this.contactService}) : super(key: key);
|
||||
|
||||
@override
|
||||
_ContatcDetailPageState createState() => _ContatcDetailPageState();
|
||||
}
|
||||
|
||||
|
||||
class _ContatcDetailPageState extends State<ContatcDetailPage> {
|
||||
late TextEditingController _nameController;
|
||||
late TextEditingController _emailController;
|
||||
late TextEditingController _phoneController;
|
||||
late TextEditingController _streetController;
|
||||
late TextEditingController _street2Controller;
|
||||
late TextEditingController _cityController;
|
||||
late TextEditingController _countryController;
|
||||
|
||||
|
||||
// Future<ContactDetail> getContactDetailValues() async {
|
||||
// final response = await _contactService.contactDetailRequest();
|
||||
// final ContactDetail contactResult = ContactDetail.fromJson(response);
|
||||
// print(contactResult);
|
||||
// return contactResult;
|
||||
// }
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_nameController = TextEditingController(text: widget.contact.name.toString());
|
||||
_emailController = TextEditingController(text: widget.contact.email.toString());
|
||||
_phoneController = TextEditingController(text: widget.contact.phone.toString());
|
||||
_streetController = TextEditingController(text: widget.contact.street.toString());
|
||||
_street2Controller = TextEditingController(text: widget.contact.street2.toString());
|
||||
_cityController = TextEditingController(text: widget.contact.city.toString());
|
||||
_countryController = TextEditingController(text: widget.contact.country.toString());
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_nameController.dispose();
|
||||
_emailController.dispose();
|
||||
_phoneController.dispose();
|
||||
_streetController.dispose();
|
||||
_cityController.dispose();
|
||||
_countryController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
getCircleAvatar(image) {
|
||||
if (image != null && image != false){
|
||||
Uint8List imageBytes = base64.decode(image);
|
||||
ImageProvider imageProvider = MemoryImage(imageBytes);
|
||||
return CircleAvatar(
|
||||
backgroundImage: imageProvider,
|
||||
radius: 50,
|
||||
);
|
||||
}
|
||||
else{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _deleteContact(BuildContext context) async {
|
||||
final response = await widget.contactService.contactDeleteRequest(id: widget.contact.id);// Replace with your API endpoint
|
||||
if (response['data']['delete'] == 'Success') {
|
||||
final apikey = await userCreds.read(key: 'api-key');
|
||||
Navigator.push(
|
||||
context, MaterialPageRoute(builder: (_) => HomePage(apikey:apikey.toString()))
|
||||
); // Go back to contact list page after successful delete
|
||||
} else {
|
||||
// Show error message if delete fails
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text('Error'),
|
||||
content: Text('Failed to delete contact. Please try again.'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text('OK'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Contact Details'),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.save),
|
||||
onPressed: () async {
|
||||
// Save changes and navigate back
|
||||
print('update clicked with name${widget.contact.name}');
|
||||
final ContactDetail updatedrResult = await _saveUpdateChanges();
|
||||
print(updatedrResult);
|
||||
setState(() {
|
||||
widget.contact = updatedrResult;
|
||||
});
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(Icons.delete),
|
||||
onPressed:() {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text('Confirm Delete'),
|
||||
content: Text('Are you sure you want to delete this contact?'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: (){
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text('Cancel')
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
await _deleteContact(context);
|
||||
},
|
||||
child: Text('Delete')
|
||||
)
|
||||
],
|
||||
)
|
||||
);
|
||||
},
|
||||
)
|
||||
]
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
padding: EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Center(
|
||||
child: getCircleAvatar(widget.contact.image1920),
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
TextFormField(
|
||||
controller: _nameController,
|
||||
decoration: InputDecoration(labelText: 'Name'),
|
||||
),
|
||||
TextFormField(
|
||||
controller: _emailController,
|
||||
decoration: InputDecoration(labelText: 'Email'),
|
||||
),
|
||||
TextFormField(
|
||||
controller: _phoneController,
|
||||
decoration: InputDecoration(labelText: 'Phone'),
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
Text(
|
||||
'Address:',
|
||||
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
|
||||
),
|
||||
TextFormField(
|
||||
controller: _streetController,
|
||||
decoration: InputDecoration(labelText: 'Street'),
|
||||
),
|
||||
TextFormField(
|
||||
controller: _street2Controller,
|
||||
decoration: InputDecoration(labelText: 'Street2'),
|
||||
),
|
||||
TextFormField(
|
||||
controller: _cityController,
|
||||
decoration: InputDecoration(labelText: 'City'),
|
||||
),
|
||||
TextFormField(
|
||||
controller: _countryController,
|
||||
decoration: InputDecoration(labelText: 'Country'),
|
||||
readOnly: true,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<ContactDetail> _saveUpdateChanges() async {
|
||||
final updatedContact = ContactDetail(
|
||||
id: widget.contact.id,
|
||||
name: _nameController.text,
|
||||
email: _emailController.text,
|
||||
phone: _phoneController.text,
|
||||
image1920: widget.contact.image1920,
|
||||
street: _streetController.text,
|
||||
street2: _street2Controller.text,
|
||||
city: _cityController.text,
|
||||
country: _countryController.text,
|
||||
);
|
||||
final newUpdatedContacts = await widget.contactService.contactUpdateRequest(contact: updatedContact);
|
||||
return newUpdatedContacts;
|
||||
}
|
||||
}
|
||||
61
lib/contactService.dart
Normal file
61
lib/contactService.dart
Normal file
@@ -0,0 +1,61 @@
|
||||
import 'graphqlScript.dart';
|
||||
import 'model/contactDetail.dart';
|
||||
import 'model/contactCreation.dart';
|
||||
import 'config.dart';
|
||||
|
||||
|
||||
class ContactService{
|
||||
late GPLModel contactObj;
|
||||
|
||||
ContactService(String apikey) {
|
||||
contactObj = GPLModel('res.partner', DataEndpoint, apikey);
|
||||
}
|
||||
|
||||
Future<List<dynamic>> contactListRequest () async {
|
||||
const queryDict = {'id': null, 'name': null, 'email': null, 'phone': null, 'image_1920': null};
|
||||
final result = contactObj.search(domain: const [], querydict: queryDict);
|
||||
return await result;
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> contactDetailRequest ({required int id}) async {
|
||||
const queryDict = {'id': null, 'name': null, 'email': null, 'phone': null, 'street': null, 'street2': null, 'city':null,'country_id':{'name': null}, 'image_1920': null};
|
||||
final result = contactObj.browse(id: id, querydict: queryDict);
|
||||
return await result;
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> contactDeleteRequest ({required int id}) async {
|
||||
final result = contactObj.delete(id: id);
|
||||
return await result;
|
||||
}
|
||||
|
||||
Future<ContactDetail> contactUpdateRequest ({required ContactDetail contact}) async {
|
||||
final values = {
|
||||
'name': contact.name,
|
||||
'phone': contact.phone,
|
||||
'email': contact.email,
|
||||
'street': contact.street,
|
||||
'street2': contact.street2,
|
||||
'city': contact.city,
|
||||
};
|
||||
const queryDict = {'id': null, 'name': null, 'email': null, 'phone': null, 'street': null, 'street2': null, 'city':null,'country_id':{'name': null}, 'image_1920': null};
|
||||
final result = await contactObj.write(id: contact.id, values: values, querydict: queryDict);
|
||||
final ContactDetail updatedContact = ContactDetail.fromJson(result);
|
||||
return updatedContact;
|
||||
}
|
||||
|
||||
Future<ContactDetail> contactCreateRequest ({required ContactCreation contact}) async {
|
||||
final values = {
|
||||
'name': contact.name,
|
||||
'phone': contact.phone,
|
||||
'email': contact.email,
|
||||
'street': contact.street,
|
||||
'street2': contact.street2,
|
||||
'city': contact.city,
|
||||
'image_1920': contact.image1920,
|
||||
};
|
||||
const queryDict = {'id': null, 'name': null, 'email': null, 'phone': null, 'street': null, 'street2': null, 'city':null,'country_id':{'name': null}, 'image_1920': null};
|
||||
final result = await contactObj.create(values: values, querydict: queryDict);
|
||||
final ContactDetail createdContact = ContactDetail.fromJson(result);
|
||||
return createdContact;
|
||||
}
|
||||
}
|
||||
263
lib/graphqlScript.dart
Normal file
263
lib/graphqlScript.dart
Normal file
@@ -0,0 +1,263 @@
|
||||
import 'dart:convert';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
// final String API_URL = DataEndpoint;
|
||||
// final String API_KEY = '''Af+"E<y.-Rs<q9")|'%7A&@U.fU}JV~:''';
|
||||
|
||||
class SyncError implements Exception {
|
||||
final String message;
|
||||
|
||||
SyncError([this.message = 'Connection not established.']);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Sync Error: $message';
|
||||
}
|
||||
}
|
||||
|
||||
class EasyAPIClient {
|
||||
late String apiurl;
|
||||
late String apikey;
|
||||
late Map<String, String> headers;
|
||||
|
||||
EasyAPIClient(apiurl, apikey) {
|
||||
this.apiurl = apiurl;
|
||||
this.apikey = apikey;
|
||||
this.headers = {
|
||||
'x-api-key': apikey,
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
}
|
||||
|
||||
Future<http.Response> post(String query, [Map<String, dynamic> variables = const {}]) async {
|
||||
final allbodyparts = {'query': query, 'variables': variables};
|
||||
final body = Map.fromEntries(allbodyparts.entries.where((entry) => entry.value != null));
|
||||
return await http.post(Uri.parse(this.apiurl), headers: this.headers, body: jsonEncode(body));
|
||||
}
|
||||
}
|
||||
|
||||
String pyDictToGPLString(dynamic values, [int indent = 0]) {
|
||||
String formatValue(dynamic value, [int indent = 0]) {
|
||||
if (value is bool) {
|
||||
return value.toString().toLowerCase();
|
||||
} else if (value is String) {
|
||||
return '"$value"';
|
||||
} else if (value is List) {
|
||||
final items = value.map((item) => formatValue(item, indent)).join(', ');
|
||||
return '[$items]';
|
||||
} else if (value is Map) {
|
||||
final items = value.entries.map((entry) => ' ' * (indent + 1) + '${entry.key}: ${formatValue(entry.value, indent + 1)}').join(',\n');
|
||||
return '{\n$items\n${' ' * indent}}';
|
||||
} else {
|
||||
return value.toString();
|
||||
}
|
||||
}
|
||||
|
||||
final formattedValues = formatValue(values, indent);
|
||||
return formattedValues;
|
||||
}
|
||||
|
||||
String pyDictToGPLQueryString(Map<String, dynamic> querydict, [int indent = 1]) {
|
||||
String generateQueryString(Map<String, dynamic> querydict, [int indent = 1]) {
|
||||
var queryString = '';
|
||||
querydict.forEach((key, value) {
|
||||
if (value == null) {
|
||||
queryString += ' ' * indent + '$key\n';
|
||||
} else if (value is Map<String, dynamic>) {
|
||||
queryString += ' ' * indent + '$key {\n';
|
||||
queryString += generateQueryString(value, indent + 1);
|
||||
queryString += ' ' * indent + '}\n';
|
||||
} else {
|
||||
throw ArgumentError('Invalid querydict format');
|
||||
}
|
||||
});
|
||||
return queryString;
|
||||
}
|
||||
|
||||
final queryString = generateQueryString(querydict, indent);
|
||||
return '{\n$queryString${' ' * (indent - 1)}}';
|
||||
}
|
||||
|
||||
class GPLModel {
|
||||
late EasyAPIClient _syncapi;
|
||||
final String omodel;
|
||||
late String gpl_model;
|
||||
|
||||
GPLModel(this.omodel, String apiurl, String apikey) {
|
||||
if (omodel.isNotEmpty) {
|
||||
gpl_model = convertToCamelCase(omodel);
|
||||
_syncapi = EasyAPIClient(apiurl, apikey);
|
||||
}
|
||||
else {
|
||||
gpl_model = "";
|
||||
}
|
||||
}
|
||||
|
||||
String convertToCamelCase(String input) {
|
||||
List<String> parts = input.split('.');
|
||||
List<String> capitalizedParts = parts.map((part) {
|
||||
if (part.isNotEmpty) {
|
||||
return part[0].toUpperCase() + part.substring(1);
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}).toList();
|
||||
return capitalizedParts.join();
|
||||
}
|
||||
|
||||
|
||||
Future<Map<String, dynamic>> browse({required int id, Map<String, dynamic> querydict = const {'id': null}}) async {
|
||||
final gplquery = pyDictToGPLQueryString(querydict, 2);
|
||||
final browseBody = '''query MyQuery {
|
||||
$gpl_model(id: "$id") $gplquery
|
||||
}''';
|
||||
final response = await _syncapi.post(browseBody);
|
||||
final data = json.decode(response.body)['data'];
|
||||
return data[gpl_model][0];
|
||||
}
|
||||
|
||||
Future<List<dynamic>> search({List<List<dynamic>> domain = const [], String order = 'name', int limit = 80, int offset = 0, Map<String, dynamic> querydict = const {'id': null}}) async {
|
||||
final gplquery = pyDictToGPLQueryString(querydict, 2);
|
||||
final searchBody = '''query MyQuery(\$offset: Int, \$limit: Int, \$order: String, \$domain: [[Any]]) {
|
||||
$gpl_model(
|
||||
offset: \$offset
|
||||
limit: \$limit
|
||||
order: \$order
|
||||
domain: \$domain
|
||||
) $gplquery
|
||||
}''';
|
||||
final allVariables = {
|
||||
'domain': domain,
|
||||
'order': order,
|
||||
'limit': limit,
|
||||
'offset': offset,
|
||||
};
|
||||
final response = await _syncapi.post(searchBody, allVariables);
|
||||
final data = json.decode(response.body)['data'];
|
||||
return data[gpl_model];
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> create({Map<String, dynamic> values = const {}, Map<String, dynamic> querydict = const {'id': null}}) async {
|
||||
final gplvalues = pyDictToGPLString(values, 2);
|
||||
final gplquery = pyDictToGPLQueryString(querydict, 2);
|
||||
print(gpl_model);
|
||||
final createBody = '''mutation Create {
|
||||
create$gpl_model: $gpl_model(
|
||||
$gpl_model\Values: $gplvalues
|
||||
) $gplquery
|
||||
}''';
|
||||
final response = await _syncapi.post(createBody);
|
||||
final data = json.decode(response.body)['data'];
|
||||
return data['create$gpl_model'][0];
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> write({required int id, required Map<String, dynamic> values, Map<String, dynamic> querydict = const {'id': null}}) async {
|
||||
final gplvalues = pyDictToGPLString(values, 2);
|
||||
final gplquery = pyDictToGPLQueryString(querydict, 2);
|
||||
final writeBody = '''mutation Update {
|
||||
update$gpl_model: $gpl_model(
|
||||
id: $id,
|
||||
$gpl_model\Values: $gplvalues
|
||||
) $gplquery
|
||||
}''';
|
||||
final response = await _syncapi.post(writeBody);
|
||||
final data = json.decode(response.body)['data'];
|
||||
return data['update$gpl_model'][0];
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> delete({required int id, Map<String, dynamic> querydict = const {'id': null}}) async {
|
||||
final gplquery = pyDictToGPLQueryString(querydict, 2);
|
||||
final deleteBody = '''mutation Delete {
|
||||
delete$gpl_model: $gpl_model(
|
||||
id: $id
|
||||
) $gplquery
|
||||
}''';
|
||||
final response = await _syncapi.post(deleteBody);
|
||||
final data = json.decode(response.body);
|
||||
return data;
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> methodExecute(int id, String methodName, Map<String, dynamic> methodParameters) async {
|
||||
final methodExecuteBody = '''mutation Method {
|
||||
method$gpl_model: $gpl_model(
|
||||
id: $id,
|
||||
method_name: $methodName,
|
||||
method_parameters: ${pyDictToGPLString(methodParameters)}
|
||||
)
|
||||
}''';
|
||||
final response = await _syncapi.post(methodExecuteBody);
|
||||
final data = json.decode(response.body);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Future<dynamic> makeApiLoginRequest({required String username,required String password, required baseEndpoint, required authEndpoint}) async {
|
||||
Map<String, dynamic> jsonData = {
|
||||
'base_endpoint': baseEndpoint,
|
||||
};
|
||||
String basicAuth = 'Basic ' + base64Encode(utf8.encode('$username:$password'));
|
||||
Map<String, String> headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': basicAuth,
|
||||
};
|
||||
String jsonEncoded = json.encode(jsonData);
|
||||
try{
|
||||
var response = await http.post(
|
||||
Uri.parse(authEndpoint),
|
||||
headers: headers,
|
||||
body: jsonEncoded,
|
||||
);
|
||||
return response;
|
||||
} catch(e) {
|
||||
print(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// final GPLModel resObj = GPLModel('res.partner');
|
||||
|
||||
// void main() {
|
||||
|
||||
// // For normal query operation
|
||||
// var query_dict = {'id': null, 'name': null, 'country_id': {'id': null, 'name': null}, 'mobile': null};
|
||||
// // final result = resObj.search(domain: const [], querydict: query_dict);
|
||||
// final result = resObj.browse(1079, query_dict);
|
||||
|
||||
// // final create_values = {
|
||||
// // 'active': true,
|
||||
// // 'name': 'Ekika Corporation PVT LTD.',
|
||||
// // 'bank_ids': [
|
||||
// // [0, 0, {
|
||||
// // 'sequence': 10,
|
||||
// // 'bank_id': 2,
|
||||
// // 'acc_number': '1111111',
|
||||
// // 'allow_out_payment': true,
|
||||
// // 'acc_holder_name': false,
|
||||
// // }],
|
||||
// // [0, 0, {
|
||||
// // 'sequence': 10,
|
||||
// // 'bank_id': 3,
|
||||
// // 'acc_number': '9999999999999',
|
||||
// // 'allow_out_payment': true,
|
||||
// // 'acc_holder_name': false,
|
||||
// // }]
|
||||
// // ],
|
||||
// // 'city': 'Gandhinagar',
|
||||
// // 'zip': '382421',
|
||||
// // 'comment': '<h3>Comment Here</h3>',
|
||||
// // 'mobile': '888888888',
|
||||
// // };
|
||||
// // final result = resObj.create(values: create_values, querydict: query_dict);
|
||||
|
||||
// // final write_values = {
|
||||
// // 'name': 'Ekika Corporation',
|
||||
// // 'mobile': 111111111,
|
||||
// // };
|
||||
// // final result = resObj.write(1115, write_values,query_dict);
|
||||
|
||||
// // final result = resObj.delete(1116);
|
||||
// // final result = resObj.methodExecute(4, 'parameter_test_method', {'key_3': 'new_val_3', 'key_2': 'new_val_2', 'key_1': 'new_val_1'});
|
||||
// result.then((value) => print(value));
|
||||
// // You can call other functions here like resWriteOne.
|
||||
// }
|
||||
132
lib/homepage.dart
Normal file
132
lib/homepage.dart
Normal file
@@ -0,0 +1,132 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:odoo_contact_app/secureStorage.dart';
|
||||
import 'main.dart';
|
||||
import 'contactService.dart';
|
||||
import 'model/contactList.dart';
|
||||
import 'model/contactDetail.dart';
|
||||
import 'contactDetailPage.dart';
|
||||
import 'contactCreationPage.dart';
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
class HomePage extends StatefulWidget {
|
||||
final String apikey ;
|
||||
HomePage({Key? key, required this.apikey}) : super(key: key);
|
||||
|
||||
@override
|
||||
_HomePageState createState() => _HomePageState();
|
||||
}
|
||||
|
||||
class _HomePageState extends State<HomePage> {
|
||||
late ContactService contactService;
|
||||
late Future<List<ContactList>> contactResultFuture;
|
||||
|
||||
Future<List<ContactList>> getContactValues() async {
|
||||
final response = await contactService.contactListRequest();
|
||||
final List<ContactList>contactResult = response.map((json) => ContactList.fromJson(json)).toList();
|
||||
return contactResult;
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
contactService = ContactService(widget.apikey);
|
||||
contactResultFuture = getContactValues();
|
||||
}
|
||||
|
||||
getCircleAvatar(image) {
|
||||
if (image != null && image != false){
|
||||
Uint8List imageBytes = base64.decode(image);
|
||||
ImageProvider imageProvider = MemoryImage(imageBytes);
|
||||
return CircleAvatar(
|
||||
backgroundImage: imageProvider,
|
||||
);
|
||||
}
|
||||
else{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Contact List'),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text('Create'),
|
||||
onPressed: () async {
|
||||
// Save changes and navigate back
|
||||
_openContactCreationForm();
|
||||
},
|
||||
),
|
||||
TextButton(
|
||||
child: Text('Logout'),
|
||||
onPressed: () async {
|
||||
// Save changes and navigate back
|
||||
print('Create Clicked');
|
||||
_performLogoutOperation(context);
|
||||
},
|
||||
),
|
||||
]
|
||||
),
|
||||
body: FutureBuilder(
|
||||
future: contactResultFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return Center(child: CircularProgressIndicator());
|
||||
} else if (snapshot.hasError) {
|
||||
return Center(child: Text('Error: ${snapshot.error}'));
|
||||
} else {
|
||||
final contacts = snapshot.data!;
|
||||
return ListView.builder(
|
||||
itemCount: contacts.length,
|
||||
itemBuilder: (context, index) {
|
||||
final contact = contacts[index];
|
||||
return ListTile(
|
||||
leading: getCircleAvatar(contact.image1920),
|
||||
title: Text(contact.name),
|
||||
subtitle: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(contact.email.toString()),
|
||||
// Text(contact.phone),
|
||||
],
|
||||
),
|
||||
onTap: () async {
|
||||
contactService.contactDetailRequest(id: contact.id).then((value){
|
||||
final ContactDetail contactResult = ContactDetail.fromJson(value);
|
||||
Navigator.push(
|
||||
context, MaterialPageRoute(builder: (_) => ContatcDetailPage(contact: contactResult, contactService: contactService,)));
|
||||
});
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_openContactCreationForm() {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => ContactCreationPage(contactService: contactService,)),
|
||||
).then((newContact) {
|
||||
if (newContact != null) {
|
||||
setState(() {
|
||||
contactResultFuture = getContactValues(); // Add new contact to the list
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _performLogoutOperation(BuildContext context) async {
|
||||
await userCreds.deleteAll(); // Reset user credentials
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => LoginPage()),
|
||||
);
|
||||
}
|
||||
}
|
||||
152
lib/main.dart
Normal file
152
lib/main.dart
Normal file
@@ -0,0 +1,152 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:odoo_contact_app/config.dart';
|
||||
import 'secureStorage.dart';
|
||||
import 'graphqlScript.dart';
|
||||
import 'homepage.dart';
|
||||
import 'dart:convert';
|
||||
|
||||
|
||||
|
||||
void main() {
|
||||
runApp(MyApp());
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
home: LoginPage(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class LoginPage extends StatefulWidget {
|
||||
@override
|
||||
_LoginPageState createState() => _LoginPageState();
|
||||
}
|
||||
|
||||
class _LoginPageState extends State<LoginPage> {
|
||||
TextEditingController _loginFieldController = TextEditingController();
|
||||
TextEditingController _passwordFieldController = TextEditingController();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
appBar: AppBar(
|
||||
title: Text("Login Page"),
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 60.0),
|
||||
child: Center(
|
||||
child: Container(
|
||||
width: 200,
|
||||
height: 150,
|
||||
/*decoration: BoxDecoration(
|
||||
color: Colors.red,
|
||||
borderRadius: BorderRadius.circular(50.0)),*/
|
||||
child: Image.network('https://flutter.github.io/assets-for-api-docs/assets/widgets/owl-2.jpg')),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
//padding: const EdgeInsets.only(left:15.0,right: 15.0,top:0,bottom: 0),
|
||||
padding: EdgeInsets.symmetric(horizontal: 15),
|
||||
child: TextField(
|
||||
controller: _loginFieldController,
|
||||
decoration: InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
labelText: 'Login',
|
||||
hintText: 'Enter Login'),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 15.0, right: 15.0, top: 15, bottom: 0),
|
||||
//padding: EdgeInsets.symmetric(horizontal: 15),
|
||||
child: TextField(
|
||||
controller: _passwordFieldController,
|
||||
obscureText: true,
|
||||
decoration: InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
labelText: 'Password',
|
||||
hintText: 'Enter password'),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: (){
|
||||
//TODO FORGOT PASSWORD SCREEN GOES HERE
|
||||
},
|
||||
child: Text(
|
||||
'Forgot Password',
|
||||
style: TextStyle(color: Colors.blue, fontSize: 15),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
height: 50,
|
||||
width: 250,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue, borderRadius: BorderRadius.circular(20)),
|
||||
child: TextButton(
|
||||
onPressed: () async {
|
||||
final loginRequest = makeApiLoginRequest(
|
||||
username: _loginFieldController.text, password: _passwordFieldController.text,
|
||||
baseEndpoint: BaseEndpoint, authEndpoint: AuthEndpoint
|
||||
);
|
||||
// Retrieve API key from secure storage
|
||||
loginRequest.then((value){
|
||||
if (value.statusCode == 200) {
|
||||
final String apikey = json.decode(value.body)['your-api-key'];
|
||||
userCreds.write(key: 'api-key', value: apikey).then((val) {
|
||||
Navigator.pushReplacement(
|
||||
context, MaterialPageRoute(builder: (_) => HomePage(apikey: apikey)));
|
||||
},);
|
||||
}
|
||||
else {
|
||||
showAlertBox();
|
||||
}
|
||||
}).catchError((onError){
|
||||
showAlertBox();
|
||||
});
|
||||
},
|
||||
child: Text(
|
||||
'Login',
|
||||
style: TextStyle(color: Colors.white, fontSize: 25),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 130,
|
||||
),
|
||||
Text('New User? Create Account')
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
showAlertBox() {
|
||||
return showDialog(
|
||||
context: context,
|
||||
builder: (ctx) => AlertDialog(
|
||||
title: const Text("Invalid Credentials"),
|
||||
content: const Text("Provide correct login and password"),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(ctx).pop();
|
||||
},
|
||||
child: Container(
|
||||
color: Color.fromARGB(255, 247, 148, 0),
|
||||
padding: const EdgeInsets.all(14),
|
||||
child: const Text("Ok"),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
50
lib/model/contactCreation.dart
Normal file
50
lib/model/contactCreation.dart
Normal file
@@ -0,0 +1,50 @@
|
||||
class ContactCreation {
|
||||
final dynamic name;
|
||||
final dynamic email;
|
||||
final dynamic phone;
|
||||
final dynamic street;
|
||||
final dynamic street2;
|
||||
final dynamic city;
|
||||
final dynamic image1920;
|
||||
|
||||
ContactCreation({
|
||||
required this.name,
|
||||
required this.email,
|
||||
required this.phone,
|
||||
required this.street,
|
||||
required this.street2,
|
||||
required this.city,
|
||||
required this.image1920,
|
||||
});
|
||||
|
||||
factory ContactCreation.fromJson(Map<dynamic, dynamic> json) {
|
||||
getCountry(){
|
||||
if (json['country_id'] != null){
|
||||
return json['country_id']['name'];
|
||||
}
|
||||
else{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return ContactCreation(
|
||||
name: json['name'],
|
||||
email: json['email'],
|
||||
phone: json['phone'],
|
||||
street: json['street'],
|
||||
street2: json['street2'],
|
||||
city: json['city'],
|
||||
image1920: json['image_1920'],
|
||||
);
|
||||
}
|
||||
Map<dynamic, dynamic> toJson() {
|
||||
return {
|
||||
'name': name,
|
||||
'email': email,
|
||||
'phone': phone,
|
||||
'street': street,
|
||||
'street2': street2,
|
||||
'city': city,
|
||||
'image_1920': image1920,
|
||||
};
|
||||
}
|
||||
}
|
||||
58
lib/model/contactDetail.dart
Normal file
58
lib/model/contactDetail.dart
Normal file
@@ -0,0 +1,58 @@
|
||||
class ContactDetail {
|
||||
final int id;
|
||||
final dynamic name;
|
||||
final dynamic email;
|
||||
final dynamic phone;
|
||||
final dynamic image1920;
|
||||
final dynamic street;
|
||||
final dynamic street2;
|
||||
final dynamic city;
|
||||
final dynamic country;
|
||||
|
||||
ContactDetail({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.email,
|
||||
required this.phone,
|
||||
required this.image1920,
|
||||
required this.street,
|
||||
required this.street2,
|
||||
required this.city,
|
||||
required this.country,
|
||||
});
|
||||
|
||||
factory ContactDetail.fromJson(Map<dynamic, dynamic> json) {
|
||||
getCountry(){
|
||||
if (json['country_id'] != null){
|
||||
return json['country_id']['name'];
|
||||
}
|
||||
else{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return ContactDetail(
|
||||
id: json['id'],
|
||||
name: json['name'],
|
||||
email: json['email'],
|
||||
phone: json['phone'],
|
||||
image1920: json['image_1920'],
|
||||
street: json['street'],
|
||||
street2: json['street2'],
|
||||
city: json['city'],
|
||||
country: getCountry(),
|
||||
);
|
||||
}
|
||||
Map<dynamic, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'name': name,
|
||||
'email': email,
|
||||
'phone': phone,
|
||||
'image1920': image1920,
|
||||
'street': street,
|
||||
'street2': street2,
|
||||
'city': city,
|
||||
'country': country,
|
||||
};
|
||||
}
|
||||
}
|
||||
27
lib/model/contactList.dart
Normal file
27
lib/model/contactList.dart
Normal file
@@ -0,0 +1,27 @@
|
||||
class ContactList {
|
||||
final int id;
|
||||
final String name;
|
||||
final dynamic email;
|
||||
final dynamic image1920;
|
||||
|
||||
|
||||
ContactList({required this.id, required this.name, required this.email, required this.image1920});
|
||||
|
||||
factory ContactList.fromJson(Map<String, dynamic> json) {
|
||||
return ContactList(
|
||||
id: json['id'],
|
||||
name: json['name'],
|
||||
email: json['email'],
|
||||
image1920: json['image_1920'],
|
||||
);
|
||||
}
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'name': name,
|
||||
'email': email,
|
||||
'image1920': image1920,
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
8
lib/secureStorage.dart
Normal file
8
lib/secureStorage.dart
Normal file
@@ -0,0 +1,8 @@
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
|
||||
// Create storage
|
||||
final userCreds = new FlutterSecureStorage();
|
||||
|
||||
Future getUserCredsValues(String key) async {
|
||||
return await userCreds.read(key: key);
|
||||
}
|
||||
Reference in New Issue
Block a user