Skip to content

Commit e03b8f8

Browse files
author
code3-dev
committed
add per-app tunnel
1 parent 7c9f43d commit e03b8f8

File tree

7 files changed

+131
-151
lines changed

7 files changed

+131
-151
lines changed

android/app/build.gradle.kts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ android {
2626
// For more information, see: https://flutter.dev/to/review-gradle-config.
2727
minSdk = flutter.minSdkVersion
2828
targetSdk = flutter.targetSdkVersion
29-
versionCode = 16
30-
versionName = "2.4.0"
29+
versionCode = 17
30+
versionName = "2.5.0"
3131
}
3232

3333
buildTypes {

lib/models/app_update.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ class AppUpdate {
66
final String messText;
77

88
// Current app version - manually set
9-
static const String currentAppVersion = '2.4.0';
9+
static const String currentAppVersion = '2.5.0';
1010

1111
AppUpdate({required this.version, required this.url, required this.messText});
1212

lib/providers/v2ray_provider.dart

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -715,6 +715,28 @@ class V2RayProvider with ChangeNotifier, WidgetsBindingObserver {
715715
notifyListeners();
716716
}
717717

718+
// Add method to ping all servers
719+
Future<void> pingAllServers() async {
720+
if (_configs.isEmpty) return;
721+
722+
try {
723+
// Use the batch ping method from the service
724+
await _v2rayService.batchPingServers(
725+
_configs,
726+
maxConcurrent: 3,
727+
onProgress: (completed, total) {
728+
// Progress updates could be handled here if needed
729+
debugPrint('Ping progress: $completed/$total');
730+
},
731+
);
732+
733+
// Notify listeners to refresh the UI
734+
notifyListeners();
735+
} catch (e) {
736+
debugPrint('Error pinging all servers: $e');
737+
}
738+
}
739+
718740
// تغییر وضعیت بین حالت پروکسی و تونل
719741
void toggleProxyMode(bool isProxy) {
720742
_isProxyMode = isProxy;

lib/screens/about_screen.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ class AboutScreen extends StatelessWidget {
4848

4949
// App Version
5050
const Text(
51-
'Version 2.4.0',
51+
'Version 2.5.0',
5252
style: TextStyle(fontSize: 16, color: Colors.grey),
5353
),
5454

lib/screens/server_selection_screen.dart

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -684,6 +684,49 @@ class _ServerSelectionScreenState extends State<ServerSelectionScreen> {
684684
elevation: 0,
685685
actions: [
686686
...appBarActions,
687+
IconButton(
688+
icon: Icon(
689+
Icons.network_ping,
690+
color: _isPingingServers ? AppTheme.primaryGreen : null,
691+
),
692+
onPressed: _isPingingServers ? null : () async {
693+
setState(() {
694+
_isPingingServers = true;
695+
});
696+
697+
try {
698+
final provider = Provider.of<V2RayProvider>(context, listen: false);
699+
await provider.pingAllServers();
700+
701+
if (mounted) {
702+
ScaffoldMessenger.of(context).showSnackBar(
703+
const SnackBar(
704+
content: Text('Ping test completed!'),
705+
backgroundColor: Colors.green,
706+
duration: Duration(seconds: 2),
707+
),
708+
);
709+
}
710+
} catch (e) {
711+
if (mounted) {
712+
ScaffoldMessenger.of(context).showSnackBar(
713+
SnackBar(
714+
content: Text('Ping test failed: $e'),
715+
backgroundColor: Colors.red,
716+
duration: const Duration(seconds: 3),
717+
),
718+
);
719+
}
720+
} finally {
721+
if (mounted) {
722+
setState(() {
723+
_isPingingServers = false;
724+
});
725+
}
726+
}
727+
},
728+
tooltip: 'Ping All Servers',
729+
),
687730
if (_selectedFilter != 'Local')
688731
IconButton(
689732
icon: const Icon(Icons.refresh),

lib/services/v2ray_service.dart

Lines changed: 17 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -383,134 +383,29 @@ class V2RayService extends ChangeNotifier {
383383
}
384384
}
385385

386-
// Modern crash-proof ping handling with rate limiting and proper async patterns
387-
static final Map<String, Future<int?>> _ongoingPings = {};
388-
static const int _maxConcurrentPings = 5; // Limit concurrent pings
389-
static int _currentPingCount = 0;
390-
386+
/// Simple and reliable ping method similar to flutter_v2ray example
391387
Future<int?> getServerDelay(V2RayConfig config) async {
392-
return _safeExecute<int?>(
393-
operation: () => _performPingWithRateLimit(config),
394-
fallbackValue: null,
395-
context: 'getServerDelay for ${config.remark}',
396-
);
397-
}
398-
399-
Future<int?> _performPingWithRateLimit(V2RayConfig config) async {
400-
final configId = config.id;
401-
final hostKey = '${config.address}:${config.port}';
402-
403-
// Return cached result if available and not expired (5 minutes cache)
404-
final cachedTime = _pingCacheTime[hostKey];
405-
if (_pingCache.containsKey(hostKey) &&
406-
cachedTime != null &&
407-
DateTime.now().difference(cachedTime).inMinutes < 5) {
408-
return _pingCache[hostKey];
409-
}
410-
411-
// Check if ping is already in progress for this host
412-
if (_ongoingPings.containsKey(hostKey)) {
413-
try {
414-
return await _ongoingPings[hostKey]!.timeout(
415-
const Duration(seconds: 15),
416-
onTimeout: () => null,
417-
);
418-
} catch (e) {
419-
debugPrint('Error waiting for ongoing ping: $e');
420-
_ongoingPings.remove(hostKey);
421-
return null;
422-
}
423-
}
424-
425-
// Rate limiting - wait if too many concurrent pings
426-
while (_currentPingCount >= _maxConcurrentPings) {
427-
await Future.delayed(const Duration(milliseconds: 100));
428-
}
429-
430-
// Start new ping operation
431-
_currentPingCount++;
432-
final pingFuture = _executePingOperation(config, hostKey);
433-
_ongoingPings[hostKey] = pingFuture;
434-
435388
try {
436-
final result = await pingFuture;
437-
return result;
438-
} finally {
439-
_currentPingCount--;
440-
_ongoingPings.remove(hostKey);
441-
}
442-
}
443-
444-
Future<int?> _executePingOperation(V2RayConfig config, String hostKey) async {
445-
try {
446-
// Ensure V2Ray is initialized with timeout
447-
await _safeInitialize();
448-
449-
// Parse config with comprehensive error handling
450-
final parser = await _safeParseConfig(config);
451-
if (parser == null) {
452-
_updatePingCache(hostKey, null);
453-
return null;
454-
}
455-
456-
// Execute ping with multiple fallback strategies
457-
final delay = await _performPingWithFallback(parser, config);
389+
// Ensure V2Ray is initialized
390+
await initialize();
458391

459-
// Cache result with timestamp
460-
_updatePingCache(hostKey, delay);
461-
return delay;
392+
// Parse the configuration
393+
final parser = FlutterV2ray.parseFromURL(config.fullConfig);
462394

463-
} catch (e) {
464-
debugPrint('Ping operation failed for ${config.remark}: $e');
465-
_updatePingCache(hostKey, null);
466-
return null;
467-
}
468-
}
469-
470-
Future<int?> _performPingWithFallback(V2RayURL parser, V2RayConfig config) async {
471-
const maxAttempts = 2;
472-
473-
for (int attempt = 1; attempt <= maxAttempts; attempt++) {
474-
try {
475-
final delay = await _flutterV2ray.getServerDelay(
476-
config: parser.getFullConfiguration(),
477-
).timeout(
478-
Duration(seconds: 6 + attempt), // Progressive timeout
479-
onTimeout: () => throw TimeoutException('Ping timeout', Duration(seconds: 6 + attempt)),
480-
);
481-
482-
if (_isValidPing(delay)) {
483-
return delay;
484-
}
485-
486-
if (attempt < maxAttempts) {
487-
await Future.delayed(Duration(milliseconds: 500 * attempt));
488-
}
489-
} on TimeoutException catch (e) {
490-
debugPrint('Ping timeout attempt $attempt for ${config.remark}: $e');
491-
if (attempt == maxAttempts) return null;
492-
} catch (e) {
493-
debugPrint('Ping error attempt $attempt for ${config.remark}: $e');
494-
if (attempt == maxAttempts) return null;
495-
}
496-
}
497-
498-
return null;
499-
}
500-
501-
bool _isValidPing(int? delay) {
502-
return delay != null && delay >= 0 && delay < 30000; // Max 30 seconds
503-
}
504-
505-
void _updatePingCache(String hostKey, int? delay) {
506-
try {
507-
_pingCache[hostKey] = delay;
508-
_pingCacheTime[hostKey] = DateTime.now();
395+
// Get server delay with timeout
396+
final delay = await _flutterV2ray.getServerDelay(
397+
config: parser.getFullConfiguration(),
398+
).timeout(
399+
const Duration(seconds: 8),
400+
onTimeout: () => -1,
401+
);
402+
403+
// Return valid ping or null for invalid/timeout results
404+
return (delay >= 0) ? delay : null;
509405

510-
// Cleanup old cache entries to prevent memory leaks
511-
_cleanupPingCache();
512406
} catch (e) {
513-
debugPrint('Error updating ping cache: $e');
407+
debugPrint('Error getting server delay for ${config.remark}: $e');
408+
return null;
514409
}
515410
}
516411

@@ -1213,27 +1108,4 @@ class V2RayService extends ChangeNotifier {
12131108
return results;
12141109
}
12151110

1216-
/// Enhanced memory management
1217-
void _performMemoryCleanup() {
1218-
try {
1219-
// Cleanup ping cache and stale timestamps
1220-
_cleanupPingCache();
1221-
debugPrint('Memory cleanup completed');
1222-
} catch (e) {
1223-
debugPrint('Error during memory cleanup: $e');
1224-
}
1225-
}
1226-
1227-
/// Force cleanup all resources
1228-
void forceCleanup() {
1229-
try {
1230-
_currentPingCount = 0;
1231-
_ongoingPings.clear();
1232-
_performMemoryCleanup();
1233-
1234-
debugPrint('Force cleanup completed');
1235-
} catch (e) {
1236-
debugPrint('Error in force cleanup: $e');
1237-
}
1238-
}
12391111
}

lib/widgets/server_list_item.dart

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,26 @@ class _ServerListItemState extends State<ServerListItem> {
9797
),
9898
overflow: TextOverflow.ellipsis,
9999
),
100-
// Removed delay display as requested
100+
const SizedBox(height: 4),
101+
// Show ping information
102+
Row(
103+
children: [
104+
Icon(
105+
Icons.network_ping,
106+
size: 12,
107+
color: _getPingColor(),
108+
),
109+
const SizedBox(width: 4),
110+
Text(
111+
_getPingText(),
112+
style: TextStyle(
113+
fontSize: 12,
114+
color: _getPingColor(),
115+
fontWeight: FontWeight.w500,
116+
),
117+
),
118+
],
119+
),
101120
],
102121
),
103122
),
@@ -226,7 +245,31 @@ class _ServerListItemState extends State<ServerListItem> {
226245
}
227246
}
228247

229-
// Removed _getPingColor method
248+
Color _getPingColor() {
249+
if (_isLoadingPing) {
250+
return Colors.orange;
251+
}
252+
if (_ping == null) {
253+
return Colors.red;
254+
}
255+
if (_ping! < 2000) {
256+
return Colors.green;
257+
} else if (_ping! < 3000) {
258+
return Colors.orange;
259+
} else {
260+
return Colors.red;
261+
}
262+
}
263+
264+
String _getPingText() {
265+
if (_isLoadingPing) {
266+
return 'Testing...';
267+
}
268+
if (_ping == null) {
269+
return 'Timeout';
270+
}
271+
return '${_ping}ms';
272+
}
230273

231274
String _getSubscriptionName(BuildContext context) {
232275
final provider = Provider.of<V2RayProvider>(context, listen: false);

0 commit comments

Comments
 (0)