Skip to content

Commit

Permalink
Merge branch 'main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
tunadreno authored Jan 7, 2025
2 parents 6e31505 + cd277a1 commit 30e8aa8
Show file tree
Hide file tree
Showing 46 changed files with 870 additions and 1,075 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ class ConnectScreenTest {
)

// Assert
onNodeWithText("DISCONNECTED").assertExists()
onNodeWithText("DISCONNECTING...").assertExists()
onNodeWithText(mockLocationName).assertExists()
onNodeWithText("Disconnect").assertExists()
}
Expand Down Expand Up @@ -310,7 +310,7 @@ class ConnectScreenTest {
)

// Assert
onNodeWithText("CONNECTED").assertExists()
onNodeWithText("BLOCKING...").assertExists()
onNodeWithText(mockLocationName).assertExists()
onNodeWithText("Disconnect").assertExists()
onNodeWithText("BLOCKING INTERNET").assertExists()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ private fun TunnelState.text() =
is TunnelState.Disconnected -> textResource(id = R.string.disconnected)
is TunnelState.Disconnecting ->
when (actionAfterDisconnect) {
ActionAfterDisconnect.Nothing -> textResource(id = R.string.disconnected)
ActionAfterDisconnect.Block -> textResource(id = R.string.connected)
ActionAfterDisconnect.Nothing -> textResource(id = R.string.disconnecting)
ActionAfterDisconnect.Block -> textResource(id = R.string.blocking)
ActionAfterDisconnect.Reconnect -> textResource(id = R.string.connecting)
}
is TunnelState.Error ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.calculateEndPadding
import androidx.compose.foundation.layout.calculateStartPadding
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
Expand Down Expand Up @@ -39,6 +41,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.layout
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
Expand Down Expand Up @@ -271,7 +274,14 @@ fun ConnectScreen(
if (screenHeight < SCREEN_HEIGHT_THRESHOLD) SHORT_SCREEN_INDICATOR_BIAS
else TALL_SCREEN_INDICATOR_BIAS

Box(Modifier.padding(it).fillMaxSize()) {
Box(
Modifier.padding(
top = it.calculateTopPadding(),
start = it.calculateStartPadding(LocalLayoutDirection.current),
end = it.calculateEndPadding(LocalLayoutDirection.current),
)
.fillMaxSize()
) {
MullvadMap(state, indicatorPercentOffset)

MullvadCircularProgressIndicatorLarge(
Expand All @@ -293,22 +303,24 @@ fun ConnectScreen(
.testTag(CIRCULAR_PROGRESS_INDICATOR),
)

NotificationBanner(
notification = state.inAppNotification,
isPlayBuild = state.isPlayBuild,
openAppListing = onOpenAppListing,
onClickShowAccount = onManageAccountClick,
onClickDismissNewDevice = onDismissNewDeviceClick,
)
ConnectionCard(
state = state,
modifier = Modifier.align(Alignment.BottomCenter),
onSwitchLocationClick,
onDisconnectClick,
onReconnectClick,
onCancelClick,
onConnectClick,
)
Box(modifier = Modifier.fillMaxSize().padding(bottom = it.calculateBottomPadding())) {
NotificationBanner(
notification = state.inAppNotification,
isPlayBuild = state.isPlayBuild,
openAppListing = onOpenAppListing,
onClickShowAccount = onManageAccountClick,
onClickDismissNewDevice = onDismissNewDeviceClick,
)
ConnectionCard(
state = state,
modifier = Modifier.align(Alignment.BottomCenter),
onSwitchLocationClick = onSwitchLocationClick,
onDisconnectClick = onDisconnectClick,
onReconnectClick = onReconnectClick,
onCancelClick = onCancelClick,
onConnectClick = onConnectClick,
)
}
}
}
}
Expand Down Expand Up @@ -365,18 +377,9 @@ private fun ConnectionCard(
Shapes.large,
colors = CardDefaults.cardColors(containerColor = containerColor.value),
) {
Column(
modifier =
Modifier.padding(
top = Dimens.mediumPadding,
start = Dimens.mediumPadding,
end = Dimens.mediumPadding,
bottom = Dimens.smallPadding,
)
) {
Column(modifier = Modifier.padding(all = Dimens.mediumPadding)) {
ConnectionCardHeader(state, state.location, expanded) { expanded = !expanded }

Logger.d("Tunnelstate: ${state.tunnelState}, expanded: $expanded")
AnimatedContent(
(state.tunnelState as? TunnelState.Connected)?.featureIndicators to expanded,
modifier = Modifier.weight(1f, fill = false),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,13 @@ private fun DaitaCell(isDaitaEnabled: Boolean, onDaitaClick: () -> Unit) {
}
),
onCellClicked = onDaitaClick,
bodyView = {
Icon(
imageVector = Icons.Default.ChevronRight,
contentDescription = title,
tint = MaterialTheme.colorScheme.onPrimary,
)
},
)
}

Expand All @@ -258,5 +265,12 @@ private fun MultihopCell(isMultihopEnabled: Boolean, onMultihopClick: () -> Unit
}
),
onCellClicked = onMultihopClick,
bodyView = {
Icon(
imageVector = Icons.Default.ChevronRight,
contentDescription = title,
tint = MaterialTheme.colorScheme.onPrimary,
)
},
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,15 @@ import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
Expand Down Expand Up @@ -255,7 +258,10 @@ fun SearchLocationScreen(
onHideBottomSheet = { locationBottomSheetState = null },
)
Column(modifier = Modifier.padding(it)) {
val focusRequester = remember { FocusRequester() }
LaunchedEffect(Unit) { focusRequester.requestFocus() }
SearchBar(
modifier = Modifier.focusRequester(focusRequester),
searchTerm = state.searchTerm,
backgroundColor = backgroundColor,
onBackgroundColor = onBackgroundColor,
Expand Down Expand Up @@ -325,9 +331,10 @@ private fun SearchBar(
onSearchInputChanged: (String) -> Unit,
hideKeyboard: () -> Unit,
onGoBack: () -> Unit,
modifier: Modifier = Modifier,
) {
SearchBarDefaults.InputField(
modifier = Modifier.height(Dimens.searchFieldHeightExpanded).fillMaxWidth(),
modifier = modifier.height(Dimens.searchFieldHeightExpanded).fillMaxWidth(),
query = searchTerm,
onQueryChange = onSearchInputChanged,
onSearch = { hideKeyboard() },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package net.mullvad.mullvadvpn.util
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow

inline fun <T1, T2, T3, T4, T5, T6, R> combine(
flow: Flow<T1>,
Expand Down Expand Up @@ -61,3 +62,11 @@ fun <T> Deferred<T>.getOrDefault(default: T) =
} catch (e: IllegalStateException) {
default
}

fun <T> Flow<T>.withPrev(): Flow<Pair<T, T?>> = flow {
var prev: T? = null
collect { curr ->
emit(curr to prev)
prev = curr
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.map
Expand Down Expand Up @@ -36,6 +35,7 @@ import net.mullvad.mullvadvpn.usecase.SelectedLocationTitleUseCase
import net.mullvad.mullvadvpn.util.combine
import net.mullvad.mullvadvpn.util.daysFromNow
import net.mullvad.mullvadvpn.util.isSuccess
import net.mullvad.mullvadvpn.util.withPrev

@Suppress("LongParameterList")
class ConnectViewModel(
Expand All @@ -62,14 +62,14 @@ class ConnectViewModel(
combine(
selectedLocationTitleUseCase(),
inAppNotificationController.notifications,
connectionProxy.tunnelState,
connectionProxy.tunnelState.withPrev(),
lastKnownLocationUseCase.lastKnownDisconnectedLocation,
accountRepository.accountData,
deviceRepository.deviceState.map { it?.displayName() },
) {
selectedRelayItemTitle,
notifications,
tunnelState,
(tunnelState, prevTunnelState),
lastKnownDisconnectedLocation,
accountData,
deviceName ->
Expand All @@ -80,14 +80,22 @@ class ConnectViewModel(
tunnelState.location ?: lastKnownDisconnectedLocation
is TunnelState.Connecting -> tunnelState.location
is TunnelState.Connected -> tunnelState.location
is TunnelState.Disconnecting -> lastKnownDisconnectedLocation
is TunnelState.Disconnecting ->
when (tunnelState.actionAfterDisconnect) {
ActionAfterDisconnect.Nothing -> lastKnownDisconnectedLocation
ActionAfterDisconnect.Block -> lastKnownDisconnectedLocation
// Keep the previous connected location when reconnecting, after
// this state we will reach Connecting with the new relay
// location
ActionAfterDisconnect.Reconnect -> prevTunnelState?.location()
}
is TunnelState.Error -> lastKnownDisconnectedLocation
},
selectedRelayItemTitle = selectedRelayItemTitle,
tunnelState = tunnelState,
showLocation =
when (tunnelState) {
is TunnelState.Disconnected -> true
is TunnelState.Disconnected -> tunnelState.location != null
is TunnelState.Disconnecting -> {
when (tunnelState.actionAfterDisconnect) {
ActionAfterDisconnect.Nothing -> false
Expand All @@ -105,7 +113,6 @@ class ConnectViewModel(
isPlayBuild = isPlayBuild,
)
}
.debounce(UI_STATE_DEBOUNCE_DURATION_MILLIS)
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), ConnectUiState.INITIAL)

init {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,12 +171,11 @@ class ConnectViewModelTest {
fun `given RelayListUseCase returns new selectedRelayItem uiState should emit new selectedRelayItem`() =
runTest {
val selectedRelayItemTitle = "Item"
selectedRelayItemFlow.value = selectedRelayItemTitle

viewModel.uiState.test {
assertEquals(ConnectUiState.INITIAL, awaitItem())
val result = awaitItem()
assertEquals(selectedRelayItemTitle, result.selectedRelayItemTitle)

selectedRelayItemFlow.value = selectedRelayItemTitle
assertEquals(selectedRelayItemTitle, awaitItem().selectedRelayItemTitle)
}
}

Expand All @@ -196,7 +195,6 @@ class ConnectViewModelTest {

// Act, Assert
viewModel.uiState.test {
assertEquals(ConnectUiState.INITIAL, awaitItem())
tunnelState.emit(TunnelState.Disconnected(null))

// Start of with no location
Expand All @@ -215,12 +213,7 @@ class ConnectViewModelTest {
val locationTestItem = null

// Act, Assert
viewModel.uiState.test {
assertEquals(ConnectUiState.INITIAL, awaitItem())
expectNoEvents()
val result = awaitItem()
assertEquals(locationTestItem, result.location)
}
viewModel.uiState.test { assertEquals(locationTestItem, awaitItem().location) }
}

@Test
Expand Down Expand Up @@ -278,15 +271,12 @@ class ConnectViewModelTest {
val mockErrorState: ErrorState = mockk()
val expectedConnectNotificationState =
InAppNotification.TunnelStateError(mockErrorState)
val tunnelStateError = TunnelState.Error(mockErrorState)
notifications.value = listOf(expectedConnectNotificationState)

// Act, Assert
viewModel.uiState.test {
assertEquals(ConnectUiState.INITIAL, awaitItem())
tunnelState.emit(tunnelStateError)
val result = awaitItem()
assertEquals(expectedConnectNotificationState, result.inAppNotification)
notifications.value = listOf(expectedConnectNotificationState)
assertEquals(expectedConnectNotificationState, awaitItem().inAppNotification)
}
}

Expand Down Expand Up @@ -315,7 +305,6 @@ class ConnectViewModelTest {
viewModel.uiState.test {
awaitItem()
outOfTimeViewFlow.value = true
awaitItem()
}

// Assert
Expand All @@ -328,12 +317,13 @@ class ConnectViewModelTest {
// Arrange
val tunnel = TunnelState.Error(mockk(relaxed = true))
val lastKnownLocation: GeoIpLocation = mockk(relaxed = true)
lastKnownLocationFlow.emit(lastKnownLocation)
tunnelState.emit(tunnel)

// Act, Assert
viewModel.uiState.test {
assertEquals(ConnectUiState.INITIAL, awaitItem())
lastKnownLocationFlow.emit(lastKnownLocation)
tunnelState.emit(tunnel)
awaitItem()
val result = awaitItem()
assertEquals(lastKnownLocation, result.location)
}
Expand Down
4 changes: 2 additions & 2 deletions android/buildSrc/src/main/kotlin/Versions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ object Versions {
const val minSdkVersion = 26
const val targetSdkVersion = 35

const val junitJupiter = "5.11.3"
const val junitJupiter = "5.11.4"
const val junit5Android = "1.6.0"
const val junit5Plugin = "1.11.2.0"
const val junit5Plugin = "1.11.3.0"
}
2 changes: 1 addition & 1 deletion android/config/dependency-check-suppression.xml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
<packageUrl regex="true">^pkg:maven/com\.google\.protobuf/protobuf-.*@.*$</packageUrl>
<cve>CVE-2024-7254</cve>
</suppress>
<suppress until="2025-01-04Z">
<suppress until="2025-04-04Z">
<notes><![CDATA[
No impact since the app doesn't process externally crafted XML.
]]></notes>
Expand Down
Loading

0 comments on commit 30e8aa8

Please sign in to comment.