Skip to content

Commit

Permalink
Simplify notification messages
Browse files Browse the repository at this point in the history
  • Loading branch information
Rawa committed Nov 25, 2024
1 parent 536347c commit e71f8b7
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 134 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.text.toUpperCase
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.constraintlayout.compose.ConstraintLayout
Expand Down Expand Up @@ -129,7 +130,7 @@ private fun Notification(notificationBannerData: NotificationData) {
},
)
Text(
text = title.uppercase(),
text = title.toUpperCase(),
modifier =
Modifier.constrainAs(textTitle) {
top.linkTo(parent.top)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,26 +12,30 @@ 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 java.net.InetAddress
import net.mullvad.mullvadvpn.R
import net.mullvad.mullvadvpn.compose.extensions.getExpiryQuantityString
import net.mullvad.mullvadvpn.compose.extensions.toAnnotatedString
import net.mullvad.mullvadvpn.lib.common.util.notificationResources
import net.mullvad.mullvadvpn.lib.model.AuthFailedError
import net.mullvad.mullvadvpn.lib.model.ErrorState
import net.mullvad.mullvadvpn.lib.model.ErrorStateCause
import net.mullvad.mullvadvpn.lib.model.ParameterGenerationError
import net.mullvad.mullvadvpn.lib.model.PrepareError
import net.mullvad.mullvadvpn.repository.InAppNotification
import net.mullvad.mullvadvpn.ui.notification.StatusLevel

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

data class NotificationAction(
Expand All @@ -50,22 +54,11 @@ fun InAppNotification.toNotificationData(
when (this) {
is InAppNotification.NewDevice ->
NotificationData(
title = stringResource(id = R.string.new_device_notification_title),
title =
AnnotatedString(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.onSurface,
fontWeight = FontWeight.ExtraBold,
)
),
stringResource(id = R.string.new_device_notification_message, deviceName)
.formatWithHtml(),
statusLevel = StatusLevel.Info,
action =
NotificationAction(
Expand Down Expand Up @@ -110,23 +103,110 @@ fun InAppNotification.toNotificationData(

@Composable
private fun errorMessageBannerData(error: ErrorState) =
with(error.notificationResources()) {
NotificationData(
title = stringResource(id = titleResourceId),
message =
HtmlCompat.fromHtml(
optionalMessageArgument?.let { stringResource(id = messageResourceId, it) }
?: stringResource(id = messageResourceId),
HtmlCompat.FROM_HTML_MODE_COMPACT,
)
.toAnnotatedString(
boldSpanStyle =
SpanStyle(
color = MaterialTheme.colorScheme.onSurface,
fontWeight = FontWeight.ExtraBold,
)
),
statusLevel = StatusLevel.Error,
action = null,
NotificationData(
title = error.title().formatWithHtml(),
message = error.message().formatWithHtml(),
statusLevel = StatusLevel.Error,
action = null,
)

@Composable
private fun String.formatWithHtml(): AnnotatedString =
HtmlCompat.fromHtml(this, HtmlCompat.FROM_HTML_MODE_COMPACT)
.toAnnotatedString(
boldSpanStyle =
SpanStyle(
color = MaterialTheme.colorScheme.onSurface,
fontWeight = FontWeight.ExtraBold,
)
)

@Composable
private fun ErrorState.title(): String {
val cause = this.cause
return when {
cause is ErrorStateCause.InvalidDnsServers -> stringResource(R.string.blocking_internet)
cause is ErrorStateCause.VpnPermissionDenied -> cause.prepareError.errorTitle()
isBlocking -> stringResource(R.string.blocking_internet)
else -> stringResource(R.string.critical_error)
}
}

@Composable
private fun ErrorState.message(): String {
val cause = this.cause
return when {
cause is ErrorStateCause.InvalidDnsServers -> {
stringResource(
cause.errorMessageId(),
cause.addresses.joinToString { address -> address.addressString() },
)
}
cause is ErrorStateCause.VpnPermissionDenied ->
cause.prepareError.errorNotificationMessage()

isBlocking -> stringResource(cause.errorMessageId())
else -> stringResource(R.string.failed_to_block_internet)
}
}

@Composable
private fun PrepareError.errorTitle(): String =
when (this) {
is PrepareError.NotPrepared ->
stringResource(R.string.vpn_permission_error_notification_title)
is PrepareError.OtherAlwaysOnApp ->
stringResource(R.string.always_on_vpn_error_notification_title, appName)
is PrepareError.LegacyLockdown ->
stringResource(R.string.legacy_always_on_vpn_error_notification_title)
}

@Composable
private fun PrepareError.errorNotificationMessage(): String =
when (this) {
is PrepareError.NotPrepared ->
stringResource(R.string.vpn_permission_error_notification_message)
is PrepareError.OtherAlwaysOnApp ->
stringResource(R.string.always_on_vpn_error_notification_content, appName)
is PrepareError.LegacyLockdown ->
stringResource(R.string.legacy_always_on_vpn_error_notification_content)
}

private fun ErrorStateCause.errorMessageId(): Int =
when (this) {
is ErrorStateCause.InvalidDnsServers -> R.string.invalid_dns_servers
is ErrorStateCause.AuthFailed -> error.errorMessageId()
is ErrorStateCause.Ipv6Unavailable -> R.string.ipv6_unavailable
is ErrorStateCause.FirewallPolicyError -> R.string.set_firewall_policy_error
is ErrorStateCause.DnsError -> R.string.set_dns_error
is ErrorStateCause.StartTunnelError -> R.string.start_tunnel_error
is ErrorStateCause.IsOffline -> R.string.is_offline
is ErrorStateCause.TunnelParameterError -> {
when (error) {
ParameterGenerationError.NoMatchingRelay,
ParameterGenerationError.NoMatchingBridgeRelay -> {
R.string.no_matching_relay
}
ParameterGenerationError.NoWireguardKey -> R.string.no_wireguard_key
ParameterGenerationError.CustomTunnelHostResultionError -> {
R.string.custom_tunnel_host_resolution_error
}
}
}
is ErrorStateCause.VpnPermissionDenied -> R.string.vpn_permission_denied_error
}

private fun AuthFailedError.errorMessageId(): Int =
when (this) {
AuthFailedError.ExpiredAccount -> R.string.account_credit_has_expired
AuthFailedError.InvalidAccount,
AuthFailedError.TooManyConnections,
AuthFailedError.Unknown -> R.string.auth_failed
}

private fun InetAddress.addressString(): String {
val hostNameAndAddress = this.toString().split('/', limit = 2)
val address = hostNameAndAddress[1]

return address
}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,9 @@ private fun NotificationTunnelState.contentTitleResourceId(context: Context): St
NotificationTunnelState.Error.VpnPermissionDenied ->
context.getString(R.string.vpn_permission_error_notification_title)
is NotificationTunnelState.Error.AlwaysOnVpn ->
context.getString(R.string.always_on_vpn_error_notification_title_2, appName)
context.getString(R.string.always_on_vpn_error_notification_title, appName)
NotificationTunnelState.Error.LegacyLockdown ->
context.getString(R.string.always_on_vpn_error_notification_title)
context.getString(R.string.legacy_always_on_vpn_error_notification_title)
}

internal fun NotificationAction.Tunnel.toCompatAction(context: Context): NotificationCompat.Action {
Expand Down

0 comments on commit e71f8b7

Please sign in to comment.