Skip to content

Commit 0c8bd94

Browse files
author
gopi2401
committed
add more
1 parent 7c6ae49 commit 0c8bd94

30 files changed

+1637
-382
lines changed

analysis_options.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77

88
# The following line activates a set of recommended lints for Flutter apps,
99
# packages, and plugins designed to encourage good coding practices.
10+
analyzer:
11+
errors:
12+
use_build_context_synchronously: ignore
1013
include: package:flutter_lints/flutter.yaml
1114

1215
linter:

lib/issues.dart

Lines changed: 96 additions & 167 deletions
Original file line numberDiff line numberDiff line change
@@ -1,135 +1,85 @@
1-
import 'dart:convert';
21
import 'dart:io';
2+
33
import 'package:flutter/material.dart';
4+
import 'package:get/get.dart';
45
import 'package:image_picker/image_picker.dart';
5-
import 'package:http/http.dart' as http;
6-
import 'package:path/path.dart' as path;
7-
import 'package:flutter_dotenv/flutter_dotenv.dart';
8-
import 'utils/appdata.dart';
6+
7+
import 'services/feedback_service.dart';
98

109
class IssueForm extends StatefulWidget {
1110
const IssueForm({super.key});
1211

1312
@override
14-
IssueFormState createState() => IssueFormState();
13+
State<IssueForm> createState() => _IssueFormState();
1514
}
1615

17-
class IssueFormState extends State<IssueForm> {
16+
class _IssueFormState extends State<IssueForm> {
1817
final TextEditingController _nameController = TextEditingController();
1918
final TextEditingController _titleController = TextEditingController();
2019
final TextEditingController _bodyController = TextEditingController();
21-
File? _imageFile;
2220
final ImagePicker _picker = ImagePicker();
23-
String option = 'Issue';
24-
bool isLoading = false;
2521

26-
Future<void> createIssue(
27-
String title, String body, String? label, File? imageFile) async {
28-
List<String> labels = label == 'Issue'
29-
? ['bug']
30-
: label == 'Suggestion'
31-
? ['ui']
32-
: ['bug'];
22+
File? _imageFile;
23+
bool isLoading = false;
24+
FeedbackCategory category = FeedbackCategory.issue;
3325

34-
String imageUrl = '';
35-
if (imageFile != null) {
36-
// Upload image to GitHub
37-
imageUrl = await uploadImageToGitHub(imageFile);
26+
FeedbackService get feedbackService {
27+
if (!Get.isRegistered<FeedbackService>()) {
28+
Get.put(FeedbackService(), permanent: true);
3829
}
30+
return FeedbackService.to;
31+
}
3932

40-
final issueBody = body +
41-
(imageUrl.isNotEmpty ? '\n\n<img src="$imageUrl" width=250/>' : '') +
42-
(_nameController.text.isNotEmpty
43-
? '\n\n$option raised by ${_nameController.text.trim()}.'
44-
: '');
45-
46-
final response = await http.post(
47-
Uri.parse('$githubApi/issues'),
48-
headers: {
49-
'Authorization': 'token ${dotenv.env['githubToken']}',
50-
'Accept': 'application/vnd.github.v3+json',
51-
'Content-Type': 'application/json',
52-
},
53-
body: jsonEncode({
54-
'title': title,
55-
'body': issueBody,
56-
'labels': labels, // Optional: Labels for the issue
57-
}),
58-
);
59-
60-
if (response.statusCode == 201) {
61-
// Success
62-
String issueUrl = jsonDecode(response.body)['html_url'];
33+
Future<void> pickImage() async {
34+
final picked = await _picker.pickImage(source: ImageSource.gallery);
35+
if (!mounted) return;
36+
setState(() {
37+
_imageFile = picked != null ? File(picked.path) : null;
38+
});
39+
}
6340

64-
// Clear inputs
41+
Future<void> _submit() async {
42+
final title = _titleController.text.trim();
43+
final body = _bodyController.text.trim();
44+
if (title.isEmpty || body.isEmpty) return;
45+
46+
setState(() => isLoading = true);
47+
try {
48+
final issueUrl = await feedbackService.createIssue(
49+
title: title,
50+
body: body,
51+
category: category,
52+
reporter: _nameController.text.trim(),
53+
screenshot: _imageFile,
54+
);
55+
if (!mounted) return;
6556
_nameController.clear();
6657
_titleController.clear();
6758
_bodyController.clear();
68-
setState(() {
69-
_imageFile = null;
70-
});
71-
72-
// Show success dialog
59+
setState(() => _imageFile = null);
7360
showDialog(
7461
context: context,
75-
builder: (BuildContext context) {
76-
return AlertDialog(
77-
title: const Text('Success'),
78-
content: Text('Issue created: $issueUrl'),
79-
actions: [
80-
TextButton(
81-
onPressed: () {
82-
Navigator.of(context).pop();
83-
},
84-
child: const Text('OK'),
85-
),
86-
],
87-
);
88-
},
62+
builder: (_) => AlertDialog(
63+
title: const Text('Success'),
64+
content: Text('Issue created: ${issueUrl ?? 'N/A'}'),
65+
actions: [
66+
TextButton(
67+
onPressed: () => Navigator.of(context).pop(),
68+
child: const Text('OK'),
69+
),
70+
],
71+
),
8972
);
90-
} else {
91-
// Error handling
92-
print('Failed to create issue: ${response.statusCode}');
93-
print(response.body);
94-
}
95-
}
96-
97-
Future<String> uploadImageToGitHub(File imageFile) async {
98-
final String base64Image = base64Encode(imageFile.readAsBytesSync());
99-
final String fileName = path.basename(imageFile.path);
100-
101-
final response = await http.put(
102-
Uri.parse('$githubApi/contents/$fileName'),
103-
headers: {
104-
'Authorization': 'token ${dotenv.env['githubToken']}',
105-
'Accept': 'application/vnd.github.v3+json',
106-
'Content-Type': 'application/json',
107-
},
108-
body: jsonEncode({
109-
'message': 'Uploading screenshot',
110-
'content': base64Image,
111-
'branch': 'assets',
112-
}),
113-
);
114-
115-
if (response.statusCode == 201) {
116-
return jsonDecode(response.body)['content']['download_url'];
117-
} else {
118-
print('Failed to upload image: ${response.statusCode}');
119-
return '';
120-
}
121-
}
122-
123-
Future<void> pickImage() async {
124-
final pickedFile = await _picker.pickImage(source: ImageSource.gallery);
125-
126-
setState(() {
127-
if (pickedFile != null) {
128-
_imageFile = File(pickedFile.path);
129-
} else {
130-
print('No image selected.');
73+
} catch (e) {
74+
if (!mounted) return;
75+
ScaffoldMessenger.of(context).showSnackBar(
76+
SnackBar(content: Text('Failed to submit feedback: $e')),
77+
);
78+
} finally {
79+
if (mounted) {
80+
setState(() => isLoading = false);
13181
}
132-
});
82+
}
13383
}
13484

13585
@override
@@ -143,41 +93,37 @@ class IssueFormState extends State<IssueForm> {
14393
@override
14494
Widget build(BuildContext context) {
14595
return Scaffold(
146-
appBar: AppBar(
147-
title: const Text('Feedback'),
148-
),
96+
appBar: AppBar(title: const Text('Feedback')),
14997
body: SingleChildScrollView(
150-
padding: const EdgeInsets.all(20.0),
98+
padding: const EdgeInsets.all(20),
15199
child: Column(
152100
crossAxisAlignment: CrossAxisAlignment.start,
153101
children: [
154102
const Text(
155-
'Choose the any option',
156-
style: TextStyle(
157-
fontSize: 18,
158-
fontWeight: FontWeight.bold,
159-
),
103+
'Choose an option',
104+
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
160105
),
161-
DropdownButton<String>(
162-
value: option, // Set the initial value
163-
items: <String>['Issue', 'Suggestion'].map((String value) {
164-
return DropdownMenuItem<String>(
165-
value: value,
166-
child: Text(value),
167-
);
168-
}).toList(),
106+
DropdownButton<FeedbackCategory>(
107+
value: category,
108+
items: const [
109+
DropdownMenuItem(
110+
value: FeedbackCategory.issue,
111+
child: Text('Issue'),
112+
),
113+
DropdownMenuItem(
114+
value: FeedbackCategory.suggestion,
115+
child: Text('Suggestion'),
116+
),
117+
],
169118
onChanged: (newValue) {
170-
setState(() {
171-
option = newValue!;
172-
});
119+
if (newValue == null) return;
120+
setState(() => category = newValue);
173121
},
174122
),
123+
const SizedBox(height: 10),
175124
const Text(
176125
'Your Name',
177-
style: TextStyle(
178-
fontSize: 18,
179-
fontWeight: FontWeight.bold,
180-
),
126+
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
181127
),
182128
TextField(
183129
controller: _nameController,
@@ -186,33 +132,28 @@ class IssueFormState extends State<IssueForm> {
186132
border: OutlineInputBorder(),
187133
),
188134
),
135+
const SizedBox(height: 20),
189136
Text(
190-
'$option Title',
191-
style: const TextStyle(
192-
fontSize: 18,
193-
fontWeight: FontWeight.bold,
194-
),
137+
'${category == FeedbackCategory.issue ? 'Issue' : 'Suggestion'} Title',
138+
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
195139
),
196140
TextField(
197141
controller: _titleController,
198-
decoration: InputDecoration(
199-
hintText: 'Enter ${option.toLowerCase()} title',
200-
border: const OutlineInputBorder(),
142+
decoration: const InputDecoration(
143+
hintText: 'Enter title',
144+
border: OutlineInputBorder(),
201145
),
202146
),
203147
const SizedBox(height: 20),
204148
Text(
205-
'$option Body',
206-
style: const TextStyle(
207-
fontSize: 18,
208-
fontWeight: FontWeight.bold,
209-
),
149+
'${category == FeedbackCategory.issue ? 'Issue' : 'Suggestion'} Body',
150+
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
210151
),
211152
TextField(
212153
controller: _bodyController,
213-
decoration: InputDecoration(
214-
hintText: 'Enter ${option.toLowerCase()} body',
215-
border: const OutlineInputBorder(),
154+
decoration: const InputDecoration(
155+
hintText: 'Enter details',
156+
border: OutlineInputBorder(),
216157
),
217158
maxLines: null,
218159
),
@@ -227,34 +168,22 @@ class IssueFormState extends State<IssueForm> {
227168
if (_imageFile != null)
228169
ClipRRect(
229170
borderRadius: BorderRadius.circular(5),
230-
child: Image.file(
231-
_imageFile!,
232-
height: 100,
233-
fit: BoxFit.cover,
234-
),
171+
child: Image.file(_imageFile!, height: 100, fit: BoxFit.cover),
235172
),
236173
],
237174
),
238175
const SizedBox(height: 20),
239176
ElevatedButton(
240-
onPressed: () async {
241-
setState(() {
242-
isLoading = true;
243-
});
244-
final title = _titleController.text;
245-
final body = _bodyController.text;
246-
if (title.isNotEmpty && body.isNotEmpty) {
247-
await createIssue(title, body, option, _imageFile);
248-
}
249-
setState(() {
250-
isLoading = false;
251-
});
252-
},
177+
onPressed: isLoading ? null : _submit,
253178
child: isLoading
254-
? const CircularProgressIndicator(
255-
color: Colors.white,
179+
? const SizedBox(
180+
width: 20,
181+
height: 20,
182+
child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white),
256183
)
257-
: Text('Send $option'),
184+
: Text(
185+
'Send ${category == FeedbackCategory.issue ? 'Issue' : 'Suggestion'}',
186+
),
258187
),
259188
],
260189
),

0 commit comments

Comments
 (0)