@@ -123,17 +123,25 @@ class WSPRStation(
123123 if (audioInitializationResult.isFailure)
124124 {
125125 _stationState .value = WSPRStationState .Error (" Audio source initialization failed: ${audioInitializationResult.exceptionOrNull()?.message} " )
126- return audioInitializationResult
126+ return audioInitializationResult
127127 }
128128
129- // Start the main station operation loop
129+ // Single structured scope owns both the operation loop and the cycle info
130+ // updates. When stationOperationJob is cancelled (via stopStation()), both
131+ // child coroutines are cancelled together
130132 stationOperationJob = CoroutineScope (Dispatchers .IO + SupervisorJob ()).launch {
133+
134+ launch {
135+ while (isActive)
136+ {
137+ _cycleInformation .value = timingCoordinator.getCurrentCycleInformation()
138+ delay(CYCLE_INFORMATION_UPDATE_INTERVAL_MILLISECONDS )
139+ }
140+ }
141+
131142 executeStationOperationLoop()
132143 }
133144
134- // Start cycle information updates for UI
135- startCycleInformationUpdates()
136-
137145 _stationState .value = WSPRStationState .Running
138146 Result .success(Unit )
139147 }
@@ -389,22 +397,6 @@ class WSPRStation(
389397 )
390398 }
391399 }
392-
393- /* *
394- * Starts background updates for cycle information display.
395- * Updates cycle position and timing information every second for UI consumption.
396- */
397- private fun startCycleInformationUpdates ()
398- {
399- CoroutineScope (Dispatchers .IO ).launch {
400- while (stationOperationJob?.isActive == true )
401- {
402- _cycleInformation .value = timingCoordinator.getCurrentCycleInformation()
403- delay(CYCLE_INFORMATION_UPDATE_INTERVAL_MILLISECONDS )
404- }
405- }
406- }
407-
408400}
409401
410402/* *
0 commit comments