Skip to content

Commit 41bc854

Browse files
committed
Extract & Save Contacts feature
1 parent e2a5ff3 commit 41bc854

14 files changed

+804
-108
lines changed

android/app/src/main/AndroidManifest.xml

+3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
2+
<uses-permission android:name="android.permission.READ_CONTACTS"/>
3+
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
4+
25
<application
36
android:label="web_project"
47
android:name="${applicationName}"

assets/key.json

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"type": "service_account",
3+
"project_id": "lab6-402407",
4+
"private_key_id": "a0c1e50a847467ff61b3b708db6dea724ec16be7",
5+
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDDVWJ1Tcfy7n4+\nG0GDCAa+jOIM2LXjKc8CVJQNXq/sknUMc3/fYkZ1I+sMXXQCzYjrycGL5jR4dcO4\n3ARBQYTVvUPmApax6HJUkHMkgkm44C9NLOR8eKW650GQfowpnvdCpEkkES9YGld0\nmUMaZw1M99BmFwTVevRznbamIXZQcvZvRx3P9z/W7hvMORg93k6ul/nrkubilP2Y\nCH+E/ZFau+ketnpmfnj58F2584y/D17+l3uLbzTLcWFZKJJPXYWtiiWuQdxkiNW0\n9AwELRtCIYNyla8zFQMv0miRKf7xTkCvVbQXQ73NRjvbMTLKBnd4uJI95MubMeFm\nWGWHAhdPAgMBAAECggEAPklh5gnxcnO+acuDLmdGz0hZZMNN7KwOSK3zr/e7nXJu\nRJQn+HF8Cipz7zN1pNcpjBZ/0z6LCMZ0rOPrr2tXElkR56x3pS98FZ8iOEgbfgpu\nyus/yLkoD9tiOtM/mp4INoXwlMGuGSf9Lz8X+LlXsm1rVMKOVgypGpz1+y+8bJZN\nhKKwuYjmCcdWLPSLJaV3orqtDXxYGITpQl6qnFA+CA6Pl5LYRTvRt88QlE9+BzZk\nYJDesoRFZ1pmwcVWZbHhFK2EnCyLT/5a8D49b2TBTyKgYuJmpE3+IBh5Wc+FrQO0\ngrjnhOwhRH+UCCZxyEfZpriGSFTsW2zO10PBuwC8gQKBgQDhPIUinYVlCSRlNrP3\neHPJlEQcRdPayqTYKpTTJq62HfbtFL+8r7Gyp7U8uPjWvrxu262jj4NfXyleo3ya\nhHb+gSqgGGzg3ctkMyWfBrl/gqVit59yDVdUHEPwy+3wzu5QNQZgIBQgN5eE6OqK\nXEQDKLjV6xo1TwKiTEeVnnVlLwKBgQDeA0x8wAKUxlK3rtgbrD9DBikK5Zm9Kyh/\nsbWqB2Vw7a0mfuWRiijujgBTBBDBumVd9TrykvOZ5IeJoPfXnIsPaSZyFkuqpTjW\nlibAg86XtPNf/sSh26t5pBMLO2+ojQfX5ipz3dt6+6pY3rHYljaokFuyOAwRKRxg\n1l1vkLYn4QKBgQC0Z1ELpLePcX8hQmtrL6MuNf9H1fYWLHFUYubJKRaO7/kzc4cP\njnn56rITbOSCvEsZUAMIGo7S+Nmd37yR1r+oobSOfoHWqk+adg0QDsbNBsuJAiJH\nq7/isrEIUY7HbjcbLx1oKMl19JD798LEB5rCHP/O93wKbrphXE+J212UKQKBgD7o\nXGH03kZds86MYnvk4MATHMoTqO5rLjyQlFMraVkIX8nKpy4IIfUGk5zyR/U85cAr\n3pohfZkbojQjQlb76oNhXh1xp9sgmexj+3MNZhqikVugCwN6BSqgjSzfmJH/9Dr+\n4wSV1r0QVoJ8B5TBa5dz4Cetre2m4n2mAC++p37BAoGAEcpGx67DNr2G2+91CNDA\nF/eOkRcn7MvjGoS7D3giKT4tvABM+ToeDtL+dsl6pbn9Ch+fd9Mq0nAjTmgCzLka\nSOoMDGURys5A/UW7swTwWLiAN8KEsD6UYQA2Rw4iCOsfs8zelcF4T8l50aarEoKM\nGmRtsc/a7Cvy+yWr14rNa7g=\n-----END PRIVATE KEY-----\n",
6+
"client_email": "[email protected]",
7+
"client_id": "110685857023937735453",
8+
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
9+
"token_uri": "https://oauth2.googleapis.com/token",
10+
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
11+
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/jalsa-630%40lab6-402407.iam.gserviceaccount.com",
12+
"universe_domain": "googleapis.com"
13+
}
+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import 'dart:convert';
2+
import 'dart:io';
3+
4+
import 'package:get/get.dart';
5+
import 'package:flutter/material.dart';
6+
7+
import 'package:googleapis/vision/v1.dart';
8+
import 'package:flutter/services.dart';
9+
import 'package:googleapis_auth/auth_io.dart';
10+
11+
class ContactsViewModel extends GetxController {
12+
RxString image = ''.obs;
13+
RxBool isloading = false.obs;
14+
RxBool isPicSelected = false.obs;
15+
void picUploaded() {
16+
isPicSelected.value = true;
17+
}
18+
19+
void notUploaded() {
20+
isPicSelected.value = false;
21+
}
22+
23+
void uploadImage(String path) {
24+
image.value = path;
25+
}
26+
27+
//----------------------------------------------------------
28+
29+
//-----------------------------------------------------------
30+
Future<ServiceAccountCredentials> get _credentials async {
31+
String _file = await rootBundle.loadString('assets/key.json');
32+
return ServiceAccountCredentials.fromJson(_file);
33+
}
34+
35+
Future<AutoRefreshingAuthClient> get _client async {
36+
AutoRefreshingAuthClient _client = await clientViaServiceAccount(
37+
await _credentials, [VisionApi.cloudVisionScope]).then((c) => c);
38+
return _client;
39+
}
40+
//............................................................
41+
42+
Future<List<List<String?>>> search(String assetImagePath) async {
43+
try {
44+
File imageFile = File(assetImagePath);
45+
List<int> bytes = await imageFile.readAsBytes();
46+
47+
// Convert bytes to base64
48+
String base64Image = base64Encode(bytes);
49+
50+
// Convert bytes to base64
51+
52+
// Vision API call
53+
var _vision = VisionApi(await _client);
54+
var _api = _vision.images;
55+
var _response = await _api.annotate(BatchAnnotateImagesRequest.fromJson({
56+
"requests": [
57+
{
58+
"image": {
59+
"content": base64Image,
60+
},
61+
"features": [
62+
{"type": "DOCUMENT_TEXT_DETECTION"}
63+
],
64+
}
65+
]
66+
}));
67+
68+
// Check if the response is not null and contains a valid result
69+
if (_response != null &&
70+
_response.responses != null &&
71+
_response.responses!.isNotEmpty &&
72+
_response.responses![0].fullTextAnnotation != null) {
73+
List<String?> names = [];
74+
List<String?> numbers = [];
75+
76+
String? apiResponse = _response.responses![0].fullTextAnnotation!.text;
77+
List<String> namesList = apiResponse!.split('\n');
78+
79+
for (var annotation in namesList) {
80+
String? description = annotation;
81+
82+
// Check if the description contains alphabets (names)
83+
if (RegExp(r'[a-zA-Z]').hasMatch(description!)) {
84+
names.add(description);
85+
}
86+
// Check if the description contains numbers (phone numbers)
87+
else if (RegExp(r'\d').hasMatch(description)) {
88+
numbers.add(description);
89+
}
90+
}
91+
// if (names.length != numbers.length) {
92+
// Get.snackbar('Error', 'Please upload a clear image\nNames and Phone Numbers are not equal\nTry Again with a clear image');
93+
// return [];
94+
// } else {return [names, numbers];}
95+
return [names, numbers];
96+
} else {
97+
print("No valid text found in the image.");
98+
99+
Get.snackbar('Error', 'No valid text found in the image.');
100+
return [];
101+
}
102+
} catch (e) {
103+
print("Error during API call: $e");
104+
Get.snackbar('Error Occured', e.toString());
105+
return [];
106+
}
107+
}
108+
}

lib/design_course/home_design_course.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ class _DesignCourseHomeScreenState extends State<DesignCourseHomeScreen> {
159159
crossAxisAlignment: CrossAxisAlignment.start,
160160
children: <Widget>[
161161
Text(
162-
'Popular Course',
162+
'Popular Events',
163163
textAlign: TextAlign.left,
164164
style: TextStyle(
165165
fontWeight: FontWeight.w600,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
2+
import 'package:flutter/material.dart';
3+
import 'package:flutter_contacts/flutter_contacts.dart';
4+
class ExtractedContacts extends StatefulWidget {
5+
6+
List<List<String?>> list;
7+
ExtractedContacts({Key? key, required this.list}) : super(key: key);
8+
9+
@override
10+
State<ExtractedContacts> createState() => _ExtractedContactsState();
11+
}
12+
13+
class _ExtractedContactsState extends State<ExtractedContacts> {
14+
Future<void> saveToPhone(list) async {
15+
if (await FlutterContacts.requestPermission()) {
16+
for (int i = 0; i < list[0].length; i++) {
17+
final newContact = Contact()
18+
..name.first = list[0][i]
19+
..phones = [Phone(list[1][i])];
20+
await newContact.insert();
21+
}
22+
print('Done with it');
23+
}
24+
}
25+
26+
String newValue = '';
27+
28+
@override
29+
Widget build(BuildContext context) {
30+
return Scaffold(
31+
appBar: AppBar(
32+
title: Text('CheckList',style: TextStyle(color: Colors.brown,fontWeight: FontWeight.bold),),
33+
actions: [
34+
TextButton(
35+
onPressed: () {
36+
showDialog(
37+
context: context,
38+
builder: (context) {
39+
return AlertDialog(
40+
content: Text(
41+
'Are you sure you want to Save the Cantacts !!'),
42+
actions: [
43+
TextButton(
44+
onPressed: () {
45+
Navigator.pop(context);
46+
},
47+
child: Text(
48+
'Cancel',
49+
style: TextStyle(color: Colors.white),
50+
),
51+
style: TextButton.styleFrom(
52+
backgroundColor: Colors.grey),
53+
),
54+
TextButton(
55+
style: TextButton.styleFrom(
56+
backgroundColor: Colors.brown),
57+
onPressed: () {
58+
saveToPhone(widget.list);
59+
Navigator.pop(context);
60+
},
61+
child: Text(
62+
'Save',
63+
style: TextStyle(color: Colors.white),
64+
)),
65+
],
66+
);
67+
});
68+
},
69+
child: Card(
70+
elevation: 5,
71+
color: Colors.brown,
72+
child: Container(
73+
height: 70,
74+
width: 80,
75+
child: Center(
76+
child: Text(
77+
'Save',
78+
style: TextStyle(color: Colors.white,fontWeight: FontWeight.bold,fontSize: 20),
79+
),
80+
),
81+
82+
),
83+
)),
84+
],
85+
),
86+
floatingActionButton: FloatingActionButton(
87+
backgroundColor: Colors.brown,
88+
child: Padding(
89+
padding: const EdgeInsets.all(8.0),
90+
child: Text(
91+
'Add Prefix',
92+
style: const TextStyle(color: Colors.white,fontWeight: FontWeight.bold,fontSize: 10),
93+
),
94+
),
95+
onPressed: () {
96+
showDialog(
97+
context: context,
98+
builder: (context) {
99+
return AlertDialog(
100+
title: Text("Insert Prefix"),
101+
content: Column(
102+
mainAxisSize: MainAxisSize.min,
103+
children: [
104+
TextField(
105+
decoration:
106+
InputDecoration(hintText: "Add Prefix to Name"),
107+
onChanged: (value) {
108+
setState(() {
109+
newValue = value;
110+
});
111+
},
112+
),
113+
],
114+
),
115+
actions: [
116+
TextButton(
117+
onPressed: () {
118+
Navigator.pop(context);
119+
},
120+
child: Text("Cancel")),
121+
TextButton(
122+
onPressed: () {
123+
for (int i = 0; i < widget.list[0].length; i++) {
124+
widget.list[0][i] =
125+
newValue + ' ' + widget.list[0][i]!;
126+
}
127+
setState(() {});
128+
Navigator.pop(context);
129+
},
130+
child: Text("Save")),
131+
],
132+
);
133+
});
134+
},
135+
),
136+
body: ListView.builder(
137+
itemCount: widget.list[0].length,
138+
itemBuilder: (context, index) {
139+
return Padding(
140+
padding: const EdgeInsets.all(8.0),
141+
child: Container(
142+
decoration: BoxDecoration(
143+
border: Border.all(color: Colors.grey),
144+
borderRadius: BorderRadius.circular(10),
145+
),
146+
child: ListTile(
147+
title: Text(widget.list[0][index]!),
148+
subtitle: Text(widget.list[1][index]!),
149+
trailing: Row(
150+
mainAxisSize: MainAxisSize.min,
151+
children: [
152+
IconButton(
153+
onPressed: () {
154+
showDialog(
155+
context: context,
156+
builder: (context) {
157+
return AlertDialog(
158+
title: Text("Edit Contact"),
159+
content: Column(
160+
mainAxisSize: MainAxisSize.min,
161+
children: [
162+
TextField(
163+
controller: TextEditingController(
164+
text: widget.list[0][index]),
165+
decoration: InputDecoration(
166+
hintText: "Enter Name"),
167+
onChanged: (value) {
168+
setState(() {
169+
widget.list[0][index] = value;
170+
});
171+
},
172+
),
173+
TextField(
174+
controller: TextEditingController(
175+
text: widget.list[1][index]),
176+
decoration: InputDecoration(
177+
hintText: "Enter Number"),
178+
onChanged: (value) {
179+
setState(() {
180+
widget.list[1][index] = value;
181+
});
182+
},
183+
),
184+
],
185+
),
186+
actions: [
187+
TextButton(
188+
onPressed: () {
189+
Navigator.pop(context);
190+
},
191+
child: Text("Cancel")),
192+
TextButton(
193+
onPressed: () {
194+
Navigator.pop(context);
195+
},
196+
child: Text("Save")),
197+
],
198+
);
199+
});
200+
},
201+
icon: Icon(Icons.edit),
202+
),
203+
IconButton(
204+
onPressed: () {
205+
setState(() {
206+
widget.list[0].removeAt(index);
207+
widget.list[1].removeAt(index);
208+
});
209+
},
210+
icon: Icon(Icons.delete),
211+
),
212+
],
213+
)),
214+
),
215+
);
216+
},
217+
));
218+
}
219+
}

0 commit comments

Comments
 (0)