Skip to content

Commit 12e7763

Browse files
committed
v3
1 parent e7a561d commit 12e7763

2 files changed

Lines changed: 242 additions & 25 deletions

File tree

lib/main.dart

Lines changed: 241 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@ import 'dart:io';
44
import 'dart:isolate';
55
import 'dart:typed_data';
66

7-
import 'package:excel/excel' hide Border;
7+
import 'package:excel/excel.dart' hide Border;
88
import 'package:file_picker/file_picker.dart';
99
import 'package:flutter/material.dart';
1010
import 'package:html/parser.dart' as html;
1111
import 'package:http/http.dart' as http;
1212
import 'package:path/path.dart' as path;
1313
import 'package:permission_handler/permission_handler.dart';
14+
import 'package:shared_preferences/shared_preferences.dart';
1415

1516
void main() {
1617
runApp(const FBDataManagerApp());
@@ -94,7 +95,7 @@ class _MainScreenState extends State<MainScreen> with SingleTickerProviderStateM
9495
@override
9596
void initState() {
9697
super.initState();
97-
_tabController = TabController(length: 2, vsync: this);
98+
_tabController = TabController(length: 3, vsync: this);
9899
}
99100

100101
@override
@@ -113,6 +114,7 @@ class _MainScreenState extends State<MainScreen> with SingleTickerProviderStateM
113114
tabs: const [
114115
Tab(text: 'JSON Processor'),
115116
Tab(text: 'JSON to Excel'),
117+
Tab(text: 'Settings'),
116118
],
117119
),
118120
),
@@ -121,6 +123,7 @@ class _MainScreenState extends State<MainScreen> with SingleTickerProviderStateM
121123
children: const [
122124
JSONProcessorTab(),
123125
JSONToExcelTab(),
126+
SettingsTab(),
124127
],
125128
),
126129
);
@@ -200,8 +203,8 @@ class _JSONProcessorTabState extends State<JSONProcessorTab> with SingleTickerPr
200203
int _failCount = 0;
201204
int _skippedCount = 0;
202205
Stopwatch _stopwatch = Stopwatch();
203-
final int _maxConcurrentIsolates = Platform.numberOfProcessors * 2; // Double the CPU cores
204-
final int _recordsPerBatch = 50; // Smaller batches for better load balancing
206+
int _maxConcurrentIsolates = Platform.numberOfProcessors * 2; // Double the CPU cores
207+
int _recordsPerBatch = 50; // Default batch size
205208

206209
@override
207210
void initState() {
@@ -279,7 +282,7 @@ class _JSONProcessorTabState extends State<JSONProcessorTab> with SingleTickerPr
279282
}
280283

281284
// Enhanced UID extraction with better patterns and fallbacks
282-
static Future<String?> _extractUidIsolate(String link) async {
285+
static Future<String?> _extractUid(String link) async {
283286
if (link.isEmpty) return null;
284287

285288
try {
@@ -410,7 +413,7 @@ class _JSONProcessorTabState extends State<JSONProcessorTab> with SingleTickerPr
410413
if (!username.contains('facebook.com') || RegExp(r'^\d+$').hasMatch(username)) {
411414
results.add(record);
412415
} else {
413-
final uid = await _extractUidIsolate(username);
416+
final uid = await _extractUid(username);
414417
if (uid != null) {
415418
final updatedRecord = Map<String, dynamic>.from(record);
416419
updatedRecord['username'] = uid;
@@ -471,6 +474,10 @@ class _JSONProcessorTabState extends State<JSONProcessorTab> with SingleTickerPr
471474
return;
472475
}
473476

477+
final prefs = await SharedPreferences.getInstance();
478+
final concurrentEnabled = prefs.getBool('concurrent_enabled') ?? true;
479+
_recordsPerBatch = prefs.getInt('batch_size') ?? 50;
480+
474481
setState(() {
475482
_isProcessing = true;
476483
_isProcessed = false;
@@ -482,12 +489,13 @@ class _JSONProcessorTabState extends State<JSONProcessorTab> with SingleTickerPr
482489
_stopwatch.reset();
483490
_stopwatch.start();
484491

485-
_addLog('🚀 Starting ULTRA-FAST concurrent processing...', type: LogType.info);
486-
_addLog('🎯 Processing ${_data.length} records using $_maxConcurrentIsolates concurrent workers', type: LogType.info);
492+
_addLog('🚀 Starting processing...', type: LogType.info);
493+
_addLog('🎯 Processing ${_data.length} records${concurrentEnabled ? ' concurrently with $_maxConcurrentIsolates workers' : ' sequentially'}', type: LogType.info);
487494

488495
try {
489-
// Use enhanced concurrent processing
490-
final results = await _processDataWithEnhancedConcurrency();
496+
final results = concurrentEnabled
497+
? await _processDataWithEnhancedConcurrency()
498+
: await _processDataSequentially();
491499

492500
_stopwatch.stop();
493501

@@ -500,7 +508,7 @@ class _JSONProcessorTabState extends State<JSONProcessorTab> with SingleTickerPr
500508
final totalProcessed = _successCount + _failCount + _skippedCount;
501509
final successRate = totalProcessed == 0 ? 0 : ((_successCount / totalProcessed) * 100);
502510

503-
_addLog('✅ CONCURRENT PROCESSING COMPLETED!', type: LogType.success);
511+
_addLog('✅ PROCESSING COMPLETED!', type: LogType.success);
504512
_addLog('⏱️ Processing time: ${_stopwatch.elapsed}', type: LogType.success);
505513
_addLog('📊 Records processed: $totalProcessed', type: LogType.info);
506514
_addLog('🎯 UIDs extracted: $_successCount', type: LogType.success);
@@ -521,6 +529,49 @@ class _JSONProcessorTabState extends State<JSONProcessorTab> with SingleTickerPr
521529
}
522530
}
523531

532+
Future<List<Map<String, dynamic>>> _processDataSequentially() async {
533+
final List<Map<String, dynamic>> results = [];
534+
int index = 0;
535+
536+
for (var record in _data) {
537+
index++;
538+
try {
539+
final username = record['username']?.toString() ?? '';
540+
541+
if (!username.contains('facebook.com') || RegExp(r'^\d+$').hasMatch(username)) {
542+
results.add(record);
543+
_skippedCount++;
544+
} else {
545+
final uid = await _extractUid(username);
546+
if (uid != null) {
547+
final updatedRecord = Map<String, dynamic>.from(record);
548+
updatedRecord['username'] = uid;
549+
results.add(updatedRecord);
550+
_successCount++;
551+
} else {
552+
results.add(record);
553+
_failCount++;
554+
}
555+
}
556+
} catch (e) {
557+
results.add(record);
558+
_failCount++;
559+
_addLog('Error processing record $index: $e', type: LogType.error);
560+
}
561+
562+
if (index % 10 == 0 && mounted) {
563+
setState(() {});
564+
_addLog('📦 Processed $index / ${_data.length} records', type: LogType.info);
565+
}
566+
}
567+
568+
if (mounted) {
569+
setState(() {});
570+
}
571+
572+
return results;
573+
}
574+
524575
Future<List<Map<String, dynamic>>> _processDataWithEnhancedConcurrency() async {
525576
final int totalRecords = _data.length;
526577
final int batchSize = _recordsPerBatch;
@@ -1008,7 +1059,7 @@ class LogEntry {
10081059
}
10091060

10101061
/* ---------------------------------------------------------------------------
1011-
JSON to Excel Tab (unchanged but included for completeness)
1062+
JSON to Excel Tab
10121063
--------------------------------------------------------------------------- */
10131064

10141065
class JSONToExcelTab extends StatefulWidget {
@@ -1059,6 +1110,14 @@ class _JSONToExcelTabState extends State<JSONToExcelTab> {
10591110
}
10601111
}
10611112

1113+
String _keyToDisplay(String key) {
1114+
return key
1115+
.replaceAll('_', ' ')
1116+
.split(' ')
1117+
.map((w) => w.isNotEmpty ? w[0].toUpperCase() + w.substring(1) : '')
1118+
.join(' ');
1119+
}
1120+
10621121
Future<void> _convertJsonToExcel() async {
10631122
if (_selectedJsonFile == null) {
10641123
_showError('Please select a JSON file first');
@@ -1083,24 +1142,25 @@ class _JSONToExcelTabState extends State<JSONToExcelTab> {
10831142
final content = utf8.decode(bytes);
10841143
final List<dynamic> data = jsonDecode(content);
10851144

1145+
final prefs = await SharedPreferences.getInstance();
1146+
final orderJson = prefs.getString('excel_column_order');
1147+
List<String> columnOrder = ['username', 'password', 'auth_code', 'email'];
1148+
if (orderJson != null) {
1149+
columnOrder = (json.decode(orderJson) as List).cast<String>();
1150+
}
1151+
10861152
var excelFile = Excel.createExcel();
10871153
Sheet sheet = excelFile['Sheet1'];
10881154

1089-
sheet.appendRow([
1090-
TextCellValue('Username'),
1091-
TextCellValue('Password'),
1092-
TextCellValue('Authcode'),
1093-
TextCellValue('Email'),
1094-
]);
1155+
sheet.appendRow(
1156+
columnOrder.map((key) => TextCellValue(_keyToDisplay(key))).toList(),
1157+
);
10951158

10961159
for (var row in data) {
10971160
final map = row as Map<String, dynamic>;
1098-
sheet.appendRow([
1099-
TextCellValue(map['username']?.toString() ?? ''),
1100-
TextCellValue(map['password']?.toString() ?? ''),
1101-
TextCellValue(map['tfa']?.toString() ?? ''),
1102-
TextCellValue(map['email']?.toString() ?? ''),
1103-
]);
1161+
sheet.appendRow(
1162+
columnOrder.map((key) => TextCellValue(map[key]?.toString() ?? '')).toList(),
1163+
);
11041164
}
11051165

11061166
if (Platform.isAndroid) {
@@ -1244,7 +1304,7 @@ class _JSONToExcelTabState extends State<JSONToExcelTab> {
12441304
' "email": "example@email.com",\n'
12451305
' "username": "facebook_link_or_uid",\n'
12461306
' "password": "password",\n'
1247-
' "tfa": "2fa_code"\n'
1307+
' "auth_code": "2fa_code"\n'
12481308
' }\n'
12491309
']',
12501310
style: TextStyle(fontFamily: 'Monospace', fontSize: 12),
@@ -1257,4 +1317,160 @@ class _JSONToExcelTabState extends State<JSONToExcelTab> {
12571317
),
12581318
);
12591319
}
1320+
}
1321+
1322+
/* ---------------------------------------------------------------------------
1323+
Settings Tab
1324+
--------------------------------------------------------------------------- */
1325+
1326+
class SettingsTab extends StatefulWidget {
1327+
const SettingsTab({super.key});
1328+
1329+
@override
1330+
State<SettingsTab> createState() => _SettingsTabState();
1331+
}
1332+
1333+
class _SettingsTabState extends State<SettingsTab> {
1334+
bool _concurrentEnabled = true;
1335+
TextEditingController _batchSizeController = TextEditingController();
1336+
List<String> _columnOrder = ['username', 'password', 'auth_code', 'email'];
1337+
bool _isLoading = true;
1338+
1339+
@override
1340+
void initState() {
1341+
super.initState();
1342+
_loadSettings();
1343+
}
1344+
1345+
Future<void> _loadSettings() async {
1346+
final prefs = await SharedPreferences.getInstance();
1347+
setState(() {
1348+
_concurrentEnabled = prefs.getBool('concurrent_enabled') ?? true;
1349+
int batchSize = prefs.getInt('batch_size') ?? 50;
1350+
_batchSizeController.text = batchSize.toString();
1351+
String? orderJson = prefs.getString('excel_column_order');
1352+
if (orderJson != null) {
1353+
_columnOrder = (json.decode(orderJson) as List).cast<String>();
1354+
}
1355+
_isLoading = false;
1356+
});
1357+
}
1358+
1359+
Future<void> _saveConcurrentSettings() async {
1360+
final prefs = await SharedPreferences.getInstance();
1361+
int? batchSize = int.tryParse(_batchSizeController.text);
1362+
if (batchSize == null || batchSize <= 0) {
1363+
ScaffoldMessenger.of(context).showSnackBar(
1364+
const SnackBar(content: Text('Invalid batch size')),
1365+
);
1366+
return;
1367+
}
1368+
await prefs.setBool('concurrent_enabled', _concurrentEnabled);
1369+
await prefs.setInt('batch_size', batchSize);
1370+
ScaffoldMessenger.of(context).showSnackBar(
1371+
const SnackBar(content: Text('Concurrent settings saved')),
1372+
);
1373+
}
1374+
1375+
Future<void> _saveColumnOrder() async {
1376+
final prefs = await SharedPreferences.getInstance();
1377+
await prefs.setString('excel_column_order', json.encode(_columnOrder));
1378+
ScaffoldMessenger.of(context).showSnackBar(
1379+
const SnackBar(content: Text('Column order saved')),
1380+
);
1381+
}
1382+
1383+
String _keyToDisplay(String key) {
1384+
return key
1385+
.replaceAll('_', ' ')
1386+
.split(' ')
1387+
.map((w) => w.isNotEmpty ? w[0].toUpperCase() + w.substring(1) : '')
1388+
.join(' ');
1389+
}
1390+
1391+
@override
1392+
Widget build(BuildContext context) {
1393+
if (_isLoading) {
1394+
return const Center(child: CircularProgressIndicator());
1395+
}
1396+
return Padding(
1397+
padding: const EdgeInsets.all(16.0),
1398+
child: ListView(
1399+
children: [
1400+
Card(
1401+
child: Padding(
1402+
padding: const EdgeInsets.all(16.0),
1403+
child: Column(
1404+
crossAxisAlignment: CrossAxisAlignment.start,
1405+
children: [
1406+
const Text(
1407+
'Concurrent Processing',
1408+
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
1409+
),
1410+
SwitchListTile(
1411+
title: const Text('Enable Concurrent Processing'),
1412+
value: _concurrentEnabled,
1413+
onChanged: (val) {
1414+
setState(() {
1415+
_concurrentEnabled = val;
1416+
});
1417+
},
1418+
),
1419+
TextField(
1420+
controller: _batchSizeController,
1421+
keyboardType: TextInputType.number,
1422+
decoration: const InputDecoration(labelText: 'Batch Size'),
1423+
),
1424+
const SizedBox(height: 16),
1425+
ElevatedButton(
1426+
onPressed: _saveConcurrentSettings,
1427+
child: const Text('Save Concurrent Settings'),
1428+
),
1429+
],
1430+
),
1431+
),
1432+
),
1433+
const SizedBox(height: 16),
1434+
Card(
1435+
child: Padding(
1436+
padding: const EdgeInsets.all(16.0),
1437+
child: Column(
1438+
crossAxisAlignment: CrossAxisAlignment.start,
1439+
children: [
1440+
const Text(
1441+
'Excel Column Order',
1442+
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
1443+
),
1444+
const SizedBox(height: 8),
1445+
const Text('Drag to reorder columns'),
1446+
ReorderableListView(
1447+
shrinkWrap: true,
1448+
onReorder: (oldIndex, newIndex) {
1449+
setState(() {
1450+
if (newIndex > oldIndex) newIndex--;
1451+
final item = _columnOrder.removeAt(oldIndex);
1452+
_columnOrder.insert(newIndex, item);
1453+
});
1454+
},
1455+
children: _columnOrder
1456+
.map((key) => ListTile(
1457+
key: ValueKey(key),
1458+
title: Text(_keyToDisplay(key)),
1459+
leading: const Icon(Icons.drag_handle),
1460+
))
1461+
.toList(),
1462+
),
1463+
const SizedBox(height: 16),
1464+
ElevatedButton(
1465+
onPressed: _saveColumnOrder,
1466+
child: const Text('Save Column Order'),
1467+
),
1468+
],
1469+
),
1470+
),
1471+
),
1472+
],
1473+
),
1474+
);
1475+
}
12601476
}

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+
shared_preferences: ^2.5.3
4041
permission_handler: ^12.0.1
4142

4243
dev_dependencies:

0 commit comments

Comments
 (0)