From ae5f31944dd5e97ee4e21fd38e096e0e9733fbe5 Mon Sep 17 00:00:00 2001 From: Viktor Shcherbyna Date: Tue, 18 Jun 2019 13:08:54 +0300 Subject: [PATCH 1/9] HW-52637: PayPal Account second line --- .../ListTransferMethodTest.java | 5 +- .../ListTransferMethodFragment.java | 41 ++++---------- .../transfermethod/TransferMethodUtils.java | 54 +++++++++++++++++++ .../TransferMethodUtilsTest.java | 50 +++++++++++++++++ 4 files changed, 119 insertions(+), 31 deletions(-) diff --git a/ui/src/androidTest/java/com/hyperwallet/android/ui/transfermethod/ListTransferMethodTest.java b/ui/src/androidTest/java/com/hyperwallet/android/ui/transfermethod/ListTransferMethodTest.java index 3f8412743..fc567b50e 100644 --- a/ui/src/androidTest/java/com/hyperwallet/android/ui/transfermethod/ListTransferMethodTest.java +++ b/ui/src/androidTest/java/com/hyperwallet/android/ui/transfermethod/ListTransferMethodTest.java @@ -131,6 +131,8 @@ public void testListTransferMethod_userHasMultipleTransferMethods() { onView(withId(R.id.list_transfer_method_item)).check( matches(atPosition(3, hasDescendant(withText(R.string.paper_check))))); onView(withId(R.id.list_transfer_method_item)).check(matches(atPosition(3, hasDescendant(withText("Canada"))))); + onView(withId(R.id.list_transfer_method_item)).check( + matches(atPosition(3, hasDescendant(withText(""))))); onView(withId(R.id.list_transfer_method_item)).check( matches(atPosition(3, hasDescendant(withDrawable(R.drawable.ic_three_dots_16dp))))); @@ -150,7 +152,8 @@ public void testListTransferMethod_userHasMultipleTransferMethods() { matches(atPosition(5, hasDescendant(withText(R.string.paypal_account))))); onView(withId(R.id.list_transfer_method_item)).check( matches(atPosition(5, hasDescendant(withText("United States"))))); - //TODO: Try to check for non existence of transfer_method_type_description_2 + onView(withId(R.id.list_transfer_method_item)).check( + matches(atPosition(5, hasDescendant(withText("honey.thigpen@ukbuilder.com"))))); onView(withId(R.id.list_transfer_method_item)).check( matches(atPosition(5, hasDescendant(withDrawable(R.drawable.ic_three_dots_16dp))))); 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 634bc0af2..fde0b7446 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 @@ -16,14 +16,8 @@ */ package com.hyperwallet.android.ui.transfermethod; -import static com.hyperwallet.android.model.transfermethod.HyperwalletTransferMethod.TransferMethodFields.BANK_ACCOUNT_ID; -import static com.hyperwallet.android.model.transfermethod.HyperwalletTransferMethod.TransferMethodFields.CARD_NUMBER; import static com.hyperwallet.android.model.transfermethod.HyperwalletTransferMethod.TransferMethodFields.TRANSFER_METHOD_COUNTRY; import static com.hyperwallet.android.model.transfermethod.HyperwalletTransferMethod.TransferMethodFields.TYPE; -import static com.hyperwallet.android.model.transfermethod.HyperwalletTransferMethod.TransferMethodTypes.BANK_ACCOUNT; -import static com.hyperwallet.android.model.transfermethod.HyperwalletTransferMethod.TransferMethodTypes.BANK_CARD; -import static com.hyperwallet.android.model.transfermethod.HyperwalletTransferMethod.TransferMethodTypes.PREPAID_CARD; -import static com.hyperwallet.android.model.transfermethod.HyperwalletTransferMethod.TransferMethodTypes.WIRE_ACCOUNT; import static com.hyperwallet.android.ui.transfermethod.TransferMethodUtils.getStringFontIcon; import static com.hyperwallet.android.ui.transfermethod.TransferMethodUtils.getStringResourceByName; @@ -65,7 +59,7 @@ public class ListTransferMethodFragment extends Fragment implements ListTransfer static final String ARGUMENT_IS_TRANSFER_METHODS_RELOAD_NEEDED = "ARGUMENT_IS_TRANSFER_METHODS_RELOAD_NEEDED"; - private static final int LAST_FOUR_DIGIT = 4; + private static final String ARGUMENT_TRANSFER_METHOD_LIST = "ARGUMENT_TRANSFER_METHOD_LIST"; private View mEmptyListView; @@ -310,7 +304,7 @@ private static class ListTransferMethodAdapter extends RecyclerView.Adapter LAST_FOUR_DIGIT - ? transferIdentification.substring(transferIdentification.length() - LAST_FOUR_DIGIT) - : transferIdentification); - } @Override public int getItemCount() { @@ -373,16 +350,20 @@ class ViewHolder extends RecyclerView.ViewHolder { void bind(@NonNull final HyperwalletTransferMethod transferMethod) { + String type = transferMethod.getField(TYPE); + final String transferMethodIdentification = TransferMethodUtils.getTransferMethodDetail( + mTransferMethodIdentification.getContext(), + transferMethod, + type); + mTitle.setText( - getStringResourceByName(mTitle.getContext(), transferMethod.getField(TYPE))); + getStringResourceByName(mTitle.getContext(), type)); Locale locale = new Locale.Builder().setRegion( transferMethod.getField(TRANSFER_METHOD_COUNTRY)).build(); - mIcon.setText(getStringFontIcon(mIcon.getContext(), transferMethod.getField(TYPE))); + mIcon.setText(getStringFontIcon(mIcon.getContext(), type)); mTransferMethodCountry.setText(locale.getDisplayName()); - mTransferMethodIdentification.setText(mTransferMethodIdentification - .getContext().getString(R.string.transfer_method_list_item_description, - getAccountIdentifier(transferMethod))); + mTransferMethodIdentification.setText(transferMethodIdentification); mImageButton.setOnClickListener(new View.OnClickListener() { @Override diff --git a/ui/src/main/java/com/hyperwallet/android/ui/transfermethod/TransferMethodUtils.java b/ui/src/main/java/com/hyperwallet/android/ui/transfermethod/TransferMethodUtils.java index 127c21818..95d10041a 100644 --- a/ui/src/main/java/com/hyperwallet/android/ui/transfermethod/TransferMethodUtils.java +++ b/ui/src/main/java/com/hyperwallet/android/ui/transfermethod/TransferMethodUtils.java @@ -16,6 +16,9 @@ */ package com.hyperwallet.android.ui.transfermethod; +import static com.hyperwallet.android.model.transfermethod.HyperwalletTransferMethod.TransferMethodFields.BANK_ACCOUNT_ID; +import static com.hyperwallet.android.model.transfermethod.HyperwalletTransferMethod.TransferMethodFields.CARD_NUMBER; +import static com.hyperwallet.android.model.transfermethod.HyperwalletTransferMethod.TransferMethodFields.EMAIL; import static com.hyperwallet.android.model.transfermethod.HyperwalletTransferMethod.TransferMethodFields.TYPE; import static com.hyperwallet.android.model.transfermethod.HyperwalletTransferMethod.TransferMethodTypes.BANK_ACCOUNT; import static com.hyperwallet.android.model.transfermethod.HyperwalletTransferMethod.TransferMethodTypes.BANK_CARD; @@ -28,6 +31,7 @@ import android.content.res.Resources; import androidx.annotation.NonNull; +import androidx.annotation.StringRes; import com.hyperwallet.android.model.transfermethod.HyperwalletTransferMethod; import com.hyperwallet.android.model.transfermethod.HyperwalletTransferMethod.TransferMethodType; @@ -37,6 +41,8 @@ public class TransferMethodUtils { + private static final int LAST_FOUR_DIGIT = 4; + /** * Get string resource by TransferMethodType * @@ -125,4 +131,52 @@ public static String getTransferMethodName(@NonNull final Context context, return title; } + + /** + * Gets Transfer method identifier from the {@link HyperwalletTransferMethod} field + * by a {@link TransferMethodType}. + * + * @param context Context + * @param transferMethod HyperwalletTransferMethod + * @param type TransferMethodType + */ + public static String getTransferMethodDetail(@NonNull Context context, + @NonNull final HyperwalletTransferMethod transferMethod, + @TransferMethodType final String type) { + if (type == null) { + return ""; + } + + switch (type) { + case BANK_CARD: + case PREPAID_CARD: + return getFourDigitsIdentification(context, + transferMethod, + CARD_NUMBER, + R.string.transfer_method_list_item_description); + case BANK_ACCOUNT: + case WIRE_ACCOUNT: + return getFourDigitsIdentification(context, transferMethod, BANK_ACCOUNT_ID, + R.string.transfer_method_list_item_description); + case PAYPAL_ACCOUNT: + final String transferIdentification = transferMethod.getField(EMAIL); + return transferIdentification != null ? transferIdentification : ""; + default: + return ""; + } + } + + private static String getFourDigitsIdentification(@NonNull final Context context, + @NonNull final HyperwalletTransferMethod transferMethod, + @NonNull @HyperwalletTransferMethod.TransferMethodFieldKey final String fieldKey, + @StringRes final int stringResId) { + final String transferIdentification = transferMethod.getField(fieldKey); + + final String identificationText = + transferIdentification != null && transferIdentification.length() > LAST_FOUR_DIGIT + ? transferIdentification.substring(transferIdentification.length() - LAST_FOUR_DIGIT) + : ""; + + return context.getString(stringResId, identificationText); + } } diff --git a/ui/src/test/java/com/hyperwallet/android/ui/transfermethod/TransferMethodUtilsTest.java b/ui/src/test/java/com/hyperwallet/android/ui/transfermethod/TransferMethodUtilsTest.java index 186428ef3..8d3dd0b5b 100644 --- a/ui/src/test/java/com/hyperwallet/android/ui/transfermethod/TransferMethodUtilsTest.java +++ b/ui/src/test/java/com/hyperwallet/android/ui/transfermethod/TransferMethodUtilsTest.java @@ -5,11 +5,16 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static com.hyperwallet.android.model.transfermethod.HyperwalletTransferMethod.TransferMethodTypes.BANK_ACCOUNT; import static com.hyperwallet.android.model.transfermethod.HyperwalletTransferMethod.TransferMethodTypes.BANK_CARD; +import static com.hyperwallet.android.model.transfermethod.HyperwalletTransferMethod.TransferMethodTypes.PAPER_CHECK; +import static com.hyperwallet.android.model.transfermethod.HyperwalletTransferMethod.TransferMethodTypes.PAYPAL_ACCOUNT; import android.content.Context; import android.content.res.Resources; @@ -18,6 +23,7 @@ import com.hyperwallet.android.ui.R; import com.hyperwallet.android.ui.rule.HyperwalletExternalResourceManager; +import org.hamcrest.CoreMatchers; import org.json.JSONException; import org.json.JSONObject; import org.junit.Before; @@ -134,4 +140,48 @@ public void testGetStringFontIcon_returnsDefaultValue() { verify(mResources, times(2)).getIdentifier(anyString(), anyString(), anyString()); verify(mContext, times(1)).getString(anyInt()); } + + @Test + public void testGetSecondLine_returnsPayPalSecondLine() throws JSONException { + String json = mExternalResourceManager.getResourceContent("paypal_response.json"); + JSONObject htmJsonObject = new JSONObject(json); + HyperwalletTransferMethod transferMethod = new HyperwalletTransferMethod(htmJsonObject); + + String actual = TransferMethodUtils.getTransferMethodDetail(mContext, transferMethod, PAYPAL_ACCOUNT); + assertThat(actual, is("sunshine.carreiro@hyperwallet.com")); + } + + @Test + public void testGetSecondLine_returnsCardSecondLine() throws JSONException { + String json = mExternalResourceManager.getResourceContent("bank_card_response.json"); + JSONObject htmJsonObject = new JSONObject(json); + HyperwalletTransferMethod transferMethod = new HyperwalletTransferMethod(htmJsonObject); + when(mContext.getString(eq(R.string.transfer_method_list_item_description), eq("0006"))).thenReturn( + "Ending on 0006"); + String actual = TransferMethodUtils.getTransferMethodDetail(mContext, transferMethod, BANK_CARD); + assertThat(actual, is("Ending on 0006")); + } + + @Test + public void testGetSecondLine_returnsAccountSecondLine() throws JSONException { + String json = mExternalResourceManager.getResourceContent("bank_account_response.json"); + JSONObject htmJsonObject = new JSONObject(json); + HyperwalletTransferMethod transferMethod = new HyperwalletTransferMethod(htmJsonObject); + when(mContext.getString(eq(R.string.transfer_method_list_item_description), eq("0254"))).thenReturn( + "Ending on 0254"); + String actual = TransferMethodUtils.getTransferMethodDetail(mContext, transferMethod, BANK_ACCOUNT); + assertThat(actual, is("Ending on 0254")); + } + + @Test + public void testGetSecondLine_returnsPaperCheckSecondLine() throws JSONException { + Context context = mock(Context.class); + String json = mExternalResourceManager.getResourceContent("paper_check_response.json"); + JSONObject htmJsonObject = new JSONObject(json); + HyperwalletTransferMethod transferMethod = new HyperwalletTransferMethod(htmJsonObject); + String actual = TransferMethodUtils.getTransferMethodDetail(mContext, transferMethod, PAPER_CHECK); + + assertThat(actual, CoreMatchers.is("")); + verify(context, never()).getString(eq(R.string.transfer_method_list_item_description), anyString()); + } } From 45f76d1a1ae5eaf0825d761f412cf4b5b91c9d2a Mon Sep 17 00:00:00 2001 From: Viktor Shcherbyna Date: Tue, 18 Jun 2019 13:08:54 +0300 Subject: [PATCH 2/9] HW-52637: PayPal Account second line --- .../test/resources/paper_check_response.json | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 ui/src/test/resources/paper_check_response.json diff --git a/ui/src/test/resources/paper_check_response.json b/ui/src/test/resources/paper_check_response.json new file mode 100644 index 000000000..7cc6d5fd5 --- /dev/null +++ b/ui/src/test/resources/paper_check_response.json @@ -0,0 +1,34 @@ +{ + "token": "trm-316843d4-bfde-4dec-a391-ef926e4affbb", + "type": "PAPER_CHECK", + "status": "ACTIVATED", + "verificationStatus": "NOT_REQUIRED", + "createdOn": "2019-02-26T22:03:53", + "transferMethodCountry": "CA", + "transferMethodCurrency": "USD", + "bankAccountRelationship": "SELF", + "profileType": "INDIVIDUAL", + "firstName": "Gannon", + "lastName": "Evan", + "dateOfBirth": "1980-01-01", + "countryOfBirth": "US", + "countryOfNationality": "CA", + "gender": "MALE", + "phoneNumber": "+1 604 6666666", + "mobileNumber": "604 666 6666", + "governmentId": "987654321", + "addressLine1": "950 Granville Street", + "city": "Vancouver", + "stateProvince": "BC", + "country": "CA", + "postalCode": "V6Z1L2", + "shippingMethod": "EXPEDITED", + "links": [ + { + "params": { + "rel": "self" + }, + "href": "https://localhost:8181/rest/v3/users/usr-a51c7522-ccba-4bcf-a6a7-bc59dae8f9b0/bank-accounts/trm-92320ebd-c569-4b16-8790-6d22b477ac3c" + } + ] +} \ No newline at end of file From 5f0312cb91f11747ac15738c1b46ab058709390d Mon Sep 17 00:00:00 2001 From: Anna <48258136+azakrevska-epam@users.noreply.github.com> Date: Wed, 19 Jun 2019 00:56:03 +0300 Subject: [PATCH 3/9] HW-53765. renamed *Util to *Utils (#45) --- .../AddTransferMethodFragment.java | 4 +-- .../ui/view/WidgetDateDialogFragment.java | 8 +++--- .../widget/{DateUtil.java => DateUtils.java} | 4 +-- .../android/ui/view/widget/DateWidget.java | 8 +++--- ...pireDateUtil.java => ExpireDateUtils.java} | 4 +-- .../ui/view/widget/ExpiryDateWidget.java | 28 +++++++++---------- .../{DateUtilTest.java => DateUtilsTest.java} | 22 +++++++-------- ...UtilTest.java => ExpireDateUtilsTest.java} | 4 +-- 8 files changed, 41 insertions(+), 41 deletions(-) rename ui/src/main/java/com/hyperwallet/android/ui/view/widget/{DateUtil.java => DateUtils.java} (98%) rename ui/src/main/java/com/hyperwallet/android/ui/view/widget/{ExpireDateUtil.java => ExpireDateUtils.java} (99%) rename ui/src/test/java/com/hyperwallet/android/ui/view/widget/{DateUtilTest.java => DateUtilsTest.java} (73%) rename ui/src/test/java/com/hyperwallet/android/ui/view/widget/{ExpireDateUtilTest.java => ExpireDateUtilsTest.java} (97%) diff --git a/ui/src/main/java/com/hyperwallet/android/ui/transfermethod/AddTransferMethodFragment.java b/ui/src/main/java/com/hyperwallet/android/ui/transfermethod/AddTransferMethodFragment.java index 471f184f5..e34075468 100644 --- a/ui/src/main/java/com/hyperwallet/android/ui/transfermethod/AddTransferMethodFragment.java +++ b/ui/src/main/java/com/hyperwallet/android/ui/transfermethod/AddTransferMethodFragment.java @@ -61,7 +61,7 @@ import com.hyperwallet.android.ui.view.WidgetSelectionDialogFragment; import com.hyperwallet.android.ui.view.widget.AbstractWidget; import com.hyperwallet.android.ui.view.widget.DateChangedListener; -import com.hyperwallet.android.ui.view.widget.DateUtil; +import com.hyperwallet.android.ui.view.widget.DateUtils; import com.hyperwallet.android.ui.view.widget.DateWidget; import com.hyperwallet.android.ui.view.widget.WidgetEventListener; import com.hyperwallet.android.ui.view.widget.WidgetFactory; @@ -99,7 +99,7 @@ public class AddTransferMethodFragment extends Fragment implements WidgetEventLi private HyperwalletTransferMethod mTransferMethod; private String mTransferMethodProfileType; private HashMap mWidgetInputStateHashMap; - private final DateUtil mDateUtil = new DateUtil(); + private final DateUtils mDateUtils = new DateUtils(); /** * Please do not use this to have instance of AddTransferMethodFragment this is reserved for android framework diff --git a/ui/src/main/java/com/hyperwallet/android/ui/view/WidgetDateDialogFragment.java b/ui/src/main/java/com/hyperwallet/android/ui/view/WidgetDateDialogFragment.java index d81e51369..274879b85 100644 --- a/ui/src/main/java/com/hyperwallet/android/ui/view/WidgetDateDialogFragment.java +++ b/ui/src/main/java/com/hyperwallet/android/ui/view/WidgetDateDialogFragment.java @@ -13,7 +13,7 @@ import androidx.fragment.app.FragmentManager; import com.hyperwallet.android.ui.R; -import com.hyperwallet.android.ui.view.widget.DateUtil; +import com.hyperwallet.android.ui.view.widget.DateUtils; import java.util.Calendar; @@ -23,7 +23,7 @@ public class WidgetDateDialogFragment extends DialogFragment { private static final String ARGUMENT_DATE = "ARGUMENT_DATE"; private static final String ARGUMENT_FIELD_NAME = "ARGUMENT_FIELD_NAME"; private OnSelectedDateCallback mOnSelectedDateCallback; - private final DateUtil mDateUtil = new DateUtil(); + private final DateUtils mDateUtils = new DateUtils(); /** * Please do not use this to have instance of DateDialogFragment this is reserved for android framework @@ -75,7 +75,7 @@ public Dialog onCreateDialog(@Nullable Bundle state) { final String fieldName = getArguments().getString(ARGUMENT_FIELD_NAME); Calendar calendar; try { - calendar = mDateUtil.convertDateFromServerFormatToCalendar(storedDate); + calendar = mDateUtils.convertDateFromServerFormatToCalendar(storedDate); } catch (Exception e) { calendar = Calendar.getInstance(); } @@ -85,7 +85,7 @@ public Dialog onCreateDialog(@Nullable Bundle state) { new DatePickerDialog.OnDateSetListener() { @Override public void onDateSet(DatePicker view, int resultYear, int resultMonth, int resultDayOfMonth) { - final String selectedDate = mDateUtil + final String selectedDate = mDateUtils .buildDateFromDateDialogToServerFormat(resultYear, resultMonth, resultDayOfMonth); mOnSelectedDateCallback.setSelectedDateField(fieldName, selectedDate); } diff --git a/ui/src/main/java/com/hyperwallet/android/ui/view/widget/DateUtil.java b/ui/src/main/java/com/hyperwallet/android/ui/view/widget/DateUtils.java similarity index 98% rename from ui/src/main/java/com/hyperwallet/android/ui/view/widget/DateUtil.java rename to ui/src/main/java/com/hyperwallet/android/ui/view/widget/DateUtils.java index 8da501d0b..4c0ccb59f 100644 --- a/ui/src/main/java/com/hyperwallet/android/ui/view/widget/DateUtil.java +++ b/ui/src/main/java/com/hyperwallet/android/ui/view/widget/DateUtils.java @@ -31,7 +31,7 @@ /** * Class is used for manage and convert date {@link DateWidget} */ -public final class DateUtil { +public final class DateUtils { private static final String SERVER_DATE_PATTERN = "yyyy-MM-dd"; private static final String WIDGET_DATE_PATTERN = "dd MMMM yyyy"; @@ -39,7 +39,7 @@ public final class DateUtil { private final SimpleDateFormat mServerDateFormat = new SimpleDateFormat(SERVER_DATE_PATTERN, Locale.getDefault()); private final SimpleDateFormat mWidgetDateFormat; - public DateUtil() { + public DateUtils() { mWidgetDateFormat = new SimpleDateFormat( DateFormat.getBestDateTimePattern(Locale.getDefault(), WIDGET_DATE_PATTERN), Locale.getDefault()); } diff --git a/ui/src/main/java/com/hyperwallet/android/ui/view/widget/DateWidget.java b/ui/src/main/java/com/hyperwallet/android/ui/view/widget/DateWidget.java index 0d576066a..c193db3d7 100644 --- a/ui/src/main/java/com/hyperwallet/android/ui/view/widget/DateWidget.java +++ b/ui/src/main/java/com/hyperwallet/android/ui/view/widget/DateWidget.java @@ -37,7 +37,7 @@ public class DateWidget extends AbstractWidget implements DateChangedListener { - private final DateUtil mDateUtil; + private final DateUtils mDateUtils; private ViewGroup mContainer; private String mValue; private TextInputLayout mTextInputLayout; @@ -46,7 +46,7 @@ public class DateWidget extends AbstractWidget implements DateChangedListener { public DateWidget(@NonNull HyperwalletField field, @NonNull WidgetEventListener listener, @Nullable String defaultValue, @NonNull View defaultFocusView) { super(field, listener, defaultValue, defaultFocusView); - mDateUtil = new DateUtil(); + mDateUtils = new DateUtils(); mValue = defaultValue; } @@ -67,7 +67,7 @@ public View getView(@NonNull final ViewGroup viewGroup) { mEditText = new EditText( new ContextThemeWrapper(viewGroup.getContext(), R.style.Widget_Hyperwallet_TextInputEditText)); try { - mEditText.setText(mDateUtil.convertDateFromServerToWidgetFormat( + mEditText.setText(mDateUtils.convertDateFromServerToWidgetFormat( TextUtils.isEmpty(mDefaultValue) ? mValue = mField.getValue() : mDefaultValue)); } catch (ParseException e) { mEditText.setText(""); @@ -114,7 +114,7 @@ public void onUpdate(@Nullable final String selectedDate) { if (!TextUtils.isEmpty(selectedDate)) { mValue = selectedDate; try { - mEditText.setText(mDateUtil.convertDateFromServerToWidgetFormat(selectedDate)); + mEditText.setText(mDateUtils.convertDateFromServerToWidgetFormat(selectedDate)); mListener.saveTextChanged(getName(), getValue()); mListener.valueChanged(); } catch (ParseException e) { diff --git a/ui/src/main/java/com/hyperwallet/android/ui/view/widget/ExpireDateUtil.java b/ui/src/main/java/com/hyperwallet/android/ui/view/widget/ExpireDateUtils.java similarity index 99% rename from ui/src/main/java/com/hyperwallet/android/ui/view/widget/ExpireDateUtil.java rename to ui/src/main/java/com/hyperwallet/android/ui/view/widget/ExpireDateUtils.java index aee64f92e..0e334ce8f 100644 --- a/ui/src/main/java/com/hyperwallet/android/ui/view/widget/ExpireDateUtil.java +++ b/ui/src/main/java/com/hyperwallet/android/ui/view/widget/ExpireDateUtils.java @@ -26,7 +26,7 @@ /** * Class is used for manage and convert card expire date {@link ExpiryDateWidget} */ -class ExpireDateUtil { +class ExpireDateUtils { static final char ONE_CHAR = '1'; static final int MAX_INPUT_LENGTH = 5; @@ -40,7 +40,7 @@ class ExpireDateUtil { private final Calendar mUpperValidDate = Calendar.getInstance(); - ExpireDateUtil() { + ExpireDateUtils() { mUpperValidDate.add(Calendar.YEAR, VALID_PERIOD_IN_YEARS); } diff --git a/ui/src/main/java/com/hyperwallet/android/ui/view/widget/ExpiryDateWidget.java b/ui/src/main/java/com/hyperwallet/android/ui/view/widget/ExpiryDateWidget.java index 834629f85..9f2bde50f 100644 --- a/ui/src/main/java/com/hyperwallet/android/ui/view/widget/ExpiryDateWidget.java +++ b/ui/src/main/java/com/hyperwallet/android/ui/view/widget/ExpiryDateWidget.java @@ -16,10 +16,10 @@ */ package com.hyperwallet.android.ui.view.widget; -import static com.hyperwallet.android.ui.view.widget.ExpireDateUtil.MAX_INPUT_LENGTH; -import static com.hyperwallet.android.ui.view.widget.ExpireDateUtil.ONE_CHAR; -import static com.hyperwallet.android.ui.view.widget.ExpireDateUtil.SEPARATOR; -import static com.hyperwallet.android.ui.view.widget.ExpireDateUtil.ZERO_CHAR; +import static com.hyperwallet.android.ui.view.widget.ExpireDateUtils.MAX_INPUT_LENGTH; +import static com.hyperwallet.android.ui.view.widget.ExpireDateUtils.ONE_CHAR; +import static com.hyperwallet.android.ui.view.widget.ExpireDateUtils.SEPARATOR; +import static com.hyperwallet.android.ui.view.widget.ExpireDateUtils.ZERO_CHAR; import android.content.Context; import android.text.Editable; @@ -42,7 +42,7 @@ import com.hyperwallet.android.ui.R; public class ExpiryDateWidget extends AbstractWidget { - private final ExpireDateUtil mExpireDateUtil; + private final ExpireDateUtils mExpireDateUtils; private ViewGroup mContainer; private TextInputLayout mTextInputLayout; private String mValue; @@ -53,7 +53,7 @@ public ExpiryDateWidget(@NonNull HyperwalletField field, @NonNull WidgetEventLis @Nullable String defaultValue, @NonNull View defaultFocusView) { super(field, listener, defaultValue, defaultFocusView); mValue = defaultValue; - mExpireDateUtil = new ExpireDateUtil(); + mExpireDateUtils = new ExpireDateUtils(); } @Override @@ -82,7 +82,7 @@ public View getView(@NonNull final ViewGroup viewGroup) { @Override public void onFocusChange(View v, boolean hasFocus) { if (!hasFocus) { - mValue = mExpireDateUtil.convertDateToServerFormat(((EditText) v).getText().toString()); + mValue = mExpireDateUtils.convertDateToServerFormat(((EditText) v).getText().toString()); mListener.valueChanged(); } else { mListener.widgetFocused(ExpiryDateWidget.this.getName()); @@ -141,9 +141,9 @@ public void onTextChanged(CharSequence s, int start, int before, int count) { } } - dateParts = mExpireDateUtil.getDateParts(input); + dateParts = mExpireDateUtils.getDateParts(input); - if (!mExpireDateUtil.isValidMonth(dateParts[0])) { + if (!mExpireDateUtils.isValidMonth(dateParts[0])) { inErrorState = true; } @@ -161,7 +161,7 @@ public void onTextChanged(CharSequence s, int start, int before, int count) { dateBuilder.append(dateParts[1]); String formattedDate = dateBuilder.toString(); - int cursorPosition = mExpireDateUtil.getCursorPosition(formattedDate.length(), changeStart, + int cursorPosition = mExpireDateUtils.getCursorPosition(formattedDate.length(), changeStart, insertionSize); ignoreConcurrentChanges = true; @@ -169,7 +169,7 @@ public void onTextChanged(CharSequence s, int start, int before, int count) { editText.setSelection(cursorPosition); if (before != count) { - mValue = mExpireDateUtil.convertDateToServerFormat(formattedDate); + mValue = mExpireDateUtils.convertDateToServerFormat(formattedDate); mListener.saveTextChanged(getName(), mValue); } ignoreConcurrentChanges = false; @@ -182,7 +182,7 @@ public void afterTextChanged(Editable s) { editText.setInputType(InputType.TYPE_CLASS_DATETIME); editText.setHint(mField.getLabel()); - editText.setText(mExpireDateUtil.convertDateFromServerFormat( + editText.setText(mExpireDateUtils.convertDateFromServerFormat( TextUtils.isEmpty(mDefaultValue) ? mField.getValue() : mDefaultValue)); editText.setOnKeyListener(new DefaultKeyListener(mDefaultFocusView, editText)); @@ -219,7 +219,7 @@ public String getErrorMessage() { return mMessageInvalidDateLength; } - if (mExpireDateUtil.isInvalidDate(mValue)) { + if (mExpireDateUtils.isInvalidDate(mValue)) { return mMessageInvalidDate; } @@ -228,7 +228,7 @@ public String getErrorMessage() { @Override protected boolean isInvalidRegex() { - return mExpireDateUtil.isInvalidDate(mValue); + return mExpireDateUtils.isInvalidDate(mValue); } } diff --git a/ui/src/test/java/com/hyperwallet/android/ui/view/widget/DateUtilTest.java b/ui/src/test/java/com/hyperwallet/android/ui/view/widget/DateUtilsTest.java similarity index 73% rename from ui/src/test/java/com/hyperwallet/android/ui/view/widget/DateUtilTest.java rename to ui/src/test/java/com/hyperwallet/android/ui/view/widget/DateUtilsTest.java index d016de8af..7b96181d1 100644 --- a/ui/src/test/java/com/hyperwallet/android/ui/view/widget/DateUtilTest.java +++ b/ui/src/test/java/com/hyperwallet/android/ui/view/widget/DateUtilsTest.java @@ -15,8 +15,8 @@ import java.util.Collection; @RunWith(RobolectricTestRunner.class) -public class DateUtilTest { - private final DateUtil mDateUtil = new DateUtil(); +public class DateUtilsTest { + private final DateUtils mDateUtils = new DateUtils(); @Rule public final ExpectedException mThrown = ExpectedException.none(); @@ -25,32 +25,32 @@ public class DateUtilTest { public void testConvertDateFromServerToWidgetFormat() throws Exception { String serverDate = "2005-05-23"; String widgetDate = "23 May 2005"; - assertThat(mDateUtil.convertDateFromServerToWidgetFormat(serverDate), is(widgetDate)); + assertThat(mDateUtils.convertDateFromServerToWidgetFormat(serverDate), is(widgetDate)); } @Test public void testBuildParamsDateFromServerToWidget_whenIncorrectDate() throws Exception { mThrown.expect(ParseException.class); - mDateUtil.convertDateFromServerToWidgetFormat("1990-01"); + mDateUtils.convertDateFromServerToWidgetFormat("1990-01"); } @Test public void testConvertDateFromServerToWidgetFormat_whenDateIsNullOrEmpty() throws Exception { - assertThat(mDateUtil.convertDateFromServerToWidgetFormat(""), is("")); - assertThat(mDateUtil.convertDateFromServerToWidgetFormat(null), is("")); + assertThat(mDateUtils.convertDateFromServerToWidgetFormat(""), is("")); + assertThat(mDateUtils.convertDateFromServerToWidgetFormat(null), is("")); } @Test public void testBuildParamsDateFromServerToCalendar_whenIncorrectDate() throws Exception { mThrown.expect(ParseException.class); - mDateUtil.convertDateFromServerFormatToCalendar("123-32").getTime(); + mDateUtils.convertDateFromServerFormatToCalendar("123-32").getTime(); } @Test public void testConvertDateFromServerFormatToCalendar_whenDateIsNullOrEmpty() throws ParseException { - assertThat(mDateUtil.convertDateFromServerFormatToCalendar(null).getTime().toString(), + assertThat(mDateUtils.convertDateFromServerFormatToCalendar(null).getTime().toString(), is(Calendar.getInstance().getTime().toString())); - assertThat(mDateUtil.convertDateFromServerFormatToCalendar("").getTime().toString(), + assertThat(mDateUtils.convertDateFromServerFormatToCalendar("").getTime().toString(), is(Calendar.getInstance().getTime().toString())); } @@ -59,7 +59,7 @@ public void testConvertDateFromServerFormatToCalendar() throws ParseException { String serverDate = "2005-05-23"; final Calendar mayCalendar = Calendar.getInstance(); mayCalendar.set(2005, 4, 23, 0, 0, 0); - assertThat(mDateUtil.convertDateFromServerFormatToCalendar(serverDate).getTime().toString(), + assertThat(mDateUtils.convertDateFromServerFormatToCalendar(serverDate).getTime().toString(), is(mayCalendar.getTime().toString())); } @@ -75,7 +75,7 @@ public void testBuildDateFromDateDialogToServerFormat() { month = (int) item[1]; dayOfMonth = (int) item[2]; widgetDate = (String) item[3]; - assertThat(mDateUtil.buildDateFromDateDialogToServerFormat(year, month, dayOfMonth), is(widgetDate)); + assertThat(mDateUtils.buildDateFromDateDialogToServerFormat(year, month, dayOfMonth), is(widgetDate)); } } diff --git a/ui/src/test/java/com/hyperwallet/android/ui/view/widget/ExpireDateUtilTest.java b/ui/src/test/java/com/hyperwallet/android/ui/view/widget/ExpireDateUtilsTest.java similarity index 97% rename from ui/src/test/java/com/hyperwallet/android/ui/view/widget/ExpireDateUtilTest.java rename to ui/src/test/java/com/hyperwallet/android/ui/view/widget/ExpireDateUtilsTest.java index d9f46e916..c0045f060 100644 --- a/ui/src/test/java/com/hyperwallet/android/ui/view/widget/ExpireDateUtilTest.java +++ b/ui/src/test/java/com/hyperwallet/android/ui/view/widget/ExpireDateUtilsTest.java @@ -15,8 +15,8 @@ import junitparams.Parameters; @RunWith(JUnitParamsRunner.class) -public class ExpireDateUtilTest { - private final ExpireDateUtil helper = new ExpireDateUtil(); +public class ExpireDateUtilsTest { + private final ExpireDateUtils helper = new ExpireDateUtils(); @Test @Parameters(method = "parametersToTestValidDate") From 66e34789064b5d4e25cb3ed3a5d736e1fd5007df Mon Sep 17 00:00:00 2001 From: Viktor Shcherbyna Date: Tue, 18 Jun 2019 13:08:54 +0300 Subject: [PATCH 4/9] HW-52637: PayPal Account second line --- .../ListTransferMethodFragment.java | 3 +- .../TransferMethodUtilsTest.java | 45 +++++++++---------- 2 files changed, 24 insertions(+), 24 deletions(-) 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 fde0b7446..b2f675149 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 @@ -20,6 +20,7 @@ import static com.hyperwallet.android.model.transfermethod.HyperwalletTransferMethod.TransferMethodFields.TYPE; import static com.hyperwallet.android.ui.transfermethod.TransferMethodUtils.getStringFontIcon; import static com.hyperwallet.android.ui.transfermethod.TransferMethodUtils.getStringResourceByName; +import static com.hyperwallet.android.ui.transfermethod.TransferMethodUtils.getTransferMethodDetail; import android.content.Context; import android.content.Intent; @@ -351,7 +352,7 @@ class ViewHolder extends RecyclerView.ViewHolder { void bind(@NonNull final HyperwalletTransferMethod transferMethod) { String type = transferMethod.getField(TYPE); - final String transferMethodIdentification = TransferMethodUtils.getTransferMethodDetail( + final String transferMethodIdentification = getTransferMethodDetail( mTransferMethodIdentification.getContext(), transferMethod, type); diff --git a/ui/src/test/java/com/hyperwallet/android/ui/transfermethod/TransferMethodUtilsTest.java b/ui/src/test/java/com/hyperwallet/android/ui/transfermethod/TransferMethodUtilsTest.java index 8d3dd0b5b..7fd0bbece 100644 --- a/ui/src/test/java/com/hyperwallet/android/ui/transfermethod/TransferMethodUtilsTest.java +++ b/ui/src/test/java/com/hyperwallet/android/ui/transfermethod/TransferMethodUtilsTest.java @@ -5,7 +5,6 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -15,11 +14,15 @@ import static com.hyperwallet.android.model.transfermethod.HyperwalletTransferMethod.TransferMethodTypes.BANK_CARD; import static com.hyperwallet.android.model.transfermethod.HyperwalletTransferMethod.TransferMethodTypes.PAPER_CHECK; import static com.hyperwallet.android.model.transfermethod.HyperwalletTransferMethod.TransferMethodTypes.PAYPAL_ACCOUNT; +import static com.hyperwallet.android.ui.transfermethod.TransferMethodUtils.getTransferMethodDetail; import android.content.Context; import android.content.res.Resources; +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.PayPalAccount; import com.hyperwallet.android.ui.R; import com.hyperwallet.android.ui.rule.HyperwalletExternalResourceManager; @@ -142,46 +145,42 @@ public void testGetStringFontIcon_returnsDefaultValue() { } @Test - public void testGetSecondLine_returnsPayPalSecondLine() throws JSONException { - String json = mExternalResourceManager.getResourceContent("paypal_response.json"); - JSONObject htmJsonObject = new JSONObject(json); - HyperwalletTransferMethod transferMethod = new HyperwalletTransferMethod(htmJsonObject); + public void getTransferMethodDetail_returnsPayPalDetails() { + HyperwalletTransferMethod transferMethod = new PayPalAccount.Builder().email( + "sunshine.carreiro@hyperwallet.com").build(); - String actual = TransferMethodUtils.getTransferMethodDetail(mContext, transferMethod, PAYPAL_ACCOUNT); + String actual = getTransferMethodDetail(mContext, transferMethod, PAYPAL_ACCOUNT); assertThat(actual, is("sunshine.carreiro@hyperwallet.com")); } @Test - public void testGetSecondLine_returnsCardSecondLine() throws JSONException { - String json = mExternalResourceManager.getResourceContent("bank_card_response.json"); - JSONObject htmJsonObject = new JSONObject(json); - HyperwalletTransferMethod transferMethod = new HyperwalletTransferMethod(htmJsonObject); + public void getTransferMethodDetail_returnsCardDetails() { + HyperwalletTransferMethod transferMethod = new HyperwalletBankCard.Builder().cardNumber( + "************0006").build(); + when(mContext.getString(eq(R.string.transfer_method_list_item_description), eq("0006"))).thenReturn( "Ending on 0006"); - String actual = TransferMethodUtils.getTransferMethodDetail(mContext, transferMethod, BANK_CARD); + String actual = getTransferMethodDetail(mContext, transferMethod, BANK_CARD); assertThat(actual, is("Ending on 0006")); } @Test - public void testGetSecondLine_returnsAccountSecondLine() throws JSONException { - String json = mExternalResourceManager.getResourceContent("bank_account_response.json"); - JSONObject htmJsonObject = new JSONObject(json); - HyperwalletTransferMethod transferMethod = new HyperwalletTransferMethod(htmJsonObject); + public void getTransferMethodDetail_returnsBankAccountDetails() { + HyperwalletTransferMethod transferMethod = new HyperwalletBankAccount.Builder().bankAccountId( + "8017110254").build(); + when(mContext.getString(eq(R.string.transfer_method_list_item_description), eq("0254"))).thenReturn( "Ending on 0254"); - String actual = TransferMethodUtils.getTransferMethodDetail(mContext, transferMethod, BANK_ACCOUNT); + String actual = getTransferMethodDetail(mContext, transferMethod, BANK_ACCOUNT); assertThat(actual, is("Ending on 0254")); } @Test - public void testGetSecondLine_returnsPaperCheckSecondLine() throws JSONException { - Context context = mock(Context.class); - String json = mExternalResourceManager.getResourceContent("paper_check_response.json"); - JSONObject htmJsonObject = new JSONObject(json); - HyperwalletTransferMethod transferMethod = new HyperwalletTransferMethod(htmJsonObject); - String actual = TransferMethodUtils.getTransferMethodDetail(mContext, transferMethod, PAPER_CHECK); + public void getTransferMethodDetail_returnsPaperCheckDetails() { + HyperwalletTransferMethod transferMethod = new HyperwalletTransferMethod(); + String actual = getTransferMethodDetail(mContext, transferMethod, PAPER_CHECK); assertThat(actual, CoreMatchers.is("")); - verify(context, never()).getString(eq(R.string.transfer_method_list_item_description), anyString()); + verify(mContext, never()).getString(eq(R.string.transfer_method_list_item_description), anyString()); } } From 37f39b75d75858b07e068f54ea10bd539e6261e5 Mon Sep 17 00:00:00 2001 From: Viktor_Shcherbyna Date: Wed, 19 Jun 2019 10:57:42 +0300 Subject: [PATCH 5/9] remove unnecessary response paper_check_response.json --- .../test/resources/paper_check_response.json | 34 ------------------- 1 file changed, 34 deletions(-) delete mode 100644 ui/src/test/resources/paper_check_response.json diff --git a/ui/src/test/resources/paper_check_response.json b/ui/src/test/resources/paper_check_response.json deleted file mode 100644 index 7cc6d5fd5..000000000 --- a/ui/src/test/resources/paper_check_response.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "token": "trm-316843d4-bfde-4dec-a391-ef926e4affbb", - "type": "PAPER_CHECK", - "status": "ACTIVATED", - "verificationStatus": "NOT_REQUIRED", - "createdOn": "2019-02-26T22:03:53", - "transferMethodCountry": "CA", - "transferMethodCurrency": "USD", - "bankAccountRelationship": "SELF", - "profileType": "INDIVIDUAL", - "firstName": "Gannon", - "lastName": "Evan", - "dateOfBirth": "1980-01-01", - "countryOfBirth": "US", - "countryOfNationality": "CA", - "gender": "MALE", - "phoneNumber": "+1 604 6666666", - "mobileNumber": "604 666 6666", - "governmentId": "987654321", - "addressLine1": "950 Granville Street", - "city": "Vancouver", - "stateProvince": "BC", - "country": "CA", - "postalCode": "V6Z1L2", - "shippingMethod": "EXPEDITED", - "links": [ - { - "params": { - "rel": "self" - }, - "href": "https://localhost:8181/rest/v3/users/usr-a51c7522-ccba-4bcf-a6a7-bc59dae8f9b0/bank-accounts/trm-92320ebd-c569-4b16-8790-6d22b477ac3c" - } - ] -} \ No newline at end of file From 3a4bb4c2bd70da8d0e4bda3d9d8f510cb5ac844b Mon Sep 17 00:00:00 2001 From: Viktor Shcherbyna Date: Tue, 18 Jun 2019 13:08:54 +0300 Subject: [PATCH 6/9] HW-52637: PayPal Account second line --- .../android/ui/receipt/repository/ReceiptDataSource.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/receipt/src/main/java/com/hyperwallet/android/ui/receipt/repository/ReceiptDataSource.java b/receipt/src/main/java/com/hyperwallet/android/ui/receipt/repository/ReceiptDataSource.java index f6e30479b..0b6a7a1e4 100644 --- a/receipt/src/main/java/com/hyperwallet/android/ui/receipt/repository/ReceiptDataSource.java +++ b/receipt/src/main/java/com/hyperwallet/android/ui/receipt/repository/ReceiptDataSource.java @@ -71,7 +71,7 @@ public void loadInitial(@NonNull final LoadInitialParams params, .limit(params.requestedLoadSize) .sortByCreatedOnDesc().build(); - getHyperwallet().listReceipts(queryParam, + getHyperwallet().listUserReceipts(queryParam, new HyperwalletListener>() { @Override public void onSuccess(@Nullable HyperwalletPageList result) { @@ -129,7 +129,7 @@ public void loadAfter(@NonNull LoadParams params, .offset(params.key) .sortByCreatedOnDesc().build(); - getHyperwallet().listReceipts(queryParam, + getHyperwallet().listUserReceipts(queryParam, new HyperwalletListener>() { @Override public void onSuccess(@Nullable HyperwalletPageList result) { From 23ce12ad80fa099f47e140087a7fffa56f3c9b79 Mon Sep 17 00:00:00 2001 From: Viktor Shcherbyna Date: Wed, 19 Jun 2019 19:22:19 +0300 Subject: [PATCH 7/9] fix tests --- .../repository/ReceiptDataSourceTest.java | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/receipt/src/test/java/com/hyperwallet/android/ui/receipt/repository/ReceiptDataSourceTest.java b/receipt/src/test/java/com/hyperwallet/android/ui/receipt/repository/ReceiptDataSourceTest.java index 9cf699e83..50aa0ad5e 100644 --- a/receipt/src/test/java/com/hyperwallet/android/ui/receipt/repository/ReceiptDataSourceTest.java +++ b/receipt/src/test/java/com/hyperwallet/android/ui/receipt/repository/ReceiptDataSourceTest.java @@ -96,13 +96,13 @@ public Object answer(InvocationOnMock invocation) { listener.onSuccess(response); return listener; } - }).when(mHyperwallet).listReceipts(any(ReceiptQueryParam.class), + }).when(mHyperwallet).listUserReceipts(any(ReceiptQueryParam.class), ArgumentMatchers.>>any()); // test mReceiptDataSource.loadInitial(mInitialParams, mInitialCallback); - verify(mHyperwallet).listReceipts(any(ReceiptQueryParam.class), + verify(mHyperwallet).listUserReceipts(any(ReceiptQueryParam.class), ArgumentMatchers.>>any()); verify(mInitialCallback).onResult(mListArgumentCaptor.capture(), mPreviousCaptor.capture(), mNextCaptor.capture()); @@ -149,13 +149,13 @@ public Object answer(InvocationOnMock invocation) { listener.onSuccess(null); return listener; } - }).when(mHyperwallet).listReceipts(any(ReceiptQueryParam.class), + }).when(mHyperwallet).listUserReceipts(any(ReceiptQueryParam.class), ArgumentMatchers.>>any()); // test mReceiptDataSource.loadInitial(mInitialParams, mInitialCallback); - verify(mHyperwallet).listReceipts(any(ReceiptQueryParam.class), + verify(mHyperwallet).listUserReceipts(any(ReceiptQueryParam.class), ArgumentMatchers.>>any()); verify(mInitialCallback, never()).onResult(ArgumentMatchers.anyList(), anyInt(), anyInt()); @@ -177,13 +177,13 @@ public Object answer(InvocationOnMock invocation) { listener.onFailure(new HyperwalletException(errors)); return listener; } - }).when(mHyperwallet).listReceipts(any(ReceiptQueryParam.class), + }).when(mHyperwallet).listUserReceipts(any(ReceiptQueryParam.class), ArgumentMatchers.>>any()); // test mReceiptDataSource.loadInitial(mInitialParams, mInitialCallback); - verify(mHyperwallet).listReceipts(any(ReceiptQueryParam.class), + verify(mHyperwallet).listUserReceipts(any(ReceiptQueryParam.class), ArgumentMatchers.>>any()); verify(mInitialCallback, never()).onResult(ArgumentMatchers.anyList(), anyInt(), anyInt()); @@ -211,13 +211,13 @@ public Object answer(InvocationOnMock invocation) { listener.onFailure(new HyperwalletException(errors)); return listener; } - }).when(mHyperwallet).listReceipts(any(ReceiptQueryParam.class), + }).when(mHyperwallet).listUserReceipts(any(ReceiptQueryParam.class), ArgumentMatchers.>>any()); // test mReceiptDataSource.loadInitial(mInitialParams, mInitialCallback); - verify(mHyperwallet).listReceipts(any(ReceiptQueryParam.class), + verify(mHyperwallet).listUserReceipts(any(ReceiptQueryParam.class), ArgumentMatchers.>>any()); verify(mInitialCallback, never()).onResult(ArgumentMatchers.anyList(), anyInt(), anyInt()); @@ -248,13 +248,13 @@ public Object answer(InvocationOnMock invocation) { listener.onSuccess(response); return listener; } - }).when(mHyperwallet).listReceipts(any(ReceiptQueryParam.class), + }).when(mHyperwallet).listUserReceipts(any(ReceiptQueryParam.class), ArgumentMatchers.>>any()); // test mReceiptDataSource.loadAfter(mLoadAfterParams, mLoadAfterCallback); - verify(mHyperwallet).listReceipts(any(ReceiptQueryParam.class), + verify(mHyperwallet).listUserReceipts(any(ReceiptQueryParam.class), ArgumentMatchers.>>any()); verify(mLoadAfterCallback).onResult(mListArgumentCaptor.capture(), mNextCaptor.capture()); @@ -299,13 +299,13 @@ public Object answer(InvocationOnMock invocation) { listener.onSuccess(null); return listener; } - }).when(mHyperwallet).listReceipts(any(ReceiptQueryParam.class), + }).when(mHyperwallet).listUserReceipts(any(ReceiptQueryParam.class), ArgumentMatchers.>>any()); // test mReceiptDataSource.loadAfter(mLoadAfterParams, mLoadAfterCallback); - verify(mHyperwallet).listReceipts(any(ReceiptQueryParam.class), + verify(mHyperwallet).listUserReceipts(any(ReceiptQueryParam.class), ArgumentMatchers.>>any()); verify(mLoadAfterCallback, never()).onResult(ArgumentMatchers.>any(), anyInt()); @@ -327,13 +327,13 @@ public Object answer(InvocationOnMock invocation) { listener.onFailure(new HyperwalletException(errors)); return listener; } - }).when(mHyperwallet).listReceipts(any(ReceiptQueryParam.class), + }).when(mHyperwallet).listUserReceipts(any(ReceiptQueryParam.class), ArgumentMatchers.>>any()); // test mReceiptDataSource.loadAfter(mLoadAfterParams, mLoadAfterCallback); - verify(mHyperwallet).listReceipts(any(ReceiptQueryParam.class), + verify(mHyperwallet).listUserReceipts(any(ReceiptQueryParam.class), ArgumentMatchers.>>any()); verify(mLoadAfterCallback, never()).onResult(ArgumentMatchers.anyList(), anyInt()); @@ -362,13 +362,13 @@ public Object answer(InvocationOnMock invocation) { listener.onFailure(new HyperwalletException(errors)); return listener; } - }).when(mHyperwallet).listReceipts(any(ReceiptQueryParam.class), + }).when(mHyperwallet).listUserReceipts(any(ReceiptQueryParam.class), ArgumentMatchers.>>any()); // test mReceiptDataSource.loadAfter(mLoadAfterParams, mLoadAfterCallback); - verify(mHyperwallet).listReceipts(any(ReceiptQueryParam.class), + verify(mHyperwallet).listUserReceipts(any(ReceiptQueryParam.class), ArgumentMatchers.>>any()); verify(mLoadAfterCallback, never()).onResult(ArgumentMatchers.anyList(), anyInt()); From 66222577650e514c6350ce51bd21da91d3027620 Mon Sep 17 00:00:00 2001 From: Peter Joseph Olamit Date: Wed, 19 Jun 2019 15:01:50 -0700 Subject: [PATCH 8/9] HW-52585 UI - Receipt Details (#42) --- .../ui/common/view}/OneClickListener.java | 5 +- .../android/ui/common/viewmodel/Event.java | 8 +- .../common/viewmodel/ListDetailNavigator.java | 30 ++ .../main/res/drawable/view_item_ripple.xml | 8 + common/src/main/res/values/styles.xml | 5 + receipt/build.gradle | 2 +- receipt/config/lint.xml | 3 + receipt/src/androidTest/AndroidManifest.xml | 3 +- ...yperwalletInstrumentedTestApplication.java | 31 ++ .../android/ui/receipt/ListReceiptsTest.java | 219 ++++++++- .../HyperwalletExternalResourceManager.java | 73 +++ .../rule/HyperwalletMockWebServer.java | 115 +++++ .../ui/receipt/util/EspressoUtils.java | 187 ++++++++ .../ui/receipt/util/NestedScrollToAction.java | 41 ++ .../util/RecyclerViewCountAssertion.java | 30 ++ .../util/TestAuthenticationProvider.java | 51 +++ receipt/src/main/AndroidManifest.xml | 12 +- .../receipt/repository/ReceiptDataSource.java | 7 + .../ui/receipt/view/ListReceiptActivity.java | 21 +- .../ui/receipt/view/ListReceiptFragment.java | 108 +++-- .../receipt/view/ReceiptDetailActivity.java | 83 ++++ .../receipt/view/ReceiptDetailFragment.java | 220 +++++++++ .../viewmodel/ListReceiptViewModel.java | 9 + .../viewmodel/ReceiptDetailViewModel.java | 34 ++ .../res/layout/activity_receipt_detail.xml | 32 ++ ...fragment.xml => fragment_list_receipt.xml} | 0 .../res/layout/fragment_receipt_detail.xml | 425 ++++++++++++++++++ receipt/src/main/res/layout/item_receipt.xml | 70 +-- .../res/layout/item_receipt_with_header.xml | 10 +- receipt/src/main/res/layout/receipt.xml | 69 +++ receipt/src/main/res/values/strings.xml | 16 + .../test/resources/receipt_list_response.json | 8 +- ui/src/androidTest/AndroidManifest.xml | 2 +- .../ListTransferMethodActivity.java | 2 +- .../ListTransferMethodFragment.java | 2 +- .../SelectTransferMethodFragment.java | 2 +- .../res/layout/item_transfer_method_type.xml | 3 +- 37 files changed, 1796 insertions(+), 150 deletions(-) rename {ui/src/main/java/com/hyperwallet/android/ui/view/widget => common/src/main/java/com/hyperwallet/android/ui/common/view}/OneClickListener.java (94%) create mode 100644 common/src/main/java/com/hyperwallet/android/ui/common/viewmodel/ListDetailNavigator.java create mode 100644 common/src/main/res/drawable/view_item_ripple.xml create mode 100644 receipt/src/androidTest/java/com/hyperwallet/android/ui/receipt/HyperwalletInstrumentedTestApplication.java create mode 100644 receipt/src/androidTest/java/com/hyperwallet/android/ui/receipt/rule/HyperwalletExternalResourceManager.java create mode 100644 receipt/src/androidTest/java/com/hyperwallet/android/ui/receipt/rule/HyperwalletMockWebServer.java create mode 100644 receipt/src/androidTest/java/com/hyperwallet/android/ui/receipt/util/EspressoUtils.java create mode 100644 receipt/src/androidTest/java/com/hyperwallet/android/ui/receipt/util/NestedScrollToAction.java create mode 100644 receipt/src/androidTest/java/com/hyperwallet/android/ui/receipt/util/RecyclerViewCountAssertion.java create mode 100644 receipt/src/androidTest/java/com/hyperwallet/android/ui/receipt/util/TestAuthenticationProvider.java create mode 100644 receipt/src/main/java/com/hyperwallet/android/ui/receipt/view/ReceiptDetailActivity.java create mode 100644 receipt/src/main/java/com/hyperwallet/android/ui/receipt/view/ReceiptDetailFragment.java create mode 100644 receipt/src/main/java/com/hyperwallet/android/ui/receipt/viewmodel/ReceiptDetailViewModel.java create mode 100644 receipt/src/main/res/layout/activity_receipt_detail.xml rename receipt/src/main/res/layout/{list_receipt_fragment.xml => fragment_list_receipt.xml} (100%) create mode 100644 receipt/src/main/res/layout/fragment_receipt_detail.xml create mode 100644 receipt/src/main/res/layout/receipt.xml diff --git a/ui/src/main/java/com/hyperwallet/android/ui/view/widget/OneClickListener.java b/common/src/main/java/com/hyperwallet/android/ui/common/view/OneClickListener.java similarity index 94% rename from ui/src/main/java/com/hyperwallet/android/ui/view/widget/OneClickListener.java rename to common/src/main/java/com/hyperwallet/android/ui/common/view/OneClickListener.java index 33e575c31..e5822917b 100644 --- a/ui/src/main/java/com/hyperwallet/android/ui/view/widget/OneClickListener.java +++ b/common/src/main/java/com/hyperwallet/android/ui/common/view/OneClickListener.java @@ -1,6 +1,6 @@ /* * The MIT License (MIT) - * Copyright (c) 2018 Hyperwallet Systems Inc. + * 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, @@ -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.widget; +package com.hyperwallet.android.ui.common.view; import android.os.SystemClock; import android.view.View; @@ -44,3 +44,4 @@ public void onClick(View v) { */ public abstract void onOneClick(View v); } + diff --git a/common/src/main/java/com/hyperwallet/android/ui/common/viewmodel/Event.java b/common/src/main/java/com/hyperwallet/android/ui/common/viewmodel/Event.java index 0914c82f5..984bb9995 100644 --- a/common/src/main/java/com/hyperwallet/android/ui/common/viewmodel/Event.java +++ b/common/src/main/java/com/hyperwallet/android/ui/common/viewmodel/Event.java @@ -24,11 +24,11 @@ */ public class Event { - private final T content; + private final T mContent; private boolean mIsContentConsumed; public Event(@NonNull final T t) { - content = t; + mContent = t; } /** @@ -38,7 +38,7 @@ public Event(@NonNull final T t) { @NonNull public T getContent() { mIsContentConsumed = true; - return content; + return mContent; } /** @@ -58,7 +58,7 @@ public boolean isContentConsumed() { public T getContentIfNotConsumed() { if (!mIsContentConsumed) { mIsContentConsumed = true; - return content; + return mContent; } return null; } diff --git a/common/src/main/java/com/hyperwallet/android/ui/common/viewmodel/ListDetailNavigator.java b/common/src/main/java/com/hyperwallet/android/ui/common/viewmodel/ListDetailNavigator.java new file mode 100644 index 000000000..b5916f1b2 --- /dev/null +++ b/common/src/main/java/com/hyperwallet/android/ui/common/viewmodel/ListDetailNavigator.java @@ -0,0 +1,30 @@ +/* + * 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.ui.common.viewmodel; + +/** + * Detail interface for having a list UI with detail information through navigation + */ +public interface ListDetailNavigator { + + /** + * Navigate action + * + * @param e Navigation event + */ + void navigate(Event e); +} diff --git a/common/src/main/res/drawable/view_item_ripple.xml b/common/src/main/res/drawable/view_item_ripple.xml new file mode 100644 index 000000000..4cabe0d43 --- /dev/null +++ b/common/src/main/res/drawable/view_item_ripple.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/common/src/main/res/values/styles.xml b/common/src/main/res/values/styles.xml index 8221087b3..ba0f0ee2d 100644 --- a/common/src/main/res/values/styles.xml +++ b/common/src/main/res/values/styles.xml @@ -232,6 +232,11 @@ @color/colorPrimaryDark @color/colorPrimary + + diff --git a/receipt/build.gradle b/receipt/build.gradle index 03133faa3..cff209c86 100644 --- a/receipt/build.gradle +++ b/receipt/build.gradle @@ -108,4 +108,4 @@ sonarqube { property "sonar.libraries", libraries property "sonar.projectName", "android-ui-sdk-receipt" } -} \ No newline at end of file +} diff --git a/receipt/config/lint.xml b/receipt/config/lint.xml index 3c83d4bb5..ff446fa9e 100644 --- a/receipt/config/lint.xml +++ b/receipt/config/lint.xml @@ -4,4 +4,7 @@ + + + \ No newline at end of file diff --git a/receipt/src/androidTest/AndroidManifest.xml b/receipt/src/androidTest/AndroidManifest.xml index eb1e37113..f65437a0d 100644 --- a/receipt/src/androidTest/AndroidManifest.xml +++ b/receipt/src/androidTest/AndroidManifest.xml @@ -2,7 +2,8 @@ - + diff --git a/receipt/src/androidTest/java/com/hyperwallet/android/ui/receipt/HyperwalletInstrumentedTestApplication.java b/receipt/src/androidTest/java/com/hyperwallet/android/ui/receipt/HyperwalletInstrumentedTestApplication.java new file mode 100644 index 000000000..fb0b7c4cc --- /dev/null +++ b/receipt/src/androidTest/java/com/hyperwallet/android/ui/receipt/HyperwalletInstrumentedTestApplication.java @@ -0,0 +1,31 @@ +package com.hyperwallet.android.ui.receipt; + + +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/ui/receipt/ListReceiptsTest.java b/receipt/src/androidTest/java/com/hyperwallet/android/ui/receipt/ListReceiptsTest.java index b68f3ec04..ee3bb4f03 100644 --- a/receipt/src/androidTest/java/com/hyperwallet/android/ui/receipt/ListReceiptsTest.java +++ b/receipt/src/androidTest/java/com/hyperwallet/android/ui/receipt/ListReceiptsTest.java @@ -1,9 +1,19 @@ package com.hyperwallet.android.ui.receipt; +import static android.text.format.DateUtils.FORMAT_ABBREV_WEEKDAY; +import static android.text.format.DateUtils.FORMAT_SHOW_DATE; +import static android.text.format.DateUtils.FORMAT_SHOW_TIME; +import static android.text.format.DateUtils.FORMAT_SHOW_WEEKDAY; +import static android.text.format.DateUtils.FORMAT_SHOW_YEAR; +import static android.text.format.DateUtils.formatDateTime; + import static androidx.test.espresso.Espresso.onView; +import static androidx.test.espresso.action.ViewActions.click; +import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist; 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.withEffectiveVisibility; import static androidx.test.espresso.matcher.ViewMatchers.withId; import static androidx.test.espresso.matcher.ViewMatchers.withParent; import static androidx.test.espresso.matcher.ViewMatchers.withText; @@ -14,7 +24,7 @@ import static java.net.HttpURLConnection.HTTP_NO_CONTENT; import static java.net.HttpURLConnection.HTTP_OK; -import static com.hyperwallet.android.util.EspressoUtils.atPosition; +import static com.hyperwallet.android.ui.receipt.util.EspressoUtils.atPosition; import android.content.Context; import android.content.res.Configuration; @@ -22,17 +32,21 @@ import android.widget.TextView; import androidx.test.core.app.ApplicationProvider; +import androidx.test.espresso.IdlingRegistry; import androidx.test.espresso.contrib.RecyclerViewActions; +import androidx.test.espresso.matcher.ViewMatchers; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.rule.ActivityTestRule; import com.hyperwallet.android.Hyperwallet; -import com.hyperwallet.android.rule.HyperwalletExternalResourceManager; -import com.hyperwallet.android.rule.HyperwalletMockWebServer; +import com.hyperwallet.android.ui.common.util.DateUtils; +import com.hyperwallet.android.ui.common.util.EspressoIdlingResource; import com.hyperwallet.android.ui.receipt.repository.ReceiptRepositoryFactory; +import com.hyperwallet.android.ui.receipt.rule.HyperwalletExternalResourceManager; +import com.hyperwallet.android.ui.receipt.rule.HyperwalletMockWebServer; +import com.hyperwallet.android.ui.receipt.util.RecyclerViewCountAssertion; +import com.hyperwallet.android.ui.receipt.util.TestAuthenticationProvider; import com.hyperwallet.android.ui.receipt.view.ListReceiptActivity; -import com.hyperwallet.android.util.RecyclerViewCountAssertion; -import com.hyperwallet.android.util.TestAuthenticationProvider; import org.junit.After; import org.junit.Before; @@ -41,7 +55,11 @@ import org.junit.Test; import org.junit.runner.RunWith; +import java.util.Date; import java.util.Locale; +import java.util.concurrent.TimeUnit; + +import okhttp3.mockwebserver.MockResponse; @RunWith(AndroidJUnit4.class) public class ListReceiptsTest { @@ -69,8 +87,18 @@ public void cleanup() { ReceiptRepositoryFactory.clearInstance(); } + @Before + public void registerIdlingResource() { + IdlingRegistry.getInstance().register(EspressoIdlingResource.getIdlingResource()); + } + + @After + public void unregisterIdlingResource() { + IdlingRegistry.getInstance().unregister(EspressoIdlingResource.getIdlingResource()); + } + @Test - public void testListReceipts_userHasMultipleTransactions() { + public void testListReceipt_userHasMultipleTransactions() { mMockWebServer.mockResponse().withHttpResponseCode(HTTP_OK).withBody(sResourceManager .getResourceContent("receipt_list_response.json")).mock(); mMockWebServer.mockResponse().withHttpResponseCode(HTTP_NO_CONTENT).withBody("").mock(); @@ -92,7 +120,7 @@ public void testListReceipts_userHasMultipleTransactions() { 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"))))); + matches(atPosition(0, hasDescendant(withText("June 7, 2019"))))); onView(withId(R.id.list_receipts)).check(matches(atPosition(0, hasDescendant(withText("USD"))))); onView(withId(R.id.list_receipts)).check(matches(atPosition(1, @@ -102,7 +130,7 @@ public void testListReceipts_userHasMultipleTransactions() { 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"))))); + matches(atPosition(1, hasDescendant(withText("June 2, 2019"))))); onView(withId(R.id.list_receipts)).check(matches(atPosition(1, hasDescendant(withText("CAD"))))); onView(withId(R.id.list_receipts)).check(matches(atPosition(2, @@ -112,7 +140,7 @@ public void testListReceipts_userHasMultipleTransactions() { 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"))))); + matches(atPosition(2, hasDescendant(withText("June 1, 2019"))))); onView(withId(R.id.list_receipts)).check(matches(atPosition(2, hasDescendant(withText("USD"))))); onView(withId(R.id.list_receipts)) @@ -124,14 +152,14 @@ public void testListReceipts_userHasMultipleTransactions() { 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"))))); + matches(atPosition(3, hasDescendant(withText("December 1, 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() { + public void testListReceipt_userHasCreditTransaction() { mMockWebServer.mockResponse().withHttpResponseCode(HTTP_OK).withBody(sResourceManager .getResourceContent("receipt_credit_response.json")).mock(); mMockWebServer.mockResponse().withHttpResponseCode(HTTP_NO_CONTENT).withBody("").mock(); @@ -153,14 +181,14 @@ public void testListReceipts_displayCreditTransaction() { 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"))))); + matches(atPosition(0, hasDescendant(withText("June 2, 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() { + public void testListReceipt_userHasDebitTransaction() { mMockWebServer.mockResponse().withHttpResponseCode(HTTP_OK).withBody(sResourceManager .getResourceContent("receipt_debit_response.json")).mock(); mMockWebServer.mockResponse().withHttpResponseCode(HTTP_NO_CONTENT).withBody("").mock(); @@ -182,14 +210,14 @@ public void testListReceipts_displayDebitTransaction() { 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"))))); + matches(atPosition(0, hasDescendant(withText("May 2, 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() { + public void testListReceipt_userHasUnknownTransactionType() { mMockWebServer.mockResponse().withHttpResponseCode(HTTP_OK).withBody(sResourceManager .getResourceContent("receipt_unknown_type_response.json")).mock(); mMockWebServer.mockResponse().withHttpResponseCode(HTTP_NO_CONTENT).withBody("").mock(); @@ -211,7 +239,7 @@ public void testListReceipts_displayUnknownTransactionType() { 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"))))); + matches(atPosition(0, hasDescendant(withText("June 2, 2019"))))); onView(withId(R.id.list_receipts)).check(matches(atPosition(0, hasDescendant(withText("CAD"))))); onView(withId(R.id.list_receipts)).check(new RecyclerViewCountAssertion(1)); @@ -231,7 +259,120 @@ public void testListReceipt_userHasNoTransactions() { } @Test - public void testListReceipt_displayPagedTransactions() throws InterruptedException { + public void testListReceipt_clickTransactionDisplaysDetails() { + 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)).perform(RecyclerViewActions.actionOnItemAtPosition(0, click())); + + onView(withId(R.id.transaction_header_text)).check(matches(withText(R.string.transaction_header_text))); + onView(withId(R.id.transaction_type_icon)).check(matches(withText(R.string.credit))); + onView(withId(R.id.transaction_title)).check(matches(withText(R.string.payment))); + onView(withId(R.id.transaction_amount)).check(matches(withText("+ 20.00"))); + onView(withId(R.id.transaction_currency)).check(matches(withText("USD"))); + onView(withId(R.id.transaction_date)).check(matches(withText("June 7, 2019"))); + + onView(withId(R.id.receipt_details_header_label)).check(matches(withText(R.string.receipt_header_label))); + onView(withId(R.id.receipt_id_label)).check(matches(withText(R.string.journalId))); + onView(withId(R.id.receipt_id_value)).check(matches(withText("3051579"))); + onView(withId(R.id.date_label)).check(matches(withText(R.string.createdOn))); + + Date date = DateUtils.fromDateTimeString("2019-06-07T17:08:58"); + String timezone = DateUtils.toDateFormat(date, "zzz"); + String text = mActivityTestRule.getActivity().getApplicationContext().getString( + R.string.concat_string_view_format, + formatDateTime(mActivityTestRule.getActivity().getApplicationContext(), date.getTime(), + FORMAT_SHOW_DATE | FORMAT_SHOW_TIME | FORMAT_SHOW_YEAR + | FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_WEEKDAY), timezone); + onView(withId(R.id.date_value)).check(matches(withText(text))); + + onView(withId(R.id.client_id_label)).check(matches(withText(R.string.clientPaymentId))); + onView(withId(R.id.client_id_value)).check(matches(withText("8OxXefx5"))); + onView(withId(R.id.charity_label)).check(matches(withText(R.string.charityName))); + onView(withId(R.id.charity_value)).check(matches(withText("Sample Charity"))); + onView(withId(R.id.check_number_label)).check(matches(withText(R.string.checkNumber))); + onView(withId(R.id.check_number_value)).check(matches(withText("Sample Check Number"))); + onView(withId(R.id.website_label)).check(matches(withText(R.string.website))); + onView(withId(R.id.website_value)).check(matches(withText("https://api.sandbox.hyperwallet.com"))); + onView(withText("A Person")).check(doesNotExist()); + + onView(withId(R.id.receipt_notes_header_label)).check(matches(withText(R.string.notes))); + onView(withId(R.id.notes_value)).check( + matches(withText("Sample payment for the period of June 15th, 2019 to July 23, 2019"))); + + onView(withId(R.id.details_header_text)).check(matches(withText(R.string.fee_details_header_text))); + onView(withId(R.id.details_amount_label)).check(matches(withText(R.string.details_amount_label))); + onView(withId(R.id.details_amount_value)).check(matches(withText("20.00 USD"))); + onView(withId(R.id.details_fee_label)).check(matches(withText(R.string.fee_label))); + onView(withId(R.id.details_fee_value)).check(matches(withText("2.25 USD"))); + onView(withId(R.id.details_transfer_amount_label)).check(matches(withText(R.string.transfer_amount_label))); + onView(withId(R.id.details_transfer_amount_value)).check(matches(withText("17.75 USD"))); + } + + @Test + public void testListReceipt_clickTransactionDisplaysDetailsWithoutFees() { + 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)).perform(RecyclerViewActions.actionOnItemAtPosition(3, click())); + + onView(withId(R.id.transaction_header_text)).check(matches(withText(R.string.transaction_header_text))); + onView(withId(R.id.transaction_type_icon)).check(matches(withText(R.string.debit))); + onView(withId(R.id.transaction_title)).check(matches(withText(R.string.transfer_to_prepaid_card))); + onView(withId(R.id.transaction_amount)).check(matches(withText("- 18.05"))); + onView(withId(R.id.transaction_currency)).check(matches(withText("USD"))); + onView(withId(R.id.transaction_date)).check(matches(withText("December 1, 2018"))); + + onView(withId(R.id.receipt_details_header_label)).check(matches(withText(R.string.receipt_header_label))); + onView(withId(R.id.receipt_id_label)).check(matches(withText(R.string.journalId))); + onView(withId(R.id.receipt_id_value)).check(matches(withText("3051590"))); + onView(withId(R.id.date_label)).check(matches(withText(R.string.createdOn))); + + Date date = DateUtils.fromDateTimeString("2018-12-01T17:12:18"); + String timezone = DateUtils.toDateFormat(date, "zzz"); + String text = mActivityTestRule.getActivity().getApplicationContext().getString( + R.string.concat_string_view_format, + formatDateTime(mActivityTestRule.getActivity().getApplicationContext(), date.getTime(), + FORMAT_SHOW_DATE | FORMAT_SHOW_TIME | FORMAT_SHOW_YEAR + | FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_WEEKDAY), timezone); + onView(withId(R.id.date_value)).check(matches(withText(text))); + + onView(withId(R.id.client_id_label)).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE))); + onView(withId(R.id.client_id_value)).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE))); + onView(withId(R.id.charity_label)).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE))); + onView(withId(R.id.charity_value)).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE))); + onView(withId(R.id.check_number_label)).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE))); + onView(withId(R.id.check_number_value)).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE))); + onView(withId(R.id.website_label)).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE))); + onView(withId(R.id.website_value)).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE))); + + onView(withId(R.id.receipt_notes_information)).check( + matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE))); + onView(withId(R.id.receipt_notes_header_label)).check( + matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE))); + onView(withId(R.id.notes_value)).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE))); + } + + @Test + public void testListReceipt_verifyTransactionsLoadedUponScrolling() throws InterruptedException { mMockWebServer.mockResponse().withHttpResponseCode(HTTP_OK).withBody(sResourceManager .getResourceContent("receipt_list_paged_response.json")).mock(); mMockWebServer.mockResponse().withHttpResponseCode(HTTP_OK).withBody(sResourceManager @@ -265,7 +406,7 @@ public void testListReceipt_displayPagedTransactions() throws InterruptedExcepti } @Test - public void testListReceipts_checkDateTextOnLocaleChange() { + public void testListReceipt_checkDateTextOnLocaleChange() { mMockWebServer.mockResponse().withHttpResponseCode(HTTP_OK).withBody(sResourceManager .getResourceContent("receipt_debit_response.json")).mock(); mMockWebServer.mockResponse().withHttpResponseCode(HTTP_NO_CONTENT).withBody("").mock(); @@ -276,13 +417,47 @@ public void testListReceipts_checkDateTextOnLocaleChange() { // 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); + onView(withId(R.id.list_receipts)).check(matches(atPosition(0, hasDescendant(withText("2 maggio 2019"))))); + } + + @Test + public void testListReceipt_displaysNetworkErrorDialogOnConnectionTimeout() { + mMockWebServer.getServer().enqueue(new MockResponse().setResponseCode(HTTP_OK).setBody(sResourceManager + .getResourceContent("receipt_debit_response.json")).throttleBody(512, 15, TimeUnit.SECONDS)); + mMockWebServer.mockResponse().withHttpResponseCode(HTTP_OK).withBody(sResourceManager + .getResourceContent("receipt_debit_response.json")).mock(); + mMockWebServer.mockResponse().withHttpResponseCode(HTTP_NO_CONTENT).withBody("").mock(); + mActivityTestRule.launchActivity(null); + + // assert error dialog information exist in portrait mode + onView(withText(R.string.error_dialog_connectivity_title)).check(matches(isDisplayed())); + onView(withText(R.string.io_exception)).check(matches(isDisplayed())); + onView(withId(android.R.id.button1)).check(matches(withText(R.string.try_again_button_label))); + onView(withId(android.R.id.button2)).check(matches(withText(R.string.cancel_button_label))); + + // retry button clicked + onView(withId(android.R.id.button1)).perform(click()); + onView(withText(R.string.error_dialog_connectivity_title)).check(doesNotExist()); + + // 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("May 02, 2019"))))); + onView(withId(R.id.list_receipts)).check(matches(atPosition(0, + hasDescendant(withText(com.hyperwallet.android.ui.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 2, 2019"))))); + onView(withId(R.id.list_receipts)).check(matches(atPosition(0, hasDescendant(withText("USD"))))); + + onView(withId(R.id.list_receipts)).check(new RecyclerViewCountAssertion(1)); } private void setLocale(Locale locale) { diff --git a/receipt/src/androidTest/java/com/hyperwallet/android/ui/receipt/rule/HyperwalletExternalResourceManager.java b/receipt/src/androidTest/java/com/hyperwallet/android/ui/receipt/rule/HyperwalletExternalResourceManager.java new file mode 100644 index 000000000..b5baa31da --- /dev/null +++ b/receipt/src/androidTest/java/com/hyperwallet/android/ui/receipt/rule/HyperwalletExternalResourceManager.java @@ -0,0 +1,73 @@ +package com.hyperwallet.android.ui.receipt.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/ui/receipt/rule/HyperwalletMockWebServer.java b/receipt/src/androidTest/java/com/hyperwallet/android/ui/receipt/rule/HyperwalletMockWebServer.java new file mode 100644 index 000000000..fd55bd2bb --- /dev/null +++ b/receipt/src/androidTest/java/com/hyperwallet/android/ui/receipt/rule/HyperwalletMockWebServer.java @@ -0,0 +1,115 @@ +package com.hyperwallet.android.ui.receipt.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/ui/receipt/util/EspressoUtils.java b/receipt/src/androidTest/java/com/hyperwallet/android/ui/receipt/util/EspressoUtils.java new file mode 100644 index 000000000..549a99704 --- /dev/null +++ b/receipt/src/androidTest/java/com/hyperwallet/android/ui/receipt/util/EspressoUtils.java @@ -0,0 +1,187 @@ +package com.hyperwallet.android.ui.receipt.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/ui/receipt/util/NestedScrollToAction.java b/receipt/src/androidTest/java/com/hyperwallet/android/ui/receipt/util/NestedScrollToAction.java new file mode 100644 index 000000000..b3619e5c3 --- /dev/null +++ b/receipt/src/androidTest/java/com/hyperwallet/android/ui/receipt/util/NestedScrollToAction.java @@ -0,0 +1,41 @@ +package com.hyperwallet.android.ui.receipt.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/ui/receipt/util/RecyclerViewCountAssertion.java b/receipt/src/androidTest/java/com/hyperwallet/android/ui/receipt/util/RecyclerViewCountAssertion.java new file mode 100644 index 000000000..7420f7e5c --- /dev/null +++ b/receipt/src/androidTest/java/com/hyperwallet/android/ui/receipt/util/RecyclerViewCountAssertion.java @@ -0,0 +1,30 @@ +package com.hyperwallet.android.ui.receipt.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/ui/receipt/util/TestAuthenticationProvider.java b/receipt/src/androidTest/java/com/hyperwallet/android/ui/receipt/util/TestAuthenticationProvider.java new file mode 100644 index 000000000..c50513b47 --- /dev/null +++ b/receipt/src/androidTest/java/com/hyperwallet/android/ui/receipt/util/TestAuthenticationProvider.java @@ -0,0 +1,51 @@ +package com.hyperwallet.android.ui.receipt.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 d817501e5..fbfa141a9 100644 --- a/receipt/src/main/AndroidManifest.xml +++ b/receipt/src/main/AndroidManifest.xml @@ -5,10 +5,16 @@ - - + + + \ No newline at end of file diff --git a/receipt/src/main/java/com/hyperwallet/android/ui/receipt/repository/ReceiptDataSource.java b/receipt/src/main/java/com/hyperwallet/android/ui/receipt/repository/ReceiptDataSource.java index 0b6a7a1e4..fd0c3b654 100644 --- a/receipt/src/main/java/com/hyperwallet/android/ui/receipt/repository/ReceiptDataSource.java +++ b/receipt/src/main/java/com/hyperwallet/android/ui/receipt/repository/ReceiptDataSource.java @@ -31,6 +31,7 @@ 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.ui.common.util.EspressoIdlingResource; import com.hyperwallet.android.ui.common.viewmodel.Event; import java.util.Calendar; @@ -71,6 +72,7 @@ public void loadInitial(@NonNull final LoadInitialParams params, .limit(params.requestedLoadSize) .sortByCreatedOnDesc().build(); + EspressoIdlingResource.increment(); getHyperwallet().listUserReceipts(queryParam, new HyperwalletListener>() { @Override @@ -86,12 +88,14 @@ public void onSuccess(@Nullable HyperwalletPageList result) { // reset mLoadInitialCallback = null; mLoadInitialParams = null; + EspressoIdlingResource.decrement(); } @Override public void onFailure(HyperwalletException exception) { mIsFetchingData.postValue(Boolean.FALSE); mErrors.postValue(new Event<>(exception.getHyperwalletErrors())); + EspressoIdlingResource.decrement(); } @Override @@ -129,6 +133,7 @@ public void loadAfter(@NonNull LoadParams params, .offset(params.key) .sortByCreatedOnDesc().build(); + EspressoIdlingResource.increment(); getHyperwallet().listUserReceipts(queryParam, new HyperwalletListener>() { @Override @@ -144,12 +149,14 @@ public void onSuccess(@Nullable HyperwalletPageList result) { // reset mLoadAfterCallback = null; mLoadAfterParams = null; + EspressoIdlingResource.decrement(); } @Override public void onFailure(HyperwalletException exception) { mIsFetchingData.postValue(Boolean.FALSE); mErrors.postValue(new Event<>(exception.getHyperwalletErrors())); + EspressoIdlingResource.decrement(); } @Override diff --git a/receipt/src/main/java/com/hyperwallet/android/ui/receipt/view/ListReceiptActivity.java b/receipt/src/main/java/com/hyperwallet/android/ui/receipt/view/ListReceiptActivity.java index 6f203949d..ef56fa89b 100644 --- a/receipt/src/main/java/com/hyperwallet/android/ui/receipt/view/ListReceiptActivity.java +++ b/receipt/src/main/java/com/hyperwallet/android/ui/receipt/view/ListReceiptActivity.java @@ -32,16 +32,19 @@ import com.hyperwallet.android.model.HyperwalletError; import com.hyperwallet.android.model.HyperwalletErrors; +import com.hyperwallet.android.model.receipt.Receipt; import com.hyperwallet.android.ui.common.view.error.DefaultErrorDialogFragment; import com.hyperwallet.android.ui.common.view.error.OnNetworkErrorCallback; import com.hyperwallet.android.ui.common.viewmodel.Event; +import com.hyperwallet.android.ui.common.viewmodel.ListDetailNavigator; import com.hyperwallet.android.ui.receipt.R; import com.hyperwallet.android.ui.receipt.repository.ReceiptRepositoryFactory; import com.hyperwallet.android.ui.receipt.viewmodel.ListReceiptViewModel; import java.util.List; -public class ListReceiptActivity extends AppCompatActivity implements OnNetworkErrorCallback { +public class ListReceiptActivity extends AppCompatActivity implements OnNetworkErrorCallback, + ListDetailNavigator> { private ListReceiptViewModel mListReceiptViewModel; @@ -76,6 +79,13 @@ public void onChanged(Event event) { } }); + mListReceiptViewModel.getDetailNavigation().observe(this, new Observer>() { + @Override + public void onChanged(Event event) { + navigate(event); + } + }); + if (savedInstanceState == null) { initFragment(ListReceiptFragment.newInstance()); } @@ -130,4 +140,13 @@ private void showErrorOnLoadReceipt(@NonNull final List errors fragment.show(fragmentManager); } } + + @Override + public void navigate(@NonNull final Event event) { + if (!event.isContentConsumed()) { + Intent intent = new Intent(this, ReceiptDetailActivity.class); + intent.putExtra(ReceiptDetailActivity.EXTRA_RECEIPT, event.getContent()); + startActivity(intent); + } + } } diff --git a/receipt/src/main/java/com/hyperwallet/android/ui/receipt/view/ListReceiptFragment.java b/receipt/src/main/java/com/hyperwallet/android/ui/receipt/view/ListReceiptFragment.java index 2e2e33cac..e1fd220a2 100644 --- a/receipt/src/main/java/com/hyperwallet/android/ui/receipt/view/ListReceiptFragment.java +++ b/receipt/src/main/java/com/hyperwallet/android/ui/receipt/view/ListReceiptFragment.java @@ -16,6 +16,11 @@ */ package com.hyperwallet.android.ui.receipt.view; +import static android.text.format.DateUtils.FORMAT_NO_MONTH_DAY; +import static android.text.format.DateUtils.FORMAT_SHOW_DATE; +import static android.text.format.DateUtils.FORMAT_SHOW_YEAR; +import static android.text.format.DateUtils.formatDateTime; + import static com.hyperwallet.android.model.receipt.Receipt.Entries.CREDIT; import static com.hyperwallet.android.model.receipt.Receipt.Entries.DEBIT; @@ -39,10 +44,13 @@ import com.hyperwallet.android.model.receipt.Receipt; import com.hyperwallet.android.ui.common.util.DateUtils; +import com.hyperwallet.android.ui.common.view.OneClickListener; import com.hyperwallet.android.ui.receipt.R; import com.hyperwallet.android.ui.receipt.viewmodel.ListReceiptViewModel; +import java.text.DecimalFormat; import java.util.Calendar; +import java.util.Date; import java.util.Locale; import java.util.Objects; @@ -77,14 +85,14 @@ public void onCreate(@Nullable final Bundle savedInstanceState) { @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); + return inflater.inflate(R.layout.fragment_list_receipt, 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()); + mListReceiptAdapter = new ListReceiptAdapter(mListReceiptViewModel, new ListReceiptItemDiffCallback()); mListReceiptsView = view.findViewById(R.id.list_receipts); mListReceiptsView.setHasFixedSize(true); mListReceiptsView.setLayoutManager(new LinearLayoutManager(getActivity())); @@ -96,8 +104,8 @@ public void onViewCreated(@NonNull final View view, @Nullable final Bundle saved private void registerObservers() { mListReceiptViewModel.getReceiptList().observe(getViewLifecycleOwner(), new Observer>() { @Override - public void onChanged(PagedList transferMethods) { - mListReceiptAdapter.submitList(transferMethods); + public void onChanged(PagedList receipts) { + mListReceiptAdapter.submitList(receipts); } }); @@ -135,13 +143,15 @@ public boolean areContentsTheSame(@NonNull final Receipt oldItem, @NonNull final 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"; + static final String AMOUNT_FORMAT = "###0.00"; private static final int HEADER_VIEW_TYPE = 1; private static final int DATA_VIEW_TYPE = 0; + private final ListReceiptViewModel mReceiptViewModel; - ListReceiptAdapter(@NonNull final DiffUtil.ItemCallback diffCallback) { + ListReceiptAdapter(@NonNull final ListReceiptViewModel receiptViewModel, + @NonNull final DiffUtil.ItemCallback diffCallback) { super(diffCallback); + mReceiptViewModel = receiptViewModel; } @Override @@ -173,10 +183,10 @@ public ReceiptViewHolder onCreateViewHolder(final @NonNull ViewGroup viewGroup, if (viewType == HEADER_VIEW_TYPE) { View headerView = layout.inflate(R.layout.item_receipt_with_header, viewGroup, false); - return new ReceiptViewHolderWithHeader(headerView); + return new ReceiptViewHolderWithHeader(mReceiptViewModel, headerView); } View dataView = layout.inflate(R.layout.item_receipt, viewGroup, false); - return new ReceiptViewHolder(dataView); + return new ReceiptViewHolder(mReceiptViewModel, dataView); } @Override @@ -188,48 +198,64 @@ public void onBindViewHolder(@NonNull final ReceiptViewHolder holder, final int } 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) { + private ListReceiptViewModel mListReceiptViewModel; + private View mView; + + ReceiptViewHolder(@NonNull final ListReceiptViewModel receiptViewModel, + @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); + mView = item.findViewById(R.id.receipt_item); + mListReceiptViewModel = receiptViewModel; } void bind(@NonNull final Receipt receipt) { + mView.setOnClickListener(new OneClickListener() { + @Override + public void onOneClick(View v) { + mListReceiptViewModel.setDetailNavigation(receipt); + } + }); + + // By design decision from here under, this code is also repeated in ReceiptDetailFragment + TextView transactionTypeIcon = itemView.findViewById(R.id.transaction_type_icon); + TextView transactionTitle = itemView.findViewById(R.id.transaction_title); + TextView transactionDate = itemView.findViewById(R.id.transaction_date); + TextView transactionAmount = itemView.findViewById(R.id.transaction_amount); + TextView transactionCurrency = itemView.findViewById(R.id.transaction_currency); + + //TODO localization of currencies in consideration + DecimalFormat decimalFormat = new DecimalFormat(AMOUNT_FORMAT); + double amount = Double.parseDouble(receipt.getAmount()); + String formattedAmount = decimalFormat.format(amount); + if (CREDIT.equals(receipt.getEntry())) { - mTransactionAmount.setTextColor(mTransactionAmount.getContext() + transactionAmount.setTextColor(transactionAmount.getContext() .getResources().getColor(R.color.positiveColor)); - mTransactionAmount.setText(mTransactionAmount.getContext() - .getString(R.string.credit_sign, receipt.getAmount())); - mTransactionTypeIcon.setTextColor(mTransactionTypeIcon.getContext() + transactionAmount.setText(transactionAmount.getContext() + .getString(R.string.credit_sign, formattedAmount)); + transactionTypeIcon.setTextColor(transactionTypeIcon.getContext() .getResources().getColor(R.color.positiveColor)); - mTransactionTypeIcon.setBackground(mTransactionTypeIcon.getContext() + transactionTypeIcon.setBackground(transactionTypeIcon.getContext() .getDrawable(R.drawable.circle_positive)); - mTransactionTypeIcon.setText(mTransactionTypeIcon.getContext().getText(R.string.credit)); + transactionTypeIcon.setText(transactionTypeIcon.getContext().getText(R.string.credit)); } else if (DEBIT.equals(receipt.getEntry())) { - mTransactionAmount.setTextColor(mTransactionAmount.getContext() + transactionAmount.setTextColor(transactionAmount.getContext() .getResources().getColor(R.color.colorAccent)); - mTransactionAmount.setText(mTransactionAmount.getContext() - .getString(R.string.debit_sign, receipt.getAmount())); - mTransactionTypeIcon.setTextColor(mTransactionTypeIcon.getContext() + transactionAmount.setText(transactionAmount.getContext() + .getString(R.string.debit_sign, formattedAmount)); + transactionTypeIcon.setTextColor(transactionTypeIcon.getContext() .getResources().getColor(R.color.colorAccent)); - mTransactionTypeIcon.setBackground(mTransactionTypeIcon.getContext() + transactionTypeIcon.setBackground(transactionTypeIcon.getContext() .getDrawable(R.drawable.circle_negative)); - mTransactionTypeIcon.setText(mTransactionTypeIcon.getContext().getText(R.string.debit)); + transactionTypeIcon.setText(transactionTypeIcon.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)); + transactionCurrency.setText(receipt.getCurrency()); + transactionTitle.setText(getTransactionTitle(receipt.getType(), transactionTitle.getContext())); + Date date = DateUtils.fromDateTimeString(receipt.getCreatedOn()); + transactionDate.setText(formatDateTime(itemView.getContext(), date.getTime(), + FORMAT_SHOW_DATE | FORMAT_SHOW_YEAR)); } String getTransactionTitle(@NonNull final String receiptType, @NonNull final Context context) { @@ -248,16 +274,18 @@ class ReceiptViewHolderWithHeader extends ReceiptViewHolder { private final TextView mTransactionHeaderText; - ReceiptViewHolderWithHeader(@NonNull final View item) { - super(item); + ReceiptViewHolderWithHeader(@NonNull final ListReceiptViewModel receiptViewModel, + @NonNull final View item) { + super(receiptViewModel, 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)); + Date date = DateUtils.fromDateTimeString(receipt.getCreatedOn()); + mTransactionHeaderText.setText(formatDateTime(mTransactionHeaderText.getContext(), date.getTime(), + FORMAT_SHOW_DATE | FORMAT_SHOW_YEAR | FORMAT_NO_MONTH_DAY)); } } } diff --git a/receipt/src/main/java/com/hyperwallet/android/ui/receipt/view/ReceiptDetailActivity.java b/receipt/src/main/java/com/hyperwallet/android/ui/receipt/view/ReceiptDetailActivity.java new file mode 100644 index 000000000..9dd1a85fa --- /dev/null +++ b/receipt/src/main/java/com/hyperwallet/android/ui/receipt/view/ReceiptDetailActivity.java @@ -0,0 +1,83 @@ +/* + * 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.ui.receipt.view; + +import android.os.Bundle; +import android.os.Parcelable; +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.ViewModelProviders; + +import com.hyperwallet.android.model.receipt.Receipt; +import com.hyperwallet.android.ui.receipt.R; +import com.hyperwallet.android.ui.receipt.viewmodel.ReceiptDetailViewModel; + +public class ReceiptDetailActivity extends AppCompatActivity { + + public static final String EXTRA_RECEIPT = "RECEIPT"; + + @Override + protected void onCreate(@Nullable final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_receipt_detail); + + Toolbar toolbar = findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + getSupportActionBar().setDisplayShowHomeEnabled(true); + getSupportActionBar().setTitle(R.string.title_activity_receipt_detail); + toolbar.setNavigationOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + finish(); + } + }); + + Parcelable parcel = getIntent().getParcelableExtra(EXTRA_RECEIPT); + if (parcel instanceof Receipt) { + ReceiptDetailViewModel viewModel = ViewModelProviders.of(this).get(ReceiptDetailViewModel.class); + viewModel.setReceipt((Receipt) parcel); + } else { + throw new IllegalArgumentException("Argument is not an instance of " + + ReceiptDetailActivity.class.getName()); + } + + if (savedInstanceState == null) { + initFragment(ReceiptDetailFragment.newInstance()); + } + } + + private void initFragment(@NonNull final Fragment fragment) { + FragmentManager fragmentManager = getSupportFragmentManager(); + FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); + fragmentTransaction.add(R.id.receipt_detail_fragment, fragment); + fragmentTransaction.commit(); + } + + @Override + public boolean onSupportNavigateUp() { + onBackPressed(); + return true; + } +} diff --git a/receipt/src/main/java/com/hyperwallet/android/ui/receipt/view/ReceiptDetailFragment.java b/receipt/src/main/java/com/hyperwallet/android/ui/receipt/view/ReceiptDetailFragment.java new file mode 100644 index 000000000..8e8f99dec --- /dev/null +++ b/receipt/src/main/java/com/hyperwallet/android/ui/receipt/view/ReceiptDetailFragment.java @@ -0,0 +1,220 @@ +/* + * 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.ui.receipt.view; + +import static android.text.format.DateUtils.FORMAT_ABBREV_WEEKDAY; +import static android.text.format.DateUtils.FORMAT_SHOW_DATE; +import static android.text.format.DateUtils.FORMAT_SHOW_TIME; +import static android.text.format.DateUtils.FORMAT_SHOW_WEEKDAY; +import static android.text.format.DateUtils.FORMAT_SHOW_YEAR; +import static android.text.format.DateUtils.formatDateTime; + +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.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.annotation.IdRes; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelProviders; + +import com.hyperwallet.android.model.receipt.Receipt; +import com.hyperwallet.android.model.receipt.ReceiptDetails; +import com.hyperwallet.android.ui.common.util.DateUtils; +import com.hyperwallet.android.ui.receipt.R; +import com.hyperwallet.android.ui.receipt.viewmodel.ReceiptDetailViewModel; + +import java.text.DecimalFormat; +import java.util.Date; +import java.util.Locale; + +public class ReceiptDetailFragment extends Fragment { + + static final String AMOUNT_FORMAT = "###0.00"; + static final String DETAIL_TIMEZONE = "zzz"; + + private ReceiptDetailViewModel mReceiptDetailViewModel; + + public ReceiptDetailFragment() { + } + + public static ReceiptDetailFragment newInstance() { + ReceiptDetailFragment fragment = new ReceiptDetailFragment(); + return fragment; + } + + @Override + public void onCreate(@Nullable final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mReceiptDetailViewModel = ViewModelProviders.of(requireActivity()).get(ReceiptDetailViewModel.class); + } + + @Override + public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final ViewGroup container, + @Nullable final Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_receipt_detail, container, false); + } + + @Override + public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + Receipt receipt = mReceiptDetailViewModel.getReceipt(); + + // transactions + setTransactionView(receipt, view); + + // receipt details + setDetailsView(receipt, view); + + // fee details + setFeeDetailsView(receipt, view); + } + + // By design decision, this code is also repeated in ListReceiptFragment + private void setTransactionView(@NonNull final Receipt receipt, @NonNull final View view) { + TextView transactionTypeIcon = view.findViewById(R.id.transaction_type_icon); + TextView transactionTitle = view.findViewById(R.id.transaction_title); + TextView transactionDate = view.findViewById(R.id.transaction_date); + TextView transactionAmount = view.findViewById(R.id.transaction_amount); + TextView transactionCurrency = view.findViewById(R.id.transaction_currency); + + //TODO localization of currencies in consideration + DecimalFormat decimalFormat = new DecimalFormat(AMOUNT_FORMAT); + double amount = Double.parseDouble(receipt.getAmount()); + String formattedAmount = decimalFormat.format(amount); + + if (CREDIT.equals(receipt.getEntry())) { + transactionAmount.setTextColor(transactionAmount.getContext() + .getResources().getColor(R.color.positiveColor)); + transactionAmount.setText(transactionAmount.getContext() + .getString(R.string.credit_sign, formattedAmount)); + transactionTypeIcon.setTextColor(transactionTypeIcon.getContext() + .getResources().getColor(R.color.positiveColor)); + transactionTypeIcon.setBackground(transactionTypeIcon.getContext() + .getDrawable(R.drawable.circle_positive)); + transactionTypeIcon.setText(transactionTypeIcon.getContext().getText(R.string.credit)); + } else if (DEBIT.equals(receipt.getEntry())) { + transactionAmount.setTextColor(transactionAmount.getContext() + .getResources().getColor(R.color.colorAccent)); + transactionAmount.setText(transactionAmount.getContext() + .getString(R.string.debit_sign, formattedAmount)); + transactionTypeIcon.setTextColor(transactionTypeIcon.getContext() + .getResources().getColor(R.color.colorAccent)); + transactionTypeIcon.setBackground(transactionTypeIcon.getContext() + .getDrawable(R.drawable.circle_negative)); + transactionTypeIcon.setText(transactionTypeIcon.getContext().getText(R.string.debit)); + } + + transactionCurrency.setText(receipt.getCurrency()); + transactionTitle.setText(getTransactionTitle(receipt.getType(), transactionTitle.getContext())); + Date date = DateUtils.fromDateTimeString(receipt.getCreatedOn()); + transactionDate.setText(formatDateTime(view.getContext(), date.getTime(), + FORMAT_SHOW_DATE | FORMAT_SHOW_YEAR)); + } + + private 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; + } + + private void setFeeDetailsView(@NonNull final Receipt receipt, @NonNull final View view) { + if (!TextUtils.isEmpty(receipt.getFee())) { + view.findViewById(R.id.fee_details_layout).setVisibility(View.VISIBLE); + double feeAmount = Double.parseDouble(receipt.getFee()); + double amount = Double.parseDouble(receipt.getAmount()); + double transferAmount = amount - feeAmount; + + //TODO localization of currencies in consideration + DecimalFormat decimalFormat = new DecimalFormat(AMOUNT_FORMAT); + + TextView amountView = view.findViewById(R.id.details_amount_value); + amountView.setText(view.getContext().getString(R.string.concat_string_view_format, + decimalFormat.format(amount), receipt.getCurrency())); + + TextView fee = view.findViewById(R.id.details_fee_value); + fee.setText(view.getContext().getString(R.string.concat_string_view_format, + decimalFormat.format(feeAmount), receipt.getCurrency())); + + TextView transfer = view.findViewById(R.id.details_transfer_amount_value); + transfer.setText(view.getContext().getString(R.string.concat_string_view_format, + decimalFormat.format(transferAmount), receipt.getCurrency())); + } + } + + private void setDetailsView(@NonNull final Receipt receipt, @NonNull final View view) { + TextView receiptId = view.findViewById(R.id.receipt_id_value); + receiptId.setText(receipt.getJournalId()); + TextView dateView = view.findViewById(R.id.date_value); + + Date date = DateUtils.fromDateTimeString(receipt.getCreatedOn()); + String timezone = DateUtils.toDateFormat(date, DETAIL_TIMEZONE); + dateView.setText(view.getContext().getString(R.string.concat_string_view_format, + formatDateTime(view.getContext(), date.getTime(), + FORMAT_SHOW_DATE | FORMAT_SHOW_TIME | FORMAT_SHOW_YEAR + | FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_WEEKDAY), timezone)); + + if (receipt.getDetails() != null) { + ReceiptDetails receiptDetails = receipt.getDetails(); + if (!TextUtils.isEmpty(receiptDetails.getCharityName())) { + setViewInformation(R.id.charity_layout, R.id.charity_value, + view, receiptDetails.getCharityName()); + } + + if (!TextUtils.isEmpty(receiptDetails.getCheckNumber())) { + setViewInformation(R.id.check_number_layout, R.id.check_number_value, + view, receiptDetails.getCheckNumber()); + } + + if (!TextUtils.isEmpty(receiptDetails.getClientPaymentId())) { + setViewInformation(R.id.client_id_layout, R.id.client_id_value, + view, receiptDetails.getClientPaymentId()); + } + + if (!TextUtils.isEmpty(receiptDetails.getWebsite())) { + setViewInformation(R.id.website_layout, R.id.website_value, + view, receiptDetails.getWebsite()); + } + + if (!TextUtils.isEmpty(receiptDetails.getNotes())) { + setViewInformation(R.id.receipt_notes_information, R.id.notes_value, + view, receiptDetails.getNotes()); + } + } + } + + private void setViewInformation(@IdRes final int layout, @IdRes final int viewValue, + @NonNull final View view, @NonNull final String value) { + view.findViewById(layout).setVisibility(View.VISIBLE); + TextView textView = view.findViewById(viewValue); + textView.setText(value); + } +} diff --git a/receipt/src/main/java/com/hyperwallet/android/ui/receipt/viewmodel/ListReceiptViewModel.java b/receipt/src/main/java/com/hyperwallet/android/ui/receipt/viewmodel/ListReceiptViewModel.java index 264e78974..0d1533a58 100644 --- a/receipt/src/main/java/com/hyperwallet/android/ui/receipt/viewmodel/ListReceiptViewModel.java +++ b/receipt/src/main/java/com/hyperwallet/android/ui/receipt/viewmodel/ListReceiptViewModel.java @@ -32,6 +32,7 @@ public class ListReceiptViewModel extends ViewModel { private MutableLiveData> mErrorEvent = new MutableLiveData<>(); + private MutableLiveData> mDetailNavigation = new MutableLiveData<>(); private Observer> mErrorEventObserver; private ReceiptRepository mReceiptRepository; @@ -66,6 +67,14 @@ public void retryLoadReceipts() { mReceiptRepository.retryLoadReceipt(); } + public LiveData> getDetailNavigation() { + return mDetailNavigation; + } + + public void setDetailNavigation(@NonNull final Receipt receipt) { + mDetailNavigation.postValue(new Event<>(receipt)); + } + @Override protected void onCleared() { super.onCleared(); diff --git a/receipt/src/main/java/com/hyperwallet/android/ui/receipt/viewmodel/ReceiptDetailViewModel.java b/receipt/src/main/java/com/hyperwallet/android/ui/receipt/viewmodel/ReceiptDetailViewModel.java new file mode 100644 index 000000000..8d628168d --- /dev/null +++ b/receipt/src/main/java/com/hyperwallet/android/ui/receipt/viewmodel/ReceiptDetailViewModel.java @@ -0,0 +1,34 @@ +/* + * 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.ui.receipt.viewmodel; + +import androidx.lifecycle.ViewModel; + +import com.hyperwallet.android.model.receipt.Receipt; + +public class ReceiptDetailViewModel extends ViewModel { + + private Receipt mReceipt; + + public Receipt getReceipt() { + return mReceipt; + } + + public void setReceipt(Receipt receipt) { + mReceipt = receipt; + } +} diff --git a/receipt/src/main/res/layout/activity_receipt_detail.xml b/receipt/src/main/res/layout/activity_receipt_detail.xml new file mode 100644 index 000000000..d2b7c038a --- /dev/null +++ b/receipt/src/main/res/layout/activity_receipt_detail.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + diff --git a/receipt/src/main/res/layout/list_receipt_fragment.xml b/receipt/src/main/res/layout/fragment_list_receipt.xml similarity index 100% rename from receipt/src/main/res/layout/list_receipt_fragment.xml rename to receipt/src/main/res/layout/fragment_list_receipt.xml diff --git a/receipt/src/main/res/layout/fragment_receipt_detail.xml b/receipt/src/main/res/layout/fragment_receipt_detail.xml new file mode 100644 index 000000000..a9abb5ea6 --- /dev/null +++ b/receipt/src/main/res/layout/fragment_receipt_detail.xml @@ -0,0 +1,425 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ 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 index 83a0fe875..cfb204c4c 100644 --- a/receipt/src/main/res/layout/item_receipt.xml +++ b/receipt/src/main/res/layout/item_receipt.xml @@ -1,74 +1,10 @@ - - - - - - - - - - + \ 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 index 29f002169..9256f3ff8 100644 --- a/receipt/src/main/res/layout/item_receipt_with_header.xml +++ b/receipt/src/main/res/layout/item_receipt_with_header.xml @@ -21,6 +21,14 @@ android:textAppearance="@style/TextAppearance.Hyperwallet.Body2"/> - + + + + + \ No newline at end of file diff --git a/receipt/src/main/res/layout/receipt.xml b/receipt/src/main/res/layout/receipt.xml new file mode 100644 index 000000000..88429d094 --- /dev/null +++ b/receipt/src/main/res/layout/receipt.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/receipt/src/main/res/values/strings.xml b/receipt/src/main/res/values/strings.xml index 6c6730a9e..b40bd2485 100644 --- a/receipt/src/main/res/values/strings.xml +++ b/receipt/src/main/res/values/strings.xml @@ -1,6 +1,22 @@ receipt Transactions + Transaction Details + Transaction + Fee Specification + Amount: + Fee: + Transfer Amount: + Receipt + Receipt ID: + Date: + Client Transaction ID: + Notes + Charity Name: + Check Number: + Promo Website: + %s %s + Seems like, you don\'t have any Transactions, yet. diff --git a/receipt/src/test/resources/receipt_list_response.json b/receipt/src/test/resources/receipt_list_response.json index 7e7cc58af..4ec2de4fe 100644 --- a/receipt/src/test/resources/receipt_list_response.json +++ b/receipt/src/test/resources/receipt_list_response.json @@ -11,11 +11,15 @@ "sourceToken": "act-12345", "destinationToken": "usr-fa76a738-f43d-48b9-9a7a-7048d44a5d2d", "amount": "20.00", - "fee": "0.00", + "fee": "2.25", "currency": "USD", "details": { "clientPaymentId": "8OxXefx5", - "payeeName": "A Person" + "payeeName": "A Person", + "website": "https://api.sandbox.hyperwallet.com", + "notes": "Sample payment for the period of June 15th, 2019 to July 23, 2019", + "charityName": "Sample Charity", + "checkNumber": "Sample Check Number" } }, { diff --git a/ui/src/androidTest/AndroidManifest.xml b/ui/src/androidTest/AndroidManifest.xml index ac7de9735..138dc247d 100644 --- a/ui/src/androidTest/AndroidManifest.xml +++ b/ui/src/androidTest/AndroidManifest.xml @@ -2,7 +2,7 @@ - + diff --git a/ui/src/main/java/com/hyperwallet/android/ui/transfermethod/ListTransferMethodActivity.java b/ui/src/main/java/com/hyperwallet/android/ui/transfermethod/ListTransferMethodActivity.java index 64ba48309..6aef7e314 100644 --- a/ui/src/main/java/com/hyperwallet/android/ui/transfermethod/ListTransferMethodActivity.java +++ b/ui/src/main/java/com/hyperwallet/android/ui/transfermethod/ListTransferMethodActivity.java @@ -33,9 +33,9 @@ import com.hyperwallet.android.model.HyperwalletError; import com.hyperwallet.android.model.transfermethod.HyperwalletTransferMethod; import com.hyperwallet.android.ui.R; +import com.hyperwallet.android.ui.common.view.OneClickListener; import com.hyperwallet.android.ui.common.view.error.DefaultErrorDialogFragment; import com.hyperwallet.android.ui.common.view.error.OnNetworkErrorCallback; -import com.hyperwallet.android.ui.view.widget.OneClickListener; import java.util.List; 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 b2f675149..e38385540 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 @@ -49,8 +49,8 @@ import com.hyperwallet.android.ui.HyperwalletLocalBroadcast; import com.hyperwallet.android.ui.R; import com.hyperwallet.android.ui.common.view.HorizontalDividerItemDecorator; +import com.hyperwallet.android.ui.common.view.OneClickListener; import com.hyperwallet.android.ui.repository.RepositoryFactory; -import com.hyperwallet.android.ui.view.widget.OneClickListener; import java.util.ArrayList; import java.util.List; 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 8c757bb2e..5f284a8d4 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 @@ -41,10 +41,10 @@ import com.hyperwallet.android.model.HyperwalletError; import com.hyperwallet.android.ui.R; import com.hyperwallet.android.ui.common.view.HorizontalDividerItemDecorator; +import com.hyperwallet.android.ui.common.view.OneClickListener; 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.widget.OneClickListener; import java.util.ArrayList; import java.util.List; diff --git a/ui/src/main/res/layout/item_transfer_method_type.xml b/ui/src/main/res/layout/item_transfer_method_type.xml index 2e6045e68..973409968 100644 --- a/ui/src/main/res/layout/item_transfer_method_type.xml +++ b/ui/src/main/res/layout/item_transfer_method_type.xml @@ -29,8 +29,7 @@ android:layout_marginStart="@dimen/grid_margin_left" android:layout_marginTop="@dimen/grid_margin_top" android:text="@string/not_available" - android:textAppearance="@style/TextAppearance.MaterialComponents.Subtitle1" - android:textStyle="bold" + android:textAppearance="@style/TextAppearance.Hyperwallet.Subtitle1" app:layout_constraintLeft_toRightOf="@+id/transfer_method_type_icon" app:layout_constraintTop_toTopOf="parent" /> From bf24cb89aeebc7499ec9cf9cb3f21cd875d8731d Mon Sep 17 00:00:00 2001 From: Viktor Shcherbyna Date: Thu, 20 Jun 2019 08:41:00 +0300 Subject: [PATCH 9/9] add @Nullable for better readability --- .../android/ui/transfermethod/TransferMethodUtils.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ui/src/main/java/com/hyperwallet/android/ui/transfermethod/TransferMethodUtils.java b/ui/src/main/java/com/hyperwallet/android/ui/transfermethod/TransferMethodUtils.java index 95d10041a..2566e479f 100644 --- a/ui/src/main/java/com/hyperwallet/android/ui/transfermethod/TransferMethodUtils.java +++ b/ui/src/main/java/com/hyperwallet/android/ui/transfermethod/TransferMethodUtils.java @@ -31,6 +31,7 @@ import android.content.res.Resources; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.StringRes; import com.hyperwallet.android.model.transfermethod.HyperwalletTransferMethod; @@ -142,7 +143,7 @@ public static String getTransferMethodName(@NonNull final Context context, */ public static String getTransferMethodDetail(@NonNull Context context, @NonNull final HyperwalletTransferMethod transferMethod, - @TransferMethodType final String type) { + @Nullable @TransferMethodType final String type) { if (type == null) { return ""; }