@@ -5,8 +5,11 @@ import android.net.Uri
5
5
import androidx.compose.animation.animateContentSize
6
6
import androidx.compose.animation.core.animateFloatAsState
7
7
import androidx.compose.animation.core.tween
8
+ import androidx.compose.foundation.ScrollState
8
9
import androidx.compose.foundation.layout.Arrangement
9
10
import androidx.compose.foundation.layout.Column
11
+ import androidx.compose.foundation.layout.ColumnScope
12
+ import androidx.compose.foundation.layout.PaddingValues
10
13
import androidx.compose.foundation.layout.Spacer
11
14
import androidx.compose.foundation.layout.defaultMinSize
12
15
import androidx.compose.foundation.layout.fillMaxHeight
@@ -158,7 +161,6 @@ fun Connect(navigator: DestinationsNavigator) {
158
161
)
159
162
}
160
163
161
- @Suppress(" LongMethod" )
162
164
@Composable
163
165
fun ConnectScreen (
164
166
state : ConnectUiState ,
@@ -175,15 +177,6 @@ fun ConnectScreen(
175
177
) {
176
178
177
179
val scrollState = rememberScrollState()
178
- var lastConnectionActionTimestamp by remember { mutableLongStateOf(0L ) }
179
-
180
- fun handleThrottledAction (action : () -> Unit ) {
181
- val currentTime = System .currentTimeMillis()
182
- if ((currentTime - lastConnectionActionTimestamp) > CONNECT_BUTTON_THROTTLE_MILLIS ) {
183
- lastConnectionActionTimestamp = currentTime
184
- action.invoke()
185
- }
186
- }
187
180
188
181
ScaffoldWithTopBarAndDeviceName (
189
182
topBarColor = state.tunnelUiState.topBarColor(),
@@ -195,45 +188,11 @@ fun ConnectScreen(
195
188
) {
196
189
var progressIndicatorBias by remember { mutableFloatStateOf(0f ) }
197
190
198
- // Distance to marker when secure/unsecure
199
- val baseZoom =
200
- animateFloatAsState(
201
- targetValue =
202
- if (state.tunnelRealState is TunnelState .Connected ) SECURE_ZOOM
203
- else UNSECURE_ZOOM ,
204
- animationSpec = tween(SECURE_ZOOM_ANIMATION_MILLIS ),
205
- label = " baseZoom"
206
- )
207
-
208
- val markers =
209
- state.tunnelRealState.toMarker(state.location)?.let { listOf (it) } ? : emptyList()
210
-
211
- AnimatedMap (
212
- modifier = Modifier .padding(top = it.calculateTopPadding()),
213
- cameraLocation = state.location?.toLatLong() ? : fallbackLatLong,
214
- cameraBaseZoom = baseZoom.value,
215
- cameraVerticalBias = progressIndicatorBias,
216
- markers = markers,
217
- globeColors =
218
- GlobeColors (
219
- landColor = MaterialTheme .colorScheme.primary,
220
- oceanColor = MaterialTheme .colorScheme.secondary,
221
- )
222
- )
223
-
224
- Column (
225
- verticalArrangement = Arrangement .Bottom ,
226
- horizontalAlignment = Alignment .Start ,
227
- modifier =
228
- Modifier .animateContentSize()
229
- .padding(top = it.calculateTopPadding())
230
- .fillMaxHeight()
231
- .drawVerticalScrollbar(
232
- scrollState,
233
- color = MaterialTheme .colorScheme.onPrimary.copy(alpha = AlphaScrollbar )
234
- )
235
- .verticalScroll(scrollState)
236
- .testTag(SCROLLABLE_COLUMN_TEST_TAG )
191
+ MapColumn (
192
+ state,
193
+ it,
194
+ progressIndicatorBias,
195
+ scrollState,
237
196
) {
238
197
Spacer (modifier = Modifier .defaultMinSize(minHeight = Dimens .mediumPadding).weight(1f ))
239
198
MullvadCircularProgressIndicatorLarge (
@@ -260,66 +219,19 @@ fun ConnectScreen(
260
219
}
261
220
)
262
221
Spacer (modifier = Modifier .defaultMinSize(minHeight = Dimens .mediumPadding).weight(1f ))
263
- ConnectionStatusText (
264
- state = state.tunnelRealState,
265
- modifier = Modifier .padding(horizontal = Dimens .sideMargin)
266
- )
267
- Text (
268
- text = state.location?.country ? : " " ,
269
- style = MaterialTheme .typography.headlineLarge,
270
- color = MaterialTheme .colorScheme.onPrimary,
271
- modifier = Modifier .padding(horizontal = Dimens .sideMargin)
272
- )
273
- Text (
274
- text = state.location?.city ? : " " ,
275
- style = MaterialTheme .typography.headlineLarge,
276
- color = MaterialTheme .colorScheme.onPrimary,
277
- modifier = Modifier .padding(horizontal = Dimens .sideMargin)
278
- )
279
- var expanded by rememberSaveable { mutableStateOf(false ) }
280
- LocationInfo (
281
- onToggleTunnelInfo = { expanded = ! expanded },
282
- isVisible = state.showLocationInfo,
283
- isExpanded = expanded,
284
- location = state.location,
285
- inAddress = state.inAddress,
286
- outAddress = state.outAddress,
287
- modifier =
288
- Modifier .fillMaxWidth()
289
- .padding(horizontal = Dimens .sideMargin)
290
- .testTag(LOCATION_INFO_TEST_TAG )
291
- )
292
- Spacer (modifier = Modifier .height(Dimens .buttonSpacing))
293
- SwitchLocationButton (
294
- modifier =
295
- Modifier .fillMaxWidth()
296
- .padding(horizontal = Dimens .sideMargin)
297
- .testTag(SELECT_LOCATION_BUTTON_TEST_TAG ),
298
- onClick = onSwitchLocationClick,
299
- showChevron = state.showLocation,
300
- text =
301
- if (state.showLocation && state.selectedRelayItem != null ) {
302
- state.selectedRelayItem.locationName
303
- } else {
304
- stringResource(id = R .string.switch_location)
305
- }
306
- )
222
+
223
+ ConnectionInfo (state = state)
224
+
307
225
Spacer (modifier = Modifier .height(Dimens .buttonSpacing))
308
- ConnectionButton (
309
- state = state.tunnelUiState,
310
- modifier =
311
- Modifier .padding(horizontal = Dimens .sideMargin)
312
- .padding(bottom = Dimens .screenVerticalMargin)
313
- .testTag(CONNECT_BUTTON_TEST_TAG ),
314
- disconnectClick = onDisconnectClick,
315
- reconnectClick = { handleThrottledAction(onReconnectClick) },
316
- cancelClick = onCancelClick,
317
- connectClick = { handleThrottledAction(onConnectClick) },
318
- reconnectButtonTestTag = RECONNECT_BUTTON_TEST_TAG
226
+
227
+ ButtonPanel (
228
+ state,
229
+ onSwitchLocationClick,
230
+ onDisconnectClick,
231
+ onReconnectClick,
232
+ onCancelClick,
233
+ onConnectClick,
319
234
)
320
- // We need to manually add this padding so we align size with the map
321
- // component and marker with the progress indicator.
322
- Spacer (modifier = Modifier .height(it.calculateBottomPadding()))
323
235
}
324
236
325
237
NotificationBanner (
@@ -333,6 +245,141 @@ fun ConnectScreen(
333
245
}
334
246
}
335
247
248
+ @Composable
249
+ private fun MapColumn (
250
+ state : ConnectUiState ,
251
+ it : PaddingValues ,
252
+ progressIndicatorBias : Float ,
253
+ scrollState : ScrollState ,
254
+ content : @Composable ColumnScope .() -> Unit
255
+ ) {
256
+
257
+ // Distance to marker when secure/unsecure
258
+ val baseZoom =
259
+ animateFloatAsState(
260
+ targetValue =
261
+ if (state.tunnelRealState is TunnelState .Connected ) SECURE_ZOOM else UNSECURE_ZOOM ,
262
+ animationSpec = tween(SECURE_ZOOM_ANIMATION_MILLIS ),
263
+ label = " baseZoom"
264
+ )
265
+
266
+ val markers = state.tunnelRealState.toMarker(state.location)?.let { listOf (it) } ? : emptyList()
267
+
268
+ AnimatedMap (
269
+ modifier = Modifier .padding(top = it.calculateTopPadding()),
270
+ cameraLocation = state.location?.toLatLong() ? : fallbackLatLong,
271
+ cameraBaseZoom = baseZoom.value,
272
+ cameraVerticalBias = progressIndicatorBias,
273
+ markers = markers,
274
+ globeColors =
275
+ GlobeColors (
276
+ landColor = MaterialTheme .colorScheme.primary,
277
+ oceanColor = MaterialTheme .colorScheme.secondary,
278
+ )
279
+ )
280
+
281
+ Column (
282
+ verticalArrangement = Arrangement .Bottom ,
283
+ horizontalAlignment = Alignment .Start ,
284
+ modifier =
285
+ Modifier .animateContentSize()
286
+ .padding(top = it.calculateTopPadding())
287
+ .fillMaxHeight()
288
+ .drawVerticalScrollbar(
289
+ scrollState,
290
+ color = MaterialTheme .colorScheme.onPrimary.copy(alpha = AlphaScrollbar )
291
+ )
292
+ .verticalScroll(scrollState)
293
+ .testTag(SCROLLABLE_COLUMN_TEST_TAG )
294
+ ) {
295
+ content()
296
+ // We need to manually add this padding so we align size with the map
297
+ // component and marker with the progress indicator.
298
+ Spacer (modifier = Modifier .height(it.calculateBottomPadding()))
299
+ }
300
+ }
301
+
302
+ @Composable
303
+ private fun ConnectionInfo (state : ConnectUiState ) {
304
+ ConnectionStatusText (
305
+ state = state.tunnelRealState,
306
+ modifier = Modifier .padding(horizontal = Dimens .sideMargin)
307
+ )
308
+ Text (
309
+ text = state.location?.country ? : " " ,
310
+ style = MaterialTheme .typography.headlineLarge,
311
+ color = MaterialTheme .colorScheme.onPrimary,
312
+ modifier = Modifier .padding(horizontal = Dimens .sideMargin)
313
+ )
314
+ Text (
315
+ text = state.location?.city ? : " " ,
316
+ style = MaterialTheme .typography.headlineLarge,
317
+ color = MaterialTheme .colorScheme.onPrimary,
318
+ modifier = Modifier .padding(horizontal = Dimens .sideMargin)
319
+ )
320
+ var expanded by rememberSaveable { mutableStateOf(false ) }
321
+ LocationInfo (
322
+ onToggleTunnelInfo = { expanded = ! expanded },
323
+ isVisible = state.showLocationInfo,
324
+ isExpanded = expanded,
325
+ location = state.location,
326
+ inAddress = state.inAddress,
327
+ outAddress = state.outAddress,
328
+ modifier =
329
+ Modifier .fillMaxWidth()
330
+ .padding(horizontal = Dimens .sideMargin)
331
+ .testTag(LOCATION_INFO_TEST_TAG )
332
+ )
333
+ }
334
+
335
+ @Composable
336
+ private fun ButtonPanel (
337
+ state : ConnectUiState ,
338
+ onSwitchLocationClick : () -> Unit ,
339
+ onDisconnectClick : () -> Unit ,
340
+ onReconnectClick : () -> Unit ,
341
+ onCancelClick : () -> Unit ,
342
+ onConnectClick : () -> Unit ,
343
+ ) {
344
+ var lastConnectionActionTimestamp by remember { mutableLongStateOf(0L ) }
345
+
346
+ fun handleThrottledAction (action : () -> Unit ) {
347
+ val currentTime = System .currentTimeMillis()
348
+ if ((currentTime - lastConnectionActionTimestamp) > CONNECT_BUTTON_THROTTLE_MILLIS ) {
349
+ lastConnectionActionTimestamp = currentTime
350
+ action.invoke()
351
+ }
352
+ }
353
+
354
+ SwitchLocationButton (
355
+ modifier =
356
+ Modifier .fillMaxWidth()
357
+ .padding(horizontal = Dimens .sideMargin)
358
+ .testTag(SELECT_LOCATION_BUTTON_TEST_TAG ),
359
+ onClick = onSwitchLocationClick,
360
+ showChevron = state.showLocation,
361
+ text =
362
+ if (state.showLocation && state.selectedRelayItem != null ) {
363
+ state.selectedRelayItem.locationName
364
+ } else {
365
+ stringResource(id = R .string.switch_location)
366
+ }
367
+ )
368
+ Spacer (modifier = Modifier .height(Dimens .buttonSpacing))
369
+ ConnectionButton (
370
+ state = state.tunnelUiState,
371
+ modifier =
372
+ Modifier .padding(horizontal = Dimens .sideMargin)
373
+ .padding(bottom = Dimens .screenVerticalMargin)
374
+ .testTag(CONNECT_BUTTON_TEST_TAG ),
375
+ disconnectClick = onDisconnectClick,
376
+ reconnectClick = { handleThrottledAction(onReconnectClick) },
377
+ cancelClick = onCancelClick,
378
+ connectClick = { handleThrottledAction(onConnectClick) },
379
+ reconnectButtonTestTag = RECONNECT_BUTTON_TEST_TAG
380
+ )
381
+ }
382
+
336
383
@Composable
337
384
fun TunnelState.toMarker (location : GeoIpLocation ? ): Marker ? {
338
385
if (location == null ) return null
0 commit comments