@loyalytics/swan-react-native-sdk 2.5.0 → 2.5.1-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,6 +7,7 @@ buildscript {
7
7
  mavenCentral()
8
8
  }
9
9
  dependencies {
10
+ classpath("com.android.tools.build:gradle:${safeExtGet('androidGradlePluginVersion', '8.2.2')}")
10
11
  classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${safeExtGet('kotlinVersion', '1.9.22')}")
11
12
  }
12
13
  }
@@ -19,7 +20,7 @@ android {
19
20
  compileSdkVersion safeExtGet('compileSdkVersion', 34)
20
21
 
21
22
  defaultConfig {
22
- minSdkVersion safeExtGet('minSdkVersion', 21)
23
+ minSdkVersion safeExtGet('minSdkVersion', 24)
23
24
  targetSdkVersion safeExtGet('targetSdkVersion', 34)
24
25
  }
25
26
 
@@ -29,6 +30,12 @@ android {
29
30
  }
30
31
  }
31
32
 
33
+ testOptions {
34
+ unitTests {
35
+ includeAndroidResources = true
36
+ }
37
+ }
38
+
32
39
  def javaVersion = safeExtGet('jvmTargetVersion', '17')
33
40
 
34
41
  compileOptions {
@@ -55,6 +62,18 @@ repositories {
55
62
  mavenCentral()
56
63
  }
57
64
 
65
+ // For standalone builds (CI, local `./gradlew test`), react-native:+ can't resolve
66
+ // because the host app's Maven repo isn't available. Substitute with react-android
67
+ // from Maven Central. When consumed by an app, rootProject.name != 'swan-android'
68
+ // so this block is skipped and the app's own dependency resolution applies.
69
+ if (rootProject.name == 'swan-android') {
70
+ configurations.all {
71
+ resolutionStrategy.dependencySubstitution {
72
+ substitute module('com.facebook.react:react-native') using module('com.facebook.react:react-android:0.76.2')
73
+ }
74
+ }
75
+ }
76
+
58
77
  dependencies {
59
78
  implementation "com.facebook.react:react-native:+"
60
79
  implementation "org.jetbrains.kotlin:kotlin-stdlib:${safeExtGet('kotlinVersion', '1.9.22')}"
@@ -63,4 +82,7 @@ dependencies {
63
82
 
64
83
  testImplementation "junit:junit:4.13.2"
65
84
  testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3"
85
+ testImplementation "org.robolectric:robolectric:4.14.1"
86
+ testImplementation "androidx.test:core:1.6.1"
87
+ testImplementation "org.json:json:20231013"
66
88
  }
@@ -0,0 +1 @@
1
+ android.useAndroidX=true
@@ -0,0 +1 @@
1
+ rootProject.name = 'swan-android'
@@ -5,6 +5,7 @@ import android.content.Context
5
5
  import android.graphics.Bitmap
6
6
  import android.widget.RemoteViews
7
7
  import com.loyalytics.swan.R
8
+ import org.json.JSONObject
8
9
 
9
10
  /**
10
11
  * Builds RemoteViews for auto-carousel using ViewFlipper.
@@ -12,6 +13,9 @@ import com.loyalytics.swan.R
12
13
  * All images are added as children to the ViewFlipper which handles
13
14
  * cycling natively with slide animations. No AlarmManager needed.
14
15
  *
16
+ * Each child embeds per-item title and body text alongside the image,
17
+ * so text rotates together with the corresponding image.
18
+ *
15
19
  * The entire flipper area gets a single click action (auto-carousel
16
20
  * uses one deep link for the whole notification, same as CleverTap).
17
21
  *
@@ -31,6 +35,7 @@ object CarouselAutoRemoteViews {
31
35
  notificationId: Int,
32
36
  title: String,
33
37
  body: String,
38
+ items: List<JSONObject>,
34
39
  bitmaps: List<Bitmap?>,
35
40
  intervalMs: Int,
36
41
  messageId: String,
@@ -44,12 +49,16 @@ object CarouselAutoRemoteViews {
44
49
 
45
50
  // autoStart and flipInterval are set in XML (RemoteViews doesn't allow setAutoStart)
46
51
 
47
- // Add each bitmap as a child of the ViewFlipper
52
+ // Add each item as a child of the ViewFlipper with image + title + body
48
53
  removeAllViews(R.id.swan_carousel_flipper)
49
- val validBitmaps = bitmaps.filterNotNull().take(MAX_FLIPPER_IMAGES)
50
- for (bitmap in validBitmaps) {
51
- val childView = RemoteViews(packageName, R.layout.swan_carousel_flipper_item)
52
- childView.setImageViewBitmap(R.id.swan_carousel_flipper_image, bitmap)
54
+ val limit = minOf(items.size, bitmaps.size, MAX_FLIPPER_IMAGES)
55
+ for (i in 0 until limit) {
56
+ val bitmap = bitmaps[i] ?: continue
57
+ val item = items[i]
58
+ val childView = RemoteViews(packageName, R.layout.swan_carousel_auto_flipper_item)
59
+ childView.setImageViewBitmap(R.id.swan_carousel_auto_flipper_image, bitmap)
60
+ childView.setTextViewText(R.id.swan_carousel_auto_flipper_title, item.optString("title", ""))
61
+ childView.setTextViewText(R.id.swan_carousel_auto_flipper_body, item.optString("body", ""))
53
62
  addView(R.id.swan_carousel_flipper, childView)
54
63
  }
55
64
 
@@ -62,9 +71,10 @@ object CarouselAutoRemoteViews {
62
71
  )
63
72
  }
64
73
 
74
+ val firstItemTitle = items.firstOrNull()?.optString("title", "") ?: ""
65
75
  val collapsed = RemoteViews(packageName, R.layout.swan_carousel_collapsed).apply {
66
76
  setTextViewText(R.id.swan_carousel_collapsed_title, title)
67
- setTextViewText(R.id.swan_carousel_collapsed_body, body)
77
+ setTextViewText(R.id.swan_carousel_collapsed_body, firstItemTitle.ifEmpty { body })
68
78
  }
69
79
 
70
80
  return AutoCarouselViews(expanded, collapsed)
@@ -28,6 +28,8 @@ object CarouselRemoteViews {
28
28
  notificationId: Int,
29
29
  title: String,
30
30
  body: String,
31
+ itemTitle: String,
32
+ itemBody: String,
31
33
  bitmaps: List<Bitmap?>,
32
34
  currentIndex: Int,
33
35
  totalItems: Int,
@@ -39,6 +41,8 @@ object CarouselRemoteViews {
39
41
  val expanded = RemoteViews(packageName, R.layout.swan_carousel_expanded).apply {
40
42
  setTextViewText(R.id.swan_carousel_title, title)
41
43
  setTextViewText(R.id.swan_carousel_body, body)
44
+ setTextViewText(R.id.swan_carousel_item_title, itemTitle)
45
+ setTextViewText(R.id.swan_carousel_item_body, itemBody)
42
46
  setTextViewText(R.id.swan_carousel_counter, "${currentIndex + 1} / $totalItems")
43
47
 
44
48
  // Populate ViewFlipper with all images
@@ -92,7 +96,7 @@ object CarouselRemoteViews {
92
96
 
93
97
  val collapsed = RemoteViews(packageName, R.layout.swan_carousel_collapsed).apply {
94
98
  setTextViewText(R.id.swan_carousel_collapsed_title, title)
95
- setTextViewText(R.id.swan_carousel_collapsed_body, body)
99
+ setTextViewText(R.id.swan_carousel_collapsed_body, itemTitle.ifEmpty { body })
96
100
  }
97
101
 
98
102
  return CarouselViews(expanded, collapsed)
@@ -321,7 +321,9 @@ class CarouselTemplate : SwanNotificationTemplate {
321
321
  context = context,
322
322
  notificationId = notificationId,
323
323
  title = title,
324
- body = currentItem.optString("title", body),
324
+ body = body,
325
+ itemTitle = currentItem.optString("title", ""),
326
+ itemBody = currentItem.optString("body", ""),
325
327
  bitmaps = scaledBitmaps,
326
328
  currentIndex = currentIndex,
327
329
  totalItems = items.size,
@@ -398,6 +400,7 @@ class CarouselTemplate : SwanNotificationTemplate {
398
400
  notificationId = notificationId,
399
401
  title = title,
400
402
  body = body,
403
+ items = items,
401
404
  bitmaps = scaledBitmaps,
402
405
  intervalMs = intervalMs,
403
406
  messageId = messageId,
@@ -517,7 +520,9 @@ class CarouselTemplate : SwanNotificationTemplate {
517
520
  }
518
521
  else -> {
519
522
  expanded.setViewPadding(R.id.swan_carousel_header, px16, 0, px16, px8)
520
- expanded.setViewPadding(R.id.swan_carousel_counter, 0, px2, px16, px4)
523
+ expanded.setViewPadding(R.id.swan_carousel_item_title, px16, 0, px16, 0)
524
+ expanded.setViewPadding(R.id.swan_carousel_item_body, px16, 0, px16, 0)
525
+ expanded.setViewPadding(R.id.swan_carousel_counter, px16, 0, px16, 0)
521
526
  }
522
527
  }
523
528
  }
@@ -41,7 +41,7 @@
41
41
  <ViewFlipper
42
42
  android:id="@+id/swan_carousel_flipper"
43
43
  android:layout_width="match_parent"
44
- android:layout_height="144dp"
44
+ android:layout_height="wrap_content"
45
45
  android:layout_below="@id/swan_carousel_auto_header"
46
46
  android:inAnimation="@anim/swan_slide_in_right"
47
47
  android:outAnimation="@anim/swan_slide_out_left"
@@ -0,0 +1,33 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
3
+ android:layout_width="match_parent"
4
+ android:layout_height="wrap_content"
5
+ android:orientation="vertical">
6
+
7
+ <ImageView
8
+ android:id="@+id/swan_carousel_auto_flipper_image"
9
+ android:layout_width="match_parent"
10
+ android:layout_height="144dp"
11
+ android:scaleType="centerCrop"
12
+ android:contentDescription="Carousel image" />
13
+
14
+ <TextView
15
+ android:id="@+id/swan_carousel_auto_flipper_title"
16
+ android:layout_width="match_parent"
17
+ android:layout_height="wrap_content"
18
+ android:textSize="13sp"
19
+ android:textStyle="bold"
20
+ android:textColor="#DE000000"
21
+ android:maxLines="1"
22
+ android:ellipsize="end"
23
+ android:paddingTop="4dp" />
24
+
25
+ <TextView
26
+ android:id="@+id/swan_carousel_auto_flipper_body"
27
+ android:layout_width="match_parent"
28
+ android:layout_height="wrap_content"
29
+ android:textSize="12sp"
30
+ android:textColor="#8A000000"
31
+ android:maxLines="1"
32
+ android:ellipsize="end" />
33
+ </LinearLayout>
@@ -78,17 +78,51 @@
78
78
  android:contentDescription="Next" />
79
79
  </RelativeLayout>
80
80
 
81
- <!-- Counter -->
82
- <TextView
83
- android:id="@+id/swan_carousel_counter"
84
- android:layout_width="wrap_content"
81
+ <!-- Item title + body + counter -->
82
+ <LinearLayout
83
+ android:layout_width="match_parent"
85
84
  android:layout_height="wrap_content"
86
- android:layout_gravity="end"
87
- android:textSize="11sp"
88
- android:textColor="#8A000000"
85
+ android:orientation="horizontal"
89
86
  android:paddingStart="0dp"
90
87
  android:paddingEnd="0dp"
91
- android:paddingTop="2dp"
92
- android:paddingBottom="4dp" />
88
+ android:paddingTop="4dp"
89
+ android:paddingBottom="4dp"
90
+ android:gravity="center_vertical">
91
+
92
+ <LinearLayout
93
+ android:layout_width="0dp"
94
+ android:layout_height="wrap_content"
95
+ android:layout_weight="1"
96
+ android:orientation="vertical">
97
+
98
+ <TextView
99
+ android:id="@+id/swan_carousel_item_title"
100
+ android:layout_width="match_parent"
101
+ android:layout_height="wrap_content"
102
+ android:textSize="13sp"
103
+ android:textStyle="bold"
104
+ android:textColor="#DE000000"
105
+ android:maxLines="1"
106
+ android:ellipsize="end" />
107
+
108
+ <TextView
109
+ android:id="@+id/swan_carousel_item_body"
110
+ android:layout_width="match_parent"
111
+ android:layout_height="wrap_content"
112
+ android:textSize="12sp"
113
+ android:textColor="#8A000000"
114
+ android:maxLines="1"
115
+ android:ellipsize="end" />
116
+ </LinearLayout>
117
+
118
+ <TextView
119
+ android:id="@+id/swan_carousel_counter"
120
+ android:layout_width="wrap_content"
121
+ android:layout_height="wrap_content"
122
+ android:textSize="11sp"
123
+ android:textColor="#8A000000"
124
+ android:paddingStart="8dp"
125
+ android:paddingEnd="0dp" />
126
+ </LinearLayout>
93
127
 
94
128
  </LinearLayout>
@@ -8,5 +8,5 @@ exports.SDK_VERSION = void 0;
8
8
  // This file is generated from package.json version during build.
9
9
  // See scripts/generate-version.js
10
10
 
11
- const SDK_VERSION = exports.SDK_VERSION = '2.5.0';
11
+ const SDK_VERSION = exports.SDK_VERSION = '2.5.1-beta.1';
12
12
  //# sourceMappingURL=version.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["SDK_VERSION","exports"],"sourceRoot":"../../src","sources":["version.ts"],"mappings":";;;;;;AAAA;AACA;AACA;;AAEO,MAAMA,WAAW,GAAAC,OAAA,CAAAD,WAAA,GAAG,OAAO","ignoreList":[]}
1
+ {"version":3,"names":["SDK_VERSION","exports"],"sourceRoot":"../../src","sources":["version.ts"],"mappings":";;;;;;AAAA;AACA;AACA;;AAEO,MAAMA,WAAW,GAAAC,OAAA,CAAAD,WAAA,GAAG,cAAc","ignoreList":[]}
@@ -4,5 +4,5 @@
4
4
  // This file is generated from package.json version during build.
5
5
  // See scripts/generate-version.js
6
6
 
7
- export const SDK_VERSION = '2.5.0';
7
+ export const SDK_VERSION = '2.5.1-beta.1';
8
8
  //# sourceMappingURL=version.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["SDK_VERSION"],"sourceRoot":"../../src","sources":["version.ts"],"mappings":";;AAAA;AACA;AACA;;AAEA,OAAO,MAAMA,WAAW,GAAG,OAAO","ignoreList":[]}
1
+ {"version":3,"names":["SDK_VERSION"],"sourceRoot":"../../src","sources":["version.ts"],"mappings":";;AAAA;AACA;AACA;;AAEA,OAAO,MAAMA,WAAW,GAAG,cAAc","ignoreList":[]}
@@ -1,2 +1,2 @@
1
- export declare const SDK_VERSION = "2.5.0";
1
+ export declare const SDK_VERSION = "2.5.1-beta.1";
2
2
  //# sourceMappingURL=version.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../../../../src/version.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,WAAW,UAAU,CAAC"}
1
+ {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../../../../src/version.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,WAAW,iBAAiB,CAAC"}
@@ -1,2 +1,2 @@
1
- export declare const SDK_VERSION = "2.5.0";
1
+ export declare const SDK_VERSION = "2.5.1-beta.1";
2
2
  //# sourceMappingURL=version.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../../../../src/version.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,WAAW,UAAU,CAAC"}
1
+ {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../../../../src/version.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,WAAW,iBAAiB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loyalytics/swan-react-native-sdk",
3
- "version": "2.5.0",
3
+ "version": "2.5.1-beta.1",
4
4
  "description": "React Native SDK for Swan",
5
5
  "source": "./src/index.tsx",
6
6
  "main": "./lib/commonjs/index.js",