@@ -214,70 +214,98 @@ class _ServerSelectionScreenState extends State<ServerSelectionScreen> {
214214 );
215215
216216 final completer = Completer <V2RayConfig ?>();
217- _batchTimeoutTimer = Timer (const Duration (seconds: 20 ), () {
217+
218+ // Create a timeout that won't crash the app
219+ _batchTimeoutTimer? .cancel ();
220+ _batchTimeoutTimer = Timer (const Duration (seconds: 10 ), () {
218221 if (! completer.isCompleted) {
219- _autoConnectStatusStream.add (
220- 'Batch timeout reached, moving to next batch' ,
221- );
222+ debugPrint ('Batch timeout reached, moving to next batch' );
223+ _autoConnectStatusStream.add ('Batch timeout reached, trying next servers...' );
222224 completer.complete (null );
223225 }
224226 });
225227
226- final pingTasks =
227- currentBatch
228- .map ((config) => _processPingTask (config, completer))
229- .toList ();
230- selectedConfig = await completer.future;
231-
232- _batchTimeoutTimer? .cancel ();
233- _batchTimeoutTimer = null ;
228+ try {
229+ final pingTasks = currentBatch.map ((config) => _processPingTask (config, completer)).toList ();
230+ selectedConfig = await completer.future;
231+ _batchTimeoutTimer? .cancel ();
232+ } catch (e) {
233+ debugPrint ('Error in batch processing: $e ' );
234+ // Don't rethrow, just continue to next batch
235+ continue ;
236+ }
234237 }
235238
236- if (selectedConfig != null && mounted) {
239+ // Clean up timer
240+ _batchTimeoutTimer? .cancel ();
241+ _batchTimeoutTimer = null ;
242+
243+ if (! mounted) return ;
244+
245+ if (selectedConfig != null ) {
237246 _autoConnectStatusStream.add (
238247 'Connecting to ${selectedConfig .remark } (${_pings [selectedConfig .id ]}ms)' ,
239248 );
240- await widget.onConfigSelected (selectedConfig);
241- if (mounted && Navigator .of (context).canPop ()) {
242- Navigator .of (context).pop ();
243- Navigator .of (context).pop ();
249+ try {
250+ await widget.onConfigSelected (selectedConfig);
251+ if (mounted && Navigator .of (context).canPop ()) {
252+ Navigator .of (context).pop ();
253+ Navigator .of (context).pop ();
254+ }
255+ } catch (e) {
256+ debugPrint ('Error connecting to selected server: $e ' );
257+ if (mounted) {
258+ ScaffoldMessenger .of (context).showSnackBar (
259+ SnackBar (content: Text ('Failed to connect: ${e .toString ()}' )),
260+ );
261+ }
244262 }
245263 } else {
246- _autoConnectStatusStream. add ( 'No suitable server found' );
247- if (mounted && Navigator . of (context). canPop ()) {
248- Navigator .of (context).pop ();
249- ScaffoldMessenger .of (context).showSnackBar (
250- const SnackBar (
251- content : Text (
252- 'No server with valid ping found. Please try again.' ,
264+ if (mounted) {
265+ _autoConnectStatusStream. add ( 'No suitable server found' );
266+ if ( Navigator .of (context).canPop ()) {
267+ Navigator .of (context).pop ();
268+ ScaffoldMessenger . of (context). showSnackBar (
269+ const SnackBar (
270+ content : Text ( 'No server with valid ping found. Please try again.' ) ,
253271 ),
254- ),
255- );
272+ );
273+ }
256274 }
257275 }
258276 } catch (e) {
259277 debugPrint ('Error in auto connect algorithm: $e ' );
260278 if (mounted && Navigator .of (context).canPop ()) {
261279 Navigator .of (context).pop ();
262280 ScaffoldMessenger .of (context).showSnackBar (
263- SnackBar (
264- content: Text ('Error connecting to server: ${e .toString ()}' ),
265- ),
281+ SnackBar (content: Text ('Error during auto-select: ${e .toString ()}' )),
266282 );
267283 }
284+ } finally {
285+ // Ensure cleanup happens
286+ _batchTimeoutTimer? .cancel ();
287+ _batchTimeoutTimer = null ;
288+ _cancelAllPingTasks ();
268289 }
269290 }
270291
271292 Future <void > _processPingTask (
272293 V2RayConfig config,
273294 Completer <V2RayConfig ?> completer,
274295 ) async {
296+ if (! mounted || completer.isCompleted) return ;
297+
275298 try {
276- if (! mounted) return ;
277299 _autoConnectStatusStream.add ('Testing ${config .remark }...' );
278300
279301 // Add timeout to prevent hanging
280- final ping = await _pingServer (config);
302+ final ping = await _pingServer (config).timeout (
303+ const Duration (seconds: 5 ),
304+ onTimeout: () {
305+ debugPrint ('Ping timeout for server ${config .remark }' );
306+ return null ;
307+ },
308+ );
281309
282310 // Check if completer is already completed or widget is unmounted
283311 if (completer.isCompleted || ! mounted) return ;
@@ -291,15 +319,13 @@ class _ServerSelectionScreenState extends State<ServerSelectionScreen> {
291319 }
292320
293321 // If we found a server with ping (not -1, not 0), select it immediately
294- if (ping != null && ping > 0 ) { // Added upper limit check
295- if (mounted) {
322+ if (ping != null && ping > 0 ) {
323+ if (mounted && ! completer.isCompleted ) {
296324 _autoConnectStatusStream.add (
297325 '${config .remark } responded with ${ping }ms' ,
298326 );
299327 _cancelAllPingTasks ();
300- if (! completer.isCompleted) {
301- completer.complete (config);
302- }
328+ completer.complete (config);
303329 }
304330 } else {
305331 if (mounted) {
@@ -308,11 +334,7 @@ class _ServerSelectionScreenState extends State<ServerSelectionScreen> {
308334 }
309335 } catch (e) {
310336 debugPrint ('Error in _processPingTask for ${config .remark }: $e ' );
311- if (! completer.isCompleted && mounted) {
312- _autoConnectStatusStream.add (
313- 'Error testing ${config .remark }: ${e .toString ()}' ,
314- );
315- }
337+ // Don't complete the completer on error, let other servers try
316338 }
317339 }
318340
0 commit comments