summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorKalle Lindström <karl.lindstrom@mullvad.net>2026-04-22 14:01:21 +0200
committerKalle Lindström <karl.lindstrom@mullvad.net>2026-04-22 14:01:21 +0200
commit711f13f71cb83362e7ea2cbd394906509a366637 (patch)
tree32439dbcf65ad6aa84a1695f9a982a9e02caf6db
parent9fb40393097ba2c2619f6873cedcc479237b4d86 (diff)
parent0ef58af29eeed5e29b7a4785eaa600196438e98e (diff)
downloadmullvadvpn-711f13f71cb83362e7ea2cbd394906509a366637.tar.xz
mullvadvpn-711f13f71cb83362e7ea2cbd394906509a366637.zip
Merge branch 'add-split-app-counter-droid-1772'
-rw-r--r--android/lib/feature/apiaccess/impl/build.gradle.kts1
-rw-r--r--android/lib/feature/apiaccess/impl/src/main/java/net/mullvad/mullvadvpn/feature/apiaccess/impl/screen/edit/EditApiAccessMethodScreen.kt14
-rw-r--r--android/lib/feature/autoconnect/impl/build.gradle.kts1
-rw-r--r--android/lib/feature/autoconnect/impl/src/main/kotlin/net/mullvad/mullvadvpn/feature/autoconnect/impl/AutoConnectAndLockdownModeScreen.kt6
-rw-r--r--android/lib/feature/home/impl/build.gradle.kts1
-rw-r--r--android/lib/feature/home/impl/src/main/kotlin/net/mullvad/mullvadvpn/feature/home/impl/connect/ConnectScreen.kt5
-rw-r--r--android/lib/feature/splittunneling/impl/build.gradle.kts1
-rw-r--r--android/lib/feature/splittunneling/impl/src/main/java/net/mullvad/mullvadvpn/feature/splittunneling/impl/SplitTunnelingScreen.kt36
-rw-r--r--android/lib/feature/splittunneling/impl/src/main/java/net/mullvad/mullvadvpn/feature/splittunneling/impl/search/SearchSplitTunnelingScreen.kt5
-rw-r--r--android/lib/ui/designsystem/src/main/kotlin/net/mullvad/mullvadvpn/lib/ui/designsystem/ListHeader.kt19
-rw-r--r--android/lib/ui/resource/src/main/res/values/strings.xml3
-rw-r--r--android/lib/ui/util/build.gradle.kts2
-rw-r--r--android/lib/ui/util/src/main/kotlin/net/mullvad/mullvadvpn/lib/ui/util/Modifier.kt11
-rw-r--r--desktop/packages/mullvad-vpn/locales/messages.pot3
14 files changed, 80 insertions, 28 deletions
diff --git a/android/lib/feature/apiaccess/impl/build.gradle.kts b/android/lib/feature/apiaccess/impl/build.gradle.kts
index 3baa370158..9f59084a47 100644
--- a/android/lib/feature/apiaccess/impl/build.gradle.kts
+++ b/android/lib/feature/apiaccess/impl/build.gradle.kts
@@ -12,6 +12,7 @@ dependencies {
implementation(projects.lib.feature.apiaccess.api)
implementation(projects.lib.navigation)
implementation(projects.lib.repository)
+ implementation(projects.lib.ui.util)
implementation(libs.koin.compose)
implementation(libs.arrow)
diff --git a/android/lib/feature/apiaccess/impl/src/main/java/net/mullvad/mullvadvpn/feature/apiaccess/impl/screen/edit/EditApiAccessMethodScreen.kt b/android/lib/feature/apiaccess/impl/src/main/java/net/mullvad/mullvadvpn/feature/apiaccess/impl/screen/edit/EditApiAccessMethodScreen.kt
index 59888c0528..1dbec14dd8 100644
--- a/android/lib/feature/apiaccess/impl/src/main/java/net/mullvad/mullvadvpn/feature/apiaccess/impl/screen/edit/EditApiAccessMethodScreen.kt
+++ b/android/lib/feature/apiaccess/impl/src/main/java/net/mullvad/mullvadvpn/feature/apiaccess/impl/screen/edit/EditApiAccessMethodScreen.kt
@@ -80,6 +80,7 @@ import net.mullvad.mullvadvpn.lib.ui.theme.Dimens
import net.mullvad.mullvadvpn.lib.ui.theme.color.AlphaInvisible
import net.mullvad.mullvadvpn.lib.ui.theme.color.AlphaScrollbar
import net.mullvad.mullvadvpn.lib.ui.theme.color.AlphaVisible
+import net.mullvad.mullvadvpn.lib.ui.util.visible
import org.koin.androidx.compose.koinViewModel
import org.koin.core.parameter.parametersOf
@@ -341,10 +342,7 @@ private fun ApiAccessMethodTypeSelection(
contentDescription = null,
modifier =
Modifier.padding(end = Dimens.selectableCellTextMargin)
- .alpha(
- if (item == formData.apiAccessMethodTypes) AlphaVisible
- else AlphaInvisible
- ),
+ .visible(item == formData.apiAccessMethodTypes),
)
},
)
@@ -564,7 +562,7 @@ private fun CipherSelection(cipher: Cipher, onCipherChange: (Cipher) -> Unit) {
contentDescription = null,
modifier =
Modifier.padding(end = Dimens.selectableCellTextMargin)
- .alpha(if (item == cipher) AlphaVisible else AlphaInvisible),
+ .visible(item == cipher),
)
},
)
@@ -602,7 +600,7 @@ private fun EnableAuthentication(
contentDescription = null,
modifier =
Modifier.padding(end = Dimens.selectableCellTextMargin)
- .alpha(if (authenticationEnabled) AlphaVisible else AlphaInvisible),
+ .visible(authenticationEnabled),
)
},
)
@@ -619,9 +617,7 @@ private fun EnableAuthentication(
contentDescription = null,
modifier =
Modifier.padding(end = Dimens.selectableCellTextMargin)
- .alpha(
- if (authenticationEnabled.not()) AlphaVisible else AlphaInvisible
- ),
+ .alpha(if (authenticationEnabled) AlphaInvisible else AlphaVisible),
)
},
)
diff --git a/android/lib/feature/autoconnect/impl/build.gradle.kts b/android/lib/feature/autoconnect/impl/build.gradle.kts
index 912bd3b1bd..996b14b8d2 100644
--- a/android/lib/feature/autoconnect/impl/build.gradle.kts
+++ b/android/lib/feature/autoconnect/impl/build.gradle.kts
@@ -10,6 +10,7 @@ android { namespace = "net.mullvad.mullvadvpn.feature.autoconnect.impl" }
dependencies {
implementation(projects.lib.feature.autoconnect.api)
+ implementation(projects.lib.ui.util)
implementation(libs.koin.compose)
implementation(libs.arrow)
diff --git a/android/lib/feature/autoconnect/impl/src/main/kotlin/net/mullvad/mullvadvpn/feature/autoconnect/impl/AutoConnectAndLockdownModeScreen.kt b/android/lib/feature/autoconnect/impl/src/main/kotlin/net/mullvad/mullvadvpn/feature/autoconnect/impl/AutoConnectAndLockdownModeScreen.kt
index a1d0ac8c5b..8c8d4761a5 100644
--- a/android/lib/feature/autoconnect/impl/src/main/kotlin/net/mullvad/mullvadvpn/feature/autoconnect/impl/AutoConnectAndLockdownModeScreen.kt
+++ b/android/lib/feature/autoconnect/impl/src/main/kotlin/net/mullvad/mullvadvpn/feature/autoconnect/impl/AutoConnectAndLockdownModeScreen.kt
@@ -36,7 +36,6 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext
@@ -72,9 +71,8 @@ import net.mullvad.mullvadvpn.lib.ui.designsystem.PrimaryButton
import net.mullvad.mullvadvpn.lib.ui.resource.R
import net.mullvad.mullvadvpn.lib.ui.theme.AppTheme
import net.mullvad.mullvadvpn.lib.ui.theme.Dimens
-import net.mullvad.mullvadvpn.lib.ui.theme.color.AlphaInvisible
import net.mullvad.mullvadvpn.lib.ui.theme.color.AlphaScrollbar
-import net.mullvad.mullvadvpn.lib.ui.theme.color.AlphaVisible
+import net.mullvad.mullvadvpn.lib.ui.util.visible
import org.koin.androidx.compose.koinViewModel
@Preview("OSS|Play")
@@ -268,7 +266,7 @@ private fun CarouselNavigationButton(
imageVector: ImageVector,
) {
IconButton(
- modifier = modifier.alpha(if (isEnabled.invoke()) AlphaVisible else AlphaInvisible),
+ modifier = modifier.visible(isEnabled.invoke()),
onClick = onClick,
enabled = isEnabled.invoke(),
) {
diff --git a/android/lib/feature/home/impl/build.gradle.kts b/android/lib/feature/home/impl/build.gradle.kts
index 7b27e30b81..be15bee07c 100644
--- a/android/lib/feature/home/impl/build.gradle.kts
+++ b/android/lib/feature/home/impl/build.gradle.kts
@@ -31,6 +31,7 @@ dependencies {
implementation(projects.lib.pushNotification)
implementation(projects.lib.repository)
implementation(projects.lib.tv)
+ implementation(projects.lib.ui.util)
implementation(projects.lib.usecase)
implementation(libs.androidx.animation)
diff --git a/android/lib/feature/home/impl/src/main/kotlin/net/mullvad/mullvadvpn/feature/home/impl/connect/ConnectScreen.kt b/android/lib/feature/home/impl/src/main/kotlin/net/mullvad/mullvadvpn/feature/home/impl/connect/ConnectScreen.kt
index 267b7162c9..7364e1f1a9 100644
--- a/android/lib/feature/home/impl/src/main/kotlin/net/mullvad/mullvadvpn/feature/home/impl/connect/ConnectScreen.kt
+++ b/android/lib/feature/home/impl/src/main/kotlin/net/mullvad/mullvadvpn/feature/home/impl/connect/ConnectScreen.kt
@@ -129,10 +129,9 @@ import net.mullvad.mullvadvpn.lib.ui.theme.Dimens
import net.mullvad.mullvadvpn.lib.ui.theme.Shapes
import net.mullvad.mullvadvpn.lib.ui.theme.color.Alpha20
import net.mullvad.mullvadvpn.lib.ui.theme.color.Alpha80
-import net.mullvad.mullvadvpn.lib.ui.theme.color.AlphaInvisible
import net.mullvad.mullvadvpn.lib.ui.theme.color.AlphaScrollbar
-import net.mullvad.mullvadvpn.lib.ui.theme.color.AlphaVisible
import net.mullvad.mullvadvpn.lib.ui.theme.color.positive
+import net.mullvad.mullvadvpn.lib.ui.util.visible
import org.koin.androidx.compose.koinViewModel
private const val CONNECT_BUTTON_THROTTLE_MILLIS = 1000
@@ -444,7 +443,7 @@ private fun Content(
)
}
}
- .alpha(if (state.showLoading) AlphaVisible else AlphaInvisible),
+ .visible(state.showLoading),
)
Box(
diff --git a/android/lib/feature/splittunneling/impl/build.gradle.kts b/android/lib/feature/splittunneling/impl/build.gradle.kts
index 66f2072c5f..2fd10ce3f9 100644
--- a/android/lib/feature/splittunneling/impl/build.gradle.kts
+++ b/android/lib/feature/splittunneling/impl/build.gradle.kts
@@ -14,4 +14,5 @@ dependencies {
implementation(libs.koin.compose)
implementation(libs.arrow)
implementation(projects.lib.feature.splittunneling.api)
+ implementation(projects.lib.ui.util)
}
diff --git a/android/lib/feature/splittunneling/impl/src/main/java/net/mullvad/mullvadvpn/feature/splittunneling/impl/SplitTunnelingScreen.kt b/android/lib/feature/splittunneling/impl/src/main/java/net/mullvad/mullvadvpn/feature/splittunneling/impl/SplitTunnelingScreen.kt
index 1f992f6069..e9b81aa249 100644
--- a/android/lib/feature/splittunneling/impl/src/main/java/net/mullvad/mullvadvpn/feature/splittunneling/impl/SplitTunnelingScreen.kt
+++ b/android/lib/feature/splittunneling/impl/src/main/java/net/mullvad/mullvadvpn/feature/splittunneling/impl/SplitTunnelingScreen.kt
@@ -64,6 +64,7 @@ import net.mullvad.mullvadvpn.lib.ui.theme.Dimens
import net.mullvad.mullvadvpn.lib.ui.theme.color.AlphaDisabled
import net.mullvad.mullvadvpn.lib.ui.theme.color.AlphaScrollbar
import net.mullvad.mullvadvpn.lib.ui.theme.color.AlphaVisible
+import net.mullvad.mullvadvpn.lib.ui.util.visible
import org.koin.androidx.compose.koinViewModel
import org.koin.core.parameter.parametersOf
@@ -225,10 +226,12 @@ private fun LazyListScope.appList(
onResolveIcon: (PackageName) -> Drawable?,
) {
if (state.excludedApps.isNotEmpty()) {
- headerItem(
+ excludedAppsHeaderItem(
key = SplitTunnelingContentKey.EXCLUDED_APPLICATIONS,
textId = R.string.exclude_applications,
enabled = state.enabled,
+ exludedAppsCount = state.excludedApps.size,
+ includedAppsCount = state.includedApps.size,
)
appItems(
apps = state.excludedApps,
@@ -320,16 +323,29 @@ internal fun LazyListScope.appItems(
internal fun LazyListScope.headerItem(key: String, textId: Int, enabled: Boolean) {
itemWithDivider(key = key, contentType = ContentType.HEADER) {
ListHeader(
- modifier =
- Modifier.animateItem()
- .alpha(
- if (enabled) {
- AlphaVisible
- } else {
- AlphaDisabled
- }
- ),
+ modifier = Modifier.animateItem().visible(enabled),
+ text = stringResource(id = textId),
+ )
+ }
+}
+
+internal fun LazyListScope.excludedAppsHeaderItem(
+ key: String,
+ textId: Int,
+ enabled: Boolean,
+ exludedAppsCount: Int,
+ includedAppsCount: Int,
+) {
+ itemWithDivider(key = key, contentType = ContentType.HEADER) {
+ ListHeader(
+ modifier = Modifier.animateItem().visible(enabled),
text = stringResource(id = textId),
+ trailingText =
+ stringResource(
+ R.string.x_out_of_y,
+ exludedAppsCount,
+ exludedAppsCount + includedAppsCount,
+ ),
)
}
}
diff --git a/android/lib/feature/splittunneling/impl/src/main/java/net/mullvad/mullvadvpn/feature/splittunneling/impl/search/SearchSplitTunnelingScreen.kt b/android/lib/feature/splittunneling/impl/src/main/java/net/mullvad/mullvadvpn/feature/splittunneling/impl/search/SearchSplitTunnelingScreen.kt
index 494757f35f..4ef0597de4 100644
--- a/android/lib/feature/splittunneling/impl/src/main/java/net/mullvad/mullvadvpn/feature/splittunneling/impl/search/SearchSplitTunnelingScreen.kt
+++ b/android/lib/feature/splittunneling/impl/src/main/java/net/mullvad/mullvadvpn/feature/splittunneling/impl/search/SearchSplitTunnelingScreen.kt
@@ -40,6 +40,7 @@ import net.mullvad.mullvadvpn.feature.splittunneling.impl.CommonContentKey
import net.mullvad.mullvadvpn.feature.splittunneling.impl.ContentType
import net.mullvad.mullvadvpn.feature.splittunneling.impl.SplitTunnelingContentKey
import net.mullvad.mullvadvpn.feature.splittunneling.impl.appItems
+import net.mullvad.mullvadvpn.feature.splittunneling.impl.excludedAppsHeaderItem
import net.mullvad.mullvadvpn.feature.splittunneling.impl.getApplicationIconOrNull
import net.mullvad.mullvadvpn.feature.splittunneling.impl.headerItem
import net.mullvad.mullvadvpn.lib.common.Lc
@@ -158,10 +159,12 @@ private fun LazyListScope.appList(
item { NoAppsMatchingSearch(state.searchTerm) }
}
if (state.excludedApps.isNotEmpty()) {
- headerItem(
+ excludedAppsHeaderItem(
key = SplitTunnelingContentKey.EXCLUDED_APPLICATIONS,
textId = R.string.exclude_applications,
enabled = true,
+ exludedAppsCount = state.excludedApps.size,
+ includedAppsCount = state.includedApps.size,
)
appItems(
apps = state.excludedApps,
diff --git a/android/lib/ui/designsystem/src/main/kotlin/net/mullvad/mullvadvpn/lib/ui/designsystem/ListHeader.kt b/android/lib/ui/designsystem/src/main/kotlin/net/mullvad/mullvadvpn/lib/ui/designsystem/ListHeader.kt
index 9f25242965..c36ff60227 100644
--- a/android/lib/ui/designsystem/src/main/kotlin/net/mullvad/mullvadvpn/lib/ui/designsystem/ListHeader.kt
+++ b/android/lib/ui/designsystem/src/main/kotlin/net/mullvad/mullvadvpn/lib/ui/designsystem/ListHeader.kt
@@ -3,9 +3,11 @@ package net.mullvad.mullvadvpn.lib.ui.designsystem
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Edit
import androidx.compose.material3.HorizontalDivider
@@ -29,6 +31,22 @@ fun ListHeader(text: String, modifier: Modifier = Modifier) {
}
@Composable
+fun ListHeader(modifier: Modifier = Modifier, text: String, trailingText: String) {
+ ListHeader(
+ modifier = modifier,
+ content = { Text(text = text) },
+ actions = {
+ Spacer(Modifier.width(Dimens.smallSpacer))
+ Text(
+ text = trailingText,
+ style = MaterialTheme.typography.bodyMedium,
+ color = MaterialTheme.colorScheme.onSurfaceVariant,
+ )
+ },
+ )
+}
+
+@Composable
fun ListHeader(
content: @Composable () -> Unit,
modifier: Modifier = Modifier,
@@ -75,4 +93,5 @@ fun PreviewListHeader() =
}
},
)
+ ListHeader(text = "Header", trailingText = "3 out of 200")
}
diff --git a/android/lib/ui/resource/src/main/res/values/strings.xml b/android/lib/ui/resource/src/main/res/values/strings.xml
index 4d92f6c27b..ccafabfc04 100644
--- a/android/lib/ui/resource/src/main/res/values/strings.xml
+++ b/android/lib/ui/resource/src/main/res/values/strings.xml
@@ -1,4 +1,4 @@
-<resources>
+<resources xmlns:tools="http://schemas.android.com/tools">
<string name="disconnecting">Disconnecting...</string>
<string name="blocking">Blocking...</string>
<string name="critical_error">Critical error (your attention is required)</string>
@@ -132,6 +132,7 @@
<string name="custom_dns_footer">Enable to add at least one DNS server.</string>
<string name="confirm_local_dns">The local DNS server will not work unless you enable \"Local Network Sharing\" under VPN settings.</string>
<string name="exclude_applications">Excluded applications</string>
+ <string name="x_out_of_y" tools:ignore="PluralsCandidate">%1$d out of %2$d</string>
<string name="all_applications">All applications</string>
<string name="show_system_apps">Show system apps</string>
<string name="toggle_vpn">Toggle VPN</string>
diff --git a/android/lib/ui/util/build.gradle.kts b/android/lib/ui/util/build.gradle.kts
index ede0822b03..48fa3b2ac9 100644
--- a/android/lib/ui/util/build.gradle.kts
+++ b/android/lib/ui/util/build.gradle.kts
@@ -14,4 +14,6 @@ dependencies {
implementation(libs.compose.ui.tooling.preview)
implementation(libs.compose.material3)
implementation(libs.compose.icons.extended)
+
+ implementation(projects.lib.ui.theme)
}
diff --git a/android/lib/ui/util/src/main/kotlin/net/mullvad/mullvadvpn/lib/ui/util/Modifier.kt b/android/lib/ui/util/src/main/kotlin/net/mullvad/mullvadvpn/lib/ui/util/Modifier.kt
index e659e68731..2cff4ff545 100644
--- a/android/lib/ui/util/src/main/kotlin/net/mullvad/mullvadvpn/lib/ui/util/Modifier.kt
+++ b/android/lib/ui/util/src/main/kotlin/net/mullvad/mullvadvpn/lib/ui/util/Modifier.kt
@@ -2,6 +2,9 @@ package net.mullvad.mullvadvpn.lib.ui.util
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.alpha
+import net.mullvad.mullvadvpn.lib.ui.theme.color.AlphaInvisible
+import net.mullvad.mullvadvpn.lib.ui.theme.color.AlphaVisible
@Composable
fun <T> Modifier.applyIfNotNull(
@@ -22,3 +25,11 @@ fun Modifier.applyIf(condition: Boolean, block: @Composable Modifier.() -> Modif
} else {
this
}
+
+@Composable
+fun Modifier.visible(visible: Boolean): Modifier =
+ if (visible) {
+ alpha(AlphaVisible)
+ } else {
+ alpha(AlphaInvisible)
+ }
diff --git a/desktop/packages/mullvad-vpn/locales/messages.pot b/desktop/packages/mullvad-vpn/locales/messages.pot
index 8aa1a9d7ac..10e106b64d 100644
--- a/desktop/packages/mullvad-vpn/locales/messages.pot
+++ b/desktop/packages/mullvad-vpn/locales/messages.pot
@@ -2930,6 +2930,9 @@ msgctxt "wireguard-settings-view"
msgid "Which TCP port the UDP-over-TCP obfuscation protocol should connect to on the VPN server."
msgstr ""
+msgid "%1$d out of %2$d"
+msgstr ""
+
msgid "%1$s more..."
msgstr ""