Skip to content

Commit 20dbfde

Browse files
committed
fisa
1 parent 7705f17 commit 20dbfde

3 files changed

Lines changed: 81 additions & 76 deletions

File tree

lib/tabs/json_processor_tab.dart

Lines changed: 44 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import 'dart:convert';
22
import 'dart:io';
33
import 'dart:async';
4-
import 'dart:typed_data';
54
import 'package:flutter/material.dart';
65
import 'package:file_picker/file_picker.dart';
76
import 'package:path_provider/path_provider.dart';
87
import 'package:path/path.dart' as path;
98
import 'package:http/http.dart' as http;
109
import 'package:html/parser.dart' as html;
10+
import 'package:permission_handler/permission_handler.dart'; // Import permission_handler
1111

1212
class JSONProcessorTab extends StatefulWidget {
1313
const JSONProcessorTab({super.key});
@@ -58,6 +58,7 @@ class _JSONProcessorTabState extends State<JSONProcessorTab> with SingleTickerPr
5858
}
5959

6060
void _addLog(String message, {LogType type = LogType.info}) {
61+
if (!mounted) return;
6162
setState(() {
6263
_logs.insert(0, LogEntry(
6364
message: message,
@@ -149,39 +150,13 @@ class _JSONProcessorTabState extends State<JSONProcessorTab> with SingleTickerPr
149150
}
150151
}
151152
} catch (e) {
152-
// Silent fail for single attempt
153+
_addLog('Error fetching UID for $link: $e', type: LogType.warning);
153154
}
154155

155156
return null;
156157
}
157158

158-
Future<String?> _extractUidWithRetry(String link, int recordIndex) async {
159-
const maxRetries = 3;
160-
161-
for (int attempt = 1; attempt <= maxRetries; attempt++) {
162-
_addLog('Record ${recordIndex + 1}: Attempt $attempt/$maxRetries', type: LogType.info);
163-
164-
try {
165-
final uid = await _extractUidSingleAttempt(link);
166-
if (uid != null) {
167-
_addLog('✓ Record ${recordIndex + 1}: UID found on attempt $attempt: $uid', type: LogType.success);
168-
return uid;
169-
}
170-
171-
if (attempt < maxRetries) {
172-
await Future.delayed(Duration(seconds: attempt * 1)); // Progressive delay
173-
}
174-
} catch (e) {
175-
_addLog('Record ${recordIndex + 1}: Attempt $attempt failed: $e', type: LogType.warning);
176-
if (attempt < maxRetries) {
177-
await Future.delayed(Duration(seconds: attempt * 1));
178-
}
179-
}
180-
}
181-
182-
_addLog('✗ Record ${recordIndex + 1}: Failed to extract UID after $maxRetries attempts', type: LogType.error);
183-
return null;
184-
}
159+
// REMOVED: _extractUidWithRetry function is no longer needed.
185160

186161
Future<Map<String, dynamic>?> _processRecordConcurrently(int index, Map<String, dynamic> record) async {
187162
final username = record['username']?.toString() ?? '';
@@ -191,13 +166,14 @@ class _JSONProcessorTabState extends State<JSONProcessorTab> with SingleTickerPr
191166
return record;
192167
}
193168

194-
_addLog('Record ${index + 1}: Starting UID extraction', type: LogType.info);
195-
final uid = await _extractUidWithRetry(username, index);
169+
_addLog('Record ${index + 1}: Starting UID extraction for $username', type: LogType.info);
170+
// MODIFIED: Call single attempt function directly.
171+
final uid = await _extractUidSingleAttempt(username);
196172

197173
if (uid != null) {
198174
final updatedRecord = Map<String, dynamic>.from(record);
199175
updatedRecord['username'] = uid;
200-
_addLog('✓ Record ${index + 1}: Successfully replaced with UID', type: LogType.success);
176+
_addLog('✓ Record ${index + 1}: Successfully replaced with UID: $uid', type: LogType.success);
201177
return updatedRecord;
202178
} else {
203179
_addLog('✗ Record ${index + 1}: Removing from final data (UID not found)', type: LogType.error);
@@ -304,39 +280,51 @@ class _JSONProcessorTabState extends State<JSONProcessorTab> with SingleTickerPr
304280
}
305281

306282
try {
307-
final String fileName = _fileName ?? 'processed_data';
308-
final String baseName = path.basenameWithoutExtension(fileName);
309-
final String newFileName = 'uid_$baseName.json';
310-
311-
Directory downloadsDir;
312-
313-
// Use the same approach that works in JSONToExcelTab
283+
// FIXED: Request permission before trying to save
314284
if (Platform.isAndroid) {
315-
downloadsDir = Directory('/storage/emulated/0/Download');
316-
if (!await downloadsDir.exists()) {
317-
downloadsDir = (await getExternalStorageDirectory())!;
285+
var status = await Permission.storage.status;
286+
if (!status.isGranted) {
287+
status = await Permission.storage.request();
288+
}
289+
if (!status.isGranted) {
290+
_addLog('Storage permission denied. Cannot save file.', type: LogType.error);
291+
ScaffoldMessenger.of(context).showSnackBar(
292+
const SnackBar(
293+
content: Text('✗ Storage permission is required to save files.'),
294+
backgroundColor: Colors.red,
295+
),
296+
);
297+
return;
318298
}
319-
} else {
320-
downloadsDir = (await getDownloadsDirectory())!;
321299
}
322-
323-
final Directory saveDir = Directory('${downloadsDir.path}/fb_saver');
324-
if (!await saveDir.exists()) {
325-
await saveDir.create(recursive: true);
300+
301+
// FIXED: Let the user pick the save location
302+
final String? outputDirectory = await FilePicker.platform.getDirectoryPath(
303+
dialogTitle: 'Please select where to save the file',
304+
);
305+
306+
if (outputDirectory == null) {
307+
// User cancelled the picker
308+
_addLog('Save operation cancelled by user.', type: LogType.warning);
309+
return;
326310
}
311+
312+
final String fileName = _fileName ?? 'processed_data';
313+
final String baseName = path.basenameWithoutExtension(fileName);
314+
final String newFileName = 'uid_$baseName.json';
327315

328-
final filePath = path.join(saveDir.path, newFileName);
316+
final filePath = path.join(outputDirectory, newFileName);
329317
final file = File(filePath);
330318

331319
await file.writeAsString(json.encode(_processedData));
332320

333321
// Verify file was created
334322
if (await file.exists()) {
335-
_addLog('File saved successfully: ${file.path}', type: LogType.success);
323+
_addLog('File saved successfully: $filePath', type: LogType.success);
336324

337325
ScaffoldMessenger.of(context).showSnackBar(
338326
SnackBar(
339-
content: Text('✓ File saved to: ${file.path}'),
327+
content: Text('✓ File saved to: $filePath'),
340328
backgroundColor: const Color(0xFF467731),
341329
behavior: SnackBarBehavior.floating,
342330
),
@@ -370,6 +358,10 @@ class _JSONProcessorTabState extends State<JSONProcessorTab> with SingleTickerPr
370358
});
371359
}
372360

361+
// --- OMITTED: The rest of the file (build method, etc.) is unchanged ---
362+
// --- Please use your existing build methods as they are correct ---
363+
// --- _buildStatItem, _buildLogCard, LogType, LogEntry, etc. are also unchanged ---
364+
373365
@override
374366
Widget build(BuildContext context) {
375367
return Padding(
@@ -611,4 +603,4 @@ class LogEntry {
611603
required this.timestamp,
612604
required this.type,
613605
});
614-
}
606+
}

lib/tabs/json_to_excel_tab.dart

Lines changed: 36 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ import 'dart:io';
33
import 'dart:typed_data';
44
import 'package:flutter/material.dart';
55
import 'package:file_picker/file_picker.dart';
6-
import 'package:path_provider/path_provider.dart';
76
import 'package:path/path.dart' as path;
87
import 'package:excel/excel.dart';
8+
import 'package:permission_handler/permission_handler.dart'; // Import permission_handler
9+
910

1011
class JSONToExcelTab extends StatefulWidget {
1112
const JSONToExcelTab({super.key});
@@ -19,6 +20,7 @@ class _JSONToExcelTabState extends State<JSONToExcelTab> {
1920
bool _isConverting = false;
2021

2122
void _showError(String message) {
23+
if (!mounted) return;
2224
ScaffoldMessenger.of(context).showSnackBar(
2325
SnackBar(
2426
content: Text(message),
@@ -28,6 +30,7 @@ class _JSONToExcelTabState extends State<JSONToExcelTab> {
2830
}
2931

3032
void _showSuccess(String message) {
33+
if (!mounted) return;
3134
ScaffoldMessenger.of(context).showSnackBar(
3235
SnackBar(
3336
content: Text(message),
@@ -84,10 +87,10 @@ class _JSONToExcelTabState extends State<JSONToExcelTab> {
8487

8588
// Add headers
8689
sheet.appendRow([
87-
TextCellValue('Username'),
88-
TextCellValue('Password'),
89-
TextCellValue('Authcode'),
90-
TextCellValue('Email'),
90+
const TextCellValue('Username'),
91+
const TextCellValue('Password'),
92+
const TextCellValue('Authcode'),
93+
const TextCellValue('Email'),
9194
]);
9295

9396
// Add data rows
@@ -101,27 +104,31 @@ class _JSONToExcelTabState extends State<JSONToExcelTab> {
101104
]);
102105
}
103106

104-
// Save Excel file
105-
final baseName = path.basenameWithoutExtension(_selectedJsonFile!.name);
106-
final fileName = '${baseName}.xlsx';
107-
108-
// Get the downloads directory - FIXED PATH
109-
Directory downloadsDir;
107+
// FIXED: Request storage permission
110108
if (Platform.isAndroid) {
111-
downloadsDir = Directory('/storage/emulated/0/Download');
112-
if (!await downloadsDir.exists()) {
113-
downloadsDir = (await getExternalStorageDirectory())!;
109+
var status = await Permission.storage.status;
110+
if (!status.isGranted) {
111+
status = await Permission.storage.request();
112+
}
113+
if (!status.isGranted) {
114+
_showError('Storage permission denied. Cannot save file.');
115+
return;
114116
}
115-
} else {
116-
downloadsDir = (await getDownloadsDirectory())!;
117117
}
118+
119+
// FIXED: Let user choose the save directory
120+
final String? outputDirectory = await FilePicker.platform.getDirectoryPath(
121+
dialogTitle: 'Please select where to save the Excel file',
122+
);
118123

119-
final Directory saveDir = Directory('${downloadsDir.path}/fb_saver');
120-
if (!await saveDir.exists()) {
121-
await saveDir.create(recursive: true);
124+
if (outputDirectory == null) {
125+
_showError('Save operation cancelled by user.');
126+
return; // User cancelled the picker
122127
}
123128

124-
final filePath = path.join(saveDir.path, fileName);
129+
final baseName = path.basenameWithoutExtension(_selectedJsonFile!.name);
130+
final fileName = '$baseName.xlsx';
131+
final filePath = path.join(outputDirectory, fileName);
125132
final file = File(filePath);
126133

127134
final excelBytes = excelFile.encode();
@@ -130,7 +137,7 @@ class _JSONToExcelTabState extends State<JSONToExcelTab> {
130137

131138
// Verify file was created
132139
if (await file.exists()) {
133-
_showSuccess('Converted and saved to ${saveDir.path}/$fileName');
140+
_showSuccess('Converted and saved to $filePath');
134141
} else {
135142
_showError('File was not created successfully');
136143
}
@@ -140,12 +147,17 @@ class _JSONToExcelTabState extends State<JSONToExcelTab> {
140147
} catch (e) {
141148
_showError('Failed to convert: $e');
142149
} finally {
143-
setState(() {
144-
_isConverting = false;
145-
});
150+
if (mounted) {
151+
setState(() {
152+
_isConverting = false;
153+
});
154+
}
146155
}
147156
}
148157

158+
// --- OMITTED: The build method is unchanged ---
159+
// --- Please use your existing build method as it is correct ---
160+
149161
@override
150162
Widget build(BuildContext context) {
151163
return Padding(

pubspec.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ dependencies:
3737
http: ^1.5.0
3838
html: ^0.15.6
3939
excel: ^4.0.6
40+
permission_handler: ^12.0.1
4041

4142
dev_dependencies:
4243
flutter_test:

0 commit comments

Comments
 (0)