Skip to content

Commit

Permalink
Refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
Rawa committed Oct 13, 2023
1 parent 66c01f7 commit 3779ee3
Show file tree
Hide file tree
Showing 3 changed files with 147 additions and 133 deletions.
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
package net.mullvad.mullvadvpn.compose.component
package net.mullvad.mullvadvpn.compose.component.notificationbanner

import androidx.annotation.DrawableRes
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateContentSize
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
Expand All @@ -18,25 +19,15 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.constraintlayout.compose.ConstraintLayout
import androidx.constraintlayout.compose.Dimension
import androidx.core.text.HtmlCompat
import net.mullvad.mullvadvpn.R
import net.mullvad.mullvadvpn.compose.extensions.getExpiryQuantityString
import net.mullvad.mullvadvpn.compose.extensions.toAnnotatedString
import net.mullvad.mullvadvpn.compose.component.MullvadTopBar
import net.mullvad.mullvadvpn.compose.test.NOTIFICATION_BANNER
import net.mullvad.mullvadvpn.compose.util.rememberPrevious
import net.mullvad.mullvadvpn.constant.IS_PLAY_BUILD
import net.mullvad.mullvadvpn.lib.common.util.getErrorNotificationResources
import net.mullvad.mullvadvpn.lib.theme.AlphaDescription
import net.mullvad.mullvadvpn.lib.theme.AppTheme
import net.mullvad.mullvadvpn.lib.theme.Dimens
Expand All @@ -51,9 +42,8 @@ import org.joda.time.DateTime
@Composable
private fun PreviewNotificationBanner() {
AppTheme {
SpacedColumn(
Modifier.background(color = MaterialTheme.colorScheme.background),
spacing = 8.dp
Column(
Modifier.background(color = MaterialTheme.colorScheme.surface),
) {
val bannerDataList =
listOf(
Expand All @@ -75,20 +65,29 @@ private fun PreviewNotificationBanner() {
)
.map { it.toNotificationData({}, {}, {}) }

bannerDataList.forEach { NotificationBanner(it) }
bannerDataList.forEach {
MullvadTopBar(
containerColor = MaterialTheme.colorScheme.primary,
onSettingsClicked = {},
onAccountClicked = {},
iconTintColor = MaterialTheme.colorScheme.primary
)
Notification(it)
Spacer(modifier = Modifier.size(16.dp))
}
}
}
}

@Composable
fun Notification(
fun NotificationBanner(
notification: InAppNotification?,
onClickUpdateVersion: () -> Unit,
onClickShowAccount: () -> Unit,
onClickDismissNewDevice: () -> Unit
) {
val previous = rememberPrevious(current = notification, shouldUpdate = { _, _ -> true })
// Fix for animating to hide
// Fix for animating t hide
AnimatedVisibility(
visible = notification != null,
enter = slideInVertically(initialOffsetY = { -it }),
Expand All @@ -97,7 +96,7 @@ fun Notification(
) {
val visibleNotification = notification ?: previous
if (visibleNotification != null)
NotificationBanner(
Notification(
visibleNotification.toNotificationData(
onClickUpdateVersion,
onClickShowAccount,
Expand All @@ -108,7 +107,7 @@ fun Notification(
}

@Composable
private fun NotificationBanner(notificationBannerData: NotificationBannerData) {
private fun Notification(notificationBannerData: NotificationData) {
val (title, message, statusLevel, action) = notificationBannerData
ConstraintLayout(
modifier =
Expand All @@ -123,7 +122,7 @@ private fun NotificationBanner(notificationBannerData: NotificationBannerData) {
.animateContentSize()
.testTag(NOTIFICATION_BANNER)
) {
val (status, textTitle, textMessage, icon) = createRefs()
val (status, textTitle, textMessage, actionIcon) = createRefs()
Box(
modifier =
Modifier.background(
Expand All @@ -149,7 +148,7 @@ private fun NotificationBanner(notificationBannerData: NotificationBannerData) {
top.linkTo(parent.top)
start.linkTo(status.end)
bottom.linkTo(anchor = textMessage.top)
end.linkTo(icon.start)
end.linkTo(actionIcon.start)
width = Dimension.fillToConstraints
}
.padding(start = Dimens.smallPadding),
Expand All @@ -165,7 +164,7 @@ private fun NotificationBanner(notificationBannerData: NotificationBannerData) {
start.linkTo(textTitle.start)
bottom.linkTo(parent.bottom)
if (action != null) {
end.linkTo(icon.start)
end.linkTo(actionIcon.start)
} else {
end.linkTo(parent.end)
}
Expand All @@ -179,7 +178,7 @@ private fun NotificationBanner(notificationBannerData: NotificationBannerData) {
action?.let {
IconButton(
modifier =
Modifier.constrainAs(icon) {
Modifier.constrainAs(actionIcon) {
top.linkTo(parent.top)
end.linkTo(parent.end)
bottom.linkTo(parent.bottom)
Expand All @@ -196,106 +195,3 @@ private fun NotificationBanner(notificationBannerData: NotificationBannerData) {
}
}
}

@Composable
fun InAppNotification.toNotificationData(
onClickUpdateVersion: () -> Unit,
onClickShowAccount: () -> Unit,
onDismissNewDevice: () -> Unit
) =
when (this) {
is InAppNotification.NewDevice ->
NotificationBannerData(
title = stringResource(id = R.string.new_device_notification_title),
message =
HtmlCompat.fromHtml(
stringResource(
id = R.string.new_device_notification_message,
deviceName
),
HtmlCompat.FROM_HTML_MODE_COMPACT
)
.toAnnotatedString(
boldSpanStyle =
SpanStyle(
color = MaterialTheme.colorScheme.onBackground,
fontWeight = FontWeight.ExtraBold
),
),
statusLevel = StatusLevel.Info,
action = NotificationAction(R.drawable.icon_close, onDismissNewDevice)
)
is InAppNotification.AccountExpiry ->
NotificationBannerData(
title = stringResource(id = R.string.account_credit_expires_soon),
message = LocalContext.current.resources.getExpiryQuantityString(expiry),
statusLevel = StatusLevel.Error,
action =
if (IS_PLAY_BUILD) null
else
NotificationAction(
R.drawable.icon_extlink,
onClickShowAccount,
),
)
InAppNotification.TunnelStateBlocked ->
NotificationBannerData(
title = stringResource(id = R.string.blocking_internet),
statusLevel = StatusLevel.Error
)
is InAppNotification.TunnelStateError -> errorMessageBannerData(error)
is InAppNotification.UnsupportedVersion ->
NotificationBannerData(
title = stringResource(id = R.string.unsupported_version),
message = stringResource(id = R.string.unsupported_version_description),
statusLevel = StatusLevel.Error,
action =
if (IS_PLAY_BUILD) null
else NotificationAction(R.drawable.icon_extlink, onClickUpdateVersion)
)
is InAppNotification.UpdateAvailable ->
NotificationBannerData(
title = stringResource(id = R.string.update_available),
message =
stringResource(
id = R.string.update_available_description,
versionInfo.upgradeVersion ?: "" // TODO Verify
),
statusLevel = StatusLevel.Warning,
action =
if (IS_PLAY_BUILD) null
else NotificationAction(R.drawable.icon_extlink, onClickUpdateVersion)
)
}

@Composable
private fun errorMessageBannerData(error: ErrorState) =
error.getErrorNotificationResources(LocalContext.current).run {
NotificationBannerData(
title = stringResource(id = titleResourceId),
message =
optionalMessageArgument?.let { stringResource(id = messageResourceId, it) }
?: stringResource(id = messageResourceId),
statusLevel = StatusLevel.Error,
action = null
)
}

data class NotificationBannerData(
val title: String,
val message: AnnotatedString? = null,
val statusLevel: StatusLevel,
val action: NotificationAction? = null
) {
constructor(
title: String,
message: String?,
statusLevel: StatusLevel,
action: NotificationAction?
) : this(title, message?.let { AnnotatedString(it) }, statusLevel, action)
}

data class NotificationAction(
@DrawableRes val icon: Int,
val onClick: (() -> Unit),
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package net.mullvad.mullvadvpn.compose.component.notificationbanner

import androidx.annotation.DrawableRes
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.core.text.HtmlCompat
import net.mullvad.mullvadvpn.R
import net.mullvad.mullvadvpn.compose.extensions.getExpiryQuantityString
import net.mullvad.mullvadvpn.compose.extensions.toAnnotatedString
import net.mullvad.mullvadvpn.constant.IS_PLAY_BUILD
import net.mullvad.mullvadvpn.lib.common.util.getErrorNotificationResources
import net.mullvad.mullvadvpn.repository.InAppNotification
import net.mullvad.mullvadvpn.ui.notification.StatusLevel
import net.mullvad.talpid.tunnel.ErrorState

data class NotificationData(
val title: String,
val message: AnnotatedString? = null,
val statusLevel: StatusLevel,
val action: NotificationAction? = null
) {
constructor(
title: String,
message: String?,
statusLevel: StatusLevel,
action: NotificationAction?
) : this(title, message?.let { AnnotatedString(it) }, statusLevel, action)
}

data class NotificationAction(
@DrawableRes val icon: Int,
val onClick: (() -> Unit),
)

@Composable
fun InAppNotification.toNotificationData(
onClickUpdateVersion: () -> Unit,
onClickShowAccount: () -> Unit,
onDismissNewDevice: () -> Unit
) =
when (this) {
is InAppNotification.NewDevice ->
NotificationData(
title = stringResource(id = R.string.new_device_notification_title),
message =
HtmlCompat.fromHtml(
stringResource(
id = R.string.new_device_notification_message,
deviceName
),
HtmlCompat.FROM_HTML_MODE_COMPACT
)
.toAnnotatedString(
boldSpanStyle =
SpanStyle(
color = MaterialTheme.colorScheme.onBackground,
fontWeight = FontWeight.ExtraBold
),
),
statusLevel = StatusLevel.Info,
action = NotificationAction(R.drawable.icon_close, onDismissNewDevice)
)
is InAppNotification.AccountExpiry ->
NotificationData(
title = stringResource(id = R.string.account_credit_expires_soon),
message = LocalContext.current.resources.getExpiryQuantityString(expiry),
statusLevel = StatusLevel.Error,
action =
if (IS_PLAY_BUILD) null
else
NotificationAction(
R.drawable.icon_extlink,
onClickShowAccount,
),
)
InAppNotification.TunnelStateBlocked ->
NotificationData(
title = stringResource(id = R.string.blocking_internet),
statusLevel = StatusLevel.Error
)
is InAppNotification.TunnelStateError -> errorMessageBannerData(error)
is InAppNotification.UnsupportedVersion ->
NotificationData(
title = stringResource(id = R.string.unsupported_version),
message = stringResource(id = R.string.unsupported_version_description),
statusLevel = StatusLevel.Error,
action =
if (IS_PLAY_BUILD) null
else NotificationAction(R.drawable.icon_extlink, onClickUpdateVersion)
)
is InAppNotification.UpdateAvailable ->
NotificationData(
title = stringResource(id = R.string.update_available),
message =
stringResource(
id = R.string.update_available_description,
versionInfo.upgradeVersion ?: "" // TODO Verify
),
statusLevel = StatusLevel.Warning,
action =
if (IS_PLAY_BUILD) null
else NotificationAction(R.drawable.icon_extlink, onClickUpdateVersion)
)
}

@Composable
private fun errorMessageBannerData(error: ErrorState) =
error.getErrorNotificationResources(LocalContext.current).run {
NotificationData(
title = stringResource(id = titleResourceId),
message = optionalMessageArgument?.let { stringResource(id = messageResourceId, it) }
?: stringResource(id = messageResourceId),
statusLevel = StatusLevel.Error,
action = null
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,8 @@ import net.mullvad.mullvadvpn.compose.button.ConnectionButton
import net.mullvad.mullvadvpn.compose.button.SwitchLocationButton
import net.mullvad.mullvadvpn.compose.component.ConnectionStatusText
import net.mullvad.mullvadvpn.compose.component.LocationInfo
import net.mullvad.mullvadvpn.compose.component.Notification
import net.mullvad.mullvadvpn.compose.component.ScaffoldWithTopBarAndDeviceName
import net.mullvad.mullvadvpn.compose.component.ScaffoldWithTopBar
import net.mullvad.mullvadvpn.compose.component.drawVerticalScrollbar
import net.mullvad.mullvadvpn.compose.component.notificationbanner.NotificationBanner
import net.mullvad.mullvadvpn.compose.state.ConnectUiState
import net.mullvad.mullvadvpn.compose.test.CIRCULAR_PROGRESS_INDICATOR
import net.mullvad.mullvadvpn.compose.test.CONNECT_BUTTON_TEST_TAG
Expand Down Expand Up @@ -142,12 +140,11 @@ fun ConnectScreen(
Modifier.padding(it)
.background(color = MaterialTheme.colorScheme.primary)
.fillMaxHeight()
.drawVerticalScrollbar(scrollState)
.verticalScroll(scrollState)
.padding(bottom = Dimens.screenVerticalMargin)
.testTag(SCROLLABLE_COLUMN_TEST_TAG)
) {
Notification(
NotificationBanner(
notification = uiState.inAppNotification,
onClickUpdateVersion = onUpdateVersionClick,
onClickShowAccount = onManageAccountClick,
Expand Down

0 comments on commit 3779ee3

Please sign in to comment.