diff --git a/build.gradle b/build.gradle
index 3df2d26b2..4cfcc4791 100644
--- a/build.gradle
+++ b/build.gradle
@@ -45,6 +45,8 @@ subprojects {
constraintlayoutVersion = '1.1.3'
legacySupportV4Version = '1.0.0'
recycleViewVersion = '1.0.0'
+ lifecycleExtensionsVersion = '2.0.0'
+ pagingRuntimeVersion = '2.1.0'
//Testing
extJunitVerson = '1.1.1'
testRunnerVersion = '1.2.0'
diff --git a/common/src/main/java/com/hyperwallet/android/common/util/DateUtils.java b/common/src/main/java/com/hyperwallet/android/common/util/DateUtils.java
new file mode 100644
index 000000000..7fd65e5aa
--- /dev/null
+++ b/common/src/main/java/com/hyperwallet/android/common/util/DateUtils.java
@@ -0,0 +1,123 @@
+/*
+ * The MIT License (MIT)
+ * Copyright (c) 2019 Hyperwallet Systems Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package com.hyperwallet.android.common.util;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * Common HW-SDK UI Date Utility class, that will assist on safe presentation of date whatever the mobile device setting
+ * is set Locale, Timezone and etc... that dictates how that dates are being presented
+ *
+ * Moreover all date string to {@link Date} object conversion is automatically converted from
+ * GMT date string from API to locale Date set by the phone
+ */
+public final class DateUtils {
+
+ private static final String DATE_FORMAT = "yyyy-MM-dd";
+ private static final String DATE_TIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss";
+ private static final String DATE_TIME_FORMAT_MILLISECONDS = "yyyy-MM-dd'T'HH:mm:ss.SSS";
+ private static final TimeZone API_TIMEZONE = TimeZone.getTimeZone("GMT");
+
+ private DateUtils() {
+ }
+
+ /**
+ * Creates a string date format: yyyy-MM-dd
+ *
+ * @param date Date object
+ * @return string date in yyyy-MM-dd format
+ */
+ public static String toDateFormat(@NonNull final Date date) {
+ SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT, Locale.getDefault());
+ dateFormat.setTimeZone(TimeZone.getDefault());
+ return dateFormat.format(date);
+ }
+
+ /**
+ * Creates a string date in specified format
+ *
+ * @param date Date object
+ * @param format specify desired format of date
+ * @return formatted date string based on format specified
+ */
+ public static String toDateFormat(@NonNull final Date date, @NonNull final String format) {
+ SimpleDateFormat dateFormat = new SimpleDateFormat(format, Locale.getDefault());
+ dateFormat.setTimeZone(TimeZone.getDefault());
+ return dateFormat.format(date);
+ }
+
+ /**
+ * Creates a string date format
+ *
+ * @param date Date object
+ * @return formatted string in yyyy-MM-dd'T'HH:mm:ss format
+ */
+ public static String toDateTimeFormat(@NonNull final Date date) {
+ SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_TIME_FORMAT, Locale.getDefault());
+ dateFormat.setTimeZone(TimeZone.getDefault());
+ return dateFormat.format(date);
+ }
+
+ /**
+ * Creates a string date format
+ *
+ * @param date Date object
+ * @return formatted string in yyyy-MM-dd'T'HH:mm:ss.SSS format
+ */
+ public static String toDateTimeMillisFormat(@NonNull final Date date) {
+ SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_TIME_FORMAT_MILLISECONDS, Locale.getDefault());
+ dateFormat.setTimeZone(TimeZone.getDefault());
+ return dateFormat.format(date);
+ }
+
+ /**
+ * Creates a Date object from string date using API Timezone
+ *
+ * @param dateString String date from API with GMT timezone
+ * @return date Date object converted to local timezone
+ * @throws IllegalArgumentException when string is un-parsable
+ */
+ public static Date fromDateTimeString(@NonNull final String dateString) {
+ try {
+ SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_TIME_FORMAT, Locale.getDefault());
+ dateFormat.setTimeZone(API_TIMEZONE);
+ return dateFormat.parse(dateString);
+ } catch (ParseException e) {
+ throw new IllegalArgumentException("An exception occurred when attempting to parse " +
+ "the date " + dateString, e);
+ }
+ }
+
+ @VisibleForTesting
+ static Date fromDateTimeString(@NonNull final String dateString, @NonNull final String format) {
+ try {
+ SimpleDateFormat dateFormat = new SimpleDateFormat(format, Locale.getDefault());
+ dateFormat.setTimeZone(API_TIMEZONE);
+ return dateFormat.parse(dateString);
+ } catch (ParseException e) {
+ throw new IllegalArgumentException("An exception occurred when attempting to parse " +
+ "the date " + dateString, e);
+ }
+ }
+}
diff --git a/ui/src/main/java/com/hyperwallet/android/ui/view/HorizontalDividerItemDecorator.java b/common/src/main/java/com/hyperwallet/android/common/view/HorizontalDividerItemDecorator.java
similarity index 96%
rename from ui/src/main/java/com/hyperwallet/android/ui/view/HorizontalDividerItemDecorator.java
rename to common/src/main/java/com/hyperwallet/android/common/view/HorizontalDividerItemDecorator.java
index 22b3b432a..3e9d3777f 100644
--- a/ui/src/main/java/com/hyperwallet/android/ui/view/HorizontalDividerItemDecorator.java
+++ b/common/src/main/java/com/hyperwallet/android/common/view/HorizontalDividerItemDecorator.java
@@ -14,7 +14,7 @@
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
-package com.hyperwallet.android.ui.view;
+package com.hyperwallet.android.common.view;
import android.content.Context;
import android.graphics.Canvas;
@@ -26,12 +26,12 @@
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
-import com.hyperwallet.android.hyperwallet_ui.R;
+import com.hyperwallet.android.common.R;
public class HorizontalDividerItemDecorator extends RecyclerView.ItemDecoration {
- private final Drawable mHorizontalItemDivider;
- private final int mDefaultPadding;
+ protected final Drawable mHorizontalItemDivider;
+ protected final int mDefaultPadding;
public HorizontalDividerItemDecorator(@NonNull final Context context, final boolean withHeaderDivider) {
mHorizontalItemDivider = context.getResources().getDrawable(R.drawable.horizontal_divider, null);
diff --git a/common/src/main/java/com/hyperwallet/android/common/viewmodel/Event.java b/common/src/main/java/com/hyperwallet/android/common/viewmodel/Event.java
new file mode 100644
index 000000000..a07aab0e1
--- /dev/null
+++ b/common/src/main/java/com/hyperwallet/android/common/viewmodel/Event.java
@@ -0,0 +1,65 @@
+/*
+ * The MIT License (MIT)
+ * Copyright (c) 2019 Hyperwallet Systems Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package com.hyperwallet.android.common.viewmodel;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+/**
+ * Class that represents {@link androidx.lifecycle.LiveData} event with content
+ */
+public class Event {
+
+ private final T content;
+ private boolean mIsContentConsumed;
+
+ public Event(@NonNull final T t) {
+ content = t;
+ }
+
+ /**
+ * @return content of this event, will also mark {@link Event#mIsContentConsumed} to true
+ * that will also mean that {@link Event#getContentIfNotConsumed()} will also return true
+ */
+ @NonNull
+ public T getContent() {
+ mIsContentConsumed = true;
+ return content;
+ }
+
+ /**
+ * @return true if content assigned to event is already referenced
+ * from {@link Event#getContent()}; false otherwise.
+ */
+ public boolean isContentConsumed() {
+ return mIsContentConsumed;
+ }
+
+ /**
+ * Retrieve assigned content based on if and only if content has not been referenced from {@link Event#getContent()}
+ *
+ * @return content if content is not yet consumed; otherwise null
+ */
+ @Nullable
+ public T getContentIfNotConsumed() {
+ if (!mIsContentConsumed) {
+ mIsContentConsumed = true;
+ return content;
+ }
+ return null;
+ }
+}
diff --git a/common/src/main/res/drawable/circle.xml b/common/src/main/res/drawable/circle.xml
index b6e02a0bc..4a83f9077 100644
--- a/common/src/main/res/drawable/circle.xml
+++ b/common/src/main/res/drawable/circle.xml
@@ -3,7 +3,7 @@
-
-
+
diff --git a/common/src/main/res/drawable/circle_white.xml b/common/src/main/res/drawable/circle_white.xml
index 237aac74a..def791761 100644
--- a/common/src/main/res/drawable/circle_white.xml
+++ b/common/src/main/res/drawable/circle_white.xml
@@ -4,11 +4,9 @@
-
-
-
-
-
-
+
+
diff --git a/common/src/main/res/font/icomoon.ttf b/common/src/main/res/font/icomoon.ttf
index f216ee945..384737f83 100644
Binary files a/common/src/main/res/font/icomoon.ttf and b/common/src/main/res/font/icomoon.ttf differ
diff --git a/common/src/main/res/values/colors.xml b/common/src/main/res/values/colors.xml
index 2d8989b26..39fdb8129 100644
--- a/common/src/main/res/values/colors.xml
+++ b/common/src/main/res/values/colors.xml
@@ -23,5 +23,6 @@
#E5F7FA
+ #737373
diff --git a/common/src/main/res/values/dimens.xml b/common/src/main/res/values/dimens.xml
index 1a3f3fb32..db66a3e95 100644
--- a/common/src/main/res/values/dimens.xml
+++ b/common/src/main/res/values/dimens.xml
@@ -73,4 +73,6 @@
4dp
6dp
32dp
+ 48dp
+ 3dp
diff --git a/common/src/test/java/com/hyperwallet/android/common/util/DateUtilsTest.java b/common/src/test/java/com/hyperwallet/android/common/util/DateUtilsTest.java
new file mode 100644
index 000000000..27102325b
--- /dev/null
+++ b/common/src/test/java/com/hyperwallet/android/common/util/DateUtilsTest.java
@@ -0,0 +1,63 @@
+package com.hyperwallet.android.common.util;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Date;
+import java.util.TimeZone;
+
+public class DateUtilsTest {
+
+ @Before
+ public void setTestLocalTimezone() {
+ TimeZone.setDefault(TimeZone.getTimeZone("PST"));
+ }
+
+ @Test
+ public void testToDateFormat_returnsExpectedStringFormat() {
+ String dateString = "2019-05-27";
+ Date dateTarget = DateUtils.fromDateTimeString("2019-05-27T15:57:49");
+
+ // test
+ String targetDate = DateUtils.toDateFormat(dateTarget);
+ assertThat(targetDate, is(notNullValue()));
+ assertThat(targetDate, is(dateString));
+ }
+
+ @Test
+ public void testToDateFormat_returnsExpectedStringFormatFromParameter() {
+ String dateString = "November 2019";
+ Date dateTarget = DateUtils.fromDateTimeString("2019-11-27T15:57:49");
+
+ // test
+ String targetDate = DateUtils.toDateFormat(dateTarget, "MMMM yyyy");
+ assertThat(targetDate, is(notNullValue()));
+ assertThat(targetDate, is(dateString));
+ }
+
+ @Test
+ public void testToDateTimeFormat_returnsExpectedStringFormat() {
+ String localTime = "2019-11-27T07:57:00";
+ Date dateTarget = DateUtils.fromDateTimeString("2019-11-27T15:57:00");
+
+ // test
+ String targetDate = DateUtils.toDateTimeFormat(dateTarget);
+ assertThat(targetDate, is(notNullValue()));
+ assertThat(targetDate, is(localTime));
+ }
+
+ @Test
+ public void testToDateTimeMillisFormat_returnsExpectedStringFormat() {
+ String localTime = "2019-11-27T07:57:00.000";
+ Date dateTarget = DateUtils.fromDateTimeString("2019-11-27T15:57:00.000", "yyyy-MM-dd'T'HH:mm:ss.SSS");
+
+ // test
+ String targetDate = DateUtils.toDateTimeMillisFormat(dateTarget);
+ assertThat(targetDate, is(notNullValue()));
+ assertThat(targetDate, is(localTime));
+ }
+}
diff --git a/receipt/build.gradle b/receipt/build.gradle
index 39c963b04..6aa8d9d42 100644
--- a/receipt/build.gradle
+++ b/receipt/build.gradle
@@ -2,6 +2,22 @@ apply from: "$rootProject.projectDir/android-library.gradle"
dependencies {
api project(":common")
+
+ implementation "com.google.android.material:material:$androidMaterialVersion"
+ implementation "androidx.constraintlayout:constraintlayout:$constraintlayoutVersion"
+ implementation "androidx.legacy:legacy-support-v4:$legacySupportV4Version"
+ implementation "androidx.recyclerview:recyclerview:$recycleViewVersion"
+ implementation "androidx.lifecycle:lifecycle-extensions:$lifecycleExtensionsVersion"
+ implementation "androidx.paging:paging-runtime:$pagingRuntimeVersion"
+
+ testImplementation "org.robolectric:robolectric:$robolectricVersion"
+
+ androidTestImplementation "androidx.test:rules:$testRulesVersion"
+ androidTestImplementation "androidx.test.espresso:espresso-contrib:$espressoVersion"
+ androidTestImplementation "androidx.test.espresso:espresso-intents:$espressoVersion"
+ androidTestImplementation "com.squareup.okhttp3:mockwebserver:$mockServerVersion"
+ androidTestImplementation "com.squareup.leakcanary:leakcanary-android-instrumentation:$leakcanaryVersion"
+ androidTestImplementation "com.squareup.leakcanary:leakcanary-support-fragment:$leakcanaryVersion"
}
def aarFile = file("$buildDir/outputs/aar/receipt-$version" + ".aar")
diff --git a/receipt/src/androidTest/AndroidManifest.xml b/receipt/src/androidTest/AndroidManifest.xml
new file mode 100644
index 000000000..8495adb45
--- /dev/null
+++ b/receipt/src/androidTest/AndroidManifest.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/receipt/src/androidTest/java/com/hyperwallet/android/HyperwalletInstrumentedTestApplication.java b/receipt/src/androidTest/java/com/hyperwallet/android/HyperwalletInstrumentedTestApplication.java
new file mode 100644
index 000000000..dfc559d2e
--- /dev/null
+++ b/receipt/src/androidTest/java/com/hyperwallet/android/HyperwalletInstrumentedTestApplication.java
@@ -0,0 +1,31 @@
+package com.hyperwallet.android;
+
+
+import android.app.Application;
+
+import com.squareup.leakcanary.InstrumentationLeakDetector;
+import com.squareup.leakcanary.LeakCanary;
+
+public class HyperwalletInstrumentedTestApplication extends Application {
+
+ @Override
+ public void onCreate() {
+
+ super.onCreate();
+ if (LeakCanary.isInAnalyzerProcess(this)) {
+ // This process is dedicated to LeakCanary for heap analysis.
+ // You should not init your app in this process.
+ return;
+ }
+ installLeakCanary();
+ }
+
+
+ protected void installLeakCanary() {
+
+ InstrumentationLeakDetector.instrumentationRefWatcher(this)
+ .buildAndInstall();
+
+ }
+
+}
\ No newline at end of file
diff --git a/receipt/src/androidTest/java/com/hyperwallet/android/receipt/ListReceiptsTest.java b/receipt/src/androidTest/java/com/hyperwallet/android/receipt/ListReceiptsTest.java
new file mode 100644
index 000000000..5eb430354
--- /dev/null
+++ b/receipt/src/androidTest/java/com/hyperwallet/android/receipt/ListReceiptsTest.java
@@ -0,0 +1,265 @@
+package com.hyperwallet.android.receipt;
+
+import static androidx.test.espresso.Espresso.onView;
+import static androidx.test.espresso.assertion.ViewAssertions.matches;
+import static androidx.test.espresso.matcher.ViewMatchers.hasDescendant;
+import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static androidx.test.espresso.matcher.ViewMatchers.withId;
+import static androidx.test.espresso.matcher.ViewMatchers.withParent;
+import static androidx.test.espresso.matcher.ViewMatchers.withText;
+
+import static org.hamcrest.Matchers.allOf;
+import static org.hamcrest.Matchers.instanceOf;
+
+import static java.net.HttpURLConnection.HTTP_NO_CONTENT;
+import static java.net.HttpURLConnection.HTTP_OK;
+
+import static com.hyperwallet.android.util.EspressoUtils.atPosition;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.widget.TextView;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.rule.ActivityTestRule;
+
+import com.hyperwallet.android.Hyperwallet;
+import com.hyperwallet.android.receipt.repository.ReceiptRepositoryFactory;
+import com.hyperwallet.android.receipt.view.ListReceiptActivity;
+import com.hyperwallet.android.rule.HyperwalletExternalResourceManager;
+import com.hyperwallet.android.rule.HyperwalletMockWebServer;
+import com.hyperwallet.android.util.RecyclerViewCountAssertion;
+import com.hyperwallet.android.util.TestAuthenticationProvider;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Locale;
+
+@RunWith(AndroidJUnit4.class)
+public class ListReceiptsTest {
+
+ @ClassRule
+ public static HyperwalletExternalResourceManager sResourceManager = new HyperwalletExternalResourceManager();
+ @Rule
+ public HyperwalletMockWebServer mMockWebServer = new HyperwalletMockWebServer(8080);
+ @Rule
+ public ActivityTestRule mActivityTestRule =
+ new ActivityTestRule<>(ListReceiptActivity.class, true, false);
+
+ @Before
+ public void setup() {
+ Hyperwallet.getInstance(new TestAuthenticationProvider());
+
+ mMockWebServer.mockResponse().withHttpResponseCode(HTTP_OK).withBody(sResourceManager
+ .getResourceContent("authentication_token_response.json")).mock();
+
+ setLocale(Locale.US);
+ }
+
+ @After
+ public void cleanup() {
+ ReceiptRepositoryFactory.clearInstance();
+ }
+
+ @Test
+ public void testListReceipts_userHasMultipleTransactions() {
+ mMockWebServer.mockResponse().withHttpResponseCode(HTTP_OK).withBody(sResourceManager
+ .getResourceContent("receipt_list_response.json")).mock();
+ mMockWebServer.mockResponse().withHttpResponseCode(HTTP_NO_CONTENT).withBody("").mock();
+
+ // run test
+ mActivityTestRule.launchActivity(null);
+
+ // assert
+ onView(allOf(instanceOf(TextView.class), withParent(withId(R.id.toolbar))))
+ .check(matches(withText(R.string.title_activity_receipt_list)));
+ onView(withId(R.id.list_receipts)).check(matches(isDisplayed()));
+
+ onView(withId(R.id.list_receipts))
+ .check(matches(atPosition(0, hasDescendant(withText("June 2019")))));
+ onView(withId(R.id.list_receipts)).check(matches(atPosition(0,
+ hasDescendant(withText(com.hyperwallet.android.receipt.R.string.credit)))));
+ onView(withId(R.id.list_receipts)).check(
+ matches(atPosition(0, hasDescendant(withText("Payment")))));
+ onView(withId(R.id.list_receipts)).check(
+ matches(atPosition(0, hasDescendant(withText("+ 20.00")))));
+ onView(withId(R.id.list_receipts)).check(
+ matches(atPosition(0, hasDescendant(withText("June 07, 2019")))));
+ onView(withId(R.id.list_receipts)).check(matches(atPosition(0, hasDescendant(withText("USD")))));
+
+ onView(withId(R.id.list_receipts)).check(matches(atPosition(1,
+ hasDescendant(withText(com.hyperwallet.android.receipt.R.string.credit)))));
+ onView(withId(R.id.list_receipts)).check(
+ matches(atPosition(1, hasDescendant(withText("Payment")))));
+ onView(withId(R.id.list_receipts)).check(
+ matches(atPosition(1, hasDescendant(withText("+ 25.00")))));
+ onView(withId(R.id.list_receipts)).check(
+ matches(atPosition(1, hasDescendant(withText("June 02, 2019")))));
+ onView(withId(R.id.list_receipts)).check(matches(atPosition(1, hasDescendant(withText("CAD")))));
+
+ onView(withId(R.id.list_receipts)).check(matches(atPosition(2,
+ hasDescendant(withText(com.hyperwallet.android.receipt.R.string.debit)))));
+ onView(withId(R.id.list_receipts)).check(
+ matches(atPosition(2, hasDescendant(withText("Card Activation Fee")))));
+ onView(withId(R.id.list_receipts)).check(
+ matches(atPosition(2, hasDescendant(withText("- 1.95")))));
+ onView(withId(R.id.list_receipts)).check(
+ matches(atPosition(2, hasDescendant(withText("June 01, 2019")))));
+ onView(withId(R.id.list_receipts)).check(matches(atPosition(2, hasDescendant(withText("USD")))));
+
+ onView(withId(R.id.list_receipts))
+ .check(matches(atPosition(3, hasDescendant(withText("December 2018")))));
+ onView(withId(R.id.list_receipts)).check(matches(atPosition(3,
+ hasDescendant(withText(com.hyperwallet.android.receipt.R.string.debit)))));
+ onView(withId(R.id.list_receipts)).check(
+ matches(atPosition(3, hasDescendant(withText("Card Load")))));
+ onView(withId(R.id.list_receipts)).check(
+ matches(atPosition(3, hasDescendant(withText("- 18.05")))));
+ onView(withId(R.id.list_receipts)).check(
+ matches(atPosition(3, hasDescendant(withText("December 01, 2018")))));
+ onView(withId(R.id.list_receipts)).check(matches(atPosition(3, hasDescendant(withText("USD")))));
+
+ onView(withId(R.id.list_receipts)).check(new RecyclerViewCountAssertion(4));
+ }
+
+ @Test
+ public void testListReceipts_displayCreditTransaction() {
+ mMockWebServer.mockResponse().withHttpResponseCode(HTTP_OK).withBody(sResourceManager
+ .getResourceContent("receipt_credit_response.json")).mock();
+ mMockWebServer.mockResponse().withHttpResponseCode(HTTP_NO_CONTENT).withBody("").mock();
+
+ // run test
+ mActivityTestRule.launchActivity(null);
+
+ // assert
+ onView(allOf(instanceOf(TextView.class), withParent(withId(R.id.toolbar))))
+ .check(matches(withText(R.string.title_activity_receipt_list)));
+ onView(withId(R.id.list_receipts)).check(matches(isDisplayed()));
+
+ onView(withId(R.id.list_receipts))
+ .check(matches(atPosition(0, hasDescendant(withText("June 2019")))));
+ onView(withId(R.id.list_receipts)).check(matches(atPosition(0,
+ hasDescendant(withText(com.hyperwallet.android.receipt.R.string.credit)))));
+ onView(withId(R.id.list_receipts)).check(
+ matches(atPosition(0, hasDescendant(withText(R.string.payment)))));
+ onView(withId(R.id.list_receipts)).check(
+ matches(atPosition(0, hasDescendant(withText("+ 25.00")))));
+ onView(withId(R.id.list_receipts)).check(
+ matches(atPosition(0, hasDescendant(withText("June 02, 2019")))));
+ onView(withId(R.id.list_receipts)).check(matches(atPosition(0, hasDescendant(withText("CAD")))));
+
+ onView(withId(R.id.list_receipts)).check(new RecyclerViewCountAssertion(1));
+ }
+
+ @Test
+ public void testListReceipts_displayDebitTransaction() {
+ mMockWebServer.mockResponse().withHttpResponseCode(HTTP_OK).withBody(sResourceManager
+ .getResourceContent("receipt_debit_response.json")).mock();
+ mMockWebServer.mockResponse().withHttpResponseCode(HTTP_NO_CONTENT).withBody("").mock();
+
+ // run test
+ mActivityTestRule.launchActivity(null);
+
+ // assert
+ onView(allOf(instanceOf(TextView.class), withParent(withId(R.id.toolbar))))
+ .check(matches(withText(R.string.title_activity_receipt_list)));
+ onView(withId(R.id.list_receipts)).check(matches(isDisplayed()));
+
+ onView(withId(R.id.list_receipts))
+ .check(matches(atPosition(0, hasDescendant(withText("May 2019")))));
+ onView(withId(R.id.list_receipts)).check(matches(atPosition(0,
+ hasDescendant(withText(com.hyperwallet.android.receipt.R.string.debit)))));
+ onView(withId(R.id.list_receipts)).check(
+ matches(atPosition(0, hasDescendant(withText(R.string.transfer_to_prepaid_card)))));
+ onView(withId(R.id.list_receipts)).check(
+ matches(atPosition(0, hasDescendant(withText("- 18.05")))));
+ onView(withId(R.id.list_receipts)).check(
+ matches(atPosition(0, hasDescendant(withText("May 02, 2019")))));
+ onView(withId(R.id.list_receipts)).check(matches(atPosition(0, hasDescendant(withText("USD")))));
+
+ onView(withId(R.id.list_receipts)).check(new RecyclerViewCountAssertion(1));
+ }
+
+ @Test
+ public void testListReceipts_displayUnknownTransactionType() {
+ mMockWebServer.mockResponse().withHttpResponseCode(HTTP_OK).withBody(sResourceManager
+ .getResourceContent("receipt_unknown_type_response.json")).mock();
+ mMockWebServer.mockResponse().withHttpResponseCode(HTTP_NO_CONTENT).withBody("").mock();
+
+ // run test
+ mActivityTestRule.launchActivity(null);
+
+ // assert
+ onView(allOf(instanceOf(TextView.class), withParent(withId(R.id.toolbar))))
+ .check(matches(withText(R.string.title_activity_receipt_list)));
+ onView(withId(R.id.list_receipts)).check(matches(isDisplayed()));
+
+ onView(withId(R.id.list_receipts))
+ .check(matches(atPosition(0, hasDescendant(withText("June 2019")))));
+ onView(withId(R.id.list_receipts)).check(matches(atPosition(0,
+ hasDescendant(withText(com.hyperwallet.android.receipt.R.string.credit)))));
+ onView(withId(R.id.list_receipts)).check(
+ matches(atPosition(0, hasDescendant(withText(R.string.unknown_type)))));
+ onView(withId(R.id.list_receipts)).check(
+ matches(atPosition(0, hasDescendant(withText("+ 25.00")))));
+ onView(withId(R.id.list_receipts)).check(
+ matches(atPosition(0, hasDescendant(withText("June 02, 2019")))));
+ onView(withId(R.id.list_receipts)).check(matches(atPosition(0, hasDescendant(withText("CAD")))));
+
+ onView(withId(R.id.list_receipts)).check(new RecyclerViewCountAssertion(1));
+ }
+
+ @Test
+ public void testListReceipt_userHasNoTransactions() {
+ mMockWebServer.mockResponse().withHttpResponseCode(HTTP_NO_CONTENT).withBody("").mock();
+
+ // run test
+ mActivityTestRule.launchActivity(null);
+
+ onView(withId(R.id.toolbar)).check(matches(isDisplayed()));
+ onView(allOf(instanceOf(TextView.class), withParent(withId(R.id.toolbar))))
+ .check(matches(withText(R.string.title_activity_receipt_list)));
+ //todo: check empty view when it will be ready
+ }
+
+ @Test
+ public void testListReceipts_checkDateTextOnLocaleChange() {
+ mMockWebServer.mockResponse().withHttpResponseCode(HTTP_OK).withBody(sResourceManager
+ .getResourceContent("receipt_debit_response.json")).mock();
+ mMockWebServer.mockResponse().withHttpResponseCode(HTTP_NO_CONTENT).withBody("").mock();
+
+ setLocale(Locale.ITALY);
+ // run test
+ mActivityTestRule.launchActivity(null);
+ // assert
+ onView(withId(R.id.list_receipts))
+ .check(matches(atPosition(0, hasDescendant(withText("maggio 2019")))));
+ onView(withId(R.id.list_receipts)).check(matches(atPosition(0, hasDescendant(withText("maggio 02, 2019")))));
+ mActivityTestRule.finishActivity();
+ setLocale(Locale.US);
+ mActivityTestRule.launchActivity(null);
+ onView(withId(R.id.list_receipts))
+ .check(matches(atPosition(0, hasDescendant(withText("May 2019")))));
+ onView(withId(R.id.list_receipts)).check(matches(atPosition(0, hasDescendant(withText("May 02, 2019")))));
+ }
+
+ private void setLocale(Locale locale) {
+ Context context = ApplicationProvider.getApplicationContext();
+ Locale.setDefault(locale);
+ Resources resources = context.getResources();
+ Configuration configuration = resources.getConfiguration();
+ if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.JELLY_BEAN) {
+ configuration.setLocale(locale);
+ } else {
+ configuration.locale = locale;
+ }
+ resources.updateConfiguration(configuration, resources.getDisplayMetrics());
+ }
+}
diff --git a/receipt/src/androidTest/java/com/hyperwallet/android/rule/HyperwalletExternalResourceManager.java b/receipt/src/androidTest/java/com/hyperwallet/android/rule/HyperwalletExternalResourceManager.java
new file mode 100644
index 000000000..e7e546063
--- /dev/null
+++ b/receipt/src/androidTest/java/com/hyperwallet/android/rule/HyperwalletExternalResourceManager.java
@@ -0,0 +1,73 @@
+package com.hyperwallet.android.rule;
+
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.net.URL;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class HyperwalletExternalResourceManager extends TestWatcher {
+
+ private static final String EMPTY = "";
+ private ClassLoader classLoader;
+ private Logger logger;
+
+ @Override
+ protected void starting(Description description) {
+ super.starting(description);
+ classLoader = description.getTestClass().getClassLoader();
+ logger = Logger.getLogger(description.getTestClass().getName());
+ }
+
+ public String getResourceContent(final String resourceName) {
+ if (resourceName == null) {
+ throw new IllegalArgumentException("Parameter resourceName cannot be null");
+ }
+
+ return getContent(resourceName);
+ }
+
+ private String getContent(final String resourceName) {
+
+ URL resource = classLoader.getResource(resourceName);
+ InputStream inputStream = null;
+ Writer writer = new StringWriter();
+ String resourceContent = EMPTY;
+ if (resource != null) {
+ try {
+ inputStream = resource.openStream();
+ BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
+ String line = reader.readLine();
+ while (line != null) {
+ writer.write(line);
+ line = reader.readLine();
+ }
+ resourceContent = writer.toString();
+
+ } catch (Exception e) {
+ logger.log(Level.WARNING, "There was an error loading an external resource", e);
+ } finally {
+ try {
+ if (inputStream != null) {
+ inputStream.close();
+ }
+ } catch (Exception e) {
+ logger.log(Level.SEVERE, "There was an error closing input stream", e);
+ }
+ try {
+ writer.close();
+ } catch (IOException e) {
+ logger.log(Level.SEVERE, "There was an error closing writer", e);
+ }
+ }
+ }
+ return resourceContent;
+ }
+}
diff --git a/receipt/src/androidTest/java/com/hyperwallet/android/rule/HyperwalletMockWebServer.java b/receipt/src/androidTest/java/com/hyperwallet/android/rule/HyperwalletMockWebServer.java
new file mode 100644
index 000000000..9f42f6128
--- /dev/null
+++ b/receipt/src/androidTest/java/com/hyperwallet/android/rule/HyperwalletMockWebServer.java
@@ -0,0 +1,115 @@
+package com.hyperwallet.android.rule;
+
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+
+import okhttp3.mockwebserver.MockResponse;
+import okhttp3.mockwebserver.MockWebServer;
+
+public final class HyperwalletMockWebServer extends TestWatcher {
+
+ private MockWebServer mServer;
+ private int port;
+
+ public HyperwalletMockWebServer(int port) {
+ this.port = port;
+ }
+
+ @Override
+ protected void starting(Description description) {
+ super.starting(description);
+ mServer = new MockWebServer();
+ try {
+ mServer.start(port);
+ } catch (IOException e) {
+ throw new IllegalStateException("Unable to start mock server", e);
+ }
+ }
+
+ @Override
+ protected void finished(Description description) {
+ super.finished(description);
+ try {
+ mServer.shutdown();
+ mServer.close();
+ } catch (IOException e) {
+ throw new IllegalStateException("Un error occurred when shutting down mock server", e);
+ }
+ }
+
+ public HyperwalletMockResponse mockResponse() {
+ return new Builder(mServer).build();
+ }
+
+ public MockWebServer getServer() {
+ return mServer;
+ }
+
+ public static class HyperwalletMockResponse {
+
+ private String path;
+ private String body;
+ private int httpResponseCode;
+ private Builder builder;
+
+ HyperwalletMockResponse(Builder builder) {
+ this.path = builder.path;
+ this.httpResponseCode = builder.responseCode;
+ this.body = builder.body;
+ this.builder = builder;
+ }
+
+ public HyperwalletMockResponse withHttpResponseCode(final int code) {
+ builder.responseCode(code);
+ return builder.build();
+ }
+
+ public HyperwalletMockResponse withBody(final String body) {
+ builder.body(body);
+ return builder.build();
+ }
+
+ public void mock() {
+ mockRequest();
+ }
+
+ private String mockRequest() {
+ builder.server.enqueue(new MockResponse().setResponseCode(httpResponseCode).setBody(body));
+ return builder.server.url(path).toString();
+ }
+
+ }
+
+ private static class Builder {
+
+ private String path;
+ private String body;
+ private int responseCode;
+ private MockWebServer server;
+
+
+ Builder(final MockWebServer server) {
+ this.path = "";
+ this.responseCode = HttpURLConnection.HTTP_OK;
+ this.body = "";
+ this.server = server;
+ }
+
+ Builder responseCode(final int code) {
+ this.responseCode = code;
+ return this;
+ }
+
+ Builder body(final String body) {
+ this.body = body;
+ return this;
+ }
+
+ HyperwalletMockResponse build() {
+ return new HyperwalletMockResponse(this);
+ }
+ }
+}
diff --git a/receipt/src/androidTest/java/com/hyperwallet/android/util/EspressoUtils.java b/receipt/src/androidTest/java/com/hyperwallet/android/util/EspressoUtils.java
new file mode 100644
index 000000000..92a711f96
--- /dev/null
+++ b/receipt/src/androidTest/java/com/hyperwallet/android/util/EspressoUtils.java
@@ -0,0 +1,187 @@
+package com.hyperwallet.android.util;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.view.View;
+import android.widget.EditText;
+import android.widget.ImageView;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.test.espresso.ViewAction;
+import androidx.test.espresso.action.ViewActions;
+import androidx.test.espresso.matcher.BoundedMatcher;
+
+import com.google.android.material.textfield.TextInputLayout;
+
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+
+import java.util.Objects;
+
+public class EspressoUtils {
+
+ public static Matcher withHint(final String expectedHint) {
+ return new TypeSafeMatcher() {
+
+ @Override
+ public boolean matchesSafely(View view) {
+ if (!(view instanceof TextInputLayout)) {
+ return false;
+ }
+
+ String hint = Objects.toString(((TextInputLayout) view).getHint());
+ return expectedHint.equals(hint);
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText(expectedHint);
+ }
+ };
+ }
+
+ public static Matcher hasErrorText(final String expectedErrorMessage) {
+ return new TypeSafeMatcher() {
+
+ @Override
+ public boolean matchesSafely(View view) {
+ if (!(view instanceof TextInputLayout)) {
+ return false;
+ }
+
+ String errorMessage = Objects.toString(((TextInputLayout) view).getError());
+ return expectedErrorMessage.equals(errorMessage);
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText(expectedErrorMessage);
+ }
+ };
+ }
+
+ public static Matcher hasErrorText(final int resourceId) {
+ return new TypeSafeMatcher() {
+
+ @Override
+ public boolean matchesSafely(View view) {
+ if (!(view instanceof TextInputLayout)) {
+ return false;
+ }
+ String expectedErrorMessage = view.getResources().getString(resourceId);
+ String errorMessage = Objects.toString(((TextInputLayout) view).getError());
+
+ return expectedErrorMessage.equals(errorMessage);
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ }
+ };
+ }
+
+ public static Matcher withDrawable(final int resourceId) {
+ return new TypeSafeMatcher() {
+
+ @Override
+ public boolean matchesSafely(View view) {
+ if (!(view instanceof ImageView)) {
+ return false;
+ }
+
+ Drawable drawable = ((ImageView) view).getDrawable();
+ if (drawable == null) {
+ return false;
+ }
+ Drawable expectedDrawable = view.getContext().getResources().getDrawable(resourceId);
+
+ Bitmap bitmap = getBitmap(drawable);
+ Bitmap expectedBitmap = getBitmap(expectedDrawable);
+
+ return bitmap.sameAs(expectedBitmap);
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ }
+ };
+ }
+
+ public static Matcher atPosition(final int position, @NonNull final Matcher matcher) {
+ return new BoundedMatcher(RecyclerView.class) {
+
+ @Override
+ protected boolean matchesSafely(final RecyclerView view) {
+ RecyclerView.ViewHolder viewHolder = view.findViewHolderForAdapterPosition(position);
+
+ if (viewHolder == null) {
+ return false;
+ }
+
+ return matcher.matches(viewHolder.itemView);
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("has item at position " + position + ": ");
+ matcher.describeTo(description);
+ }
+ };
+ }
+
+ public static ViewAction nestedScrollTo() {
+ return ViewActions.actionWithAssertions(new NestedScrollToAction());
+ }
+
+ private static Bitmap getBitmap(Drawable drawable) {
+ Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
+ drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
+
+ Canvas canvas = new Canvas(bitmap);
+ drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+ drawable.draw(canvas);
+
+ return bitmap;
+ }
+
+ public static Matcher hasNoErrorText() {
+ return new TypeSafeMatcher() {
+
+ @Override
+ public boolean matchesSafely(View view) {
+ if (!(view instanceof TextInputLayout)) {
+ return false;
+ }
+ return ((TextInputLayout) view).getError() == null;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("has no error text: ");
+ }
+ };
+ }
+
+ public static Matcher hasEmptyText() {
+ return new TypeSafeMatcher() {
+
+ @Override
+ public boolean matchesSafely(View view) {
+ if (!(view instanceof EditText)) {
+ return false;
+ }
+ String text = ((EditText) view).getText().toString();
+
+ return text.isEmpty();
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ }
+ };
+ }
+}
+
diff --git a/receipt/src/androidTest/java/com/hyperwallet/android/util/NestedScrollToAction.java b/receipt/src/androidTest/java/com/hyperwallet/android/util/NestedScrollToAction.java
new file mode 100644
index 000000000..392867387
--- /dev/null
+++ b/receipt/src/androidTest/java/com/hyperwallet/android/util/NestedScrollToAction.java
@@ -0,0 +1,41 @@
+package com.hyperwallet.android.util;
+
+import static androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom;
+import static androidx.test.espresso.matcher.ViewMatchers.isDescendantOfA;
+import static androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility;
+
+import static org.hamcrest.Matchers.allOf;
+import static org.hamcrest.Matchers.anyOf;
+
+import android.view.View;
+
+import androidx.core.widget.NestedScrollView;
+import androidx.test.espresso.UiController;
+import androidx.test.espresso.ViewAction;
+import androidx.test.espresso.action.ScrollToAction;
+import androidx.test.espresso.matcher.ViewMatchers;
+
+import org.hamcrest.Matcher;
+
+public class NestedScrollToAction implements ViewAction {
+ private static final String TAG = ScrollToAction.class.getSimpleName();
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Matcher getConstraints() {
+ return allOf(
+ withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE),
+ isDescendantOfA(
+ anyOf(isAssignableFrom(NestedScrollView.class))));
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ new ScrollToAction().perform(uiController, view);
+ }
+
+ @Override
+ public String getDescription() {
+ return "scroll to";
+ }
+}
diff --git a/receipt/src/androidTest/java/com/hyperwallet/android/util/RecyclerViewCountAssertion.java b/receipt/src/androidTest/java/com/hyperwallet/android/util/RecyclerViewCountAssertion.java
new file mode 100644
index 000000000..bb3aecaff
--- /dev/null
+++ b/receipt/src/androidTest/java/com/hyperwallet/android/util/RecyclerViewCountAssertion.java
@@ -0,0 +1,30 @@
+package com.hyperwallet.android.util;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.view.View;
+
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.test.espresso.NoMatchingViewException;
+import androidx.test.espresso.ViewAssertion;
+
+public class RecyclerViewCountAssertion implements ViewAssertion {
+ private final int mCount;
+
+ public RecyclerViewCountAssertion(int count) {
+ this.mCount = count;
+ }
+
+ @Override
+ public void check(View view, NoMatchingViewException noViewFoundException) {
+ if (noViewFoundException != null) {
+ throw noViewFoundException;
+ }
+
+ RecyclerView recyclerView = (RecyclerView) view;
+ RecyclerView.Adapter adapter = recyclerView.getAdapter();
+
+ assertThat(adapter.getItemCount(), is(mCount));
+ }
+}
diff --git a/receipt/src/androidTest/java/com/hyperwallet/android/util/TestAuthenticationProvider.java b/receipt/src/androidTest/java/com/hyperwallet/android/util/TestAuthenticationProvider.java
new file mode 100644
index 000000000..686ccbf30
--- /dev/null
+++ b/receipt/src/androidTest/java/com/hyperwallet/android/util/TestAuthenticationProvider.java
@@ -0,0 +1,51 @@
+package com.hyperwallet.android.util;
+
+import com.hyperwallet.android.HyperwalletAuthenticationTokenListener;
+import com.hyperwallet.android.HyperwalletAuthenticationTokenProvider;
+
+import java.io.IOException;
+import java.text.MessageFormat;
+import java.util.UUID;
+
+import okhttp3.Call;
+import okhttp3.Callback;
+import okhttp3.MediaType;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.RequestBody;
+import okhttp3.Response;
+
+public class TestAuthenticationProvider implements HyperwalletAuthenticationTokenProvider {
+
+ public static final MediaType JSON
+ = MediaType.get("application/json; charset=utf-8");
+ private static final String mBaseUrl = "http://localhost:8080/rest/v3/users/{0}/authentication-token";
+ private static final String mUserToken = "user_token";
+
+ @Override
+ public void retrieveAuthenticationToken(final HyperwalletAuthenticationTokenListener authenticationTokenListener) {
+
+ OkHttpClient client = new OkHttpClient();
+
+ String payload = "{}";
+ String baseUrl = MessageFormat.format(mBaseUrl, mUserToken);
+
+ RequestBody body = RequestBody.create(JSON, payload);
+ Request request = new Request.Builder()
+ .url(baseUrl)
+ .post(body)
+ .build();
+
+ client.newCall(request).enqueue(new Callback() {
+ @Override
+ public void onFailure(Call call, IOException e) {
+ authenticationTokenListener.onFailure(UUID.randomUUID(), e.getMessage());
+ }
+
+ @Override
+ public void onResponse(Call call, Response response) throws IOException {
+ authenticationTokenListener.onSuccess(response.body().string());
+ }
+ });
+ }
+}
diff --git a/receipt/src/main/AndroidManifest.xml b/receipt/src/main/AndroidManifest.xml
index bf6f4ad0c..3039f6d18 100644
--- a/receipt/src/main/AndroidManifest.xml
+++ b/receipt/src/main/AndroidManifest.xml
@@ -1,2 +1,13 @@
-
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/receipt/src/main/java/com/hyperwallet/android/receipt/repository/ReceiptDataSource.java b/receipt/src/main/java/com/hyperwallet/android/receipt/repository/ReceiptDataSource.java
new file mode 100644
index 000000000..5320214fb
--- /dev/null
+++ b/receipt/src/main/java/com/hyperwallet/android/receipt/repository/ReceiptDataSource.java
@@ -0,0 +1,189 @@
+/*
+ * The MIT License (MIT)
+ * Copyright (c) 2019 Hyperwallet Systems Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package com.hyperwallet.android.receipt.repository;
+
+import android.os.Handler;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
+import androidx.paging.PageKeyedDataSource;
+
+import com.hyperwallet.android.Hyperwallet;
+import com.hyperwallet.android.common.viewmodel.Event;
+import com.hyperwallet.android.exception.HyperwalletException;
+import com.hyperwallet.android.listener.HyperwalletListener;
+import com.hyperwallet.android.model.HyperwalletErrors;
+import com.hyperwallet.android.model.paging.HyperwalletPageList;
+import com.hyperwallet.android.model.receipt.Receipt;
+import com.hyperwallet.android.model.receipt.ReceiptQueryParam;
+
+import java.util.Calendar;
+
+/**
+ * ReceiptDataSource mediates communication to HW API Platform particularly on
+ * Receipts V3 API
+ */
+public class ReceiptDataSource extends PageKeyedDataSource {
+
+ private static final int YEAR_BEFORE_NOW = -1;
+ private final Calendar mCalendarYearBeforeNow;
+ private final MutableLiveData> mErrors = new MutableLiveData<>();
+ private final MutableLiveData mIsFetchingData = new MutableLiveData<>();
+ private LoadInitialCallback mLoadInitialCallback;
+ private LoadInitialParams mLoadInitialParams;
+ private LoadCallback mLoadAfterCallback;
+ private LoadParams mLoadAfterParams;
+
+ ReceiptDataSource() {
+ super();
+ mCalendarYearBeforeNow = Calendar.getInstance();
+ mCalendarYearBeforeNow.add(Calendar.YEAR, YEAR_BEFORE_NOW);
+ }
+
+ /**
+ * @see {@link PageKeyedDataSource#loadInitial(LoadInitialParams, LoadInitialCallback)}
+ */
+ @Override
+ public void loadInitial(@NonNull final LoadInitialParams params,
+ @NonNull final LoadInitialCallback callback) {
+ mLoadInitialCallback = callback;
+ mLoadInitialParams = params;
+ mIsFetchingData.postValue(Boolean.TRUE);
+
+ ReceiptQueryParam queryParam = new ReceiptQueryParam.Builder()
+ .createdAfter(mCalendarYearBeforeNow.getTime())
+ .limit(params.requestedLoadSize)
+ .sortByCreatedOnDesc().build();
+
+ getHyperwallet().listReceipts(queryParam,
+ new HyperwalletListener>() {
+ @Override
+ public void onSuccess(@Nullable HyperwalletPageList result) {
+ mIsFetchingData.postValue(Boolean.FALSE);
+ mErrors.postValue(null);
+
+ if (result != null) {
+ int next = result.getLimit() + result.getOffset();
+ int previous = 0;
+ callback.onResult(result.getDataList(), previous, next);
+ }
+ // reset
+ mLoadInitialCallback = null;
+ mLoadInitialParams = null;
+ }
+
+ @Override
+ public void onFailure(HyperwalletException exception) {
+ mIsFetchingData.postValue(Boolean.FALSE);
+ mErrors.postValue(new Event<>(exception.getHyperwalletErrors()));
+ }
+
+ @Override
+ public Handler getHandler() {
+ return null;
+ }
+ });
+ }
+
+ /**
+ * Unused in this case
+ *
+ * @see {@link PageKeyedDataSource#loadBefore(LoadParams, LoadCallback)}
+ */
+ @Override
+ public void loadBefore(@NonNull LoadParams params,
+ @NonNull LoadCallback callback) {
+ }
+
+ /**
+ * @see {@link PageKeyedDataSource#loadAfter(LoadParams, LoadCallback)}
+ * */
+ @Override
+ public void loadAfter(@NonNull LoadParams params,
+ final @NonNull LoadCallback callback) {
+ mLoadInitialCallback = null;
+ mLoadInitialParams = null;
+ mLoadAfterCallback = callback;
+ mLoadAfterParams = params;
+ mIsFetchingData.postValue(Boolean.TRUE);
+
+ ReceiptQueryParam queryParam = new ReceiptQueryParam.Builder()
+ .createdAfter(mCalendarYearBeforeNow.getTime())
+ .limit(params.requestedLoadSize)
+ .offset(params.key)
+ .sortByCreatedOnDesc().build();
+
+ getHyperwallet().listReceipts(queryParam,
+ new HyperwalletListener>() {
+ @Override
+ public void onSuccess(@Nullable HyperwalletPageList result) {
+ mIsFetchingData.postValue(Boolean.FALSE);
+ mErrors.postValue(null);
+
+ if (result != null) {
+ int next = result.getLimit() + result.getOffset();
+ callback.onResult(result.getDataList(), next);
+ }
+
+ // reset
+ mLoadAfterCallback = null;
+ mLoadAfterParams = null;
+ }
+
+ @Override
+ public void onFailure(HyperwalletException exception) {
+ mIsFetchingData.postValue(Boolean.FALSE);
+ mErrors.postValue(new Event<>(exception.getHyperwalletErrors()));
+ }
+
+ @Override
+ public Handler getHandler() {
+ return null;
+ }
+ });
+ }
+
+ /**
+ * Facilitates retry when network is down; any error that we can have a retry operation
+ * */
+ void retry() {
+ if (mLoadInitialCallback != null) {
+ loadInitial(mLoadInitialParams, mLoadInitialCallback);
+ } else if (mLoadAfterCallback != null) {
+ loadAfter(mLoadAfterParams, mLoadAfterCallback);
+ }
+ }
+
+ /**
+ * Retrieve reference of Hyperwallet errors inorder for consumers to observe on data changes
+ *
+ * @return Live event data of {@link HyperwalletErrors}
+ * */
+ public LiveData> getErrors() {
+ return mErrors;
+ }
+
+ LiveData isFetchingData() {
+ return mIsFetchingData;
+ }
+
+ Hyperwallet getHyperwallet() {
+ return Hyperwallet.getDefault();
+ }
+}
diff --git a/receipt/src/main/java/com/hyperwallet/android/receipt/repository/ReceiptDataSourceFactory.java b/receipt/src/main/java/com/hyperwallet/android/receipt/repository/ReceiptDataSourceFactory.java
new file mode 100644
index 000000000..a2b81216b
--- /dev/null
+++ b/receipt/src/main/java/com/hyperwallet/android/receipt/repository/ReceiptDataSourceFactory.java
@@ -0,0 +1,54 @@
+/*
+ * The MIT License (MIT)
+ * Copyright (c) 2019 Hyperwallet Systems Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package com.hyperwallet.android.receipt.repository;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
+import androidx.paging.DataSource;
+
+/**
+ * Data source factory that uses {@link DataSource.Factory} facility
+ */
+public class ReceiptDataSourceFactory extends DataSource.Factory {
+
+ private final MutableLiveData mDataSourceMutableLiveData;
+ private final ReceiptDataSource mReceiptDataSource;
+
+ ReceiptDataSourceFactory() {
+ super();
+ mReceiptDataSource = new ReceiptDataSource();
+ mDataSourceMutableLiveData = new MutableLiveData<>();
+ mDataSourceMutableLiveData.setValue(mReceiptDataSource);
+ }
+
+ /**
+ * Returns observable members of receipt data source
+ */
+ LiveData getReceiptDataSource() {
+ return mDataSourceMutableLiveData;
+ }
+
+ /**
+ * @see {@link DataSource.Factory#create()}
+ */
+ @NonNull
+ @Override
+ public DataSource create() {
+ return mReceiptDataSource;
+ }
+}
diff --git a/receipt/src/main/java/com/hyperwallet/android/receipt/repository/ReceiptRepository.java b/receipt/src/main/java/com/hyperwallet/android/receipt/repository/ReceiptRepository.java
new file mode 100644
index 000000000..542d03c89
--- /dev/null
+++ b/receipt/src/main/java/com/hyperwallet/android/receipt/repository/ReceiptRepository.java
@@ -0,0 +1,58 @@
+/*
+ * The MIT License (MIT)
+ * Copyright (c) 2019 Hyperwallet Systems Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package com.hyperwallet.android.receipt.repository;
+
+import androidx.lifecycle.LiveData;
+import androidx.paging.PagedList;
+
+import com.hyperwallet.android.common.viewmodel.Event;
+import com.hyperwallet.android.model.HyperwalletErrors;
+import com.hyperwallet.android.model.receipt.Receipt;
+
+/**
+ * Receipt Repository Contract
+ */
+public interface ReceiptRepository {
+
+ /**
+ * Load receipts information, consumer can subscribe to receipts data changes events
+ *
+ * @return live data paged receipts
+ */
+ LiveData> loadReceipts();
+
+ /**
+ * Loading indicator consumer can subscribe to loading of data events
+ *
+ * @return live data true if load receipt is in loading state; false otherwise
+ */
+ LiveData isLoading();
+
+ /**
+ * Error information, consumer can subscribe of errors occur during data retrieval
+ *
+ * @return live event data list of errors if there's an error
+ */
+ LiveData> getErrors();
+
+ /**
+ * Reload receipt information, usually invoked when error is raised after the first load and consumer opts to retry
+ * the last operation
+ */
+ void retryLoadReceipt();
+
+}
diff --git a/receipt/src/main/java/com/hyperwallet/android/receipt/repository/ReceiptRepositoryFactory.java b/receipt/src/main/java/com/hyperwallet/android/receipt/repository/ReceiptRepositoryFactory.java
new file mode 100644
index 000000000..b096ab7a0
--- /dev/null
+++ b/receipt/src/main/java/com/hyperwallet/android/receipt/repository/ReceiptRepositoryFactory.java
@@ -0,0 +1,56 @@
+/*
+ * The MIT License (MIT)
+ * Copyright (c) 2019 Hyperwallet Systems Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package com.hyperwallet.android.receipt.repository;
+
+/**
+ * {@link ReceiptRepository} factory
+ */
+public class ReceiptRepositoryFactory {
+
+ private static ReceiptRepositoryFactory sInstance;
+ private final ReceiptRepository mReceiptRepository;
+
+ private ReceiptRepositoryFactory() {
+ mReceiptRepository = new ReceiptRepositoryImpl();
+ }
+
+ /**
+ * Creates context single instance of this Factory
+ *
+ * @return receipt repository factory instance
+ */
+ public static synchronized ReceiptRepositoryFactory getInstance() {
+ if (sInstance == null) {
+ sInstance = new ReceiptRepositoryFactory();
+ }
+ return sInstance;
+ }
+
+ /**
+ * Clears instance of repository factory
+ */
+ public static void clearInstance() {
+ sInstance = null;
+ }
+
+ /**
+ * @return ReceiptRepository instance implementation
+ * */
+ public ReceiptRepository getReceiptRepository() {
+ return mReceiptRepository;
+ }
+}
diff --git a/receipt/src/main/java/com/hyperwallet/android/receipt/repository/ReceiptRepositoryImpl.java b/receipt/src/main/java/com/hyperwallet/android/receipt/repository/ReceiptRepositoryImpl.java
new file mode 100644
index 000000000..cde7edd94
--- /dev/null
+++ b/receipt/src/main/java/com/hyperwallet/android/receipt/repository/ReceiptRepositoryImpl.java
@@ -0,0 +1,93 @@
+/*
+ * The MIT License (MIT)
+ * Copyright (c) 2019 Hyperwallet Systems Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package com.hyperwallet.android.receipt.repository;
+
+import androidx.lifecycle.LiveData;
+import androidx.paging.LivePagedListBuilder;
+import androidx.paging.PagedList;
+
+import com.hyperwallet.android.common.viewmodel.Event;
+import com.hyperwallet.android.model.HyperwalletErrors;
+import com.hyperwallet.android.model.receipt.Receipt;
+
+/**
+ * {@link ReceiptRepository} implementation
+ */
+public class ReceiptRepositoryImpl implements ReceiptRepository {
+
+ private static final int PAGE_SIZE = 10;
+ private static final int INITIAL_LOAD_SIZE = 20;
+
+ private final ReceiptDataSourceFactory mDataSourceFactory;
+ private final LiveData mReceiptDataSourceLiveData;
+ private LiveData> mErrorsLiveData;
+ private LiveData mIsFetchingData;
+ private LiveData> mReceiptsLiveData;
+
+ ReceiptRepositoryImpl() {
+ mDataSourceFactory = new ReceiptDataSourceFactory();
+ mReceiptDataSourceLiveData = mDataSourceFactory.getReceiptDataSource();
+ }
+
+ /**
+ * @see {@link ReceiptRepository#loadReceipts()}
+ */
+ @Override
+ public LiveData> loadReceipts() {
+ if (mReceiptsLiveData == null) {
+ PagedList.Config config = new PagedList.Config.Builder()
+ .setPageSize(PAGE_SIZE)
+ .setEnablePlaceholders(true)
+ .setInitialLoadSizeHint(INITIAL_LOAD_SIZE)
+ .build();
+ mReceiptsLiveData = new LivePagedListBuilder<>(mDataSourceFactory, config).build();
+ }
+ return mReceiptsLiveData;
+ }
+
+ /**
+ * @see {@link ReceiptRepository#isLoading()}
+ */
+ @Override
+ public LiveData isLoading() {
+ if (mIsFetchingData == null) {
+ mIsFetchingData = mReceiptDataSourceLiveData.getValue().isFetchingData();
+ }
+ return mIsFetchingData;
+ }
+
+ /**
+ * @see {@link ReceiptRepository#getErrors()}
+ * */
+ @Override
+ public LiveData> getErrors() {
+ if (mErrorsLiveData == null) {
+ mErrorsLiveData = mReceiptDataSourceLiveData.getValue().getErrors();
+ }
+ return mErrorsLiveData;
+ }
+
+ /**
+ * @see {@link ReceiptRepository#retryLoadReceipt()}
+ * */
+ @Override
+ public void retryLoadReceipt() {
+ if (mReceiptDataSourceLiveData.getValue() != null) {
+ mReceiptDataSourceLiveData.getValue().retry();
+ }
+ }
+}
diff --git a/receipt/src/main/java/com/hyperwallet/android/receipt/view/ListReceiptActivity.java b/receipt/src/main/java/com/hyperwallet/android/receipt/view/ListReceiptActivity.java
new file mode 100644
index 000000000..e7f57c821
--- /dev/null
+++ b/receipt/src/main/java/com/hyperwallet/android/receipt/view/ListReceiptActivity.java
@@ -0,0 +1,133 @@
+/*
+ * The MIT License (MIT)
+ * Copyright (c) 2019 Hyperwallet Systems Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package com.hyperwallet.android.receipt.view;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.Toolbar;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentManager;
+import androidx.fragment.app.FragmentTransaction;
+import androidx.lifecycle.Observer;
+import androidx.lifecycle.ViewModelProviders;
+
+import com.hyperwallet.android.common.view.error.DefaultErrorDialogFragment;
+import com.hyperwallet.android.common.view.error.OnNetworkErrorCallback;
+import com.hyperwallet.android.common.viewmodel.Event;
+import com.hyperwallet.android.model.HyperwalletError;
+import com.hyperwallet.android.model.HyperwalletErrors;
+import com.hyperwallet.android.receipt.R;
+import com.hyperwallet.android.receipt.repository.ReceiptRepositoryFactory;
+import com.hyperwallet.android.receipt.viewmodel.ListReceiptViewModel;
+
+import java.util.List;
+
+public class ListReceiptActivity extends AppCompatActivity implements OnNetworkErrorCallback {
+
+ private ListReceiptViewModel mListReceiptViewModel;
+
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_list_receipt);
+
+ Toolbar toolbar = findViewById(R.id.toolbar);
+ setSupportActionBar(toolbar);
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ getSupportActionBar().setDisplayShowHomeEnabled(true);
+ getSupportActionBar().setTitle(R.string.title_activity_receipt_list);
+ toolbar.setNavigationOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ finish();
+ }
+ });
+
+ ReceiptRepositoryFactory factory = ReceiptRepositoryFactory.getInstance();
+ mListReceiptViewModel = ViewModelProviders.of(this, new ListReceiptViewModel
+ .ListReceiptViewModelFactory(factory.getReceiptRepository()))
+ .get(ListReceiptViewModel.class);
+
+ mListReceiptViewModel.getReceiptErrors().observe(this, new Observer>() {
+ @Override
+ public void onChanged(Event event) {
+ if (event != null && !event.isContentConsumed()) {
+ showErrorOnLoadReceipt(event.getContent().getErrors());
+ }
+ }
+ });
+
+ if (savedInstanceState == null) {
+ initFragment(ListReceiptFragment.newInstance());
+ }
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ ReceiptRepositoryFactory.clearInstance();
+ }
+
+ @Override
+ protected void onActivityResult(final int requestCode, final int resultCode, @Nullable final Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+
+ @Override
+ public boolean onSupportNavigateUp() {
+ onBackPressed();
+ return true;
+ }
+
+ private void initFragment(@NonNull final Fragment fragment) {
+ FragmentManager fragmentManager = getSupportFragmentManager();
+ FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
+ fragmentTransaction.add(R.id.list_receipt_fragment, fragment);
+ fragmentTransaction.commit();
+ }
+
+ @Override
+ public void retry() {
+ FragmentManager fragmentManager = getSupportFragmentManager();
+ ListReceiptFragment fragment = (ListReceiptFragment)
+ fragmentManager.findFragmentById(R.id.list_receipt_fragment);
+
+ if (fragment == null) {
+ fragment = ListReceiptFragment.newInstance();
+ }
+ fragment.retry();
+ }
+
+ private void showErrorOnLoadReceipt(@NonNull final List errors) {
+ FragmentManager fragmentManager = getSupportFragmentManager();
+ DefaultErrorDialogFragment fragment = (DefaultErrorDialogFragment)
+ fragmentManager.findFragmentByTag(DefaultErrorDialogFragment.TAG);
+
+ if (fragment == null) {
+ fragment = DefaultErrorDialogFragment.newInstance(errors);
+ }
+
+ if (!fragment.isAdded()) {
+ fragment.show(fragmentManager);
+ }
+ }
+}
diff --git a/receipt/src/main/java/com/hyperwallet/android/receipt/view/ListReceiptFragment.java b/receipt/src/main/java/com/hyperwallet/android/receipt/view/ListReceiptFragment.java
new file mode 100644
index 000000000..ce6f9fd4d
--- /dev/null
+++ b/receipt/src/main/java/com/hyperwallet/android/receipt/view/ListReceiptFragment.java
@@ -0,0 +1,264 @@
+/*
+ * The MIT License (MIT)
+ * Copyright (c) 2019 Hyperwallet Systems Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package com.hyperwallet.android.receipt.view;
+
+import static com.hyperwallet.android.model.receipt.Receipt.Entries.CREDIT;
+import static com.hyperwallet.android.model.receipt.Receipt.Entries.DEBIT;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+import androidx.lifecycle.Observer;
+import androidx.lifecycle.ViewModelProviders;
+import androidx.paging.PagedList;
+import androidx.paging.PagedListAdapter;
+import androidx.recyclerview.widget.DiffUtil;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.hyperwallet.android.common.util.DateUtils;
+import com.hyperwallet.android.model.receipt.Receipt;
+import com.hyperwallet.android.receipt.R;
+import com.hyperwallet.android.receipt.viewmodel.ListReceiptViewModel;
+
+import java.util.Calendar;
+import java.util.Locale;
+import java.util.Objects;
+
+public class ListReceiptFragment extends Fragment {
+
+ private ListReceiptAdapter mListReceiptAdapter;
+ private RecyclerView mListReceiptsView;
+ private ListReceiptViewModel mListReceiptViewModel;
+ private View mProgressBar;
+
+ /**
+ * Please don't use this constructor this is reserved for Android Core Framework
+ *
+ * @see {@link ListReceiptFragment#newInstance()} instead.
+ */
+ public ListReceiptFragment() {
+ setRetainInstance(true);
+ }
+
+ static ListReceiptFragment newInstance() {
+ ListReceiptFragment fragment = new ListReceiptFragment();
+ return fragment;
+ }
+
+ @Override
+ public void onCreate(@Nullable final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mListReceiptViewModel = ViewModelProviders.of(requireActivity()).get(
+ ListReceiptViewModel.class);
+ }
+
+ @Override
+ public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.list_receipt_fragment, container, false);
+ }
+
+ @Override
+ public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ mProgressBar = view.findViewById(R.id.list_receipt_progress_bar);
+ mListReceiptAdapter = new ListReceiptAdapter(new ListReceiptItemDiffCallback());
+ mListReceiptsView = view.findViewById(R.id.list_receipts);
+ mListReceiptsView.setHasFixedSize(true);
+ mListReceiptsView.setLayoutManager(new LinearLayoutManager(getActivity()));
+ mListReceiptsView.addItemDecoration(new ReceiptItemDividerDecorator(requireContext(), false));
+ mListReceiptsView.setAdapter(mListReceiptAdapter);
+ registerObservers();
+ }
+
+ private void registerObservers() {
+ mListReceiptViewModel.getReceiptList().observe(getViewLifecycleOwner(), new Observer>() {
+ @Override
+ public void onChanged(PagedList transferMethods) {
+ mListReceiptAdapter.submitList(transferMethods);
+ }
+ });
+
+ mListReceiptViewModel.isLoadingData().observe(getViewLifecycleOwner(), new Observer() {
+ @Override
+ public void onChanged(Boolean loading) {
+ if (loading) {
+ mProgressBar.setVisibility(View.VISIBLE);
+ } else {
+ mProgressBar.setVisibility(View.GONE);
+ }
+ }
+ });
+ }
+
+ void retry() {
+ mListReceiptViewModel.retryLoadReceipts();
+ }
+
+ private static class ListReceiptItemDiffCallback extends DiffUtil.ItemCallback {
+
+ @Override
+ public boolean areItemsTheSame(@NonNull final Receipt oldItem, @NonNull final Receipt newItem) {
+ return oldItem.hashCode() == newItem.hashCode()
+ && Objects.equals(oldItem, newItem);
+ }
+
+ @Override
+ public boolean areContentsTheSame(@NonNull final Receipt oldItem, @NonNull final Receipt newItem) {
+ return oldItem.hashCode() == newItem.hashCode()
+ && Objects.equals(oldItem, newItem);
+ }
+ }
+
+ private static class ListReceiptAdapter
+ extends PagedListAdapter {
+
+ private static final String HEADER_DATE_FORMAT = "MMMM yyyy";
+ private static final String CAPTION_DATE_FORMAT = "MMMM dd, yyyy";
+ private static final int HEADER_VIEW_TYPE = 1;
+ private static final int DATA_VIEW_TYPE = 0;
+
+ ListReceiptAdapter(@NonNull final DiffUtil.ItemCallback diffCallback) {
+ super(diffCallback);
+ }
+
+ @Override
+ public int getItemViewType(final int position) {
+ if (position != 0) {
+ Receipt previous = getItem(position - 1);
+ Receipt current = getItem(position);
+ if (isDataViewType(previous, current)) {
+ return DATA_VIEW_TYPE;
+ }
+ }
+ return HEADER_VIEW_TYPE;
+ }
+
+ boolean isDataViewType(@NonNull final Receipt previous, @NonNull final Receipt current) {
+ Calendar prev = Calendar.getInstance();
+ prev.setTime(DateUtils.fromDateTimeString(previous.getCreatedOn()));
+ Calendar curr = Calendar.getInstance();
+ curr.setTime(DateUtils.fromDateTimeString(current.getCreatedOn()));
+
+ return prev.get(Calendar.MONTH) == curr.get(Calendar.MONTH)
+ && prev.get(Calendar.YEAR) == curr.get(Calendar.YEAR);
+ }
+
+ @NonNull
+ @Override
+ public ReceiptViewHolder onCreateViewHolder(final @NonNull ViewGroup viewGroup, int viewType) {
+ LayoutInflater layout = LayoutInflater.from(viewGroup.getContext());
+
+ if (viewType == HEADER_VIEW_TYPE) {
+ View headerView = layout.inflate(R.layout.item_receipt_with_header, viewGroup, false);
+ return new ReceiptViewHolderWithHeader(headerView);
+ }
+ View dataView = layout.inflate(R.layout.item_receipt, viewGroup, false);
+ return new ReceiptViewHolder(dataView);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull final ReceiptViewHolder holder, final int position) {
+ final Receipt receipt = getItem(position);
+ if (receipt != null) {
+ holder.bind(receipt);
+ }
+ }
+
+ class ReceiptViewHolder extends RecyclerView.ViewHolder {
+ private final TextView mTransactionAmount;
+ private final TextView mTransactionCurrency;
+ private final TextView mTransactionDate;
+ private final TextView mTransactionTitle;
+ private final TextView mTransactionTypeIcon;
+
+ ReceiptViewHolder(@NonNull final View item) {
+ super(item);
+ mTransactionAmount = item.findViewById(R.id.transaction_amount);
+ mTransactionCurrency = item.findViewById(R.id.transaction_currency);
+ mTransactionDate = item.findViewById(R.id.transaction_date);
+ mTransactionTitle = item.findViewById(R.id.transaction_title);
+ mTransactionTypeIcon = item.findViewById(R.id.transaction_type_icon);
+ }
+
+ void bind(@NonNull final Receipt receipt) {
+ if (CREDIT.equals(receipt.getEntry())) {
+ mTransactionAmount.setTextColor(mTransactionAmount.getContext()
+ .getResources().getColor(R.color.positiveColor));
+ mTransactionAmount.setText(mTransactionAmount.getContext()
+ .getString(R.string.credit_sign, receipt.getAmount()));
+ mTransactionTypeIcon.setTextColor(mTransactionTypeIcon.getContext()
+ .getResources().getColor(R.color.positiveColor));
+ mTransactionTypeIcon.setBackground(mTransactionTypeIcon.getContext()
+ .getDrawable(R.drawable.circle_positive));
+ mTransactionTypeIcon.setText(mTransactionTypeIcon.getContext().getText(R.string.credit));
+ } else if (DEBIT.equals(receipt.getEntry())) {
+ mTransactionAmount.setTextColor(mTransactionAmount.getContext()
+ .getResources().getColor(R.color.colorAccent));
+ mTransactionAmount.setText(mTransactionAmount.getContext()
+ .getString(R.string.debit_sign, receipt.getAmount()));
+ mTransactionTypeIcon.setTextColor(mTransactionTypeIcon.getContext()
+ .getResources().getColor(R.color.colorAccent));
+ mTransactionTypeIcon.setBackground(mTransactionTypeIcon.getContext()
+ .getDrawable(R.drawable.circle_negative));
+ mTransactionTypeIcon.setText(mTransactionTypeIcon.getContext().getText(R.string.debit));
+ }
+
+ mTransactionCurrency.setText(receipt.getCurrency());
+ mTransactionTitle.setText(getTransactionTitle(receipt.getType(), mTransactionTitle.getContext()));
+ mTransactionDate.setText(DateUtils.toDateFormat(DateUtils.
+ fromDateTimeString(receipt.getCreatedOn()), CAPTION_DATE_FORMAT));
+ }
+
+ String getTransactionTitle(@NonNull final String receiptType, @NonNull final Context context) {
+ String showTitle = context.getResources().getString(R.string.unknown_type);
+ int resourceId = context.getResources().getIdentifier(receiptType.toLowerCase(Locale.ROOT), "string",
+ context.getPackageName());
+ if (resourceId != 0) {
+ showTitle = context.getResources().getString(resourceId);
+ }
+
+ return showTitle;
+ }
+ }
+
+ class ReceiptViewHolderWithHeader extends ReceiptViewHolder {
+
+ private final TextView mTransactionHeaderText;
+
+ ReceiptViewHolderWithHeader(@NonNull final View item) {
+ super(item);
+ mTransactionHeaderText = item.findViewById(R.id.item_date_header_title);
+ }
+
+ @Override
+ void bind(@NonNull final Receipt receipt) {
+ super.bind(receipt);
+ mTransactionHeaderText.setText(DateUtils.toDateFormat(
+ DateUtils.fromDateTimeString(receipt.getCreatedOn()), HEADER_DATE_FORMAT));
+ }
+ }
+ }
+}
diff --git a/receipt/src/main/java/com/hyperwallet/android/receipt/view/ReceiptItemDividerDecorator.java b/receipt/src/main/java/com/hyperwallet/android/receipt/view/ReceiptItemDividerDecorator.java
new file mode 100644
index 000000000..c61b22e5d
--- /dev/null
+++ b/receipt/src/main/java/com/hyperwallet/android/receipt/view/ReceiptItemDividerDecorator.java
@@ -0,0 +1,102 @@
+/*
+ * The MIT License (MIT)
+ * Copyright (c) 2019 Hyperwallet Systems Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package com.hyperwallet.android.receipt.view;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.hyperwallet.android.common.view.HorizontalDividerItemDecorator;
+
+public class ReceiptItemDividerDecorator extends HorizontalDividerItemDecorator {
+
+ ReceiptItemDividerDecorator(@NonNull final Context context, final boolean withHeaderDivider) {
+ super(context, withHeaderDivider);
+ }
+
+ @Override
+ public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
+ int right = parent.getWidth() - parent.getPaddingRight();
+
+ int childCount = parent.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ int left = 0;
+ int top;
+ int bottom;
+ View child = parent.getChildAt(i);
+ RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
+
+ if (i == 0) { // first
+ // draw top
+ top = child.getTop() + params.topMargin;
+ bottom = top + mHorizontalItemDivider.getIntrinsicHeight();
+ mHorizontalItemDivider.setBounds(left, top, right, bottom);
+ mHorizontalItemDivider.draw(c);
+
+ if (childCount > 1) { // draw bottom
+ if (child instanceof LinearLayout) { // receipt header
+ // peek if next is a header then draw line from beginning
+ if (parent.getChildAt(i + 1) != null
+ && parent.getChildAt(i + 1) instanceof LinearLayout) {
+ left = 0;
+ } else {
+ left = ((ViewGroup) ((ViewGroup) child).getChildAt(1)).getChildAt(1).getLeft();
+ }
+ } else { // receipt item
+ // peek if next is a header then draw line from beginning
+ if (parent.getChildAt(i + 1) != null
+ && parent.getChildAt(i + 1) instanceof LinearLayout) {
+ left = 0;
+ } else {
+ left = ((ViewGroup) child).getChildAt(1).getLeft();
+ }
+ }
+ }
+ top = child.getBottom() + params.bottomMargin;
+ } else if (i == parent.getChildCount() - 1) { // draw bottom
+ top = child.getBottom() + params.bottomMargin;
+ } else { //draw middle
+ if (child instanceof LinearLayout) { // header found
+ // peek if next is a header then draw line from beginning
+ if (parent.getChildAt(i + 1) != null
+ && parent.getChildAt(i + 1) instanceof LinearLayout) {
+ left = 0;
+ } else {
+ left = ((ViewGroup) ((ViewGroup) child).getChildAt(1)).getChildAt(1).getLeft();
+ }
+ } else { // non header
+ // peek if next is a header then draw line from beginning
+ if (parent.getChildAt(i + 1) != null
+ && parent.getChildAt(i + 1) instanceof LinearLayout) {
+ left = 0;
+ } else {
+ left = ((ViewGroup) child).getChildAt(1).getLeft();
+ }
+ }
+ top = child.getBottom() + params.bottomMargin;
+ }
+ bottom = top + mHorizontalItemDivider.getIntrinsicHeight();
+ mHorizontalItemDivider.setBounds(left, top, right, bottom);
+ mHorizontalItemDivider.draw(c);
+ }
+ }
+}
diff --git a/receipt/src/main/java/com/hyperwallet/android/receipt/viewmodel/ListReceiptViewModel.java b/receipt/src/main/java/com/hyperwallet/android/receipt/viewmodel/ListReceiptViewModel.java
new file mode 100644
index 000000000..57f41b5c1
--- /dev/null
+++ b/receipt/src/main/java/com/hyperwallet/android/receipt/viewmodel/ListReceiptViewModel.java
@@ -0,0 +1,93 @@
+/*
+ * The MIT License (MIT)
+ * Copyright (c) 2019 Hyperwallet Systems Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ * associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package com.hyperwallet.android.receipt.viewmodel;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
+import androidx.lifecycle.Observer;
+import androidx.lifecycle.ViewModel;
+import androidx.lifecycle.ViewModelProvider;
+import androidx.paging.PagedList;
+
+import com.hyperwallet.android.common.viewmodel.Event;
+import com.hyperwallet.android.model.HyperwalletErrors;
+import com.hyperwallet.android.model.receipt.Receipt;
+import com.hyperwallet.android.receipt.repository.ReceiptRepository;
+
+public class ListReceiptViewModel extends ViewModel {
+
+ private MutableLiveData> mErrorEvent = new MutableLiveData<>();
+ private Observer> mErrorEventObserver;
+ private ReceiptRepository mReceiptRepository;
+
+ private ListReceiptViewModel(@NonNull final ReceiptRepository receiptRepository) {
+ mReceiptRepository = receiptRepository;
+ // load initial receipts
+ mReceiptRepository.loadReceipts();
+
+ // register one time error event observer
+ mErrorEventObserver = new Observer>() {
+ @Override
+ public void onChanged(Event event) {
+ mErrorEvent.postValue(event);
+ }
+ };
+ mReceiptRepository.getErrors().observeForever(mErrorEventObserver);
+ }
+
+ public LiveData isLoadingData() {
+ return mReceiptRepository.isLoading();
+ }
+
+ public LiveData> getReceiptErrors() {
+ return mErrorEvent;
+ }
+
+ public LiveData> getReceiptList() {
+ return mReceiptRepository.loadReceipts();
+ }
+
+ public void retryLoadReceipts() {
+ mReceiptRepository.retryLoadReceipt();
+ }
+
+ @Override
+ protected void onCleared() {
+ super.onCleared();
+ mReceiptRepository.getErrors().removeObserver(mErrorEventObserver);
+ mReceiptRepository = null;
+ }
+
+ public static class ListReceiptViewModelFactory implements ViewModelProvider.Factory {
+
+ private final ReceiptRepository mReceiptRepository;
+
+ public ListReceiptViewModelFactory(@NonNull final ReceiptRepository receiptRepository) {
+ mReceiptRepository = receiptRepository;
+ }
+
+ @NonNull
+ @Override
+ public T create(@NonNull Class modelClass) {
+ if (modelClass.isAssignableFrom(ListReceiptViewModel.class)) {
+ return (T) new ListReceiptViewModel(mReceiptRepository);
+ }
+ throw new IllegalArgumentException("Expecting ViewModel class: " + ListReceiptViewModel.class.getName());
+ }
+ }
+}
diff --git a/receipt/src/main/res/drawable/circle_negative.xml b/receipt/src/main/res/drawable/circle_negative.xml
new file mode 100644
index 000000000..c7ae00b18
--- /dev/null
+++ b/receipt/src/main/res/drawable/circle_negative.xml
@@ -0,0 +1,9 @@
+
+
+ -
+
+
+
+
+
+
diff --git a/receipt/src/main/res/drawable/circle_positive.xml b/receipt/src/main/res/drawable/circle_positive.xml
new file mode 100644
index 000000000..8b6506627
--- /dev/null
+++ b/receipt/src/main/res/drawable/circle_positive.xml
@@ -0,0 +1,9 @@
+
+
+ -
+
+
+
+
+
+
diff --git a/receipt/src/main/res/drawable/item_view_border.xml b/receipt/src/main/res/drawable/item_view_border.xml
new file mode 100644
index 000000000..1bd67efd9
--- /dev/null
+++ b/receipt/src/main/res/drawable/item_view_border.xml
@@ -0,0 +1,20 @@
+
+
+ -
+
+
+
+
+
+
+ -
+
+
+
+
+
+
\ No newline at end of file
diff --git a/receipt/src/main/res/drawable/item_view_border_header.xml b/receipt/src/main/res/drawable/item_view_border_header.xml
new file mode 100644
index 000000000..cceee0055
--- /dev/null
+++ b/receipt/src/main/res/drawable/item_view_border_header.xml
@@ -0,0 +1,20 @@
+
+
+ -
+
+
+
+
+
+
+ -
+
+
+
+
+
+
\ No newline at end of file
diff --git a/receipt/src/main/res/layout/activity_list_receipt.xml b/receipt/src/main/res/layout/activity_list_receipt.xml
new file mode 100644
index 000000000..44beb1444
--- /dev/null
+++ b/receipt/src/main/res/layout/activity_list_receipt.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/receipt/src/main/res/layout/item_receipt.xml b/receipt/src/main/res/layout/item_receipt.xml
new file mode 100644
index 000000000..4388d4ab7
--- /dev/null
+++ b/receipt/src/main/res/layout/item_receipt.xml
@@ -0,0 +1,73 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/receipt/src/main/res/layout/item_receipt_with_header.xml b/receipt/src/main/res/layout/item_receipt_with_header.xml
new file mode 100644
index 000000000..29f002169
--- /dev/null
+++ b/receipt/src/main/res/layout/item_receipt_with_header.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/receipt/src/main/res/layout/list_receipt_fragment.xml b/receipt/src/main/res/layout/list_receipt_fragment.xml
new file mode 100644
index 000000000..52e9725db
--- /dev/null
+++ b/receipt/src/main/res/layout/list_receipt_fragment.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/receipt/src/main/res/values/colors.xml b/receipt/src/main/res/values/colors.xml
new file mode 100644
index 000000000..89155ac94
--- /dev/null
+++ b/receipt/src/main/res/values/colors.xml
@@ -0,0 +1,6 @@
+
+
+ #FEF7F7
+ #5FBF00
+ #F1FAE8
+
diff --git a/receipt/src/main/res/values/dimens.xml b/receipt/src/main/res/values/dimens.xml
new file mode 100644
index 000000000..999d990b2
--- /dev/null
+++ b/receipt/src/main/res/values/dimens.xml
@@ -0,0 +1,6 @@
+
+ 3dp
+ 10dp
+ 8dp
+ 70dp
+
diff --git a/receipt/src/main/res/values/strings.xml b/receipt/src/main/res/values/strings.xml
index c3ca11b97..6c6730a9e 100644
--- a/receipt/src/main/res/values/strings.xml
+++ b/receipt/src/main/res/values/strings.xml
@@ -1,6 +1,21 @@
receipt
+ Transactions
+ Seems like, you don\'t have any Transactions,
+ yet.
+
+ Transfer Funds
+ \uE900
+ Placeholder
+
+
+ \uE900
+ \uE902
+ - %s
+ + %s
+
+ Unknown Transaction Type
Annual Fee
Annual Fee Refund
Customer Service Fee
diff --git a/receipt/src/main/res/values/styles.xml b/receipt/src/main/res/values/styles.xml
new file mode 100644
index 000000000..b27a3a524
--- /dev/null
+++ b/receipt/src/main/res/values/styles.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
diff --git a/receipt/src/main/res/xml/network_security_config.xml b/receipt/src/main/res/xml/network_security_config.xml
new file mode 100644
index 000000000..5e4ba9c97
--- /dev/null
+++ b/receipt/src/main/res/xml/network_security_config.xml
@@ -0,0 +1,6 @@
+
+
+
+ localhost
+
+
\ No newline at end of file
diff --git a/receipt/src/test/java/com/hyperwallet/android/receipt/repository/ReceiptDataSourceFactoryTest.java b/receipt/src/test/java/com/hyperwallet/android/receipt/repository/ReceiptDataSourceFactoryTest.java
new file mode 100644
index 000000000..cf5c63114
--- /dev/null
+++ b/receipt/src/test/java/com/hyperwallet/android/receipt/repository/ReceiptDataSourceFactoryTest.java
@@ -0,0 +1,38 @@
+package com.hyperwallet.android.receipt.repository;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.notNullValue;
+
+import androidx.lifecycle.LiveData;
+import androidx.paging.DataSource;
+
+import org.hamcrest.CoreMatchers;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class ReceiptDataSourceFactoryTest {
+
+ @Test
+ public void testGetReceiptDataSource_returnsLiveDataReceiptSource() {
+ // initialize
+ ReceiptDataSourceFactory dataSourceFactory = new ReceiptDataSourceFactory();
+ // test
+ LiveData liveData = dataSourceFactory.getReceiptDataSource();
+ // assert
+ assertThat(liveData, is(notNullValue()));
+ }
+
+ @Test
+ public void testCreate_returnsDataSource() {
+ // initialize
+ ReceiptDataSourceFactory dataSourceFactory = new ReceiptDataSourceFactory();
+ // test
+ DataSource dataSource = dataSourceFactory.create();
+ // assert
+ assertThat(dataSource, is(notNullValue()));
+ assertThat(dataSource, CoreMatchers.instanceOf(ReceiptDataSource.class));
+ }
+}
diff --git a/receipt/src/test/java/com/hyperwallet/android/receipt/repository/ReceiptDataSourceTest.java b/receipt/src/test/java/com/hyperwallet/android/receipt/repository/ReceiptDataSourceTest.java
new file mode 100644
index 000000000..d09854710
--- /dev/null
+++ b/receipt/src/test/java/com/hyperwallet/android/receipt/repository/ReceiptDataSourceTest.java
@@ -0,0 +1,389 @@
+package com.hyperwallet.android.receipt.repository;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.nullValue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import static com.hyperwallet.android.model.receipt.Receipt.Entries.CREDIT;
+import static com.hyperwallet.android.model.receipt.Receipt.Entries.DEBIT;
+import static com.hyperwallet.android.model.receipt.Receipt.ReceiptTypes.PAYMENT;
+import static com.hyperwallet.android.model.receipt.Receipt.ReceiptTypes.TRANSFER_TO_BANK_ACCOUNT;
+
+import androidx.paging.PageKeyedDataSource;
+
+import com.hyperwallet.android.Hyperwallet;
+import com.hyperwallet.android.exception.HyperwalletException;
+import com.hyperwallet.android.listener.HyperwalletListener;
+import com.hyperwallet.android.model.HyperwalletError;
+import com.hyperwallet.android.model.HyperwalletErrors;
+import com.hyperwallet.android.model.paging.HyperwalletPageList;
+import com.hyperwallet.android.model.receipt.Receipt;
+import com.hyperwallet.android.model.receipt.ReceiptQueryParam;
+import com.hyperwallet.android.rule.HyperwalletExternalResourceManager;
+
+import org.hamcrest.Matchers;
+import org.json.JSONObject;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatchers;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.mockito.stubbing.Answer;
+import org.robolectric.RobolectricTestRunner;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(RobolectricTestRunner.class)
+public class ReceiptDataSourceTest {
+
+ @Rule
+ public MockitoRule mMockito = MockitoJUnit.rule();
+ @Rule
+ public HyperwalletExternalResourceManager mExternalResourceManager = new HyperwalletExternalResourceManager();
+
+ @Mock
+ private Hyperwallet mHyperwallet;
+ @Mock
+ private PageKeyedDataSource.LoadInitialParams mInitialParams;
+ @Mock
+ private PageKeyedDataSource.LoadInitialCallback mInitialCallback;
+ // can't be mocked due to params.key is of type Integer and autoboxing will not work with null to 0
+ private final PageKeyedDataSource.LoadParams mLoadAfterParams =
+ new PageKeyedDataSource.LoadParams<>(10, 10);
+ @Mock
+ private PageKeyedDataSource.LoadCallback mLoadAfterCallback;
+
+ @Captor
+ private ArgumentCaptor> mListArgumentCaptor;
+ @Captor
+ private ArgumentCaptor mPreviousCaptor;
+ @Captor
+ private ArgumentCaptor mNextCaptor;
+
+ @Spy
+ private ReceiptDataSource mReceiptDataSource;
+
+ @Before
+ public void setUp() {
+ doReturn(mHyperwallet).when(mReceiptDataSource).getHyperwallet();
+ }
+
+ @Test
+ public void testLoadInitial_returnsReceipts() throws Exception {
+ String json = mExternalResourceManager.getResourceContent("receipt_list_date_grouping_response.json");
+ JSONObject jsonObject = new JSONObject(json);
+ final HyperwalletPageList response = new HyperwalletPageList<>(jsonObject, Receipt.class);
+ doAnswer(new Answer() {
+ @Override
+ public Object answer(InvocationOnMock invocation) {
+ HyperwalletListener listener = (HyperwalletListener) invocation.getArguments()[1];
+ listener.onSuccess(response);
+ return listener;
+ }
+ }).when(mHyperwallet).listReceipts(any(ReceiptQueryParam.class),
+ ArgumentMatchers.>>any());
+
+ // test
+ mReceiptDataSource.loadInitial(mInitialParams, mInitialCallback);
+
+ verify(mHyperwallet).listReceipts(any(ReceiptQueryParam.class),
+ ArgumentMatchers.>>any());
+ verify(mInitialCallback).onResult(mListArgumentCaptor.capture(), mPreviousCaptor.capture(),
+ mNextCaptor.capture());
+
+ assertThat(mPreviousCaptor.getValue(), is(0));
+ assertThat(mNextCaptor.getValue(), is(10));
+
+ // assert receipts information
+ List receipts = mListArgumentCaptor.getValue();
+ assertThat(receipts, Matchers.hasSize(5));
+ assertThat(receipts.get(0).getJournalId(), is("51660665"));
+ assertThat(receipts.get(0).getType(), is(PAYMENT));
+ assertThat(receipts.get(0).getEntry(), is(CREDIT));
+ assertThat(receipts.get(0).getSourceToken(), is("act-b1f6dc28-e534-45f4-a661-3523f051f77a"));
+ assertThat(receipts.get(0).getDestinationToken(), is("usr-b4e8ec34-52d8-4a81-9566-bdde1bd745b6"));
+ assertThat(receipts.get(0).getAmount(), is("5000.00"));
+ assertThat(receipts.get(0).getFee(), is("0.00"));
+ assertThat(receipts.get(0).getCurrency(), is("USD"));
+ assertThat(receipts.get(0).getDetails(), is(notNullValue()));
+ assertThat(receipts.get(0).getDetails().getPayeeName(), is("Kevin Puckett"));
+ assertThat(receipts.get(0).getDetails().getClientPaymentId(), is("trans-0001"));
+ assertThat(receipts.get(1).getJournalId(), is("51660666"));
+ assertThat(receipts.get(1).getType(), is(TRANSFER_TO_BANK_ACCOUNT));
+ assertThat(receipts.get(1).getEntry(), is(DEBIT));
+ assertThat(receipts.get(1).getSourceToken(), is("usr-b4e8ec34-52d8-4a81-9566-bdde1bd745b6"));
+ assertThat(receipts.get(1).getDestinationToken(), is("trm-0a2ac589-2cae-4ed3-9b0b-658246a34687"));
+ assertThat(receipts.get(1).getAmount(), is("10.25"));
+ assertThat(receipts.get(1).getFee(), is("0.25"));
+ assertThat(receipts.get(1).getCurrency(), is("USD"));
+ assertThat(receipts.get(1).getDetails(), is(notNullValue()));
+ assertThat(receipts.get(1).getDetails().getPayeeName(), is("Kevin Puckett"));
+ assertThat(receipts.get(1).getDetails().getBankAccountId(), is("patzachery.mcclary@example.com"));
+
+ assertThat(mReceiptDataSource.getErrors().getValue(), is(nullValue()));
+ assertThat(mReceiptDataSource.isFetchingData().getValue(), is(false));
+ }
+
+ @Test
+ public void testLoadInitial_returnNoReceipt() {
+ doAnswer(new Answer() {
+ @Override
+ public Object answer(InvocationOnMock invocation) {
+ HyperwalletListener listener = (HyperwalletListener) invocation.getArguments()[1];
+ listener.onSuccess(null);
+ return listener;
+ }
+ }).when(mHyperwallet).listReceipts(any(ReceiptQueryParam.class),
+ ArgumentMatchers.>>any());
+
+ // test
+ mReceiptDataSource.loadInitial(mInitialParams, mInitialCallback);
+
+ verify(mHyperwallet).listReceipts(any(ReceiptQueryParam.class),
+ ArgumentMatchers.>>any());
+ verify(mInitialCallback, never()).onResult(ArgumentMatchers.anyList(), anyInt(), anyInt());
+
+ assertThat(mReceiptDataSource.getErrors().getValue(), is(nullValue()));
+ assertThat(mReceiptDataSource.isFetchingData().getValue(), is(false));
+ }
+
+ @Test
+ public void testLoadInitial_withError() {
+ final HyperwalletError error = new HyperwalletError("test message", "TEST_CODE");
+ List errorList = new ArrayList<>();
+ errorList.add(error);
+ final HyperwalletErrors errors = new HyperwalletErrors(errorList);
+
+ doAnswer(new Answer() {
+ @Override
+ public Object answer(InvocationOnMock invocation) {
+ HyperwalletListener listener = (HyperwalletListener) invocation.getArguments()[1];
+ listener.onFailure(new HyperwalletException(errors));
+ return listener;
+ }
+ }).when(mHyperwallet).listReceipts(any(ReceiptQueryParam.class),
+ ArgumentMatchers.>>any());
+
+ // test
+ mReceiptDataSource.loadInitial(mInitialParams, mInitialCallback);
+
+ verify(mHyperwallet).listReceipts(any(ReceiptQueryParam.class),
+ ArgumentMatchers.>>any());
+ verify(mInitialCallback, never()).onResult(ArgumentMatchers.anyList(), anyInt(), anyInt());
+
+ assertThat(mReceiptDataSource.getErrors().getValue(), is(notNullValue()));
+ assertThat(mReceiptDataSource.getErrors().getValue().getContent().getErrors(),
+ Matchers.hasSize(1));
+ assertThat(mReceiptDataSource.getErrors().getValue().getContent().getErrors().get(0).getCode(),
+ is("TEST_CODE"));
+ assertThat(mReceiptDataSource.getErrors().getValue().getContent().getErrors().get(0).getMessage(),
+ is("test message"));
+ assertThat(mReceiptDataSource.isFetchingData().getValue(), is(false));
+ }
+
+ @Test
+ public void testRetry_loadInitial() {
+ final HyperwalletError error = new HyperwalletError("test message", "TEST_CODE");
+ List errorList = new ArrayList<>();
+ errorList.add(error);
+ final HyperwalletErrors errors = new HyperwalletErrors(errorList);
+
+ doAnswer(new Answer() {
+ @Override
+ public Object answer(InvocationOnMock invocation) {
+ HyperwalletListener listener = (HyperwalletListener) invocation.getArguments()[1];
+ listener.onFailure(new HyperwalletException(errors));
+ return listener;
+ }
+ }).when(mHyperwallet).listReceipts(any(ReceiptQueryParam.class),
+ ArgumentMatchers.>>any());
+
+ // test
+ mReceiptDataSource.loadInitial(mInitialParams, mInitialCallback);
+
+ verify(mHyperwallet).listReceipts(any(ReceiptQueryParam.class),
+ ArgumentMatchers.>>any());
+ verify(mInitialCallback, never()).onResult(ArgumentMatchers.anyList(), anyInt(), anyInt());
+
+ // error occurred, this will save params and callback
+ assertThat(mReceiptDataSource.getErrors().getValue(), is(notNullValue()));
+
+ // test retry, saved params and callback will be used and no null pointer exception is thrown
+ mReceiptDataSource.retry();
+
+ // verify calls
+ verify(mReceiptDataSource, times(2)).loadInitial(
+ ArgumentMatchers.>any(),
+ ArgumentMatchers.>any());
+ verify(mReceiptDataSource, never()).loadAfter(
+ ArgumentMatchers.>any(),
+ ArgumentMatchers.>any());
+ }
+
+ @Test
+ public void testLoadAfter_returnsReceipts() throws Exception {
+ String json = mExternalResourceManager.getResourceContent("receipt_list_date_grouping_response.json");
+ JSONObject jsonObject = new JSONObject(json);
+ final HyperwalletPageList response = new HyperwalletPageList<>(jsonObject, Receipt.class);
+ doAnswer(new Answer() {
+ @Override
+ public Object answer(InvocationOnMock invocation) {
+ HyperwalletListener listener = (HyperwalletListener) invocation.getArguments()[1];
+ listener.onSuccess(response);
+ return listener;
+ }
+ }).when(mHyperwallet).listReceipts(any(ReceiptQueryParam.class),
+ ArgumentMatchers.>>any());
+
+ // test
+ mReceiptDataSource.loadAfter(mLoadAfterParams, mLoadAfterCallback);
+
+ verify(mHyperwallet).listReceipts(any(ReceiptQueryParam.class),
+ ArgumentMatchers.>>any());
+ verify(mLoadAfterCallback).onResult(mListArgumentCaptor.capture(), mNextCaptor.capture());
+
+ assertThat(mNextCaptor.getValue(), is(10));
+
+ // assert receipts information
+ List receipts = mListArgumentCaptor.getValue();
+ assertThat(receipts, Matchers.hasSize(5));
+ assertThat(receipts.get(3).getJournalId(), is("51660675"));
+ assertThat(receipts.get(3).getType(), is(PAYMENT));
+ assertThat(receipts.get(3).getEntry(), is(CREDIT));
+ assertThat(receipts.get(3).getSourceToken(), is("act-b1f6dc28-e534-45f4-a661-3523f051f77a"));
+ assertThat(receipts.get(3).getDestinationToken(), is("usr-b4e8ec34-52d8-4a81-9566-bdde1bd745b6"));
+ assertThat(receipts.get(3).getAmount(), is("13.00"));
+ assertThat(receipts.get(3).getFee(), is("0.00"));
+ assertThat(receipts.get(3).getCurrency(), is("USD"));
+ assertThat(receipts.get(3).getDetails(), is(notNullValue()));
+ assertThat(receipts.get(3).getDetails().getPayeeName(), is("Kevin Puckett"));
+ assertThat(receipts.get(3).getDetails().getClientPaymentId(), is("CSietnRJQQ0bscYkOoPJxNiTDiVALhjQ"));
+ assertThat(receipts.get(4).getJournalId(), is("51660676"));
+ assertThat(receipts.get(4).getType(), is(PAYMENT));
+ assertThat(receipts.get(4).getEntry(), is(CREDIT));
+ assertThat(receipts.get(4).getSourceToken(), is("act-b1f6dc28-e534-45f4-a661-3523f051f77a"));
+ assertThat(receipts.get(4).getDestinationToken(), is("usr-b4e8ec34-52d8-4a81-9566-bdde1bd745b6"));
+ assertThat(receipts.get(4).getAmount(), is("14.00"));
+ assertThat(receipts.get(4).getFee(), is("0.00"));
+ assertThat(receipts.get(4).getCurrency(), is("USD"));
+ assertThat(receipts.get(4).getDetails(), is(notNullValue()));
+ assertThat(receipts.get(4).getDetails().getPayeeName(), is("Kevin Puckett"));
+ assertThat(receipts.get(4).getDetails().getClientPaymentId(), is("wUOdfLlJONacbdHlAHOAXQT7uwX7LTPy"));
+
+ assertThat(mReceiptDataSource.getErrors().getValue(), is(nullValue()));
+ assertThat(mReceiptDataSource.isFetchingData().getValue(), is(false));
+ }
+
+ @Test
+ public void testLoadAfter_returnNoReceipt() {
+ doAnswer(new Answer() {
+ @Override
+ public Object answer(InvocationOnMock invocation) {
+ HyperwalletListener listener = (HyperwalletListener) invocation.getArguments()[1];
+ listener.onSuccess(null);
+ return listener;
+ }
+ }).when(mHyperwallet).listReceipts(any(ReceiptQueryParam.class),
+ ArgumentMatchers.>>any());
+
+ // test
+ mReceiptDataSource.loadAfter(mLoadAfterParams, mLoadAfterCallback);
+
+ verify(mHyperwallet).listReceipts(any(ReceiptQueryParam.class),
+ ArgumentMatchers.>>any());
+ verify(mLoadAfterCallback, never()).onResult(ArgumentMatchers.>any(), anyInt());
+
+ assertThat(mReceiptDataSource.getErrors().getValue(), is(nullValue()));
+ assertThat(mReceiptDataSource.isFetchingData().getValue(), is(false));
+ }
+
+ @Test
+ public void testLoadAfter_withError() {
+ final HyperwalletError error = new HyperwalletError("test message load after", "LOAD_AFTER_CODE");
+ List errorList = new ArrayList<>();
+ errorList.add(error);
+ final HyperwalletErrors errors = new HyperwalletErrors(errorList);
+
+ doAnswer(new Answer() {
+ @Override
+ public Object answer(InvocationOnMock invocation) {
+ HyperwalletListener listener = (HyperwalletListener) invocation.getArguments()[1];
+ listener.onFailure(new HyperwalletException(errors));
+ return listener;
+ }
+ }).when(mHyperwallet).listReceipts(any(ReceiptQueryParam.class),
+ ArgumentMatchers.>>any());
+
+ // test
+ mReceiptDataSource.loadAfter(mLoadAfterParams, mLoadAfterCallback);
+
+ verify(mHyperwallet).listReceipts(any(ReceiptQueryParam.class),
+ ArgumentMatchers.>>any());
+ verify(mLoadAfterCallback, never()).onResult(ArgumentMatchers.anyList(), anyInt());
+
+ // error occurred, this will save params and callback
+ assertThat(mReceiptDataSource.getErrors().getValue(), is(notNullValue()));
+ assertThat(mReceiptDataSource.getErrors().getValue().getContent().getErrors(),
+ Matchers.hasSize(1));
+ assertThat(mReceiptDataSource.getErrors().getValue().getContent().getErrors().get(0).getCode(),
+ is("LOAD_AFTER_CODE"));
+ assertThat(mReceiptDataSource.getErrors().getValue().getContent().getErrors().get(0).getMessage(),
+ is("test message load after"));
+ assertThat(mReceiptDataSource.isFetchingData().getValue(), is(false));
+ }
+
+ @Test
+ public void testRetry_loadAfter() {
+ final HyperwalletError error = new HyperwalletError("test message", "TEST_CODE");
+ List errorList = new ArrayList<>();
+ errorList.add(error);
+ final HyperwalletErrors errors = new HyperwalletErrors(errorList);
+
+ doAnswer(new Answer() {
+ @Override
+ public Object answer(InvocationOnMock invocation) {
+ HyperwalletListener listener = (HyperwalletListener) invocation.getArguments()[1];
+ listener.onFailure(new HyperwalletException(errors));
+ return listener;
+ }
+ }).when(mHyperwallet).listReceipts(any(ReceiptQueryParam.class),
+ ArgumentMatchers.>>any());
+
+ // test
+ mReceiptDataSource.loadAfter(mLoadAfterParams, mLoadAfterCallback);
+
+ verify(mHyperwallet).listReceipts(any(ReceiptQueryParam.class),
+ ArgumentMatchers.>>any());
+ verify(mLoadAfterCallback, never()).onResult(ArgumentMatchers.anyList(), anyInt());
+
+ // error occurred, this will save params and callback
+ assertThat(mReceiptDataSource.getErrors().getValue(), is(notNullValue()));
+
+ // test retry, saved params and callback will be used and no null pointer exception is thrown
+ mReceiptDataSource.retry();
+
+ // verify calls
+ verify(mReceiptDataSource, times(2)).loadAfter(
+ ArgumentMatchers.>any(),
+ ArgumentMatchers.>any());
+ verify(mReceiptDataSource, never()).loadInitial(
+ ArgumentMatchers.>any(),
+ ArgumentMatchers.>any());
+ }
+}
diff --git a/receipt/src/test/java/com/hyperwallet/android/receipt/repository/ReceiptRepositoryFactoryTest.java b/receipt/src/test/java/com/hyperwallet/android/receipt/repository/ReceiptRepositoryFactoryTest.java
new file mode 100644
index 000000000..3326f2916
--- /dev/null
+++ b/receipt/src/test/java/com/hyperwallet/android/receipt/repository/ReceiptRepositoryFactoryTest.java
@@ -0,0 +1,39 @@
+package com.hyperwallet.android.receipt.repository;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.notNullValue;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class ReceiptRepositoryFactoryTest {
+
+ @Test
+ public void testGetInstance_verifyRepositoriesInitialized() {
+ // test
+ ReceiptRepositoryFactory factory = ReceiptRepositoryFactory.getInstance();
+ assertThat(factory, is(notNullValue()));
+ assertThat(factory.getReceiptRepository(), is(notNullValue()));
+
+ ReceiptRepositoryFactory factory2 = ReceiptRepositoryFactory.getInstance();
+ assertThat(factory, is(factory2));
+ assertThat(factory.getReceiptRepository(), is(factory2.getReceiptRepository()));
+ }
+
+ @Test
+ public void testClearInstance_verifyRepositoriesCleared() {
+ ReceiptRepositoryFactory factory = ReceiptRepositoryFactory.getInstance();
+ assertThat(factory, is(notNullValue()));
+ assertThat(factory.getReceiptRepository(), is(notNullValue()));
+
+ // test clear
+ ReceiptRepositoryFactory.clearInstance();
+ ReceiptRepositoryFactory factory2 = ReceiptRepositoryFactory.getInstance();
+ assertThat(factory, is(not(factory2)));
+ assertThat(factory.getReceiptRepository(), is(not(factory2.getReceiptRepository())));
+ }
+}
diff --git a/receipt/src/test/java/com/hyperwallet/android/rule/HyperwalletExternalResourceManager.java b/receipt/src/test/java/com/hyperwallet/android/rule/HyperwalletExternalResourceManager.java
new file mode 100644
index 000000000..9061af9b8
--- /dev/null
+++ b/receipt/src/test/java/com/hyperwallet/android/rule/HyperwalletExternalResourceManager.java
@@ -0,0 +1,72 @@
+package com.hyperwallet.android.rule;
+
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.net.URL;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class HyperwalletExternalResourceManager extends TestWatcher {
+
+ private static final String EMPTY = "";
+ private ClassLoader classLoader;
+ private Logger logger;
+
+ @Override
+ protected void starting(Description description) {
+ super.starting(description);
+ classLoader = description.getTestClass().getClassLoader();
+ logger = Logger.getLogger(description.getTestClass().getName());
+ }
+
+ public String getResourceContent(final String resourceName) {
+ if (resourceName == null) {
+ throw new IllegalArgumentException("Parameter resourceName cannot be null");
+ }
+
+ return getContent(resourceName);
+ }
+
+ private String getContent(final String resourceName) {
+ URL resource = classLoader.getResource(resourceName);
+ InputStream inputStream = null;
+ Writer writer = new StringWriter();
+ String resourceContent = EMPTY;
+ if (resource != null) {
+ try {
+ inputStream = resource.openStream();
+ BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
+ String line = reader.readLine();
+ while (line != null) {
+ writer.write(line);
+ line = reader.readLine();
+ }
+ resourceContent = writer.toString();
+
+ } catch (Exception e) {
+ logger.log(Level.WARNING, "There was an error loading an external resource", e);
+ } finally {
+ try {
+ if (inputStream != null) {
+ inputStream.close();
+ }
+ } catch (Exception e) {
+ logger.log(Level.SEVERE, "There was an error closing input stream", e);
+ }
+ try {
+ writer.close();
+ } catch (IOException e) {
+ logger.log(Level.SEVERE, "There was an error closing writer", e);
+ }
+ }
+ }
+ return resourceContent;
+ }
+}
diff --git a/receipt/src/test/resources/authentication_token_response.json b/receipt/src/test/resources/authentication_token_response.json
new file mode 100644
index 000000000..e8e41bf37
--- /dev/null
+++ b/receipt/src/test/resources/authentication_token_response.json
@@ -0,0 +1,3 @@
+{
+ "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ1c3ItZjZlNmZjY2EtNTBmNy00ZWY1LWExYzUtNWZmMDJlMDU2MzgzIiwiaWF0IjoxNTQ5NTgwMzk0LCJleHAiOjI1NDk1ODA5OTQsImF1ZCI6InBndS03YTEyMzJlOC0xNDc5LTQzNzAtOWY1NC03ODc1ZjdiMTg2NmMiLCJpc3MiOiJwcmctY2NhODAyNWUtODVhMy0xMWU2LTg2MGEtNThhZDVlY2NlNjFkIiwicmVzdC11cmkiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvcmVzdC92My8iLCJncmFwaHFsLXVyaSI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9ncmFwaHFsIn0.kILSynYHbepbl4sVqENnNog09iGByfTrckHhSCjVgnuRnuspI72cx3rt0SB2V_neHwzYkD_VfhNKk9gJDOwXeQ"
+}
\ No newline at end of file
diff --git a/receipt/src/test/resources/receipt_credit_response.json b/receipt/src/test/resources/receipt_credit_response.json
new file mode 100644
index 000000000..4351d17a2
--- /dev/null
+++ b/receipt/src/test/resources/receipt_credit_response.json
@@ -0,0 +1,30 @@
+{
+ "count": 1,
+ "offset": 0,
+ "limit": 10,
+ "data": [
+ {
+ "journalId": "3051581",
+ "type": "PAYMENT",
+ "createdOn": "2019-06-02T17:09:07",
+ "entry": "CREDIT",
+ "sourceToken": "act-12345",
+ "destinationToken": "usr-fa76a738-f43d-48b9-9a7a-7048d44a5d2d",
+ "amount": "25.00",
+ "fee": "0.00",
+ "currency": "CAD",
+ "details": {
+ "clientPaymentId": "ABC1234",
+ "payeeName": "A Person"
+ }
+ }
+ ],
+ "links": [
+ {
+ "params": {
+ "rel": "self"
+ },
+ "href": "https://api.sandbox.hyperwallet.com/rest/v3/users/usr-fa76a738-f43d-48b9-9a7a-7048d44a5d2d/receipts?offset=0&limit=10"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/receipt/src/test/resources/receipt_debit_response.json b/receipt/src/test/resources/receipt_debit_response.json
new file mode 100644
index 000000000..8ed756455
--- /dev/null
+++ b/receipt/src/test/resources/receipt_debit_response.json
@@ -0,0 +1,25 @@
+{
+ "count": 1,
+ "offset": 0,
+ "limit": 10,
+ "data": [
+ {
+ "journalId": "3051590",
+ "type": "TRANSFER_TO_PREPAID_CARD",
+ "createdOn": "2019-05-02T17:12:18",
+ "entry": "DEBIT",
+ "sourceToken": "usr-fa76a738-f43d-48b9-9a7a-7048d44a5d2d",
+ "destinationToken": "trm-12345",
+ "amount": "18.05",
+ "currency": "USD"
+ }
+ ],
+ "links": [
+ {
+ "params": {
+ "rel": "self"
+ },
+ "href": "https://api.sandbox.hyperwallet.com/rest/v3/users/usr-fa76a738-f43d-48b9-9a7a-7048d44a5d2d/receipts?offset=0&limit=10"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/receipt/src/test/resources/receipt_list_date_grouping_response.json b/receipt/src/test/resources/receipt_list_date_grouping_response.json
new file mode 100644
index 000000000..d4d552972
--- /dev/null
+++ b/receipt/src/test/resources/receipt_list_date_grouping_response.json
@@ -0,0 +1,90 @@
+{
+ "count": 5,
+ "offset": 0,
+ "limit": 10,
+ "data": [
+ {
+ "journalId": "51660665",
+ "type": "PAYMENT",
+ "createdOn": "2019-05-27T15:42:07",
+ "entry": "CREDIT",
+ "sourceToken": "act-b1f6dc28-e534-45f4-a661-3523f051f77a",
+ "destinationToken": "usr-b4e8ec34-52d8-4a81-9566-bdde1bd745b6",
+ "amount": "5000.00",
+ "fee": "0.00",
+ "currency": "USD",
+ "details": {
+ "clientPaymentId": "trans-0001",
+ "payeeName": "Kevin Puckett"
+ }
+ },
+ {
+ "journalId": "51660666",
+ "type": "TRANSFER_TO_BANK_ACCOUNT",
+ "createdOn": "2019-05-27T15:57:49",
+ "entry": "DEBIT",
+ "sourceToken": "usr-b4e8ec34-52d8-4a81-9566-bdde1bd745b6",
+ "destinationToken": "trm-0a2ac589-2cae-4ed3-9b0b-658246a34687",
+ "amount": "10.25",
+ "fee": "0.25",
+ "currency": "USD",
+ "details": {
+ "payeeName": "Kevin Puckett",
+ "bankAccountId": "patzachery.mcclary@example.com"
+ }
+ },
+ {
+ "journalId": "51660667",
+ "type": "PAYMENT",
+ "createdOn": "2019-05-27T16:01:10",
+ "entry": "CREDIT",
+ "sourceToken": "act-b1f6dc28-e534-45f4-a661-3523f051f77a",
+ "destinationToken": "usr-b4e8ec34-52d8-4a81-9566-bdde1bd745b6",
+ "amount": "11.00",
+ "fee": "0.00",
+ "currency": "USD",
+ "details": {
+ "clientPaymentId": "trans-02",
+ "payeeName": "Kevin Puckett"
+ }
+ },
+ {
+ "journalId": "51660675",
+ "type": "PAYMENT",
+ "createdOn": "2019-06-04T10:35:23",
+ "entry": "CREDIT",
+ "sourceToken": "act-b1f6dc28-e534-45f4-a661-3523f051f77a",
+ "destinationToken": "usr-b4e8ec34-52d8-4a81-9566-bdde1bd745b6",
+ "amount": "13.00",
+ "fee": "0.00",
+ "currency": "USD",
+ "details": {
+ "clientPaymentId": "CSietnRJQQ0bscYkOoPJxNiTDiVALhjQ",
+ "payeeName": "Kevin Puckett"
+ }
+ },
+ {
+ "journalId": "51660676",
+ "type": "PAYMENT",
+ "createdOn": "2019-06-04T11:16:21",
+ "entry": "CREDIT",
+ "sourceToken": "act-b1f6dc28-e534-45f4-a661-3523f051f77a",
+ "destinationToken": "usr-b4e8ec34-52d8-4a81-9566-bdde1bd745b6",
+ "amount": "14.00",
+ "fee": "0.00",
+ "currency": "USD",
+ "details": {
+ "clientPaymentId": "wUOdfLlJONacbdHlAHOAXQT7uwX7LTPy",
+ "payeeName": "Kevin Puckett"
+ }
+ }
+ ],
+ "links": [
+ {
+ "params": {
+ "rel": "self"
+ },
+ "href": "https://localhost:8181/rest/v3/users/usr-b4e8ec34-52d8-4a81-9566-bdde1bd745b6/receipts?offset=0&limit=10&createdAfter=2019-1-1"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/receipt/src/test/resources/receipt_list_response.json b/receipt/src/test/resources/receipt_list_response.json
new file mode 100644
index 000000000..7e7cc58af
--- /dev/null
+++ b/receipt/src/test/resources/receipt_list_response.json
@@ -0,0 +1,64 @@
+{
+ "count": 4,
+ "offset": 0,
+ "limit": 10,
+ "data": [
+ {
+ "journalId": "3051579",
+ "type": "PAYMENT",
+ "createdOn": "2019-06-07T17:08:58",
+ "entry": "CREDIT",
+ "sourceToken": "act-12345",
+ "destinationToken": "usr-fa76a738-f43d-48b9-9a7a-7048d44a5d2d",
+ "amount": "20.00",
+ "fee": "0.00",
+ "currency": "USD",
+ "details": {
+ "clientPaymentId": "8OxXefx5",
+ "payeeName": "A Person"
+ }
+ },
+ {
+ "journalId": "3051581",
+ "type": "PAYMENT",
+ "createdOn": "2019-06-02T16:09:07",
+ "entry": "CREDIT",
+ "sourceToken": "act-12345",
+ "destinationToken": "usr-fa76a738-f43d-48b9-9a7a-7048d44a5d2d",
+ "amount": "25.00",
+ "fee": "0.00",
+ "currency": "CAD",
+ "details": {
+ "clientPaymentId": "Q3SVvpv0",
+ "payeeName": "A Person"
+ }
+ },
+ {
+ "journalId": "3051582",
+ "type": "CARD_ACTIVATION_FEE",
+ "createdOn": "2019-06-01T11:09:16",
+ "entry": "DEBIT",
+ "sourceToken": "usr-fa76a738-f43d-48b9-9a7a-7048d44a5d2d",
+ "amount": "1.95",
+ "currency": "USD"
+ },
+ {
+ "journalId": "3051590",
+ "type": "TRANSFER_TO_PREPAID_CARD",
+ "createdOn": "2018-12-01T17:12:18",
+ "entry": "DEBIT",
+ "sourceToken": "usr-fa76a738-f43d-48b9-9a7a-7048d44a5d2d",
+ "destinationToken": "trm-12345",
+ "amount": "18.05",
+ "currency": "USD"
+ }
+ ],
+ "links": [
+ {
+ "params": {
+ "rel": "self"
+ },
+ "href": "https://api.sandbox.hyperwallet.com/rest/v3/users/usr-fa76a738-f43d-48b9-9a7a-7048d44a5d2d/receipts?offset=0&limit=10"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/receipt/src/test/resources/receipt_unknown_type_response.json b/receipt/src/test/resources/receipt_unknown_type_response.json
new file mode 100644
index 000000000..bf856ed63
--- /dev/null
+++ b/receipt/src/test/resources/receipt_unknown_type_response.json
@@ -0,0 +1,30 @@
+{
+ "count": 1,
+ "offset": 0,
+ "limit": 10,
+ "data": [
+ {
+ "journalId": "3051581",
+ "type": "ICK",
+ "createdOn": "2019-06-02T17:09:07",
+ "entry": "CREDIT",
+ "sourceToken": "act-12345",
+ "destinationToken": "usr-fa76a738-f43d-48b9-9a7a-7048d44a5d2d",
+ "amount": "25.00",
+ "fee": "0.00",
+ "currency": "CAD",
+ "details": {
+ "clientPaymentId": "ABC1234",
+ "payeeName": "A Person"
+ }
+ }
+ ],
+ "links": [
+ {
+ "params": {
+ "rel": "self"
+ },
+ "href": "https://api.sandbox.hyperwallet.com/rest/v3/users/usr-fa76a738-f43d-48b9-9a7a-7048d44a5d2d/receipts?offset=0&limit=10"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/ui/src/androidTest/AndroidManifest.xml b/ui/src/androidTest/AndroidManifest.xml
index 9b2c52c35..0cd133def 100644
--- a/ui/src/androidTest/AndroidManifest.xml
+++ b/ui/src/androidTest/AndroidManifest.xml
@@ -1,6 +1,6 @@
+ package="com.hyperwallet.android.hyperwallet_ui.test">
diff --git a/ui/src/main/java/com/hyperwallet/android/ui/HyperwalletUi.java b/ui/src/main/java/com/hyperwallet/android/ui/HyperwalletUi.java
index d2f6b9123..aae63fa7e 100644
--- a/ui/src/main/java/com/hyperwallet/android/ui/HyperwalletUi.java
+++ b/ui/src/main/java/com/hyperwallet/android/ui/HyperwalletUi.java
@@ -31,6 +31,7 @@
import com.hyperwallet.android.Hyperwallet;
import com.hyperwallet.android.HyperwalletAuthenticationTokenProvider;
+import com.hyperwallet.android.receipt.view.ListReceiptActivity;
import com.hyperwallet.android.ui.transfermethod.AddTransferMethodActivity;
import com.hyperwallet.android.ui.transfermethod.ListTransferMethodActivity;
import com.hyperwallet.android.ui.transfermethod.SelectTransferMethodActivity;
@@ -78,6 +79,13 @@ public Intent getIntentSelectTransferMethodActivity(@NonNull final Context conte
return new Intent(context, SelectTransferMethodActivity.class);
}
+ /**
+ * @param context A Context of the application consuming this Intent.
+ * @return an Intent with the data necessary to launch the {@link ListReceiptActivity}
+ */
+ public Intent getIntentListReceiptActivity(@NonNull final Context context) {
+ return new Intent(context, ListReceiptActivity.class);
+ }
/**
* @param context A Context of the application consuming this Intent.
diff --git a/ui/src/main/java/com/hyperwallet/android/ui/repository/TransferMethodRepositoryImpl.java b/ui/src/main/java/com/hyperwallet/android/ui/repository/TransferMethodRepositoryImpl.java
index 7d6b77063..902971023 100644
--- a/ui/src/main/java/com/hyperwallet/android/ui/repository/TransferMethodRepositoryImpl.java
+++ b/ui/src/main/java/com/hyperwallet/android/ui/repository/TransferMethodRepositoryImpl.java
@@ -16,6 +16,7 @@
*/
package com.hyperwallet.android.ui.repository;
+import static com.hyperwallet.android.model.HyperwalletStatusTransition.StatusDefinition.ACTIVATED;
import static com.hyperwallet.android.model.transfermethod.HyperwalletTransferMethod.TransferMethodFields.TOKEN;
import static com.hyperwallet.android.model.transfermethod.HyperwalletTransferMethod.TransferMethodFields.TYPE;
import static com.hyperwallet.android.model.transfermethod.HyperwalletTransferMethod.TransferMethodTypes.BANK_ACCOUNT;
@@ -36,6 +37,7 @@
import com.hyperwallet.android.model.transfermethod.HyperwalletBankAccount;
import com.hyperwallet.android.model.transfermethod.HyperwalletBankCard;
import com.hyperwallet.android.model.transfermethod.HyperwalletTransferMethod;
+import com.hyperwallet.android.model.transfermethod.HyperwalletTransferMethodQueryParam;
import com.hyperwallet.android.model.transfermethod.PayPalAccount;
public class TransferMethodRepositoryImpl implements TransferMethodRepository {
@@ -66,7 +68,11 @@ public void createTransferMethod(@NonNull final HyperwalletTransferMethod transf
@Override
public void loadTransferMethods(@NonNull final LoadTransferMethodListCallback callback) {
- getHyperwallet().listTransferMethods(null,
+
+ HyperwalletTransferMethodQueryParam queryParam = new HyperwalletTransferMethodQueryParam.Builder()
+ .status(ACTIVATED)
+ .build();
+ getHyperwallet().listTransferMethods(queryParam,
new HyperwalletListener>() {
@Override
public void onSuccess(@Nullable HyperwalletPageList result) {
diff --git a/ui/src/main/java/com/hyperwallet/android/ui/transfermethod/ListTransferMethodFragment.java b/ui/src/main/java/com/hyperwallet/android/ui/transfermethod/ListTransferMethodFragment.java
index 385d827a6..d09829047 100644
--- a/ui/src/main/java/com/hyperwallet/android/ui/transfermethod/ListTransferMethodFragment.java
+++ b/ui/src/main/java/com/hyperwallet/android/ui/transfermethod/ListTransferMethodFragment.java
@@ -48,13 +48,13 @@
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
+import com.hyperwallet.android.common.view.HorizontalDividerItemDecorator;
import com.hyperwallet.android.hyperwallet_ui.R;
import com.hyperwallet.android.model.HyperwalletError;
import com.hyperwallet.android.model.HyperwalletStatusTransition;
import com.hyperwallet.android.model.transfermethod.HyperwalletTransferMethod;
import com.hyperwallet.android.ui.HyperwalletLocalBroadcast;
import com.hyperwallet.android.ui.repository.RepositoryFactory;
-import com.hyperwallet.android.ui.view.HorizontalDividerItemDecorator;
import com.hyperwallet.android.ui.view.widget.OneClickListener;
import java.util.ArrayList;
@@ -178,7 +178,7 @@ public void onOneClick(View v) {
recyclerView = view.findViewById(R.id.list_transfer_method_item);
recyclerView.setHasFixedSize(true);
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
- recyclerView.addItemDecoration(new HorizontalDividerItemDecorator(getContext(), false));
+ recyclerView.addItemDecoration(new HorizontalDividerItemDecorator(requireContext(), false));
}
@Override
diff --git a/ui/src/main/java/com/hyperwallet/android/ui/transfermethod/SelectTransferMethodFragment.java b/ui/src/main/java/com/hyperwallet/android/ui/transfermethod/SelectTransferMethodFragment.java
index aa4a9408f..154a2bff0 100644
--- a/ui/src/main/java/com/hyperwallet/android/ui/transfermethod/SelectTransferMethodFragment.java
+++ b/ui/src/main/java/com/hyperwallet/android/ui/transfermethod/SelectTransferMethodFragment.java
@@ -38,12 +38,12 @@
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
+import com.hyperwallet.android.common.view.HorizontalDividerItemDecorator;
import com.hyperwallet.android.hyperwallet_ui.R;
import com.hyperwallet.android.model.HyperwalletError;
import com.hyperwallet.android.ui.repository.RepositoryFactory;
import com.hyperwallet.android.ui.view.CountrySelectionDialogFragment;
import com.hyperwallet.android.ui.view.CurrencySelectionDialogFragment;
-import com.hyperwallet.android.ui.view.HorizontalDividerItemDecorator;
import com.hyperwallet.android.ui.view.widget.OneClickListener;
import java.util.ArrayList;
@@ -183,7 +183,7 @@ public void onTransferMethodSelected(TransferMethodSelectionItem transferMethodT
mRecyclerView.setAdapter(mTransferMethodTypesAdapter);
mRecyclerView.setHasFixedSize(true);
mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
- mRecyclerView.addItemDecoration(new HorizontalDividerItemDecorator(getContext(), true));
+ mRecyclerView.addItemDecoration(new HorizontalDividerItemDecorator(requireContext(), true));
}
@Override
diff --git a/ui/src/test/java/com/hyperwallet/android/ui/repository/TransferMethodRepositoryImplTest.java b/ui/src/test/java/com/hyperwallet/android/ui/repository/TransferMethodRepositoryImplTest.java
index 6135c42b2..334ad2044 100644
--- a/ui/src/test/java/com/hyperwallet/android/ui/repository/TransferMethodRepositoryImplTest.java
+++ b/ui/src/test/java/com/hyperwallet/android/ui/repository/TransferMethodRepositoryImplTest.java
@@ -33,7 +33,7 @@
import com.hyperwallet.android.model.transfermethod.HyperwalletBankAccount;
import com.hyperwallet.android.model.transfermethod.HyperwalletBankCard;
import com.hyperwallet.android.model.transfermethod.HyperwalletTransferMethod;
-import com.hyperwallet.android.model.transfermethod.HyperwalletTransferMethodPagination;
+import com.hyperwallet.android.model.transfermethod.HyperwalletTransferMethodQueryParam;
import com.hyperwallet.android.model.transfermethod.PayPalAccount;
import org.junit.Before;
@@ -423,7 +423,7 @@ public Object answer(InvocationOnMock invocation) {
listener.onSuccess(pageList);
return listener;
}
- }).when(mHyperwallet).listTransferMethods((HyperwalletTransferMethodPagination) any(),
+ }).when(mHyperwallet).listTransferMethods((HyperwalletTransferMethodQueryParam) any(),
ArgumentMatchers.>>any());
// test
@@ -448,7 +448,7 @@ public Object answer(InvocationOnMock invocation) {
listener.onSuccess(null);
return listener;
}
- }).when(mHyperwallet).listTransferMethods((HyperwalletTransferMethodPagination) any(),
+ }).when(mHyperwallet).listTransferMethods((HyperwalletTransferMethodQueryParam) any(),
ArgumentMatchers.>>any());
// test
@@ -475,7 +475,7 @@ public Object answer(InvocationOnMock invocation) {
listener.onFailure(new HyperwalletException(errors));
return listener;
}
- }).when(mHyperwallet).listTransferMethods((HyperwalletTransferMethodPagination) any(),
+ }).when(mHyperwallet).listTransferMethods((HyperwalletTransferMethodQueryParam) any(),
ArgumentMatchers.>>any());
// test