1
1
package net.mullvad.mullvadvpn.compose.component.notificationbanner
2
2
3
- import androidx.compose.animation.AnimatedVisibility
4
- import androidx.compose.animation.animateContentSize
5
- import androidx.compose.animation.slideInVertically
6
- import androidx.compose.animation.slideOutVertically
7
3
import androidx.compose.foundation.background
8
- import androidx.compose.foundation.clickable
9
- import androidx.compose.foundation.layout.Box
10
4
import androidx.compose.foundation.layout.Column
11
5
import androidx.compose.foundation.layout.Spacer
12
6
import androidx.compose.foundation.layout.fillMaxWidth
13
- import androidx.compose.foundation.layout.padding
14
7
import androidx.compose.foundation.layout.size
15
- import androidx.compose.foundation.layout.wrapContentWidth
16
- import androidx.compose.foundation.shape.CircleShape
17
- import androidx.compose.material3.Icon
18
- import androidx.compose.material3.IconButton
19
8
import androidx.compose.material3.MaterialTheme
20
- import androidx.compose.material3.Text
21
9
import androidx.compose.runtime.Composable
22
- import androidx.compose.ui.Alignment
23
10
import androidx.compose.ui.Modifier
24
- import androidx.compose.ui.graphics.vector.ImageVector
25
- import androidx.compose.ui.platform.testTag
26
- import androidx.compose.ui.semantics.Role
27
- import androidx.compose.ui.text.style.TextOverflow
28
- import androidx.compose.ui.text.toUpperCase
29
11
import androidx.compose.ui.tooling.preview.Preview
30
12
import androidx.compose.ui.unit.dp
31
- import androidx.constraintlayout.compose.ConstraintLayout
32
- import androidx.constraintlayout.compose.Dimension
33
13
import java.time.Duration
34
14
import net.mullvad.mullvadvpn.compose.component.MullvadTopBar
35
- import net.mullvad.mullvadvpn.compose.test.NOTIFICATION_BANNER
36
- import net.mullvad.mullvadvpn.compose.test.NOTIFICATION_BANNER_ACTION
37
- import net.mullvad.mullvadvpn.compose.test.NOTIFICATION_BANNER_TEXT_ACTION
38
- import net.mullvad.mullvadvpn.compose.util.rememberPrevious
15
+ import net.mullvad.mullvadvpn.compose.util.isTv
39
16
import net.mullvad.mullvadvpn.lib.model.ErrorState
40
17
import net.mullvad.mullvadvpn.lib.model.ErrorStateCause
18
+ import net.mullvad.mullvadvpn.lib.shared.InAppNotification
19
+ import net.mullvad.mullvadvpn.lib.shared.VersionInfo
20
+ import net.mullvad.mullvadvpn.lib.shared.compose.AnimatedNotificationBanner
41
21
import net.mullvad.mullvadvpn.lib.theme.AppTheme
42
- import net.mullvad.mullvadvpn.lib.theme.Dimens
43
- import net.mullvad.mullvadvpn.lib.theme.color.warning
44
- import net.mullvad.mullvadvpn.repository.InAppNotification
45
- import net.mullvad.mullvadvpn.ui.VersionInfo
46
- import net.mullvad.mullvadvpn.ui.notification.StatusLevel
22
+ import net.mullvad.mullvadvpn.lib.tv.NotificationBannerTv
47
23
48
24
@Preview
49
25
@Composable
@@ -52,18 +28,17 @@ private fun PreviewNotificationBanner() {
52
28
Column (Modifier .background(color = MaterialTheme .colorScheme.surface)) {
53
29
val bannerDataList =
54
30
listOf (
55
- InAppNotification .UnsupportedVersion (
56
- versionInfo = VersionInfo (currentVersion = " 1.0" , isSupported = false )
57
- ),
58
- InAppNotification .AccountExpiry (expiry = Duration .ZERO ),
59
- InAppNotification .TunnelStateBlocked ,
60
- InAppNotification .NewDevice (" Courageous Turtle" ),
61
- InAppNotification .TunnelStateError (
62
- error = ErrorState (ErrorStateCause .FirewallPolicyError .Generic , true )
63
- ),
64
- InAppNotification .NewVersionChangelog ,
65
- )
66
- .map { it.toNotificationData(false , {}, {}, {}, {}, {}) }
31
+ InAppNotification .UnsupportedVersion (
32
+ versionInfo = VersionInfo (currentVersion = " 1.0" , isSupported = false )
33
+ ),
34
+ InAppNotification .AccountExpiry (expiry = Duration .ZERO ),
35
+ InAppNotification .TunnelStateBlocked ,
36
+ InAppNotification .NewDevice (" Courageous Turtle" ),
37
+ InAppNotification .TunnelStateError (
38
+ error = ErrorState (ErrorStateCause .FirewallPolicyError .Generic , true )
39
+ ),
40
+ InAppNotification .NewVersionChangelog ,
41
+ )
67
42
68
43
bannerDataList.forEach {
69
44
MullvadTopBar (
@@ -72,7 +47,15 @@ private fun PreviewNotificationBanner() {
72
47
onAccountClicked = {},
73
48
iconTintColor = MaterialTheme .colorScheme.primary,
74
49
)
75
- Notification (it)
50
+ NotificationBanner (
51
+ notification = it,
52
+ isPlayBuild = false ,
53
+ openAppListing = {},
54
+ onClickShowAccount = {},
55
+ onClickShowChangelog = {},
56
+ onClickDismissChangelog = {},
57
+ onClickDismissNewDevice = {},
58
+ )
76
59
Spacer (modifier = Modifier .size(16 .dp))
77
60
}
78
61
}
@@ -90,163 +73,28 @@ fun NotificationBanner(
90
73
onClickDismissChangelog : () -> Unit ,
91
74
onClickDismissNewDevice : () -> Unit ,
92
75
) {
93
- // Fix for animating to invisible state
94
- val previous = rememberPrevious(current = notification, shouldUpdate = { _, _ -> true })
95
- AnimatedVisibility (
96
- visible = notification != null ,
97
- enter = slideInVertically(initialOffsetY = { - it }),
98
- exit = slideOutVertically(targetOffsetY = { - it }),
99
- modifier = modifier,
100
- ) {
101
- val visibleNotification = notification ? : previous
102
- if (visibleNotification != null )
103
- Notification (
104
- visibleNotification.toNotificationData(
105
- isPlayBuild = isPlayBuild,
106
- openAppListing,
107
- onClickShowAccount,
108
- onClickShowChangelog,
109
- onClickDismissChangelog,
110
- onClickDismissNewDevice,
111
- )
112
- )
113
- }
114
- }
115
-
116
- @Composable
117
- @Suppress(" LongMethod" )
118
- private fun Notification (notificationBannerData : NotificationData ) {
119
- val (title, message, statusLevel, action) = notificationBannerData
120
- ConstraintLayout (
121
- modifier =
122
- Modifier .fillMaxWidth()
123
- .background(color = MaterialTheme .colorScheme.surfaceContainer)
124
- .padding(
125
- start = Dimens .notificationBannerStartPadding,
126
- end = Dimens .notificationBannerEndPadding,
127
- top = Dimens .smallPadding,
128
- bottom = Dimens .smallPadding,
129
- )
130
- .animateContentSize()
131
- .testTag(NOTIFICATION_BANNER )
132
- ) {
133
- val (status, textTitle, textMessage, actionIcon) = createRefs()
134
- NotificationDot (
135
- statusLevel,
136
- Modifier .constrainAs(status) {
137
- top.linkTo(textTitle.top)
138
- start.linkTo(parent.start)
139
- bottom.linkTo(textTitle.bottom)
140
- },
141
- )
142
- Text (
143
- text = title.toUpperCase(),
144
- modifier =
145
- Modifier .constrainAs(textTitle) {
146
- top.linkTo(parent.top)
147
- start.linkTo(status.end)
148
- if (message != null ) {
149
- bottom.linkTo(textMessage.top)
150
- } else {
151
- bottom.linkTo(parent.bottom)
152
- }
153
- if (action != null ) {
154
- end.linkTo(actionIcon.start)
155
- } else {
156
- end.linkTo(parent.end)
157
- }
158
- width = Dimension .fillToConstraints
159
- }
160
- .padding(start = Dimens .smallPadding),
161
- style = MaterialTheme .typography.bodySmall,
162
- color = MaterialTheme .colorScheme.onSurface,
163
- maxLines = 1 ,
164
- overflow = TextOverflow .Ellipsis ,
76
+ if (isTv()) {
77
+ NotificationBannerTv (
78
+ modifier = modifier,
79
+ notification = notification,
80
+ isPlayBuild = isPlayBuild,
81
+ openAppListing = openAppListing,
82
+ onClickShowAccount = onClickShowAccount,
83
+ onClickShowChangelog = onClickShowChangelog,
84
+ onClickDismissChangelog = onClickDismissChangelog,
85
+ onClickDismissNewDevice = onClickDismissNewDevice,
165
86
)
166
- message?.let { message ->
167
- Text (
168
- text = message.text,
169
- modifier =
170
- Modifier .constrainAs(textMessage) {
171
- top.linkTo(textTitle.bottom)
172
- start.linkTo(textTitle.start)
173
- if (action != null ) {
174
- end.linkTo(actionIcon.start)
175
- bottom.linkTo(parent.bottom)
176
- } else {
177
- end.linkTo(parent.end)
178
- bottom.linkTo(parent.bottom)
179
- }
180
- width = Dimension .fillToConstraints
181
- height = Dimension .wrapContent
182
- }
183
- .padding(start = Dimens .smallPadding, top = Dimens .tinyPadding)
184
- .wrapContentWidth(Alignment .Start )
185
- .let {
186
- if (message is NotificationMessage .ClickableText ) {
187
- it.clickable(
188
- onClickLabel = message.contentDescription,
189
- role = Role .Button ,
190
- ) {
191
- message.onClick()
192
- }
193
- .testTag(NOTIFICATION_BANNER_TEXT_ACTION )
194
- } else {
195
- it
196
- }
197
- },
198
- color = MaterialTheme .colorScheme.onSurfaceVariant,
199
- style = MaterialTheme .typography.labelMedium,
200
- )
201
- }
202
- action?.let {
203
- NotificationAction (
204
- it.icon,
205
- onClick = it.onClick,
206
- contentDescription = it.contentDescription,
207
- modifier =
208
- Modifier .constrainAs(actionIcon) {
209
- top.linkTo(parent.top)
210
- end.linkTo(parent.end)
211
- bottom.linkTo(parent.bottom)
212
- },
213
- )
214
- }
215
- }
216
- }
217
-
218
- @Composable
219
- private fun NotificationDot (statusLevel : StatusLevel , modifier : Modifier ) {
220
- Box (
221
- modifier =
222
- modifier
223
- .background(
224
- color =
225
- when (statusLevel) {
226
- StatusLevel .Error -> MaterialTheme .colorScheme.error
227
- StatusLevel .Warning -> MaterialTheme .colorScheme.warning
228
- StatusLevel .Info -> MaterialTheme .colorScheme.tertiary
229
- },
230
- shape = CircleShape ,
231
- )
232
- .size(Dimens .notificationStatusIconSize)
233
- )
234
- }
235
-
236
- @Composable
237
- private fun NotificationAction (
238
- imageVector : ImageVector ,
239
- contentDescription : String? ,
240
- onClick : () -> Unit ,
241
- modifier : Modifier = Modifier ,
242
- ) {
243
-
244
- IconButton (modifier = modifier.testTag(NOTIFICATION_BANNER_ACTION ), onClick = onClick) {
245
- Icon (
246
- modifier = Modifier .padding(Dimens .notificationIconPadding),
247
- imageVector = imageVector,
248
- contentDescription = contentDescription,
249
- tint = MaterialTheme .colorScheme.onSurface,
87
+ } else {
88
+ AnimatedNotificationBanner (
89
+ modifier = modifier,
90
+ notificationModifier = Modifier .fillMaxWidth(),
91
+ notification = notification,
92
+ isPlayBuild = isPlayBuild,
93
+ openAppListing = openAppListing,
94
+ onClickShowAccount = onClickShowAccount,
95
+ onClickShowChangelog = onClickShowChangelog,
96
+ onClickDismissChangelog = onClickDismissChangelog,
97
+ onClickDismissNewDevice = onClickDismissNewDevice,
250
98
)
251
99
}
252
100
}
0 commit comments