Skip to content

Commit 1f928f0

Browse files
authored
Merge pull request #1506 from braintree/payment-buttons-feature
UI components feature
2 parents a5ab54f + cdb74b2 commit 1f928f0

File tree

55 files changed

+2206
-48
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+2206
-48
lines changed

.github/workflows/release.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,18 @@ jobs:
150150
uses: ./.github/actions/unit_test_module
151151
with:
152152
module: ThreeDSecure
153+
unit_test_ui_components:
154+
name: UIComponents Unit Tests
155+
runs-on: ubuntu-latest
156+
steps:
157+
- name: Checkout Repository
158+
uses: actions/checkout@v2
159+
- name: Setup Java
160+
uses: ./.github/actions/setup
161+
- name: Run Unit Tests
162+
uses: ./.github/actions/unit_test_module
163+
with:
164+
module: UIComponents
153165
unit_test_venmo:
154166
name: Venmo Unit Tests
155167
runs-on: ubuntu-latest
@@ -200,6 +212,7 @@ jobs:
200212
unit_test_paypal_messaging,
201213
unit_test_shopper_insights,
202214
unit_test_three_d_secure,
215+
unit_test_ui_components,
203216
unit_test_venmo,
204217
unit_test_visa_checkout,
205218
unit_test_sepa_direct_debit

.github/workflows/release_snapshot.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,18 @@ jobs:
150150
uses: ./.github/actions/unit_test_module
151151
with:
152152
module: ThreeDSecure
153+
unit_test_ui_components:
154+
name: UIComponents Unit Tests
155+
runs-on: ubuntu-latest
156+
steps:
157+
- name: Checkout Repository
158+
uses: actions/checkout@v2
159+
- name: Setup Java
160+
uses: ./.github/actions/setup
161+
- name: Run Unit Tests
162+
uses: ./.github/actions/unit_test_module
163+
with:
164+
module: UIComponents
153165
unit_test_venmo:
154166
name: Venmo Unit Tests
155167
runs-on: ubuntu-latest
@@ -200,6 +212,7 @@ jobs:
200212
unit_test_paypal_messaging,
201213
unit_test_shopper_insights,
202214
unit_test_three_d_secure,
215+
unit_test_ui_components,
203216
unit_test_venmo,
204217
unit_test_visa_checkout,
205218
unit_test_sepa_direct_debit

BraintreeCore/src/main/java/com/braintreepayments/api/core/AnalyticsParamRepository.kt

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,28 +31,19 @@ class AnalyticsParamRepository(
3131
*/
3232
var didSdkAttemptAppSwitch: Boolean? = null
3333

34-
private lateinit var _sessionId: String
35-
3634
/**
3735
* Session ID to tie analytics events together which is used for reporting conversion funnels.
3836
*/
39-
val sessionId: String
40-
get() {
41-
if (!this::_sessionId.isInitialized) {
42-
_sessionId = uuidHelper.formattedUUID
43-
}
44-
return _sessionId
45-
}
37+
val sessionId: String = uuidHelper.formattedUUID
4638

4739
/**
48-
* Resets the [sessionId] and clears all other repository values.
40+
* Clears all repository values.
4941
*
5042
* Note that this function is called in different spots of the SDK lifecycle for different payment modules. Some
5143
* modules call reset during launch of the SDK. The PayPal module calls reset at the end of the payment flow to
5244
* persist the [sessionId] value set from the Shopper Insights module.
5345
*/
5446
fun reset() {
55-
_sessionId = uuidHelper.formattedUUID
5647
linkType = null
5748
didEnablePayPalAppSwitch = null
5849
didPayPalServerAttemptAppSwitch = null

BraintreeCore/src/test/java/com/braintreepayments/api/core/AnalyticsParamRepositoryUnitTest.kt

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,8 @@ class AnalyticsParamRepositoryUnitTest {
1818
@Before
1919
fun setUp() {
2020
uuidHelper = mockk()
21-
sut = AnalyticsParamRepository(uuidHelper)
22-
2321
every { uuidHelper.formattedUUID } returnsMany listOf(uuid, newUuid)
22+
sut = AnalyticsParamRepository(uuidHelper)
2423

2524
sut.didPayPalServerAttemptAppSwitch = true
2625
sut.didSdkAttemptAppSwitch = true
@@ -39,15 +38,15 @@ class AnalyticsParamRepositoryUnitTest {
3938
}
4039

4140
@Test
42-
fun `invoking reset resets all of the repository's values`() {
41+
fun `invoking reset resets all of the repository's values except sessionId`() {
4342
assertEquals(uuid, sut.sessionId)
4443
assertEquals(true, sut.didPayPalServerAttemptAppSwitch)
4544
assertEquals(true, sut.didEnablePayPalAppSwitch)
4645
assertEquals(true, sut.didSdkAttemptAppSwitch)
4746

4847
sut.reset()
4948

50-
assertEquals(newUuid, sut.sessionId)
49+
assertEquals(uuid, sut.sessionId)
5150
assertNull(sut.didPayPalServerAttemptAppSwitch)
5251
assertNull(sut.didEnablePayPalAppSwitch)
5352
assertNull(sut.didSdkAttemptAppSwitch)

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Braintree Android SDK Release Notes
22

3+
## unreleased
4+
5+
* UIComponents
6+
* Add `PayPalButton` class to generate a PayPal-branded button for launching PayPal flow
7+
* Add `VenmoButton` class to generate a Venmo-branded button for launching Venmo flow
8+
39
## 5.20.0 (2025-12-17)
410

511
* All Modules

Demo/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ dependencies {
7878
implementation project(':ShopperInsights')
7979
implementation project(':SEPADirectDebit')
8080
implementation project(':ThreeDSecure')
81+
implementation project(':UIComponents')
8182
implementation project(':Venmo')
8283
implementation project(':VisaCheckout')
8384

@@ -103,6 +104,7 @@ dependencies {
103104

104105
androidTestImplementation libs.device.automator
105106
androidTestImplementation project(':TestUtils')
107+
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
106108

107109
testImplementation libs.junit
108110
testImplementation libs.test.parameter.injector
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
package com.braintreepayments.demo.test
2+
3+
import android.content.res.ColorStateList
4+
import android.graphics.Paint
5+
import android.graphics.drawable.GradientDrawable
6+
import android.graphics.drawable.LayerDrawable
7+
import android.os.Build
8+
import android.view.View
9+
import androidx.test.espresso.Espresso
10+
import androidx.test.espresso.NoMatchingViewException
11+
import androidx.test.espresso.ViewAssertion
12+
import androidx.test.espresso.action.ViewActions
13+
import androidx.test.espresso.matcher.ViewMatchers
14+
import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner
15+
import com.braintreepayments.AutomatorAction
16+
import com.braintreepayments.DeviceAutomator
17+
import com.braintreepayments.UiObjectMatcher
18+
import com.braintreepayments.api.uicomponents.PayPalButton
19+
import com.braintreepayments.api.uicomponents.VenmoButton
20+
import com.braintreepayments.demo.R
21+
import com.braintreepayments.demo.test.utilities.TestHelper
22+
import java.util.Objects
23+
import org.junit.Assert
24+
import org.junit.Before
25+
import org.junit.Test
26+
import org.junit.runner.RunWith
27+
28+
@RunWith(AndroidJUnit4ClassRunner::class)
29+
class PaymentButtonsColorTest : TestHelper() {
30+
@Before
31+
override fun setup() {
32+
super.setup()
33+
launchApp()
34+
DeviceAutomator.onDevice(UiObjectMatcher.withText("Payment Buttons")).waitForEnabled()
35+
.perform(AutomatorAction.click())
36+
DeviceAutomator.onDevice(UiObjectMatcher.withResourceId("com.braintreepayments.demo:id/button_pp_blue"))
37+
.waitForExists()
38+
}
39+
40+
@Suppress("TooGenericExceptionThrown")
41+
private fun getColorFromDrawable(drawable: LayerDrawable): Int {
42+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
43+
/*Assumes knowledge of the internals of the implementation, might be fragile.
44+
* Layer 0 - Focus drawable
45+
* Layer 1 - Gradient drawable for the background
46+
*/
47+
val gradientDrawable = drawable.getDrawable(1) as GradientDrawable
48+
return Objects.requireNonNull<ColorStateList>(gradientDrawable.color).defaultColor
49+
} else {
50+
try {
51+
val colorField = GradientDrawable::class.java.getDeclaredField("mFillPaint")
52+
colorField.isAccessible = true
53+
val gradientDrawable = drawable.getDrawable(1) as GradientDrawable?
54+
val paint = colorField.get(gradientDrawable) as Paint?
55+
return paint?.color ?: 0
56+
} catch (e: Exception) {
57+
throw RuntimeException("Failed to get color from GradientDrawable", e)
58+
}
59+
}
60+
}
61+
62+
@Test(timeout = 30000)
63+
fun testPayPalButton_changesToBlue() {
64+
Espresso.onView(ViewMatchers.withId(R.id.button_pp_blue)).perform(ViewActions.click())
65+
66+
Espresso.onView(ViewMatchers.withId(R.id.pp_payment_button))
67+
.check(ViewAssertion { view: View?, noViewFoundException: NoMatchingViewException? ->
68+
val button = view as PayPalButton
69+
val background = button.background as LayerDrawable
70+
val actualColor = getColorFromDrawable(background)
71+
Assert.assertEquals(-0x9f3201, actualColor.toLong())
72+
})
73+
}
74+
75+
@Test(timeout = 30000)
76+
fun testPayPalButton_changesToBlack() {
77+
Espresso.onView(ViewMatchers.withId(R.id.button_pp_black)).perform(ViewActions.click())
78+
79+
Espresso.onView(ViewMatchers.withId(R.id.pp_payment_button))
80+
.check(ViewAssertion { view: View?, noViewFoundException: NoMatchingViewException? ->
81+
val button = view as PayPalButton
82+
val background = button.background as LayerDrawable
83+
val actualColor = getColorFromDrawable(background)
84+
Assert.assertEquals(-0x1000000, actualColor.toLong())
85+
})
86+
}
87+
88+
@Test(timeout = 30000)
89+
fun testPayPalButton_changesToWhite() {
90+
Espresso.onView(ViewMatchers.withId(R.id.button_pp_white)).perform(ViewActions.click())
91+
92+
Espresso.onView(ViewMatchers.withId(R.id.pp_payment_button))
93+
.check(ViewAssertion { view: View?, noViewFoundException: NoMatchingViewException? ->
94+
val button = view as PayPalButton
95+
val background = button.background as LayerDrawable
96+
val actualColor = getColorFromDrawable(background)
97+
Assert.assertEquals(-0x1, actualColor.toLong())
98+
})
99+
}
100+
101+
@Test(timeout = 30000)
102+
fun testVenmoButton_changesToBlue() {
103+
Espresso.onView(ViewMatchers.withId(R.id.button_venmo_blue)).perform(ViewActions.click())
104+
105+
Espresso.onView(ViewMatchers.withId(R.id.venmo_payment_button))
106+
.check(ViewAssertion { view: View?, noViewFoundException: NoMatchingViewException? ->
107+
val button = view as VenmoButton
108+
val background = button.background as LayerDrawable
109+
val actualColor = getColorFromDrawable(background)
110+
Assert.assertEquals(-0xff7301, actualColor.toLong())
111+
})
112+
}
113+
114+
@Test(timeout = 30000)
115+
fun testVenmoButton_changesToBlack() {
116+
Espresso.onView(ViewMatchers.withId(R.id.button_venmo_black)).perform(ViewActions.click())
117+
118+
Espresso.onView(ViewMatchers.withId(R.id.venmo_payment_button))
119+
.check(ViewAssertion { view: View?, noViewFoundException: NoMatchingViewException? ->
120+
val button = view as VenmoButton
121+
val background = button.background as LayerDrawable
122+
val actualColor = getColorFromDrawable(background)
123+
Assert.assertEquals(-0x1000000, actualColor.toLong())
124+
})
125+
}
126+
127+
@Test(timeout = 30000)
128+
fun testVenmoButton_changesToWhite() {
129+
Espresso.onView(ViewMatchers.withId(R.id.button_venmo_white)).perform(ViewActions.click())
130+
131+
Espresso.onView(ViewMatchers.withId(R.id.venmo_payment_button))
132+
.check(ViewAssertion { view: View?, noViewFoundException: NoMatchingViewException? ->
133+
val button = view as VenmoButton
134+
val background = button.background as LayerDrawable
135+
val actualColor = getColorFromDrawable(background)
136+
Assert.assertEquals(-0x1, actualColor.toLong())
137+
})
138+
}
139+
}

0 commit comments

Comments
 (0)