Skip to content

Commit 09f49c5

Browse files
rlepinskicrowUlrico972
authored
Release 18.0.0 (#1454)
* Release 18.0.0 * Fix PC parsing * Footer fix * Fixes * Spots * Fixes * spots * Update CHANGELOG.md Co-authored-by: Ulrich Giberné <[email protected]> * Update CHANGELOG.md --------- Co-authored-by: crow <[email protected]> Co-authored-by: Ulrich Giberné <[email protected]>
1 parent 520581f commit 09f49c5

File tree

9 files changed

+362
-145
lines changed

9 files changed

+362
-145
lines changed

Diff for: CHANGELOG.md

+22
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,28 @@
22

33
[Migration Guides](https://github.com/urbanairship/android-library/tree/main/documentation/migration)
44

5+
## Version 18.0.0, June 14, 2024
6+
Major SDK release with several breaking changes.
7+
See the [Migration Guides](https://github.com/urbanairship/android-library/tree/main/documentation/migration/migration-guide-17-18.md) for more info.
8+
9+
### Changes
10+
- The Airship SDK now requires `compileSdk` version 34 (Android 14) or higher.
11+
- New Automation module
12+
- Check schedule’s start date before executing, to better handle updates to the scheduled start date
13+
- Improved image loading for In-App messages, Scenes, and Surveys
14+
- Reset GIF animations on visibility change in Scenes and Surveys
15+
- Pause Story progress while videos are loading
16+
- Concurrent automation processing to reduce latency if more than one automation is triggered at the same time
17+
- Embedded Scenes & Survey support
18+
- New module `urbanairship-automation-compose` to support embedding a Scene & Survey in compose
19+
- Added new compound triggers and IAX event triggers
20+
- Ban lists support
21+
- Added new `PrivacyManager.Feature.FEATURE_FLAGS` to control access to feature flags
22+
- Added support for multiple deferred feature flag resolution
23+
- Added contact management support in preference centers
24+
- Migrated to non-transitive R classes
25+
- Removed `urbanairship-ads-identifier` and `urbanairship-preference` modules
26+
527
## Version 17.8.1, May 13, 2024
628
Patch release that improves first run display times for Scenes, Surveys, and In-App Automations.
729

Diff for: urbanairship-preference-center/src/main/java/com/urbanairship/preferencecenter/data/Item.kt

+53-85
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,6 @@ public sealed class Item(
149149
val addPrompt: AddPrompt,
150150
val removePrompt: RemovePrompt,
151151
val emptyLabel: String?,
152-
val registrationOptions: RegistrationOptions,
153152
override val conditions: Conditions,
154153
) : Item(
155154
type = TYPE_CONTACT_MANAGEMENT,
@@ -160,52 +159,67 @@ public sealed class Item(
160159
@Throws(JsonException::class)
161160
override fun toJson(): JsonMap =
162161
jsonMapBuilder()
163-
.put(KEY_PLATFORM, platform.toJson())
162+
.apply {
163+
when (platform) {
164+
is Platform.Sms -> {
165+
this.put(KEY_PLATFORM, PLATFORM_SMS)
166+
.put(KEY_REGISTRATION_OPTIONS, platform.registrationOptions.toJson())
167+
}
168+
is Platform.Email -> {
169+
this.put(KEY_PLATFORM, PLATFORM_EMAIL)
170+
.put(KEY_REGISTRATION_OPTIONS, platform.registrationOptions.toJson())
171+
}
172+
}
173+
}
164174
.put(KEY_ADD, addPrompt.toJson())
165175
.put(KEY_REMOVE, removePrompt.toJson())
166176
.put(KEY_EMPTY_LABEL, emptyLabel)
167-
.put(KEY_REGISTRATION_OPTIONS, registrationOptions.toJson())
168177
.build()
169178

170179
internal companion object {
180+
181+
private const val PLATFORM_SMS = "sms"
182+
private const val PLATFORM_EMAIL = "email"
183+
171184
@Throws(JsonException::class)
172185
fun fromJson(json: JsonMap): ContactManagement {
173186
return ContactManagement(
174187
id = json.requireField(KEY_ID),
175-
platform = Platform.fromJson(json.requireField(KEY_PLATFORM)),
188+
platform = json.requireField<String>(KEY_PLATFORM).let {
189+
when(it) {
190+
PLATFORM_SMS -> Platform.Sms(RegistrationOptions.Sms.fromJson(json.requireField(KEY_REGISTRATION_OPTIONS)))
191+
PLATFORM_EMAIL -> Platform.Email(RegistrationOptions.Email.fromJson(json.requireField(KEY_REGISTRATION_OPTIONS)))
192+
else -> throw JsonException("Invalid registration type: $it")
193+
}
194+
},
176195
display = CommonDisplay.parse(json.requireField<JsonMap>(KEY_DISPLAY)),
177196
addPrompt = AddPrompt.fromJson(json.requireField(KEY_ADD)),
178197
removePrompt = RemovePrompt.fromJson(json.requireField(KEY_REMOVE)),
179198
emptyLabel = json.optionalField(KEY_EMPTY_LABEL),
180-
registrationOptions = RegistrationOptions.fromJson(json.requireField(KEY_REGISTRATION_OPTIONS)),
181-
conditions = Condition.parse(json.requireField<JsonValue>(KEY_CONDITIONS))
199+
conditions = Condition.parse(json.opt(KEY_CONDITIONS))
182200
)
183201
}
184202
}
185203

186-
public enum class Platform(public val jsonValue: String) {
187-
SMS("sms"),
188-
EMAIL("email");
189-
190-
public fun toJson(): JsonValue = JsonValue.wrap(jsonValue)
204+
public sealed class Platform(public val channelType: ChannelType) {
205+
public class Sms(public val registrationOptions: RegistrationOptions.Sms): Platform(ChannelType.SMS)
206+
public class Email(public val registrationOptions: RegistrationOptions.Email): Platform(ChannelType.EMAIL)
191207

192-
internal companion object {
193-
@Throws(JsonException::class)
194-
fun fromJson(jsonValue: JsonValue): Platform {
195-
val valueString = jsonValue.optString()
196-
for (platform in entries) {
197-
if (platform.jsonValue.equals(valueString, true)) {
198-
return platform
199-
}
208+
internal val resendOptions: ResendOptions
209+
get() {
210+
return when (this) {
211+
is Sms -> this.registrationOptions.resendOptions
212+
is Email -> this.registrationOptions.resendOptions
200213
}
201-
throw JsonException("Invalid platform: $valueString")
202214
}
203-
}
204215

205-
internal fun toChannelType(): ChannelType = when (this) {
206-
SMS -> ChannelType.SMS
207-
EMAIL -> ChannelType.EMAIL
208-
}
216+
internal val errorMessages: ErrorMessages
217+
get() {
218+
return when (this) {
219+
is Sms -> this.registrationOptions.errorMessages
220+
is Email -> this.registrationOptions.errorMessages
221+
}
222+
}
209223
}
210224

211225
public data class AddPrompt(
@@ -254,6 +268,7 @@ public sealed class Item(
254268
val type: String,
255269
val display: PromptDisplay,
256270
val submitButton: LabeledButton,
271+
val closeButton: IconButton?,
257272
val cancelButton: LabeledButton?,
258273
val onSubmit: ActionableMessage?,
259274
) {
@@ -263,6 +278,7 @@ public sealed class Item(
263278
KEY_DISPLAY to display.toJson(),
264279
KEY_SUBMIT_BUTTON to submitButton.toJson(),
265280
KEY_CANCEL_BUTTON to cancelButton?.toJson(),
281+
KEY_CLOSE_BUTTON to closeButton?.toJson(),
266282
KEY_ON_SUBMIT to onSubmit?.toJson(),
267283
)
268284

@@ -273,6 +289,7 @@ public sealed class Item(
273289
type = json.requireField(KEY_TYPE),
274290
display = PromptDisplay.fromJson(json.requireField(KEY_DISPLAY)),
275291
submitButton = json.requireMap(KEY_SUBMIT_BUTTON).let { LabeledButton.fromJson(it) },
292+
closeButton = json.optionalMap(KEY_CLOSE_BUTTON)?.let { IconButton.fromJson(it) },
276293
cancelButton = json.optionalMap(KEY_CANCEL_BUTTON)?.let { LabeledButton.fromJson(it) },
277294
onSubmit = json.optionalMap(KEY_ON_SUBMIT)?.let { ActionableMessage.fromJson(it) },
278295
)
@@ -284,6 +301,7 @@ public sealed class Item(
284301
val type: String,
285302
val display: PromptDisplay,
286303
val submitButton: LabeledButton,
304+
val closeButton: IconButton?,
287305
val cancelButton: LabeledButton?,
288306
val onSubmit: ActionableMessage?,
289307
) {
@@ -293,6 +311,7 @@ public sealed class Item(
293311
KEY_DISPLAY to display.toJson(),
294312
KEY_SUBMIT_BUTTON to submitButton.toJson(),
295313
KEY_CANCEL_BUTTON to cancelButton?.toJson(),
314+
KEY_CLOSE_BUTTON to closeButton?.toJson(),
296315
KEY_ON_SUBMIT to onSubmit?.toJson(),
297316
)
298317

@@ -303,57 +322,24 @@ public sealed class Item(
303322
type = json.requireField(KEY_TYPE),
304323
display = PromptDisplay.fromJson(json.requireField(KEY_DISPLAY)),
305324
submitButton = json.requireMap(KEY_SUBMIT_BUTTON).let { LabeledButton.fromJson(it) },
325+
closeButton = json.optionalMap(KEY_CLOSE_BUTTON)?.let { IconButton.fromJson(it) },
306326
cancelButton = json.optionalMap(KEY_CANCEL_BUTTON)?.let { LabeledButton.fromJson(it) },
307327
onSubmit = json.optionalMap(KEY_ON_SUBMIT)?.let { ActionableMessage.fromJson(it) },
308328
)
309329
}
310330
}
311331
}
312332

313-
public data class FormattedText(
314-
val text: String,
315-
val type: FormatType
316-
) {
317-
public enum class FormatType(public val value: String) {
318-
PLAIN("string"),
319-
MARKDOWN("markdown"),
320-
UNKNOWN("unknown");
321-
322-
internal companion object {
323-
fun from(value: String): FormatType =
324-
entries.firstOrNull { it.value.equals(value, true) } ?: UNKNOWN
325-
}
326-
}
327-
328-
internal val isMarkdown = type == FormatType.MARKDOWN
329-
330-
@Throws(JsonException::class)
331-
public fun toJson(): JsonMap = jsonMapOf(
332-
KEY_DESCRIPTION to text,
333-
KEY_TYPE to type.value
334-
)
335-
336-
internal companion object {
337-
@Throws(JsonException::class)
338-
fun fromJson(json: JsonMap): FormattedText {
339-
return FormattedText(
340-
text = json.requireField(KEY_DESCRIPTION),
341-
type = FormatType.from(json.requireField<String>(KEY_TYPE))
342-
)
343-
}
344-
}
345-
}
346-
347333
public data class PromptDisplay(
348334
val title: String,
349335
val description: String?,
350-
val footer: FormattedText?,
336+
val footer: String?,
351337
) {
352338
@Throws(JsonException::class)
353339
public fun toJson(): JsonMap = jsonMapOf(
354340
KEY_TITLE to title,
355341
KEY_DESCRIPTION to description,
356-
KEY_FOOTER to footer?.toJson()
342+
KEY_FOOTER to footer
357343
)
358344

359345
internal companion object {
@@ -365,7 +351,7 @@ public sealed class Item(
365351
return PromptDisplay(
366352
title = json.requireField(KEY_TITLE),
367353
description = json.optionalField(KEY_DESCRIPTION),
368-
footer = json.optionalMap(KEY_FOOTER)?.let { FormattedText.fromJson(it) },
354+
footer = json.optionalField(KEY_FOOTER),
369355
)
370356
}
371357
}
@@ -475,20 +461,21 @@ public sealed class Item(
475461
KEY_INTERVAL to interval,
476462
KEY_MESSAGE to message,
477463
KEY_BUTTON to button.toJson(),
478-
KEY_ON_SUBMIT to onSuccess?.toJson()
464+
KEY_ON_SUCCESS to onSuccess?.toJson()
479465
)
480466

481467
internal companion object {
482468
private const val KEY_INTERVAL = "interval"
483469
private const val KEY_MESSAGE = "message"
470+
private const val KEY_ON_SUCCESS = "on_success"
484471

485472
@Throws(JsonException::class)
486473
fun fromJson(json: JsonMap): ResendOptions {
487474
return ResendOptions(
488475
interval = json.requireField(KEY_INTERVAL),
489476
message = json.requireField(KEY_MESSAGE),
490477
button = LabeledButton.fromJson(json.requireField(KEY_BUTTON)),
491-
onSuccess = json.optionalMap(KEY_ON_SUBMIT)?.let { ActionableMessage.fromJson(it) }
478+
onSuccess = json.optionalMap(KEY_ON_SUCCESS)?.let { ActionableMessage.fromJson(it) }
492479
)
493480
}
494481
}
@@ -572,17 +559,6 @@ public sealed class Item(
572559
}
573560
}
574561
}
575-
576-
internal companion object {
577-
@Throws(JsonException::class)
578-
fun fromJson(json: JsonMap): RegistrationOptions {
579-
return when (val type = json.requireField<String>(KEY_TYPE)) {
580-
Platform.SMS.jsonValue -> Sms.fromJson(json)
581-
Platform.EMAIL.jsonValue -> Email.fromJson(json)
582-
else -> throw JsonException("Invalid registration type: $type")
583-
}
584-
}
585-
}
586562
}
587563

588564
public data class SmsSenderInfo(
@@ -644,6 +620,7 @@ public sealed class Item(
644620
private const val KEY_ON_SUBMIT = "on_submit"
645621
private const val KEY_CANCEL_BUTTON = "cancel_button"
646622
private const val KEY_SUBMIT_BUTTON = "submit_button"
623+
private const val KEY_CLOSE_BUTTON = "close_button"
647624

648625
private const val KEY_NAME = "name"
649626
private const val KEY_DESCRIPTION = "description"
@@ -695,16 +672,7 @@ public sealed class Item(
695672
button = Button.parse(json.optionalField(KEY_BUTTON)),
696673
conditions = Condition.parse(json.get(KEY_CONDITIONS))
697674
)
698-
TYPE_CONTACT_MANAGEMENT -> ContactManagement(
699-
id = id,
700-
platform = ContactManagement.Platform.fromJson(json.requireField(KEY_PLATFORM)),
701-
display = CommonDisplay.parse(json.get(KEY_DISPLAY)),
702-
emptyLabel = json.optionalField(KEY_EMPTY_LABEL),
703-
conditions = Condition.parse(json.get(KEY_CONDITIONS)),
704-
addPrompt = ContactManagement.AddPrompt.fromJson(json.requireField(KEY_ADD)),
705-
removePrompt = ContactManagement.RemovePrompt.fromJson(json.requireField(KEY_REMOVE)),
706-
registrationOptions = ContactManagement.RegistrationOptions.fromJson(json.requireField(KEY_REGISTRATION_OPTIONS))
707-
)
675+
TYPE_CONTACT_MANAGEMENT -> ContactManagement.fromJson(json)
708676
else -> throw JsonException("Unknown Preference Center Item type: '$type'")
709677
}
710678
}

Diff for: urbanairship-preference-center/src/main/java/com/urbanairship/preferencecenter/ui/PreferenceCenterFragment.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ public class PreferenceCenterFragment : Fragment(R.layout.ua_fragment_preference
235235
is Effect.ShowContactManagementRemoveDialog ->
236236
showContactManagementRemoveDialog(effect.item, effect.channel, viewModel::handle)
237237
is Effect.ShowChannelVerificationResentDialog ->
238-
effect.item.registrationOptions.resendOptions.onSuccess?.let { message ->
238+
effect.item.platform.resendOptions.onSuccess?.let { message ->
239239
showContactManagementResentDialog(message)
240240
}
241241
Effect.DismissContactManagementAddDialog ->

Diff for: urbanairship-preference-center/src/main/java/com/urbanairship/preferencecenter/ui/PreferenceCenterViewModel.kt

+4-4
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ internal class PreferenceCenterViewModel @JvmOverloads constructor(
206206
)
207207
}
208208
} else {
209-
val message = action.item.registrationOptions.errorMessages.invalidMessage
209+
val message = action.item.platform.errorMessages.invalidMessage
210210
Effect.ShowContactManagementAddDialogError(message)
211211
}
212212
)
@@ -225,14 +225,14 @@ internal class PreferenceCenterViewModel @JvmOverloads constructor(
225225
} ?: emptyFlow()
226226
}
227227
is Action.RegisterChannel.Email -> {
228-
val options = action.item.registrationOptions as? RegistrationOptions.Email
228+
val emailPlatform = action.item.platform as? Item.ContactManagement.Platform.Email
229229

230230
contact.registerEmail(
231231
action.address,
232232
EmailRegistrationOptions.options(
233233
transactionalOptedIn = null,
234234
doubleOptIn = true,
235-
properties = options?.properties
235+
properties = emailPlatform?.registrationOptions?.properties
236236
)
237237
)
238238

@@ -251,7 +251,7 @@ internal class PreferenceCenterViewModel @JvmOverloads constructor(
251251
ContactChannelState(showPendingButton = true, showResendButton = false)
252252
))
253253

254-
val resendInterval = action.item.registrationOptions.resendOptions.interval.seconds
254+
val resendInterval = action.item.platform.resendOptions.interval.seconds
255255
val resendDelay = resendInterval.coerceAtLeast(defaultResendLabelHideDelay)
256256
delay(resendDelay)
257257

0 commit comments

Comments
 (0)