@@ -4,13 +4,14 @@ import 'dart:io';
44import 'dart:isolate' ;
55import 'dart:typed_data' ;
66
7- import 'package:excel/excel' hide Border;
7+ import 'package:excel/excel.dart ' hide Border;
88import 'package:file_picker/file_picker.dart' ;
99import 'package:flutter/material.dart' ;
1010import 'package:html/parser.dart' as html;
1111import 'package:http/http.dart' as http;
1212import 'package:path/path.dart' as path;
1313import 'package:permission_handler/permission_handler.dart' ;
14+ import 'package:shared_preferences/shared_preferences.dart' ;
1415
1516void 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
10141065class 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}
0 commit comments