summaryrefslogtreecommitdiffhomepage
path: root/android/app/src/main
diff options
context:
space:
mode:
authorJonatan Rhodin <jonatan.rhodin@mullvad.net>2023-05-29 13:20:32 +0200
committerJonatan Rhodin <jonatan.rhodin@mullvad.net>2023-06-09 10:31:21 +0200
commitf220345dc31a298e4734b985d785cfdd24f03bea (patch)
tree5a61991627ade27df670c5932941a5cc50d57b65 /android/app/src/main
parenta298585757ad1c8e6c65cb405db9ce9a31dd0b33 (diff)
downloadmullvadvpn-f220345dc31a298e4734b985d785cfdd24f03bea.tar.xz
mullvadvpn-f220345dc31a298e4734b985d785cfdd24f03bea.zip
Replace ApplicationImageView with compose implementation
Diffstat (limited to 'android/app/src/main')
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/applist/ApplicationsIconManager.kt9
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/SplitTunnelingCell.kt34
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/SplitTunnelingScreen.kt13
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/SplitTunnelingFragment.kt8
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/widget/ApplicationImageView.kt69
5 files changed, 44 insertions, 89 deletions
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/applist/ApplicationsIconManager.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/applist/ApplicationsIconManager.kt
index ebfbc1f379..e1ff07022c 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/applist/ApplicationsIconManager.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/applist/ApplicationsIconManager.kt
@@ -1,22 +1,23 @@
package net.mullvad.mullvadvpn.applist
import android.content.pm.PackageManager
-import android.graphics.drawable.Drawable
+import android.graphics.Bitmap
import android.os.Looper
import androidx.annotation.WorkerThread
import androidx.collection.LruCache
+import androidx.core.graphics.drawable.toBitmap
class ApplicationsIconManager(private val packageManager: PackageManager) {
- private val iconsCache = LruCache<String, Drawable>(500)
+ private val iconsCache = LruCache<String, Bitmap>(500)
@WorkerThread
@Throws(PackageManager.NameNotFoundException::class)
- fun getAppIcon(packageName: String): Drawable {
+ fun getAppIcon(packageName: String): Bitmap {
check(!Looper.getMainLooper().isCurrentThread) { "Should not be called from MainThread" }
iconsCache.get(packageName)?.let {
return it
}
- return packageManager.getApplicationIcon(packageName).also {
+ return packageManager.getApplicationIcon(packageName).toBitmap().also {
iconsCache.put(packageName, it)
}
}
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 bab0a2caa5..48694332b7 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,5 +1,6 @@
package net.mullvad.mullvadvpn.compose.cell
+import android.graphics.Bitmap
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
@@ -12,25 +13,29 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
+import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+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.viewinterop.AndroidView
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
import net.mullvad.mullvadvpn.R
import net.mullvad.mullvadvpn.compose.theme.AppTheme
import net.mullvad.mullvadvpn.compose.theme.Dimens
import net.mullvad.mullvadvpn.compose.theme.typeface.listItemText
-import net.mullvad.mullvadvpn.ui.widget.ApplicationImageView
+import org.koin.androidx.compose.get
@Preview
@Composable
-fun PreviewTunnelingCell() {
+private fun PreviewTunnelingCell() {
AppTheme {
Column(modifier = Modifier.background(color = MaterialTheme.colorScheme.background)) {
- SplitTunnelingCell("Mullvad VPN", "", false)
- SplitTunnelingCell("Mullvad VPN", "", true)
+ SplitTunnelingCell(title = "Mullvad VPN", packageName = "", isSelected = false)
+ SplitTunnelingCell(title = "Mullvad VPN", packageName = "", isSelected = true)
}
}
}
@@ -41,8 +46,16 @@ fun SplitTunnelingCell(
packageName: String?,
isSelected: Boolean,
modifier: Modifier = Modifier,
+ onResolveIcon: (String) -> Bitmap? = { null },
onCellClicked: () -> Unit = {}
) {
+ var icon by remember(packageName) { mutableStateOf<ImageBitmap?>(null) }
+ LaunchedEffect(packageName) {
+ launch(Dispatchers.IO) {
+ val bitmap = onResolveIcon(packageName ?: "")
+ icon = bitmap?.asImageBitmap()
+ }
+ }
Row(
modifier =
modifier
@@ -53,11 +66,10 @@ fun SplitTunnelingCell(
.background(MaterialTheme.colorScheme.primaryContainer)
.clickable(onClick = onCellClicked)
) {
- AndroidView(
- factory = { context -> ApplicationImageView(context) },
- update = { applicationImageView ->
- applicationImageView.packageName = packageName ?: ""
- },
+ Image(
+ painter = icon?.let { iconImage -> BitmapPainter(iconImage) }
+ ?: painterResource(id = R.drawable.ic_icons_missing),
+ contentDescription = null,
modifier =
Modifier.padding(start = Dimens.cellStartPadding)
.align(Alignment.CenterVertically)
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 3c3044a268..e624170479 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,5 +1,6 @@
package net.mullvad.mullvadvpn.compose.screen
+import android.graphics.Bitmap
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
@@ -35,6 +36,7 @@ import net.mullvad.mullvadvpn.compose.extensions.itemWithDivider
import net.mullvad.mullvadvpn.compose.state.SplitTunnelingUiState
import net.mullvad.mullvadvpn.compose.theme.AppTheme
import net.mullvad.mullvadvpn.compose.theme.Dimens
+import org.koin.androidx.compose.get
@Preview
@Composable
@@ -48,7 +50,7 @@ fun PreviewSplitTunnelingScreen() {
AppData(
packageName = "my.package.a",
name = "TitleA",
- iconRes = R.drawable.icon_alert
+ iconRes = R.drawable.icon_alert,
),
AppData(
packageName = "my.package.b",
@@ -77,7 +79,8 @@ fun SplitTunnelingScreen(
onShowSystemAppsClick: (show: Boolean) -> Unit = {},
onExcludeAppClick: (packageName: String) -> Unit = {},
onIncludeAppClick: (packageName: String) -> Unit = {},
- onBackClick: () -> Unit = {}
+ onBackClick: () -> Unit = {},
+ onResolveIcon: (String) -> Bitmap? = { null },
) {
val state = rememberCollapsingToolbarScaffoldState()
val progress = state.toolbarState.progress
@@ -161,7 +164,8 @@ fun SplitTunnelingScreen(
title = listItem.name,
packageName = listItem.packageName,
isSelected = true,
- modifier = Modifier.animateItemPlacement().fillMaxWidth()
+ modifier = Modifier.animateItemPlacement().fillMaxWidth(),
+ onResolveIcon = onResolveIcon
) {
onIncludeAppClick(listItem.packageName)
}
@@ -205,7 +209,8 @@ fun SplitTunnelingScreen(
title = listItem.name,
packageName = listItem.packageName,
isSelected = false,
- modifier = Modifier.animateItemPlacement().fillMaxWidth()
+ modifier = Modifier.animateItemPlacement().fillMaxWidth(),
+ onResolveIcon = onResolveIcon
) {
onExcludeAppClick(listItem.packageName)
}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/SplitTunnelingFragment.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/SplitTunnelingFragment.kt
index 59328dc273..854167a03b 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/SplitTunnelingFragment.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/SplitTunnelingFragment.kt
@@ -7,13 +7,16 @@ import android.view.ViewGroup
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.platform.ComposeView
import net.mullvad.mullvadvpn.R
+import net.mullvad.mullvadvpn.applist.ApplicationsIconManager
import net.mullvad.mullvadvpn.compose.screen.SplitTunnelingScreen
import net.mullvad.mullvadvpn.compose.theme.AppTheme
import net.mullvad.mullvadvpn.viewmodel.SplitTunnelingViewModel
+import org.koin.android.ext.android.inject
import org.koin.androidx.viewmodel.ext.android.viewModel
class SplitTunnelingFragment : BaseFragment() {
private val viewModel: SplitTunnelingViewModel by viewModel()
+ private val applicationsIconManager: ApplicationsIconManager by inject()
override fun onCreateView(
inflater: LayoutInflater,
@@ -29,7 +32,10 @@ class SplitTunnelingFragment : BaseFragment() {
onShowSystemAppsClick = viewModel::onShowSystemAppsClick,
onExcludeAppClick = viewModel::onExcludeAppClick,
onIncludeAppClick = viewModel::onIncludeAppClick,
- onBackClick = { activity?.onBackPressedDispatcher?.onBackPressed() }
+ onBackClick = { activity?.onBackPressedDispatcher?.onBackPressed() },
+ onResolveIcon = { packageName ->
+ applicationsIconManager.getAppIcon(packageName)
+ }
)
}
}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/widget/ApplicationImageView.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/widget/ApplicationImageView.kt
deleted file mode 100644
index 43a8a58fbe..0000000000
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/widget/ApplicationImageView.kt
+++ /dev/null
@@ -1,69 +0,0 @@
-package net.mullvad.mullvadvpn.ui.widget
-
-import android.content.Context
-import android.content.pm.PackageManager
-import android.graphics.drawable.Drawable
-import android.util.AttributeSet
-import androidx.appcompat.widget.AppCompatImageView
-import androidx.core.content.res.ResourcesCompat
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.SupervisorJob
-import kotlinx.coroutines.cancelChildren
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
-import net.mullvad.mullvadvpn.R
-import net.mullvad.mullvadvpn.applist.ApplicationsIconManager
-import org.koin.core.component.KoinComponent
-import org.koin.core.component.inject
-
-class ApplicationImageView
-@JvmOverloads
-constructor(
- context: Context,
- attrs: AttributeSet? = null,
- defStyleAttr: Int = R.attr.applicationListItemViewStyle,
-) : AppCompatImageView(context, attrs, defStyleAttr), KoinComponent {
- private val viewScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
- private val iconManager: ApplicationsIconManager by inject()
-
- private var updateImageJob: Job? = null
-
- var packageName: String = ""
- set(value) {
- field = value
- updateImage()
- }
-
- init {
- updateImage(ResourcesCompat.getDrawable(resources, R.drawable.ic_icons_missing, null)!!)
- }
-
- private fun updateImage() {
- updateImageJob?.cancel()
- updateImageJob = viewScope.launch { loadImage()?.let { drawable -> updateImage(drawable) } }
- }
-
- override fun onAttachedToWindow() {
- super.onAttachedToWindow()
- updateImage()
- }
-
- override fun onDetachedFromWindow() {
- super.onDetachedFromWindow()
- updateImageJob?.cancel()
- viewScope.coroutineContext.cancelChildren()
- }
-
- private suspend fun loadImage(): Drawable? =
- withContext(Dispatchers.Default) {
- try {
- iconManager.getAppIcon(packageName)
- } catch (e: PackageManager.NameNotFoundException) {
- null
- }
- }
-
- private fun updateImage(drawable: Drawable) = setImageDrawable(drawable)
-}