summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJonatan Rhodin <jonatan.rhodin@mullvad.net>2025-03-26 11:53:25 +0100
committerJonatan Rhodin <jonatan.rhodin@mullvad.net>2025-03-26 11:53:25 +0100
commit430f8a3455aa75e61deebe3eea4e96c4360e147d (patch)
treeeda753e5489fe9a04b69902703a143e01aad9dc8
parent5a853f4799974f85bef6112407582355e43fae0d (diff)
parente8a709876f6d67ac8f5c0a7f3b81487e3d73a3d3 (diff)
downloadmullvadvpn-430f8a3455aa75e61deebe3eea4e96c4360e147d.tar.xz
mullvadvpn-430f8a3455aa75e61deebe3eea4e96c4360e147d.zip
Merge branch 'investigate-splittunneling-application-icon-droid-501'
-rw-r--r--android/app/build.gradle.kts2
-rw-r--r--android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/SplitTunnelingScreenTest.kt4
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/SplitTunnelingCell.kt82
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/SplitTunnelingScreen.kt14
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/util/Bitmap.kt7
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/util/Drawable.kt13
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/PackageManagerExtensions.kt7
-rw-r--r--android/gradle/libs.versions.toml2
-rw-r--r--android/gradle/verification-keyring.keys29
-rw-r--r--android/gradle/verification-metadata.keys.xml1
-rw-r--r--android/gradle/verification-metadata.xml8
11 files changed, 121 insertions, 48 deletions
diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts
index 261cc9a48e..7f411e2a07 100644
--- a/android/app/build.gradle.kts
+++ b/android/app/build.gradle.kts
@@ -403,6 +403,8 @@ dependencies {
implementation(libs.compose.destinations)
ksp(libs.compose.destinations.ksp)
+ implementation(libs.accompanist.drawablepainter)
+
implementation(libs.kermit)
implementation(libs.koin)
implementation(libs.koin.android)
diff --git a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/SplitTunnelingScreenTest.kt b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/SplitTunnelingScreenTest.kt
index 8215ccde69..c17cb9079b 100644
--- a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/SplitTunnelingScreenTest.kt
+++ b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/SplitTunnelingScreenTest.kt
@@ -1,6 +1,6 @@
package net.mullvad.mullvadvpn.compose.screen
-import android.graphics.Bitmap
+import android.graphics.drawable.Drawable
import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
@@ -39,7 +39,7 @@ class SplitTunnelingScreenTest {
onExcludeAppClick: (packageName: String) -> Unit = {},
onIncludeAppClick: (packageName: String) -> Unit = {},
onBackClick: () -> Unit = {},
- onResolveIcon: (String) -> Bitmap? = { null },
+ onResolveIcon: (String) -> Drawable? = { null },
) {
setContentWithTheme {
SplitTunnelingScreen(
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/SplitTunnelingCell.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/SplitTunnelingCell.kt
index 15a83776d4..11904157e9 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/SplitTunnelingCell.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/SplitTunnelingCell.kt
@@ -1,10 +1,13 @@
package net.mullvad.mullvadvpn.compose.cell
-import android.graphics.Bitmap
+import android.graphics.drawable.Drawable
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Remove
@@ -19,21 +22,21 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
-import androidx.compose.ui.graphics.ImageBitmap
-import androidx.compose.ui.graphics.asImageBitmap
-import androidx.compose.ui.graphics.painter.BitmapPainter
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
+import com.google.accompanist.drawablepainter.rememberDrawablePainter
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import net.mullvad.mullvadvpn.R
import net.mullvad.mullvadvpn.compose.component.SpacedColumn
-import net.mullvad.mullvadvpn.compose.util.isBelowMaxBitmapSize
+import net.mullvad.mullvadvpn.compose.util.isBelowMaxSize
import net.mullvad.mullvadvpn.lib.theme.AppTheme
import net.mullvad.mullvadvpn.lib.theme.Dimens
+import net.mullvad.mullvadvpn.lib.theme.color.AlphaDisabled
import net.mullvad.mullvadvpn.lib.theme.typeface.listItemText
@Preview
@@ -71,35 +74,23 @@ fun SplitTunnelingCell(
enabled: Boolean,
modifier: Modifier = Modifier,
backgroundColor: Color = MaterialTheme.colorScheme.surfaceContainerHigh,
- onResolveIcon: (String) -> Bitmap?,
+ onResolveIcon: (String) -> Drawable?,
onCellClicked: () -> Unit,
) {
- var icon by remember(packageName) { mutableStateOf<ImageBitmap?>(null) }
+ var icon by remember(packageName) { mutableStateOf<IconState>(IconState.Loading) }
LaunchedEffect(packageName) {
launch(Dispatchers.IO) {
- val bitmap = onResolveIcon(packageName ?: "")
- if (bitmap != null && bitmap.isBelowMaxBitmapSize()) {
- icon = bitmap.asImageBitmap()
- }
+ val drawable = onResolveIcon(packageName ?: "")
+ icon =
+ if (drawable != null && drawable.isBelowMaxSize()) {
+ IconState.Icon(drawable = drawable)
+ } else {
+ IconState.NoIcon
+ }
}
}
BaseCell(
- iconView = {
- Image(
- painter =
- icon?.let { iconImage -> BitmapPainter(iconImage) }
- ?: painterResource(id = R.drawable.ic_icons_missing),
- contentDescription = null,
- modifier =
- Modifier.align(Alignment.CenterVertically).size(size = Dimens.listIconSize),
- colorFilter =
- if (icon == null) {
- ColorFilter.tint(MaterialTheme.colorScheme.onSurface)
- } else {
- null
- },
- )
- },
+ iconView = { Icon(iconState = icon) },
headlineContent = {
Text(
text = title,
@@ -130,3 +121,40 @@ fun SplitTunnelingCell(
isRowEnabled = enabled,
)
}
+
+@Composable
+private fun RowScope.Icon(iconState: IconState) {
+ when (iconState) {
+ IconState.Loading ->
+ Box(
+ modifier =
+ Modifier.align(Alignment.CenterVertically)
+ .size(Dimens.listIconSize)
+ .clip(CircleShape)
+ .background(MaterialTheme.colorScheme.onSurface.copy(alpha = AlphaDisabled))
+ )
+ is IconState.Icon ->
+ Image(
+ painter = rememberDrawablePainter(drawable = iconState.drawable),
+ contentDescription = null,
+ modifier =
+ Modifier.align(Alignment.CenterVertically).size(size = Dimens.listIconSize),
+ )
+ IconState.NoIcon ->
+ Image(
+ painter = painterResource(id = R.drawable.ic_icons_missing),
+ contentDescription = null,
+ modifier =
+ Modifier.align(Alignment.CenterVertically).size(size = Dimens.listIconSize),
+ colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onSurface),
+ )
+ }
+}
+
+private sealed class IconState {
+ object Loading : IconState()
+
+ data class Icon(val drawable: Drawable) : IconState()
+
+ object NoIcon : IconState() // Icon not found or icon too large
+}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/SplitTunnelingScreen.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/SplitTunnelingScreen.kt
index 5061655003..bfd209b6ff 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/SplitTunnelingScreen.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/SplitTunnelingScreen.kt
@@ -1,6 +1,6 @@
package net.mullvad.mullvadvpn.compose.screen
-import android.graphics.Bitmap
+import android.graphics.drawable.Drawable
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
@@ -49,7 +49,7 @@ import net.mullvad.mullvadvpn.lib.theme.AppTheme
import net.mullvad.mullvadvpn.lib.theme.Dimens
import net.mullvad.mullvadvpn.lib.theme.color.AlphaDisabled
import net.mullvad.mullvadvpn.lib.theme.color.AlphaVisible
-import net.mullvad.mullvadvpn.util.getApplicationIconBitmapOrNull
+import net.mullvad.mullvadvpn.util.getApplicationIconOrNull
import net.mullvad.mullvadvpn.viewmodel.SplitTunnelingViewModel
import org.koin.androidx.compose.koinViewModel
@@ -77,9 +77,7 @@ fun SplitTunneling(navigator: DestinationsNavigator) {
onExcludeAppClick = viewModel::onExcludeAppClick,
onIncludeAppClick = viewModel::onIncludeAppClick,
onBackClick = dropUnlessResumed { navigator.navigateUp() },
- onResolveIcon = { packageName ->
- packageManager.getApplicationIconBitmapOrNull(packageName)
- },
+ onResolveIcon = { packageName -> packageManager.getApplicationIconOrNull(packageName) },
)
}
@@ -91,7 +89,7 @@ fun SplitTunnelingScreen(
onExcludeAppClick: (packageName: String) -> Unit,
onIncludeAppClick: (packageName: String) -> Unit,
onBackClick: () -> Unit,
- onResolveIcon: (String) -> Bitmap?,
+ onResolveIcon: (String) -> Drawable?,
) {
val focusManager = LocalFocusManager.current
@@ -164,7 +162,7 @@ private fun LazyListScope.appList(
onShowSystemAppsClick: (show: Boolean) -> Unit,
onExcludeAppClick: (packageName: String) -> Unit,
onIncludeAppClick: (packageName: String) -> Unit,
- onResolveIcon: (String) -> Bitmap?,
+ onResolveIcon: (String) -> Drawable?,
) {
if (state.excludedApps.isNotEmpty()) {
headerItem(
@@ -206,7 +204,7 @@ private fun LazyListScope.appItems(
apps: List<AppData>,
focusManager: FocusManager,
onAppClick: (String) -> Unit,
- onResolveIcon: (String) -> Bitmap?,
+ onResolveIcon: (String) -> Drawable?,
enabled: Boolean,
excluded: Boolean,
) {
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/util/Bitmap.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/util/Bitmap.kt
deleted file mode 100644
index 9a3d030780..0000000000
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/util/Bitmap.kt
+++ /dev/null
@@ -1,7 +0,0 @@
-package net.mullvad.mullvadvpn.compose.util
-
-import android.graphics.Bitmap
-
-private const val MAX_BITMAP_SIZE_BYTES = 100 * 1024 * 1024
-
-fun Bitmap.isBelowMaxBitmapSize(): Boolean = byteCount < MAX_BITMAP_SIZE_BYTES
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/util/Drawable.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/util/Drawable.kt
new file mode 100644
index 0000000000..3985590879
--- /dev/null
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/util/Drawable.kt
@@ -0,0 +1,13 @@
+package net.mullvad.mullvadvpn.compose.util
+
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.Drawable
+
+private const val MAX_BITMAP_SIZE_BYTES = 100 * 1024 * 1024 // 100MB
+
+fun Drawable.isBelowMaxSize(): Boolean =
+ if (this is BitmapDrawable) {
+ bitmap.byteCount < MAX_BITMAP_SIZE_BYTES
+ } else {
+ true
+ }
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/PackageManagerExtensions.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/PackageManagerExtensions.kt
index 1e7721710e..fc6acfeb6c 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/PackageManagerExtensions.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/PackageManagerExtensions.kt
@@ -1,12 +1,11 @@
package net.mullvad.mullvadvpn.util
import android.content.pm.PackageManager
-import android.graphics.Bitmap
-import androidx.core.graphics.drawable.toBitmapOrNull
+import android.graphics.drawable.Drawable
-fun PackageManager.getApplicationIconBitmapOrNull(packageName: String): Bitmap? =
+fun PackageManager.getApplicationIconOrNull(packageName: String): Drawable? =
try {
- getApplicationIcon(packageName).toBitmapOrNull()
+ getApplicationIcon(packageName)
} catch (e: PackageManager.NameNotFoundException) {
// Name not found is thrown if the application is not installed
null
diff --git a/android/gradle/libs.versions.toml b/android/gradle/libs.versions.toml
index 864af8f07a..e57d72f0c0 100644
--- a/android/gradle/libs.versions.toml
+++ b/android/gradle/libs.versions.toml
@@ -33,6 +33,7 @@ compose-destinations = "2.1.0"
compose-constraintlayout = "1.1.1"
compose-material3 = "1.3.1"
compose-material-tv = "1.1.0-alpha01"
+drawablepainter = "0.37.2"
# Update suppression for 'InvalidPackage' in config/lint.xml
grpc = "1.71.0"
@@ -122,6 +123,7 @@ compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref =
compose-ui-tooling-android-preview = { module = "androidx.compose.ui:ui-tooling-preview-android", version.ref = "compose" }
compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview", version.ref = "compose" }
compose-ui-util = { module = "androidx.compose.ui:ui-util", version.ref = "compose" }
+accompanist-drawablepainter = { module = "com.google.accompanist:accompanist-drawablepainter", version.ref = "drawablepainter" }
# gRPC
grpc-okhttp = { module = "io.grpc:grpc-okhttp", version.ref = "grpc" }
diff --git a/android/gradle/verification-keyring.keys b/android/gradle/verification-keyring.keys
index 015c0e7ede..d13f73a2b1 100644
--- a/android/gradle/verification-keyring.keys
+++ b/android/gradle/verification-keyring.keys
@@ -445,6 +445,35 @@ xV+8+asS+ID6jUp5FJ4izH6U90j4iiBfqIu+UEw+0gvD+TVqpqcj5pNlgU45HA==
-----END PGP PUBLIC KEY BLOCK-----
+pub 1188B69F6D6259CA
+uid Chris Banes <chris@banes.me>
+
+sub 0888B86856F9D71A
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mQENBF2hcBkBCAC2H5WcFoeByKBUAjRDjmP+5P6FRsZjLe6c1wy7G1ha6/EQUVK4
+gZUZYE9W7l/4QKHvAu4PvFWdD+5FXGZuB/2kw348CtabAlJTehm1QlPt5//ODkxB
+fWPPz3uHBo5PQJZuLpStQn+aEkTTHK6Sk5033e6fa7mV5X/c8pTmTzkunG58NFbj
+5VSVbks4pbafKoMY7rSN0/I2hEApCjB9tx/7DJuQ9gbaGhmabnhBTnwHXEV1/hIF
+369lfiqeoeqDMOKj99C6KFD/NPRrRLfoPRpqL3LPsPp7P+TzyZN2q89Xqn2ysyI9
+jDtrlssEXLskU5kA8fVa179V/QR6QtrJ29m/ABEBAAG0HENocmlzIEJhbmVzIDxj
+aHJpc0BiYW5lcy5tZT65AQ0EXaFwGQEIALaSv8Um4LBbyPM8PIfcSwl5sqBQal1n
+w7461u8gGAi3uikn62Q7IGO3SRRuJcR6hkY5Dyq1qPwEPmcBjK0Nlx1NTtk4TwDT
+ZsxexNV40qQ+BuZhCb0NmiQBRBY7o3GiFa8mqcNeTNnIxvqMYInIYcvHps3r77QN
+Y8PZznfDNpJkuD/vvoFnDjncdaQd+Kia2gqAFHQMw6DnV1oCyPx38CqK10rw3DPt
+yYo/sVo67mVLJZvoXx255DbKNATKSrhiuCIsK9r+lIvY9zXoI2cJFsNA1QSiDkt1
+yjW1us89+LMt5dVCanq0bfhprU4J3ZYqzUS1sVAekVY2jRw/ssidX+sAEQEAAYkB
+PAQYAQgAJhYhBGS5sJ8WSqC/iHQuthGItp9tYlnKBQJdoXAZAhsMBQkDwmcAAAoJ
+EBGItp9tYlnKg94H/2GsZGvWK8+dA4y0AmCtP4B6/WAUonGBMmUH1Ax8GaPKIQuC
+ePBbQI5v2dGBqUOTjTh/qcpwfTHSMSWFRTv2HZDP82gY/tAHJrKZ14+Pr1JB4Nvd
+PIj60dKFfyveC3t8TJ4kVjKvvDzGY7oHsZ1OmW1Nytsbe6Taj57UY8v67zpLRCEb
+ZHIEo4hYtehEIlCWi7M2qJmXtWK/wU3bw0C8KL4Kun968+0005iOdQ/Iig4oI4K/
+UW2HkfQM1AwuwJlsc8uXUvH4A3kkVHTGnvvi4aTMtDWPKpdNu/eufWwuMrS6Gxdu
+v7wjtN+wTGesv0OYCfxHScTi49XbBbU3PBq+pxQ=
+=Lz+t
+-----END PGP PUBLIC KEY BLOCK-----
+
+
pub 1427500BB1D27520
uid ExoQuery <ExoQueryUser@gmail.com>
diff --git a/android/gradle/verification-metadata.keys.xml b/android/gradle/verification-metadata.keys.xml
index 28ccbbc9fc..6894f5c7ec 100644
--- a/android/gradle/verification-metadata.keys.xml
+++ b/android/gradle/verification-metadata.keys.xml
@@ -96,6 +96,7 @@
<trusted-key id="600EA202B1EC682F4A788E5AAC7A514BC9F9BB70" group="io.opencensus"/>
<trusted-key id="60200AC4AE761F1614D6C46766D68DAA073BE985" group="org.slf4j"/>
<trusted-key id="648190996EC0930A6D7D49A978178478013521D0" group="com.facebook"/>
+ <trusted-key id="64B9B09F164AA0BF88742EB61188B69F6D6259CA" group="com.google.accompanist" name="accompanist-drawablepainter" version="0.37.2"/>
<trusted-key id="694621A7227D8D5289699830ABE9F3126BB741C1" group="^com[.]google($|([.].*))" regex="true"/>
<trusted-key id="696B6199A2A9D8C29CE78CC0D041CAD2E452550F" group="com.google.protobuf"/>
<trusted-key id="6DD3B8C64EF75253BEB2C53AD908A43FB7EC07AC" group="jakarta.activation"/>
diff --git a/android/gradle/verification-metadata.xml b/android/gradle/verification-metadata.xml
index 29a3983128..26f1014d59 100644
--- a/android/gradle/verification-metadata.xml
+++ b/android/gradle/verification-metadata.xml
@@ -2944,6 +2944,14 @@
<sha256 value="a8e32e17575d0188c1f3e2e57bb95db09793883c8853fde828b63ecb1f99a63a" origin="Generated by Gradle"/>
</artifact>
</component>
+ <component group="com.google.accompanist" name="accompanist-drawablepainter" version="0.37.2">
+ <artifact name="accompanist-drawablepainter-0.37.2.aar">
+ <sha256 value="f65d76ba4d169186d79015603b680571224c1df7727864b8a796315a960595a8" origin="Generated by Gradle"/>
+ </artifact>
+ <artifact name="accompanist-drawablepainter-0.37.2.module">
+ <sha256 value="c4cd79129d9809d40296a8ead6b33770287e192daa5ce1e316fab6a53bc64976" origin="Generated by Gradle"/>
+ </artifact>
+ </component>
<component group="com.google.android" name="annotations" version="4.1.1.4">
<artifact name="annotations-4.1.1.4.jar">
<sha256 value="ba734e1e84c09d615af6a09d33034b4f0442f8772dec120efb376d86a565ae15" origin="Generated by Gradle"/>