diff --git a/commonui/src/main/java/com/hyperwallet/android/ui/common/intent/HyperwalletIntent.java b/commonui/src/main/java/com/hyperwallet/android/ui/common/intent/HyperwalletIntent.java index d87c5d13d..992ddfb3f 100644 --- a/commonui/src/main/java/com/hyperwallet/android/ui/common/intent/HyperwalletIntent.java +++ b/commonui/src/main/java/com/hyperwallet/android/ui/common/intent/HyperwalletIntent.java @@ -65,6 +65,11 @@ private HyperwalletIntent() { */ public static final short SELECT_TRANSFER_SOURCE_REQUEST_CODE = 104; + /** + * Update Transfer method request code + */ + public static final short UPDATE_TRANSFER_METHOD_REQUEST_CODE = 105; + /** * SDK Broadcast payload error */ @@ -81,5 +86,10 @@ private HyperwalletIntent() { */ public static final String EXTRA_TRANSFER_METHOD_ADDED = "EXTRA_TRANSFER_METHOD_ADDED"; + /** + * Transfer method updated, extra activity parcelable transfer method payload + */ + public static final String EXTRA_TRANSFER_METHOD_UPDATED = "EXTRA_TRANSFER_METHOD_UPDATED"; + } diff --git a/commonui/src/main/res/drawable/ic_edit.xml b/commonui/src/main/res/drawable/ic_edit.xml new file mode 100644 index 000000000..bf16bd5cc --- /dev/null +++ b/commonui/src/main/res/drawable/ic_edit.xml @@ -0,0 +1,12 @@ + + + diff --git a/commonui/src/main/res/drawable/ic_trash.xml b/commonui/src/main/res/drawable/ic_trash.xml index 5fdcd6dfe..caa047ad8 100644 --- a/commonui/src/main/res/drawable/ic_trash.xml +++ b/commonui/src/main/res/drawable/ic_trash.xml @@ -1,20 +1,12 @@ - - + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + diff --git a/commonui/src/main/res/values/strings.xml b/commonui/src/main/res/values/strings.xml index d1ece3372..c350f1971 100644 --- a/commonui/src/main/res/values/strings.xml +++ b/commonui/src/main/res/values/strings.xml @@ -33,6 +33,7 @@ Authentication Error Cancel + Edit Remove Are you sure? diff --git a/transfermethodrepository/src/main/java/com/hyperwallet/android/ui/transfermethod/repository/TransferMethodConfigurationRepositoryImpl.java b/transfermethodrepository/src/main/java/com/hyperwallet/android/ui/transfermethod/repository/TransferMethodConfigurationRepositoryImpl.java index c953ff758..a6a2793a9 100644 --- a/transfermethodrepository/src/main/java/com/hyperwallet/android/ui/transfermethod/repository/TransferMethodConfigurationRepositoryImpl.java +++ b/transfermethodrepository/src/main/java/com/hyperwallet/android/ui/transfermethod/repository/TransferMethodConfigurationRepositoryImpl.java @@ -184,6 +184,11 @@ class FieldMapKey { this.mTransferMethodType = mTransferMethodType; } + FieldMapKey() { + this.mCountry = null; + this.mCurrency = null; + this.mTransferMethodType = null; + } private String getCountry() { return mCountry; } diff --git a/transfermethodrepository/src/main/java/com/hyperwallet/android/ui/transfermethod/repository/TransferMethodRepository.java b/transfermethodrepository/src/main/java/com/hyperwallet/android/ui/transfermethod/repository/TransferMethodRepository.java index f11f896c5..d94d7279c 100644 --- a/transfermethodrepository/src/main/java/com/hyperwallet/android/ui/transfermethod/repository/TransferMethodRepository.java +++ b/transfermethodrepository/src/main/java/com/hyperwallet/android/ui/transfermethod/repository/TransferMethodRepository.java @@ -46,6 +46,17 @@ void createTransferMethod(@NonNull TransferMethod transferMethod, */ void loadTransferMethods(@NonNull LoadTransferMethodListCallback callback); + + /** + * Update transfer method specified. + * + * @param transferMethod transfer method to deactivate @see {@link TransferMethod} + * @param callback @see {@link DeactivateTransferMethodCallback} + */ + void updateTransferMethod(@NonNull final TransferMethod transferMethod, + @NonNull LoadTransferMethodCallback callback); + + /** * Load latest transfer methods available, associated with current context * diff --git a/transfermethodrepository/src/main/java/com/hyperwallet/android/ui/transfermethod/repository/TransferMethodRepositoryFactory.java b/transfermethodrepository/src/main/java/com/hyperwallet/android/ui/transfermethod/repository/TransferMethodRepositoryFactory.java index ed9360c6f..daede99ff 100644 --- a/transfermethodrepository/src/main/java/com/hyperwallet/android/ui/transfermethod/repository/TransferMethodRepositoryFactory.java +++ b/transfermethodrepository/src/main/java/com/hyperwallet/android/ui/transfermethod/repository/TransferMethodRepositoryFactory.java @@ -20,10 +20,12 @@ public class TransferMethodRepositoryFactory { private static TransferMethodRepositoryFactory sInstance; private TransferMethodRepository mTransferMethodRepository; private TransferMethodConfigurationRepository mTransferMethodConfigurationRepository; + private TransferMethodUpdateConfigurationRepository mTransferMethodUpdateConfigurationRepository; private TransferMethodRepositoryFactory() { mTransferMethodRepository = new TransferMethodRepositoryImpl(); mTransferMethodConfigurationRepository = new TransferMethodConfigurationRepositoryImpl(); + mTransferMethodUpdateConfigurationRepository = new TransferMethodUpdateConfigurationRepositoryImpl(); } public static synchronized TransferMethodRepositoryFactory getInstance() { @@ -44,4 +46,9 @@ public TransferMethodRepository getTransferMethodRepository() { public TransferMethodConfigurationRepository getTransferMethodConfigurationRepository() { return mTransferMethodConfigurationRepository; } + + public TransferMethodUpdateConfigurationRepository getTransferMethodUpdateConfigurationRepository() { + return mTransferMethodUpdateConfigurationRepository; + } + } diff --git a/transfermethodrepository/src/main/java/com/hyperwallet/android/ui/transfermethod/repository/TransferMethodRepositoryImpl.java b/transfermethodrepository/src/main/java/com/hyperwallet/android/ui/transfermethod/repository/TransferMethodRepositoryImpl.java index 89299912f..5252bfa8a 100644 --- a/transfermethodrepository/src/main/java/com/hyperwallet/android/ui/transfermethod/repository/TransferMethodRepositoryImpl.java +++ b/transfermethodrepository/src/main/java/com/hyperwallet/android/ui/transfermethod/repository/TransferMethodRepositoryImpl.java @@ -123,6 +123,31 @@ public Handler getHandler() { }); } + @Override + public void updateTransferMethod(@NonNull TransferMethod transferMethod, + @NonNull LoadTransferMethodCallback callback) { + switch (transferMethod.getField(TYPE)) { + case BANK_ACCOUNT: + case WIRE_ACCOUNT: + updateBankAccount(transferMethod, callback); + break; + case BANK_CARD: + updateBankCard(transferMethod, callback); + break; + case PAYPAL_ACCOUNT: + updatePayPalAccount(transferMethod, callback); + break; + case VENMO_ACCOUNT: + updateVenmoAccount(transferMethod, callback); + break; + case PAPER_CHECK: + updatePaperCheck(transferMethod, callback); + break; + default: // error on unknown transfer type + callback.onError(getErrorsOnUnsupportedTransferType()); + } + } + /** * @see TransferMethodRepository#loadLatestTransferMethod(LoadTransferMethodCallback) */ @@ -247,7 +272,7 @@ public Handler getHandler() { } private void deactivateVenmoAccount(@NonNull final TransferMethod transferMethod, - @NonNull final DeactivateTransferMethodCallback callback) { + @NonNull final DeactivateTransferMethodCallback callback) { getHyperwallet().deactivateVenmoAccount(transferMethod.getField(TOKEN), null, new HyperwalletListener() { @Override @@ -289,7 +314,7 @@ public Handler getHandler() { } private void createBankAccount(final TransferMethod transferMethod, - final LoadTransferMethodCallback callback) { + final LoadTransferMethodCallback callback) { BankAccount bankAccount = (BankAccount) transferMethod; getHyperwallet().createBankAccount(bankAccount, new HyperwalletListener() { @@ -333,7 +358,7 @@ public Handler getHandler() { } private void createPayPalAccount(@NonNull final TransferMethod transferMethod, - @NonNull final LoadTransferMethodCallback callback) { + @NonNull final LoadTransferMethodCallback callback) { PayPalAccount payPalAccount = (PayPalAccount) transferMethod; getHyperwallet().createPayPalAccount(payPalAccount, new HyperwalletListener() { @@ -355,7 +380,7 @@ public Handler getHandler() { } private void createVenmoAccount(@NonNull final TransferMethod transferMethod, - @NonNull final LoadTransferMethodCallback callback) { + @NonNull final LoadTransferMethodCallback callback) { VenmoAccount venmoAccount = (VenmoAccount) transferMethod; getHyperwallet().createVenmoAccount(venmoAccount, new HyperwalletListener() { @@ -405,4 +430,115 @@ private Errors getErrorsOnUnsupportedTransferType() { EC_UNEXPECTED_EXCEPTION); return new Errors(Collections.singletonList(error)); } + + private void updateBankAccount(final TransferMethod transferMethod, + final LoadTransferMethodCallback callback) { + BankAccount bankAccount = (BankAccount) transferMethod; + + getHyperwallet().updateBankAccount(bankAccount, new HyperwalletListener() { + @Override + public void onSuccess(@Nullable BankAccount result) { + callback.onTransferMethodLoaded(result); + } + + @Override + public void onFailure(HyperwalletException exception) { + callback.onError(exception.getErrors()); + } + + @Override + public Handler getHandler() { + return mHandler; + } + }); + } + + private void updateBankCard(@NonNull final TransferMethod transferMethod, + @NonNull final LoadTransferMethodCallback callback) { + BankCard bankCard = (BankCard) transferMethod; + + getHyperwallet().updateBankCard(bankCard, new HyperwalletListener() { + @Override + public void onSuccess(@Nullable BankCard result) { + callback.onTransferMethodLoaded(result); + } + + @Override + public void onFailure(HyperwalletException exception) { + callback.onError(exception.getErrors()); + } + + @Override + public Handler getHandler() { + return mHandler; + } + }); + } + + private void updatePayPalAccount(@NonNull final TransferMethod transferMethod, + @NonNull final LoadTransferMethodCallback callback) { + PayPalAccount payPalAccount = (PayPalAccount) transferMethod; + + getHyperwallet().updatePayPalAccount(payPalAccount, new HyperwalletListener() { + @Override + public void onSuccess(@Nullable PayPalAccount result) { + callback.onTransferMethodLoaded(result); + } + + @Override + public void onFailure(HyperwalletException exception) { + callback.onError(exception.getErrors()); + } + + @Override + public Handler getHandler() { + return mHandler; + } + }); + } + + private void updateVenmoAccount(@NonNull final TransferMethod transferMethod, + @NonNull final LoadTransferMethodCallback callback) { + VenmoAccount venmoAccount = (VenmoAccount) transferMethod; + + getHyperwallet().updateVenmoAccount(venmoAccount, new HyperwalletListener() { + @Override + public void onSuccess(@Nullable VenmoAccount result) { + callback.onTransferMethodLoaded(result); + } + + @Override + public void onFailure(HyperwalletException exception) { + callback.onError(exception.getErrors()); + } + + @Override + public Handler getHandler() { + return mHandler; + } + }); + } + + private void updatePaperCheck(@NonNull final TransferMethod transferMethod, + @NonNull final LoadTransferMethodCallback callback) { + PaperCheck paperCheck = (PaperCheck) transferMethod; + + getHyperwallet().updatePaperCheck(paperCheck, new HyperwalletListener() { + @Override + public void onSuccess(@Nullable PaperCheck result) { + callback.onTransferMethodLoaded(result); + } + + @Override + public void onFailure(HyperwalletException exception) { + callback.onError(exception.getErrors()); + } + + @Override + public Handler getHandler() { + return mHandler; + } + }); + } + } diff --git a/transfermethodrepository/src/main/java/com/hyperwallet/android/ui/transfermethod/repository/TransferMethodUpdateConfigurationRepository.java b/transfermethodrepository/src/main/java/com/hyperwallet/android/ui/transfermethod/repository/TransferMethodUpdateConfigurationRepository.java new file mode 100644 index 000000000..5110c62f5 --- /dev/null +++ b/transfermethodrepository/src/main/java/com/hyperwallet/android/ui/transfermethod/repository/TransferMethodUpdateConfigurationRepository.java @@ -0,0 +1,49 @@ +/* + * Copyright 2018 Hyperwallet + * + * 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 above copyright notice and this permission notice shall be included in all copies or + * substantial portions of + * the Software. + * + * 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.transfermethod.repository; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.hyperwallet.android.model.Errors; +import com.hyperwallet.android.model.graphql.HyperwalletTransferMethodConfigurationField; + +public interface TransferMethodUpdateConfigurationRepository { + + void getFields(@NonNull final String transferMethodToken, + @NonNull final LoadFieldsCallback loadFieldsCallback); + + + void refreshFields(); + + interface LoadFieldsCallback { + + void onFieldsLoaded(@Nullable final HyperwalletTransferMethodConfigurationField field); + + void onError(@NonNull final Errors errors); + } +} diff --git a/transfermethodrepository/src/main/java/com/hyperwallet/android/ui/transfermethod/repository/TransferMethodUpdateConfigurationRepositoryImpl.java b/transfermethodrepository/src/main/java/com/hyperwallet/android/ui/transfermethod/repository/TransferMethodUpdateConfigurationRepositoryImpl.java new file mode 100644 index 000000000..59ba2a218 --- /dev/null +++ b/transfermethodrepository/src/main/java/com/hyperwallet/android/ui/transfermethod/repository/TransferMethodUpdateConfigurationRepositoryImpl.java @@ -0,0 +1,113 @@ +/* + * Copyright 2018 Hyperwallet + * + * 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 above copyright notice and this permission notice shall be included in all copies or + * substantial portions of + * the Software. + * + * 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.transfermethod.repository; + +import android.os.Handler; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; + +import com.hyperwallet.android.Hyperwallet; +import com.hyperwallet.android.exception.HyperwalletException; +import com.hyperwallet.android.listener.HyperwalletListener; +import com.hyperwallet.android.model.graphql.HyperwalletTransferMethodConfigurationField; +import com.hyperwallet.android.model.graphql.HyperwalletTransferMethodConfigurationKey; +import com.hyperwallet.android.model.graphql.query.TransferMethodUpdateConfigurationFieldQuery; +import com.hyperwallet.android.ui.common.repository.EspressoIdlingResource; + +import java.util.HashMap; +import java.util.Map; + +public class TransferMethodUpdateConfigurationRepositoryImpl implements TransferMethodUpdateConfigurationRepository { + private final Handler mHandler; + private final Map mFieldMap; + + public TransferMethodUpdateConfigurationRepositoryImpl() { + mHandler = new Handler(); + mFieldMap = new HashMap<>(); + } + + @VisibleForTesting() + protected TransferMethodUpdateConfigurationRepositoryImpl(@Nullable Handler handler, + Map fieldMap) { + mHandler = handler; + mFieldMap = fieldMap; + } + + @VisibleForTesting + Hyperwallet getHyperwallet() { + return Hyperwallet.getDefault(); + } + + @VisibleForTesting + void getTransferMethodConfigurationFieldResult( + @NonNull final String transferMethodToken, + @NonNull final LoadFieldsCallback loadFieldsCallback) { + TransferMethodUpdateConfigurationFieldQuery query = new TransferMethodUpdateConfigurationFieldQuery( + transferMethodToken); + EspressoIdlingResource.increment(); + + getHyperwallet().retrieveUpdateTransferMethodConfigurationFields( + query, + new HyperwalletListener() { + @Override + public void onSuccess(HyperwalletTransferMethodConfigurationField result) { + FieldMapKey fieldMapKey = new FieldMapKey(); + mFieldMap.put(fieldMapKey, result); + loadFieldsCallback.onFieldsLoaded(result); + EspressoIdlingResource.decrement(); + } + + @Override + public void onFailure(HyperwalletException exception) { + loadFieldsCallback.onError(exception.getErrors()); + EspressoIdlingResource.decrement(); + } + + @Override + public Handler getHandler() { + return mHandler; + } + }); + + } + + @Override + public synchronized void getFields( + @NonNull final String transferMethodToken, + @NonNull final LoadFieldsCallback loadFieldsCallback) { + + getTransferMethodConfigurationFieldResult(transferMethodToken, loadFieldsCallback); + } + + @Override + public void refreshFields() { + mFieldMap.clear(); + } + +} \ No newline at end of file diff --git a/transfermethodrepository/src/test/java/com/hyperwallet/android/ui/transfermethod/repository/FieldMapKeyTest.java b/transfermethodrepository/src/test/java/com/hyperwallet/android/ui/transfermethod/repository/FieldMapKeyTest.java index f0541bb9a..2b18445a9 100644 --- a/transfermethodrepository/src/test/java/com/hyperwallet/android/ui/transfermethod/repository/FieldMapKeyTest.java +++ b/transfermethodrepository/src/test/java/com/hyperwallet/android/ui/transfermethod/repository/FieldMapKeyTest.java @@ -13,6 +13,12 @@ public void testEquals_withSameReference() { assertEquals(thisKey, thisKey); } + @Test + public void testEquals_withSameReference_withAnotherConstructor() { + FieldMapKey thisKey = new FieldMapKey(); + assertEquals(thisKey, thisKey); + } + @Test public void testEquals_withDifferentObjectType() { FieldMapKey thisKey = new FieldMapKey("US", "USD", "BANK_ACCOUNT"); @@ -33,5 +39,4 @@ public void testEquals_withDifferentReferencesDifferentValues() { FieldMapKey thatKey = new FieldMapKey("CA", "CAD", "BANK_ACCOUNT"); assertNotEquals(thisKey, thatKey); } - } diff --git a/transfermethodrepository/src/test/java/com/hyperwallet/android/ui/transfermethod/repository/TransferMethodRepositoryFactoryTest.java b/transfermethodrepository/src/test/java/com/hyperwallet/android/ui/transfermethod/repository/TransferMethodRepositoryFactoryTest.java index 7e89c5a03..232f724f3 100644 --- a/transfermethodrepository/src/test/java/com/hyperwallet/android/ui/transfermethod/repository/TransferMethodRepositoryFactoryTest.java +++ b/transfermethodrepository/src/test/java/com/hyperwallet/android/ui/transfermethod/repository/TransferMethodRepositoryFactoryTest.java @@ -14,6 +14,7 @@ public void testGetInstance_verifyRepositoriesInitialized() { assertThat(repositoryFactory, is(notNullValue())); assertThat(repositoryFactory.getTransferMethodRepository(), is(notNullValue())); assertThat(repositoryFactory.getTransferMethodConfigurationRepository(), is(notNullValue())); + assertThat(repositoryFactory.getTransferMethodUpdateConfigurationRepository(), is(notNullValue())); } @Test @@ -23,10 +24,13 @@ public void testClearInstance_verifyRepositoryCleared() { TransferMethodRepository transferMethodRepository = repositoryFactory.getTransferMethodRepository(); TransferMethodConfigurationRepository configurationRepository = repositoryFactory.getTransferMethodConfigurationRepository(); + TransferMethodUpdateConfigurationRepository transferMethodUpdateConfigurationRepository = + repositoryFactory.getTransferMethodUpdateConfigurationRepository(); TransferMethodRepositoryFactory currentRepositoryFactory = TransferMethodRepositoryFactory.getInstance(); assertThat(repositoryFactory, is(currentRepositoryFactory)); assertThat(transferMethodRepository, is(currentRepositoryFactory.getTransferMethodRepository())); assertThat(configurationRepository, is(currentRepositoryFactory.getTransferMethodConfigurationRepository())); + assertThat(transferMethodUpdateConfigurationRepository,is(currentRepositoryFactory.getTransferMethodUpdateConfigurationRepository())); TransferMethodRepositoryFactory.clearInstance(); @@ -35,5 +39,6 @@ public void testClearInstance_verifyRepositoryCleared() { assertThat(transferMethodRepository, is(not(anotherRepositoryFactory.getTransferMethodRepository()))); assertThat(configurationRepository, is(not(anotherRepositoryFactory.getTransferMethodConfigurationRepository()))); + assertThat(transferMethodUpdateConfigurationRepository,is(not(anotherRepositoryFactory.getTransferMethodUpdateConfigurationRepository()))); } } \ No newline at end of file diff --git a/transfermethodrepository/src/test/java/com/hyperwallet/android/ui/transfermethod/repository/TransferMethodRepositoryImplTest.java b/transfermethodrepository/src/test/java/com/hyperwallet/android/ui/transfermethod/repository/TransferMethodRepositoryImplTest.java index 272678bee..0018dff20 100644 --- a/transfermethodrepository/src/test/java/com/hyperwallet/android/ui/transfermethod/repository/TransferMethodRepositoryImplTest.java +++ b/transfermethodrepository/src/test/java/com/hyperwallet/android/ui/transfermethod/repository/TransferMethodRepositoryImplTest.java @@ -34,6 +34,7 @@ import com.hyperwallet.android.model.paging.PageList; import com.hyperwallet.android.model.transfermethod.BankAccount; import com.hyperwallet.android.model.transfermethod.BankCard; +import com.hyperwallet.android.model.transfermethod.PaperCheck; import com.hyperwallet.android.model.transfermethod.TransferMethod; import com.hyperwallet.android.model.transfermethod.TransferMethodQueryParam; import com.hyperwallet.android.model.transfermethod.PayPalAccount; @@ -87,7 +88,7 @@ public class TransferMethodRepositoryImplTest { @Captor private ArgumentCaptor mVenmoAccountArgumentCaptor; @Captor - private ArgumentCaptor mPaperCheckArgumentCaptor; //todo paper check + private ArgumentCaptor mPaperCheckArgumentCaptor; @Captor private ArgumentCaptor mStatusTransitionArgumentCaptor; @Captor @@ -143,6 +144,41 @@ public Object answer(InvocationOnMock invocation) { assertThat(transferMethod.getField(BANK_ACCOUNT_ID), is("3423423432")); } + @Test + public void testUpdateTransferMethod_bankAccountWithSuccess() { + BankAccount bankAccount = new BankAccount + .Builder("CA", "CAD", "3423423432") + .build(); + + doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) { + HyperwalletListener listener = (HyperwalletListener) invocation.getArguments()[1]; + BankAccount returnedBank = new BankAccount + .Builder("CA", "CAD", "3423423432") + .bankName("Mock Bank Response") + .build(); + listener.onSuccess(returnedBank); + return listener; + } + }).when(mHyperwallet).updateBankAccount(any(BankAccount.class), + ArgumentMatchers.>any()); + + // test + mTransferMethodRepository.updateTransferMethod(bankAccount, mLoadTransferMethodCallback); + + verify(mLoadTransferMethodCallback).onTransferMethodLoaded(mBankAccountArgumentCaptor.capture()); + verify(mLoadTransferMethodCallback, never()).onError(any(Errors.class)); + + BankAccount transferMethod = mBankAccountArgumentCaptor.getValue(); + assertThat(transferMethod, is(notNullValue())); + assertThat(transferMethod.getField(TYPE), is(TransferMethod.TransferMethodTypes.BANK_ACCOUNT)); + assertThat(transferMethod.getField(BANK_NAME), is("Mock Bank Response")); + assertThat(transferMethod.getField(TRANSFER_METHOD_COUNTRY), is("CA")); + assertThat(transferMethod.getField(TRANSFER_METHOD_CURRENCY), is("CAD")); + assertThat(transferMethod.getField(BANK_ACCOUNT_ID), is("3423423432")); + } + @Test public void testCreateTransferMethod_bankAccountWithError() { BankAccount bankAccount = new BankAccount @@ -173,6 +209,35 @@ public Object answer(InvocationOnMock invocation) { assertThat(mErrorsArgumentCaptor.getValue().getErrors(), hasItem(error)); } + @Test + public void testUpdateTransferMethod_bankAccountWithError() { + BankAccount bankAccount = new BankAccount + .Builder(COUNTRY_US, CURRENCY_USD, "23432432") + .build(); + + final Error error = new Error(TEST_MESSAGE, TEST_CODE); + + doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) { + HyperwalletListener listener = (HyperwalletListener) invocation.getArguments()[1]; + List errorList = new ArrayList<>(); + errorList.add(error); + Errors errors = new Errors(errorList); + HyperwalletException exception = new HyperwalletException(errors); + listener.onFailure(exception); + return listener; + } + }).when(mHyperwallet).updateBankAccount(any(BankAccount.class), + ArgumentMatchers.>any()); + + // test + mTransferMethodRepository.updateTransferMethod(bankAccount, mLoadTransferMethodCallback); + + verify(mLoadTransferMethodCallback).onError(mErrorsArgumentCaptor.capture()); + verify(mLoadTransferMethodCallback, never()).onTransferMethodLoaded(any(TransferMethod.class)); + assertThat(mErrorsArgumentCaptor.getValue().getErrors(), hasItem(error)); + } @Test public void testCreateTransferMethod_withUnsupportedTransferMethodType() { BankAccount bankAccount = new BankAccount @@ -485,6 +550,40 @@ public Object answer(InvocationOnMock invocation) { assertThat(transferMethod.getField(TransferMethod.TransferMethodFields.CARD_TYPE), is("cardType")); } + @Test + public void testUpdateTransferMethod_bankCardWithSuccess() { + BankCard bankCard = new BankCard + .Builder("CA", "CAD", "1232345456784", "2019-05", "234") + .build(); + + doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) { + HyperwalletListener listener = (HyperwalletListener) invocation.getArguments()[1]; + BankCard returnedBankCard = new BankCard + .Builder("CA", "CAD", "1232345456784", "2019-05", "234") + .cardBrand("Brand") + .cardType("cardType") + .build(); + listener.onSuccess(returnedBankCard); + return listener; + } + }).when(mHyperwallet).updateBankCard(any(BankCard.class), + ArgumentMatchers.>any()); + + // test + mTransferMethodRepository.updateTransferMethod(bankCard, mLoadTransferMethodCallback); + + verify(mLoadTransferMethodCallback).onTransferMethodLoaded(mBankCardArgumentCaptor.capture()); + verify(mLoadTransferMethodCallback, never()).onError(any(Errors.class)); + + BankCard transferMethod = mBankCardArgumentCaptor.getValue(); + assertThat(transferMethod, is(notNullValue())); + assertThat(transferMethod.getField(TYPE), is(TransferMethod.TransferMethodTypes.BANK_CARD)); + assertThat(transferMethod.getField(TransferMethod.TransferMethodFields.CARD_BRAND), is("Brand")); + assertThat(transferMethod.getField(TransferMethod.TransferMethodFields.CARD_TYPE), is("cardType")); + } + @Test public void testCreateTransferMethod_bankCardWithError() { BankCard bankCard = new BankCard @@ -514,7 +613,35 @@ public Object answer(InvocationOnMock invocation) { verify(mLoadTransferMethodCallback, never()).onTransferMethodLoaded(any(TransferMethod.class)); assertThat(mErrorsArgumentCaptor.getValue().getErrors(), hasItem(error)); } + @Test + public void testUpdateTransferMethod_bankCardWithError() { + BankCard bankCard = new BankCard + .Builder("CA", "CAD", "1232345456784", "2019-05", "234") + .build(); + + final Error error = new Error("bank card test message", "BANK_CARD_TEST_CODE"); + + doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) { + HyperwalletListener listener = (HyperwalletListener) invocation.getArguments()[1]; + List errorList = new ArrayList<>(); + errorList.add(error); + Errors errors = new Errors(errorList); + HyperwalletException exception = new HyperwalletException(errors); + listener.onFailure(exception); + return listener; + } + }).when(mHyperwallet).updateBankCard(any(BankCard.class), + ArgumentMatchers.>any()); + + // test + mTransferMethodRepository.updateTransferMethod(bankCard, mLoadTransferMethodCallback); + verify(mLoadTransferMethodCallback).onError(mErrorsArgumentCaptor.capture()); + verify(mLoadTransferMethodCallback, never()).onTransferMethodLoaded(any(TransferMethod.class)); + assertThat(mErrorsArgumentCaptor.getValue().getErrors(), hasItem(error)); + } @Test public void testLoadTransferMethod_returnsBankAccount() { @@ -722,6 +849,42 @@ public Object answer(InvocationOnMock invocation) { assertThat(payPalAccount.getField(TOKEN), is("trm-token-1342242314")); } + // @Test + public void testUpdateTransferMethod_payPalAccountWithSuccess() { + // prepare + final PayPalAccount returnedPayPalAccount = new PayPalAccount.Builder() + .email("money@mail.com") + .token("trm-token-1342242314") + .build(); + + doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) { + HyperwalletListener listener = (HyperwalletListener) invocation.getArguments()[1]; + returnedPayPalAccount.setField(STATUS, ACTIVATED); + listener.onSuccess(returnedPayPalAccount); + return listener; + } + }).when(mHyperwallet).updatePayPalAccount(any(PayPalAccount.class), + ArgumentMatchers.>any()); + + PayPalAccount parameter = new PayPalAccount.Builder().build(); + + // test + mTransferMethodRepository.updateTransferMethod(parameter, mLoadTransferMethodCallback); + + // verify + verify(mLoadTransferMethodCallback).onTransferMethodLoaded(mPayPalAccountArgumentCaptor.capture()); + verify(mLoadTransferMethodCallback, never()).onError(any(Errors.class)); + + // assert + PayPalAccount payPalAccount = mPayPalAccountArgumentCaptor.getValue(); + assertThat(payPalAccount, is(notNullValue())); + assertThat(payPalAccount.getEmail(), is("money@mail.com")); + assertThat(payPalAccount.getField(STATUS), is(ACTIVATED)); + assertThat(payPalAccount.getField(TOKEN), is("trm-token-1342242314")); + } + @Test public void testCreateTransferMethod_payPalAccountWithError() { // prepare @@ -753,6 +916,37 @@ public Object answer(InvocationOnMock invocation) { assertThat(mErrorsArgumentCaptor.getValue().getErrors(), hasItem(returnedError)); } + // @Test + public void testUpdateTransferMethod_payPalAccountWithError() { + // prepare + final Error returnedError = new Error("PayPal test message", "PAYPAL_TEST_CODE"); + + doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) { + HyperwalletListener listener = (HyperwalletListener) invocation.getArguments()[1]; + + List errorList = new ArrayList<>(); + errorList.add(returnedError); + + listener.onFailure(new HyperwalletException(new Errors(errorList))); + return listener; + } + }).when(mHyperwallet).updatePayPalAccount(any(PayPalAccount.class), + ArgumentMatchers.>any()); + PayPalAccount parameter = new PayPalAccount.Builder().build(); + + // test + mTransferMethodRepository.updateTransferMethod(parameter, mLoadTransferMethodCallback); + + // verify + verify(mLoadTransferMethodCallback, never()).onTransferMethodLoaded(any(TransferMethod.class)); + verify(mLoadTransferMethodCallback).onError(mErrorsArgumentCaptor.capture()); + + // assert + assertThat(mErrorsArgumentCaptor.getValue().getErrors(), hasItem(returnedError)); + } + @Test public void createTransferMethod_venmoAccountWithSuccess() { final VenmoAccount returnedVenmoAccount = buildVenmoAccount(); @@ -782,11 +976,7 @@ public Object answer(InvocationOnMock invocation) { assertThat(venmoAccount.getField(TOKEN), is(TEST_TOKEN)); } - @Test - public void createTransferMethod_paperCheckWithSuccess() { - - //Todo paper check - + public void updateTransferMethod_venmoAccountWithSuccess() { final VenmoAccount returnedVenmoAccount = buildVenmoAccount(); doAnswer(new Answer() { @Override @@ -796,11 +986,11 @@ public Object answer(InvocationOnMock invocation) { listener.onSuccess(returnedVenmoAccount); return listener; } - }).when(mHyperwallet).createVenmoAccount(any(VenmoAccount.class), any(HyperwalletListener.class)); + }).when(mHyperwallet).updateVenmoAccount(any(VenmoAccount.class), any(HyperwalletListener.class)); VenmoAccount parameter = new VenmoAccount.Builder().build(); - mTransferMethodRepository.createTransferMethod(parameter, mLoadTransferMethodCallback); + mTransferMethodRepository.updateTransferMethod(parameter, mLoadTransferMethodCallback); verify(mLoadTransferMethodCallback).onTransferMethodLoaded(mVenmoAccountArgumentCaptor.capture()); verify(mLoadTransferMethodCallback, never()).onError(any(Errors.class)); @@ -814,6 +1004,61 @@ public Object answer(InvocationOnMock invocation) { assertThat(venmoAccount.getField(TOKEN), is(TEST_TOKEN)); } + @Test + public void createTransferMethod_paperCheckWithSuccess() { + final PaperCheck returnedPaperCheck = buildPaperCheck(); + doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) { + HyperwalletListener listener = (HyperwalletListener) invocation.getArguments()[1]; + returnedPaperCheck.setField(STATUS, ACTIVATED); + listener.onSuccess(returnedPaperCheck); + return listener; + } + }).when(mHyperwallet).createPaperCheck(any(PaperCheck.class), any(HyperwalletListener.class)); + + PaperCheck parameter = new PaperCheck.Builder().build(); + + mTransferMethodRepository.createTransferMethod(parameter, mLoadTransferMethodCallback); + + verify(mLoadTransferMethodCallback).onTransferMethodLoaded(mPaperCheckArgumentCaptor.capture()); + verify(mLoadTransferMethodCallback, never()).onError(any(Errors.class)); + + PaperCheck paperCheck = mPaperCheckArgumentCaptor.getValue(); + assertThat(paperCheck, is(notNullValue())); + assertThat(paperCheck.getCountry(), is(COUNTRY_US)); + assertThat(paperCheck.getCurrency(), is(CURRENCY_USD)); + assertThat(paperCheck.getField(STATUS), is(ACTIVATED)); + assertThat(paperCheck.getField(TOKEN), is(TEST_TOKEN)); + } + + @Test + public void updateTransferMethod_paperCheckWithSuccess() { + final PaperCheck returnedPaperCheck = buildPaperCheck(); + doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) { + HyperwalletListener listener = (HyperwalletListener) invocation.getArguments()[1]; + returnedPaperCheck.setField(STATUS, ACTIVATED); + listener.onSuccess(returnedPaperCheck); + return listener; + } + }).when(mHyperwallet).updatePaperCheck(any(PaperCheck.class), any(HyperwalletListener.class)); + + PaperCheck parameter = new PaperCheck.Builder().build(); + + mTransferMethodRepository.updateTransferMethod(parameter, mLoadTransferMethodCallback); + + verify(mLoadTransferMethodCallback).onTransferMethodLoaded(mPaperCheckArgumentCaptor.capture()); + verify(mLoadTransferMethodCallback, never()).onError(any(Errors.class)); + + PaperCheck paperCheck = mPaperCheckArgumentCaptor.getValue(); + assertThat(paperCheck, is(notNullValue())); + assertThat(paperCheck.getCountry(), is(COUNTRY_US)); + assertThat(paperCheck.getCurrency(), is(CURRENCY_USD)); + assertThat(paperCheck.getField(STATUS), is(ACTIVATED)); + assertThat(paperCheck.getField(TOKEN), is(TEST_TOKEN)); + } @Test public void createTransferMethod_venmoAccountWithError() { final Error returnedError = new Error(TEST_MESSAGE, TEST_CODE); @@ -839,10 +1084,31 @@ public Object answer(InvocationOnMock invocation) { } @Test - public void createTransferMethod_paperCheckWithError() { + public void updateTransferMethod_venmoAccountWithError() { + final Error returnedError = new Error(TEST_MESSAGE, TEST_CODE); + doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) { + HyperwalletListener listener = (HyperwalletListener) invocation.getArguments()[1]; + + List errorList = new ArrayList<>(); + errorList.add(returnedError); + + listener.onFailure(new HyperwalletException(new Errors(errorList))); + return listener; + } + }).when(mHyperwallet).updateVenmoAccount(any(VenmoAccount.class), any(HyperwalletListener.class)); + VenmoAccount parameter = new VenmoAccount.Builder().build(); - //Todo paper check + mTransferMethodRepository.updateTransferMethod(parameter, mLoadTransferMethodCallback); + + verify(mLoadTransferMethodCallback, never()).onTransferMethodLoaded(any(TransferMethod.class)); + verify(mLoadTransferMethodCallback).onError(mErrorsArgumentCaptor.capture()); + assertThat(mErrorsArgumentCaptor.getValue().getErrors(), hasItem(returnedError)); + } + @Test + public void createTransferMethod_paperCheckWithError() { final Error returnedError = new Error(TEST_MESSAGE, TEST_CODE); doAnswer(new Answer() { @Override @@ -855,8 +1121,8 @@ public Object answer(InvocationOnMock invocation) { listener.onFailure(new HyperwalletException(new Errors(errorList))); return listener; } - }).when(mHyperwallet).createVenmoAccount(any(VenmoAccount.class), any(HyperwalletListener.class)); - VenmoAccount parameter = new VenmoAccount.Builder().build(); + }).when(mHyperwallet).createPaperCheck(any(PaperCheck.class), any(HyperwalletListener.class)); + PaperCheck parameter = new PaperCheck.Builder().build(); mTransferMethodRepository.createTransferMethod(parameter, mLoadTransferMethodCallback); @@ -864,6 +1130,31 @@ public Object answer(InvocationOnMock invocation) { verify(mLoadTransferMethodCallback).onError(mErrorsArgumentCaptor.capture()); assertThat(mErrorsArgumentCaptor.getValue().getErrors(), hasItem(returnedError)); } + + @Test + public void updateTransferMethod_paperCheckWithError() { + final Error returnedError = new Error(TEST_MESSAGE, TEST_CODE); + doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) { + HyperwalletListener listener = (HyperwalletListener) invocation.getArguments()[1]; + + List errorList = new ArrayList<>(); + errorList.add(returnedError); + + listener.onFailure(new HyperwalletException(new Errors(errorList))); + return listener; + } + }).when(mHyperwallet).updatePaperCheck(any(PaperCheck.class), any(HyperwalletListener.class)); + PaperCheck parameter = new PaperCheck.Builder().build(); + + mTransferMethodRepository.updateTransferMethod(parameter, mLoadTransferMethodCallback); + + verify(mLoadTransferMethodCallback, never()).onTransferMethodLoaded(any(TransferMethod.class)); + verify(mLoadTransferMethodCallback).onError(mErrorsArgumentCaptor.capture()); + assertThat(mErrorsArgumentCaptor.getValue().getErrors(), hasItem(returnedError)); + } + @Test public void testCreateTransferMethod_wireAccountWithSuccess() { BankAccount bankAccount = new BankAccount @@ -968,4 +1259,12 @@ private VenmoAccount buildVenmoAccount() { .token(TEST_TOKEN) .build(); } + + private PaperCheck buildPaperCheck() { + return new PaperCheck.Builder() + .transferMethodCountry(COUNTRY_US) + .transferMethodCurrency(CURRENCY_USD) + .token(TEST_TOKEN) + .build(); + } } diff --git a/transfermethodrepository/src/test/java/com/hyperwallet/android/ui/transfermethod/repository/TransferMethodUpdateConfigurationRepositoryImplTest.java b/transfermethodrepository/src/test/java/com/hyperwallet/android/ui/transfermethod/repository/TransferMethodUpdateConfigurationRepositoryImplTest.java new file mode 100644 index 000000000..96b65ed05 --- /dev/null +++ b/transfermethodrepository/src/test/java/com/hyperwallet/android/ui/transfermethod/repository/TransferMethodUpdateConfigurationRepositoryImplTest.java @@ -0,0 +1,177 @@ +package com.hyperwallet.android.ui.transfermethod.repository; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.hyperwallet.android.Hyperwallet; +import com.hyperwallet.android.exception.HyperwalletException; +import com.hyperwallet.android.listener.HyperwalletListener; +import com.hyperwallet.android.model.Error; +import com.hyperwallet.android.model.Errors; +import com.hyperwallet.android.model.TypeReference; +import com.hyperwallet.android.model.graphql.HyperwalletTransferMethodConfigurationField; +import com.hyperwallet.android.model.graphql.field.FieldGroup; +import com.hyperwallet.android.model.graphql.field.TransferMethodConfiguration; +import com.hyperwallet.android.model.graphql.field.TransferMethodConfigurationFieldResult; +import com.hyperwallet.android.model.graphql.field.TransferMethodUpdateConfigurationFieldResult; +import com.hyperwallet.android.model.graphql.query.TransferMethodUpdateConfigurationFieldQuery; +import com.hyperwallet.android.ui.testutils.rule.HyperwalletExternalResourceManager; +import com.hyperwallet.android.util.JsonUtils; + +import org.hamcrest.collection.IsEmptyCollection; +import org.json.JSONException; +import org.json.JSONObject; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatchers; +import org.mockito.Captor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.mockito.stubbing.Answer; +import org.robolectric.RobolectricTestRunner; + +import java.lang.reflect.InvocationTargetException; +import java.util.HashMap; +import java.util.List; + +@RunWith(RobolectricTestRunner.class) +public class TransferMethodUpdateConfigurationRepositoryImplTest { + + private static final String TRANSFER_TOKEN = "trm-fake"; + @Rule + public HyperwalletExternalResourceManager externalResourceManager = new HyperwalletExternalResourceManager(); + @Rule + public MockitoRule mockito = MockitoJUnit.rule(); + @Mock + private TransferMethodUpdateConfigurationRepository.LoadFieldsCallback loadFieldsCallback; + @Captor + private ArgumentCaptor fieldResultArgumentCaptor; + @Captor + private ArgumentCaptor mErrorsArgumentCaptor; + @Mock + private Hyperwallet mHyperwallet; + @Mock + private HashMap mFieldsMap; + @Spy + @InjectMocks + private TransferMethodUpdateConfigurationRepositoryImpl mTransferMethodUpdateConfigurationRepositoryImplMock; + + @Before + public void setUp() { + doReturn(mHyperwallet).when(mTransferMethodUpdateConfigurationRepositoryImplMock).getHyperwallet(); + } + + @Test + public void testGetFields_callsListenerWithFieldResultOnSuccess() + throws NoSuchMethodException, InstantiationException, IllegalAccessException, JSONException, + InvocationTargetException { + String responseBody = externalResourceManager.getResourceContent( + "successful_tmc_update_field_bank_account_response.json"); + final TransferMethodUpdateConfigurationFieldResult result = JsonUtils.fromJsonString(responseBody, + new TypeReference() { + }); + doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) { + HyperwalletListener listener = (HyperwalletListener) invocation.getArguments()[1]; + listener.onSuccess(result); + return listener; + } + }).when(mHyperwallet).retrieveUpdateTransferMethodConfigurationFields( + ArgumentMatchers.any(), + ArgumentMatchers.>any()); + + mTransferMethodUpdateConfigurationRepositoryImplMock.getFields(TRANSFER_TOKEN,loadFieldsCallback); + + verify(loadFieldsCallback).onFieldsLoaded(fieldResultArgumentCaptor.capture()); + verify(loadFieldsCallback, never()).onError(any(Errors.class)); + + HyperwalletTransferMethodConfigurationField transferMethodConfigurationFieldResult = + fieldResultArgumentCaptor.getValue(); + assertNotNull(transferMethodConfigurationFieldResult); + TransferMethodConfiguration transferMethodConfiguration = + transferMethodConfigurationFieldResult.getFields(); + assertThat(transferMethodConfiguration, is(notNullValue())); + assertThat(transferMethodConfiguration.getFieldGroups(), + is(not(IsEmptyCollection.empty()))); + } + + @Test + public void testGetFields_callsListenerWithErrorResultOnFailure() + throws NoSuchMethodException, InstantiationException, IllegalAccessException, JSONException, + InvocationTargetException { + String responseBody = externalResourceManager.getResourceContent("error_tmc_keys_response.json"); + final Errors errors = JsonUtils.fromJsonString(responseBody, + new TypeReference() { + }); + final HyperwalletException exception = new HyperwalletException(errors); + doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) { + HyperwalletListener listener = (HyperwalletListener) invocation.getArguments()[1]; + + listener.onFailure(exception); + return listener; + } + }).when(mHyperwallet).retrieveUpdateTransferMethodConfigurationFields( + ArgumentMatchers.any(), + ArgumentMatchers.>any()); + + + mTransferMethodUpdateConfigurationRepositoryImplMock.getFields(TRANSFER_TOKEN, loadFieldsCallback); + + verify(loadFieldsCallback, never()).onFieldsLoaded(any(HyperwalletTransferMethodConfigurationField.class)); + verify(loadFieldsCallback).onError(mErrorsArgumentCaptor.capture()); + + Errors errorsList = mErrorsArgumentCaptor.getValue(); + assertNotNull(errorsList); + List inErrors = errorsList.getErrors(); + assertNotNull(errors); + Error inError = inErrors.get(0); + assertThat(errors.getErrors().get(0), is(inError)); + } + + @Test + public void testGetFields_callsListenerWithFieldResultFromCacheWhenNotNull() throws Exception { + String responseBody = externalResourceManager.getResourceContent( + "successful_tmc_update_field_bank_account_response.json"); + final TransferMethodUpdateConfigurationFieldResult result = JsonUtils.fromJsonString(responseBody, + new TypeReference() { + }); + + mTransferMethodUpdateConfigurationRepositoryImplMock.getFields(TRANSFER_TOKEN, loadFieldsCallback); + verify(loadFieldsCallback, never()).onError(any(Errors.class)); + } + + @Test + public void testRefreshFields_clearsFieldMapWhenNotEmpty() throws Exception { + String responseBody = externalResourceManager.getResourceContent( + "successful_tmc_update_field_bank_account_response.json"); + JSONObject jsonObject = new JSONObject(responseBody); + FieldMapKey fieldMapKey = new FieldMapKey(); + HashMap fieldMap = new HashMap<>(); + fieldMap.put(fieldMapKey, new TransferMethodUpdateConfigurationFieldResult(jsonObject)); + TransferMethodConfigurationRepositoryImpl repositoryWithCache = new TransferMethodConfigurationRepositoryImpl( + null, null, fieldMap); + repositoryWithCache.refreshFields(); + assertTrue(fieldMap.isEmpty()); + } +} diff --git a/transfermethodrepository/src/test/resources/successful_tmc_update_field_bank_account_response.json b/transfermethodrepository/src/test/resources/successful_tmc_update_field_bank_account_response.json new file mode 100644 index 000000000..7c5003d5e --- /dev/null +++ b/transfermethodrepository/src/test/resources/successful_tmc_update_field_bank_account_response.json @@ -0,0 +1,1192 @@ +{ + "data": { + "transferMethodUpdateUIConfigurations": { + "nodes": [ + { + "country": "US", + "currency": "USD", + "transferMethodType": "BANK_ACCOUNT", + "profile": "INDIVIDUAL", + "fieldGroups": { + "nodes": [ + { + "group": "ACCOUNT_INFORMATION", + "isEditable": true, + "instruction": { + "textBottom": "On a paper check, the routing number is printed as the first 9 digits on the bottom of the check, followed by 4-17 digits for the account number." + }, + "fields": [ + { + "category": "ACCOUNT", + "value": "021000021", + "dataType": "NUMBER", + "isRequired": true, + "isEditable": true, + "label": "Routing Number", + "maxLength": 9, + "minLength": 9, + "name": "branchId", + "placeholder": "", + "regularExpression": "^[0-9]{9}$", + "validationMessage": { + "length": "The exact length of this field is 9.", + "pattern": "is invalid length or format.", + "empty": "You must provide a value for this field" + }, + "fieldValueMasked": false + }, + { + "category": "ACCOUNT", + "value": "****", + "dataType": "NUMBER", + "isRequired": true, + "isEditable": true, + "label": "Account Number", + "maxLength": 17, + "minLength": 3, + "name": "bankAccountId", + "placeholder": "", + "regularExpression": "^(?![0-]+$)[0-9-]{3,17}$", + "validationMessage": { + "length": "The minimum length of this field is 3 and maximum length is 17.", + "pattern": "is invalid length or format.", + "empty": "You must provide a value for this field" + }, + "fieldValueMasked": true + }, + { + "category": "ACCOUNT", + "value": "SAVINGS", + "dataType": "SELECTION", + "isRequired": true, + "isEditable": true, + "label": "Account Type", + "name": "bankAccountPurpose", + "placeholder": "", + "fieldSelectionOptions": [ + { + "label": "Checking", + "value": "CHECKING" + }, + { + "label": "Savings", + "value": "SAVINGS" + } + ], + "validationMessage": { + "length": "", + "pattern": "is invalid length or format.", + "empty": "You must provide a value for this field" + }, + "fieldValueMasked": false + } + ] + }, + { + "group": "ACCOUNT_HOLDER", + "isEditable": true, + "instruction": { + }, + "fields": [ + { + "category": "PROFILE", + "value": "iOS Mobile", + "dataType": "TEXT", + "isRequired": true, + "isEditable": true, + "label": "First Name", + "name": "firstName", + "placeholder": "", + "regularExpression": "^[\\sa-zA-Z0-9\\-.,'\\u00C0-\\u00FF\\u0100-\\u017F\\u0180-\\u024F]{1,50}$", + "validationMessage": { + "length": "", + "pattern": "is invalid length or format.", + "empty": "You must provide a value for this field" + }, + "fieldValueMasked": false + }, + { + "category": "PROFILE", + "value": "mobile-qa", + "dataType": "TEXT", + "isRequired": false, + "isEditable": true, + "label": "Middle Name", + "name": "middleName", + "placeholder": "", + "regularExpression": "^[\\sa-zA-Z0-9\\-.,'\\u00C0-\\u00FF\\u0100-\\u017F\\u0180-\\u024F]{1,50}$", + "validationMessage": { + "length": "", + "pattern": "is invalid length or format.", + "empty": "You must provide a value for this field" + }, + "fieldValueMasked": false + }, + { + "category": "PROFILE", + "value": "UITest", + "dataType": "TEXT", + "isRequired": true, + "isEditable": true, + "label": "Last Name", + "name": "lastName", + "placeholder": "", + "regularExpression": "^[\\sa-zA-Z0-9\\-.,'\\u00C0-\\u00FF\\u0100-\\u017F\\u0180-\\u024F]{1,50}$", + "validationMessage": { + "length": "", + "pattern": "is invalid length or format.", + "empty": "You must provide a value for this field" + }, + "fieldValueMasked": false + } + ] + }, + { + "group": "ADDRESS", + "isEditable": true, + "instruction": { + }, + "fields": [ + { + "category": "ADDRESS", + "value": "CA", + "dataType": "SELECTION", + "isRequired": true, + "isEditable": true, + "label": "Country", + "name": "country", + "placeholder": "", + "fieldSelectionOptions": [ + { + "label": "Afghanistan", + "value": "AF" + }, + { + "label": "Åland Islands", + "value": "AX" + }, + { + "label": "Albania", + "value": "AL" + }, + { + "label": "Algeria", + "value": "DZ" + }, + { + "label": "American Samoa", + "value": "AS" + }, + { + "label": "Andorra", + "value": "AD" + }, + { + "label": "Angola", + "value": "AO" + }, + { + "label": "Anguilla", + "value": "AI" + }, + { + "label": "Antarctica", + "value": "AQ" + }, + { + "label": "Antigua & Barbuda", + "value": "AG" + }, + { + "label": "Argentina", + "value": "AR" + }, + { + "label": "Armenia", + "value": "AM" + }, + { + "label": "Aruba", + "value": "AW" + }, + { + "label": "Australia", + "value": "AU" + }, + { + "label": "Austria", + "value": "AT" + }, + { + "label": "Azerbaijan", + "value": "AZ" + }, + { + "label": "Bahamas", + "value": "BS" + }, + { + "label": "Bahrain", + "value": "BH" + }, + { + "label": "Bangladesh", + "value": "BD" + }, + { + "label": "Barbados", + "value": "BB" + }, + { + "label": "Belgium", + "value": "BE" + }, + { + "label": "Belize", + "value": "BZ" + }, + { + "label": "Benin", + "value": "BJ" + }, + { + "label": "Bermuda", + "value": "BM" + }, + { + "label": "Bhutan", + "value": "BT" + }, + { + "label": "Bolivia", + "value": "BO" + }, + { + "label": "Caribbean Netherlands", + "value": "BQ" + }, + { + "label": "Bosnia & Herzegovina", + "value": "BA" + }, + { + "label": "Botswana", + "value": "BW" + }, + { + "label": "Bouvet Island", + "value": "BV" + }, + { + "label": "Brazil", + "value": "BR" + }, + { + "label": "British Indian Ocean Territory", + "value": "IO" + }, + { + "label": "Brunei", + "value": "BN" + }, + { + "label": "Bulgaria", + "value": "BG" + }, + { + "label": "Burkina Faso", + "value": "BF" + }, + { + "label": "Burundi", + "value": "BI" + }, + { + "label": "Cambodia", + "value": "KH" + }, + { + "label": "Cameroon", + "value": "CM" + }, + { + "label": "Canada", + "value": "CA" + }, + { + "label": "Cape Verde", + "value": "CV" + }, + { + "label": "Cayman Islands", + "value": "KY" + }, + { + "label": "Chad", + "value": "TD" + }, + { + "label": "Chile", + "value": "CL" + }, + { + "label": "China", + "value": "CN" + }, + { + "label": "Christmas Island", + "value": "CX" + }, + { + "label": "Cocos (Keeling) Islands", + "value": "CC" + }, + { + "label": "Colombia", + "value": "CO" + }, + { + "label": "Comoros", + "value": "KM" + }, + { + "label": "Congo - Brazzaville", + "value": "CG" + }, + { + "label": "Cook Islands", + "value": "CK" + }, + { + "label": "Costa Rica", + "value": "CR" + }, + { + "label": "Côte d’Ivoire", + "value": "CI" + }, + { + "label": "Croatia", + "value": "HR" + }, + { + "label": "Curaçao", + "value": "CW" + }, + { + "label": "Cyprus", + "value": "CY" + }, + { + "label": "Czechia", + "value": "CZ" + }, + { + "label": "Denmark", + "value": "DK" + }, + { + "label": "Djibouti", + "value": "DJ" + }, + { + "label": "Dominica", + "value": "DM" + }, + { + "label": "Dominican Republic", + "value": "DO" + }, + { + "label": "Timor-Leste", + "value": "TL" + }, + { + "label": "Ecuador", + "value": "EC" + }, + { + "label": "Egypt", + "value": "EG" + }, + { + "label": "El Salvador", + "value": "SV" + }, + { + "label": "Equatorial Guinea", + "value": "GQ" + }, + { + "label": "Estonia", + "value": "EE" + }, + { + "label": "Ethiopia", + "value": "ET" + }, + { + "label": "Falkland Islands", + "value": "FK" + }, + { + "label": "Faroe Islands", + "value": "FO" + }, + { + "label": "Fiji", + "value": "FJ" + }, + { + "label": "Finland", + "value": "FI" + }, + { + "label": "France", + "value": "FR" + }, + { + "label": "French Guiana", + "value": "GF" + }, + { + "label": "French Polynesia", + "value": "PF" + }, + { + "label": "French Southern Territories", + "value": "TF" + }, + { + "label": "Gabon", + "value": "GA" + }, + { + "label": "Gambia", + "value": "GM" + }, + { + "label": "Georgia", + "value": "GE" + }, + { + "label": "Germany", + "value": "DE" + }, + { + "label": "Ghana", + "value": "GH" + }, + { + "label": "Gibraltar", + "value": "GI" + }, + { + "label": "Greece", + "value": "GR" + }, + { + "label": "Greenland", + "value": "GL" + }, + { + "label": "Grenada", + "value": "GD" + }, + { + "label": "Guadeloupe", + "value": "GP" + }, + { + "label": "Guam", + "value": "GU" + }, + { + "label": "Guatemala", + "value": "GT" + }, + { + "label": "Guernsey", + "value": "GG" + }, + { + "label": "Guinea", + "value": "GN" + }, + { + "label": "Guyana", + "value": "GY" + }, + { + "label": "Haiti", + "value": "HT" + }, + { + "label": "Heard & McDonald Islands", + "value": "HM" + }, + { + "label": "Honduras", + "value": "HN" + }, + { + "label": "Hong Kong SAR China", + "value": "HK" + }, + { + "label": "Hungary", + "value": "HU" + }, + { + "label": "Iceland", + "value": "IS" + }, + { + "label": "India", + "value": "IN" + }, + { + "label": "Indonesia", + "value": "ID" + }, + { + "label": "Ireland", + "value": "IE" + }, + { + "label": "Isle of Man", + "value": "IM" + }, + { + "label": "Israel", + "value": "IL" + }, + { + "label": "Italy", + "value": "IT" + }, + { + "label": "Jamaica", + "value": "JM" + }, + { + "label": "Japan", + "value": "JP" + }, + { + "label": "Jersey", + "value": "JE" + }, + { + "label": "Jordan", + "value": "JO" + }, + { + "label": "Kazakhstan", + "value": "KZ" + }, + { + "label": "Kenya", + "value": "KE" + }, + { + "label": "Kiribati", + "value": "KI" + }, + { + "label": "South Korea", + "value": "KR" + }, + { + "label": "Kosovo", + "value": "XK" + }, + { + "label": "Kuwait", + "value": "KW" + }, + { + "label": "Kyrgyzstan", + "value": "KG" + }, + { + "label": "Laos", + "value": "LA" + }, + { + "label": "Latvia", + "value": "LV" + }, + { + "label": "Lesotho", + "value": "LS" + }, + { + "label": "Liechtenstein", + "value": "LI" + }, + { + "label": "Lithuania", + "value": "LT" + }, + { + "label": "Luxembourg", + "value": "LU" + }, + { + "label": "Macao SAR China", + "value": "MO" + }, + { + "label": "North Macedonia", + "value": "MK" + }, + { + "label": "Madagascar", + "value": "MG" + }, + { + "label": "Malawi", + "value": "MW" + }, + { + "label": "Malaysia", + "value": "MY" + }, + { + "label": "Maldives", + "value": "MV" + }, + { + "label": "Mali", + "value": "ML" + }, + { + "label": "Malta", + "value": "MT" + }, + { + "label": "Marshall Islands", + "value": "MH" + }, + { + "label": "Martinique", + "value": "MQ" + }, + { + "label": "Mauritania", + "value": "MR" + }, + { + "label": "Mauritius", + "value": "MU" + }, + { + "label": "Mayotte", + "value": "YT" + }, + { + "label": "Mexico", + "value": "MX" + }, + { + "label": "Micronesia", + "value": "FM" + }, + { + "label": "Moldova", + "value": "MD" + }, + { + "label": "Monaco", + "value": "MC" + }, + { + "label": "Mongolia", + "value": "MN" + }, + { + "label": "Montenegro", + "value": "ME" + }, + { + "label": "Montserrat", + "value": "MS" + }, + { + "label": "Morocco", + "value": "MA" + }, + { + "label": "Mozambique", + "value": "MZ" + }, + { + "label": "Namibia", + "value": "NA" + }, + { + "label": "Nauru", + "value": "NR" + }, + { + "label": "Nepal", + "value": "NP" + }, + { + "label": "Netherlands", + "value": "NL" + }, + { + "label": "Curaçao", + "value": "AN" + }, + { + "label": "New Caledonia", + "value": "NC" + }, + { + "label": "New Zealand", + "value": "NZ" + }, + { + "label": "Nicaragua", + "value": "NI" + }, + { + "label": "Niger", + "value": "NE" + }, + { + "label": "Nigeria", + "value": "NG" + }, + { + "label": "Niue", + "value": "NU" + }, + { + "label": "Norfolk Island", + "value": "NF" + }, + { + "label": "Northern Mariana Islands", + "value": "MP" + }, + { + "label": "Norway", + "value": "NO" + }, + { + "label": "Oman", + "value": "OM" + }, + { + "label": "Pakistan", + "value": "PK" + }, + { + "label": "Palau", + "value": "PW" + }, + { + "label": "Palestinian Territories", + "value": "PS" + }, + { + "label": "Panama", + "value": "PA" + }, + { + "label": "Papua New Guinea", + "value": "PG" + }, + { + "label": "Paraguay", + "value": "PY" + }, + { + "label": "Peru", + "value": "PE" + }, + { + "label": "Philippines", + "value": "PH" + }, + { + "label": "Pitcairn Islands", + "value": "PN" + }, + { + "label": "Poland", + "value": "PL" + }, + { + "label": "Portugal", + "value": "PT" + }, + { + "label": "Puerto Rico", + "value": "PR" + }, + { + "label": "Qatar", + "value": "QA" + }, + { + "label": "Réunion", + "value": "RE" + }, + { + "label": "Romania", + "value": "RO" + }, + { + "label": "Russia", + "value": "RU" + }, + { + "label": "Rwanda", + "value": "RW" + }, + { + "label": "St. Kitts & Nevis", + "value": "KN" + }, + { + "label": "St. Lucia", + "value": "LC" + }, + { + "label": "St. Martin", + "value": "MF" + }, + { + "label": "St. Vincent & Grenadines", + "value": "VC" + }, + { + "label": "Samoa", + "value": "WS" + }, + { + "label": "San Marino", + "value": "SM" + }, + { + "label": "São Tomé & Príncipe", + "value": "ST" + }, + { + "label": "Saudi Arabia", + "value": "SA" + }, + { + "label": "Senegal", + "value": "SN" + }, + { + "label": "Serbia", + "value": "RS" + }, + { + "label": "Seychelles", + "value": "SC" + }, + { + "label": "Sierra Leone", + "value": "SL" + }, + { + "label": "Singapore", + "value": "SG" + }, + { + "label": "Sint Maarten", + "value": "SX" + }, + { + "label": "Slovakia", + "value": "SK" + }, + { + "label": "Slovenia", + "value": "SI" + }, + { + "label": "Solomon Islands", + "value": "SB" + }, + { + "label": "South Africa", + "value": "ZA" + }, + { + "label": "South Georgia & South Sandwich Islands", + "value": "GS" + }, + { + "label": "South Sudan", + "value": "SS" + }, + { + "label": "Spain", + "value": "ES" + }, + { + "label": "Sri Lanka", + "value": "LK" + }, + { + "label": "St. Barthélemy", + "value": "BL" + }, + { + "label": "St. Helena", + "value": "SH" + }, + { + "label": "St. Pierre & Miquelon", + "value": "PM" + }, + { + "label": "Suriname", + "value": "SR" + }, + { + "label": "Svalbard & Jan Mayen", + "value": "SJ" + }, + { + "label": "Eswatini", + "value": "SZ" + }, + { + "label": "Sweden", + "value": "SE" + }, + { + "label": "Switzerland", + "value": "CH" + }, + { + "label": "Taiwan", + "value": "TW" + }, + { + "label": "Tajikistan", + "value": "TJ" + }, + { + "label": "Tanzania", + "value": "TZ" + }, + { + "label": "Thailand", + "value": "TH" + }, + { + "label": "Togo", + "value": "TG" + }, + { + "label": "Tokelau", + "value": "TK" + }, + { + "label": "Tonga", + "value": "TO" + }, + { + "label": "Trinidad & Tobago", + "value": "TT" + }, + { + "label": "Tunisia", + "value": "TN" + }, + { + "label": "Turkey", + "value": "TR" + }, + { + "label": "Turkmenistan", + "value": "TM" + }, + { + "label": "Turks & Caicos Islands", + "value": "TC" + }, + { + "label": "Tuvalu", + "value": "TV" + }, + { + "label": "Uganda", + "value": "UG" + }, + { + "label": "Ukraine", + "value": "UA" + }, + { + "label": "United Arab Emirates", + "value": "AE" + }, + { + "label": "United Kingdom", + "value": "GB" + }, + { + "label": "United States", + "value": "US" + }, + { + "label": "U.S. Outlying Islands", + "value": "UM" + }, + { + "label": "Uruguay", + "value": "UY" + }, + { + "label": "Uzbekistan", + "value": "UZ" + }, + { + "label": "Vanuatu", + "value": "VU" + }, + { + "label": "Vatican City", + "value": "VA" + }, + { + "label": "Venezuela", + "value": "VE" + }, + { + "label": "Vietnam", + "value": "VN" + }, + { + "label": "British Virgin Islands", + "value": "VG" + }, + { + "label": "U.S. Virgin Islands", + "value": "VI" + }, + { + "label": "Wallis & Futuna", + "value": "WF" + }, + { + "label": "Western Sahara", + "value": "EH" + }, + { + "label": "Zambia", + "value": "ZM" + } + ], + "validationMessage": { + "length": "", + "pattern": "is invalid length or format.", + "empty": "You must provide a value for this field" + }, + "fieldValueMasked": false + }, + { + "category": "ADDRESS", + "value": "BC", + "dataType": "TEXT", + "isRequired": true, + "isEditable": true, + "label": "State/Province", + "name": "stateProvince", + "placeholder": "", + "regularExpression": "^[\\sa-zA-Z0-9\\-().,'\\u00C0-\\u00FF\\u0100-\\u017F\\u0180-\\u024F]{2,50}$", + "validationMessage": { + "length": "", + "pattern": "is invalid length or format.", + "empty": "You must provide a value for this field" + }, + "fieldValueMasked": false + }, + { + "category": "ADDRESS", + "value": "475 howe st", + "dataType": "TEXT", + "isRequired": true, + "isEditable": true, + "label": "Address Line 1", + "name": "addressLine1", + "placeholder": "", + "regularExpression": "^[\\sa-zA-Z0-9\\/\\-:().,#;'°&\\u00C0-\\u00FF\\u0100-\\u017F\\u0180-\\u024F]{2,100}$", + "validationMessage": { + "length": "", + "pattern": "is invalid length or format.", + "empty": "You must provide a value for this field" + }, + "fieldValueMasked": false + }, + { + "category": "ADDRESS", + "dataType": "TEXT", + "isRequired": false, + "isEditable": true, + "label": "Address Line 2", + "name": "addressLine2", + "placeholder": "", + "regularExpression": "^[\\sa-zA-Z0-9\\/\\-:().,#;'°&\\u00C0-\\u00FF\\u0100-\\u017F\\u0180-\\u024F]{2,100}$", + "validationMessage": { + "length": "", + "pattern": "is invalid length or format.", + "empty": "You must provide a value for this field" + }, + "fieldValueMasked": false + }, + { + "category": "ADDRESS", + "value": "vancouver", + "dataType": "TEXT", + "isRequired": true, + "isEditable": true, + "label": "City", + "name": "city", + "placeholder": "", + "regularExpression": "^[\\sa-zA-Z0-9\\-().,'\\u00C0-\\u00FF\\u0100-\\u017F\\u0180-\\u024F]{2,50}$", + "validationMessage": { + "length": "", + "pattern": "is invalid length or format.", + "empty": "You must provide a value for this field" + }, + "fieldValueMasked": false + }, + { + "category": "ADDRESS", + "value": "V6Z1L2", + "dataType": "TEXT", + "isRequired": true, + "isEditable": true, + "label": "Zip/Postal Code", + "name": "postalCode", + "placeholder": "", + "regularExpression": "^(?![\\-]+$)[\\sa-zA-Z0-9\\-]{2,16}$", + "validationMessage": { + "length": "", + "pattern": "is invalid length or format.", + "empty": "You must provide a value for this field" + }, + "fieldValueMasked": false + } + ] + } + ] + } + } + ] + } + } +} \ No newline at end of file diff --git a/transfermethodui/build.gradle b/transfermethodui/build.gradle index cd908659d..207b83eb2 100644 --- a/transfermethodui/build.gradle +++ b/transfermethodui/build.gradle @@ -25,6 +25,7 @@ dependencies { implementation project(':commonui') implementation project(":userrepository") implementation project(":transfermethodrepository") + implementation project(":transferui") androidTestImplementation "androidx.test:rules:$testRulesVersion" diff --git a/transfermethodui/src/androidTest/java/com/hyperwallet/android/ui/transfermethod/EditTransferMethodTest.java b/transfermethodui/src/androidTest/java/com/hyperwallet/android/ui/transfermethod/EditTransferMethodTest.java new file mode 100644 index 000000000..e1144cdbf --- /dev/null +++ b/transfermethodui/src/androidTest/java/com/hyperwallet/android/ui/transfermethod/EditTransferMethodTest.java @@ -0,0 +1,257 @@ +package com.hyperwallet.android.ui.transfermethod; + +import android.app.Instrumentation; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.view.MenuItem; +import android.widget.ImageButton; +import android.widget.TextView; + +import androidx.localbroadcastmanager.content.LocalBroadcastManager; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.espresso.IdlingRegistry; +import androidx.test.espresso.Root; +import androidx.test.espresso.assertion.ViewAssertions; +import androidx.test.espresso.matcher.ViewMatchers; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.rule.ActivityTestRule; + +import com.hyperwallet.android.model.StatusTransition; +import com.hyperwallet.android.ui.R; +import com.hyperwallet.android.ui.common.repository.EspressoIdlingResource; +import com.hyperwallet.android.ui.common.view.error.DefaultErrorDialogFragment; +import com.hyperwallet.android.ui.testutils.rule.HyperwalletExternalResourceManager; +import com.hyperwallet.android.ui.testutils.rule.HyperwalletMockWebServer; +import com.hyperwallet.android.ui.testutils.util.RecyclerViewCountAssertion; +import com.hyperwallet.android.ui.transfermethod.repository.TransferMethodRepositoryFactory; +import com.hyperwallet.android.ui.transfermethod.rule.HyperwalletInsightMockRule; +import com.hyperwallet.android.ui.transfermethod.view.AddTransferMethodActivity; +import com.hyperwallet.android.ui.transfermethod.view.ListTransferMethodActivity; + +import org.hamcrest.Matchers; +import org.junit.After; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.IOException; +import java.util.concurrent.CountDownLatch; + +import static androidx.test.espresso.Espresso.onView; +import static androidx.test.espresso.action.ViewActions.click; +import static androidx.test.espresso.action.ViewActions.replaceText; +import static androidx.test.espresso.action.ViewActions.typeText; +import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist; +import static androidx.test.espresso.assertion.ViewAssertions.matches; +import static androidx.test.espresso.matcher.RootMatchers.isDialog; +import static androidx.test.espresso.matcher.ViewMatchers.hasDescendant; +import static androidx.test.espresso.matcher.ViewMatchers.hasSibling; +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; +import static com.hyperwallet.android.model.StatusTransition.StatusDefinition.DE_ACTIVATED; +import static com.hyperwallet.android.ui.common.view.error.DefaultErrorDialogFragment.RESULT_ERROR; +import static com.hyperwallet.android.ui.testutils.util.EspressoUtils.atPosition; +import static com.hyperwallet.android.ui.testutils.util.EspressoUtils.nestedScrollTo; +import static com.hyperwallet.android.ui.testutils.util.EspressoUtils.withDrawable; +import static java.net.HttpURLConnection.HTTP_BAD_REQUEST; +import static java.net.HttpURLConnection.HTTP_OK; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; + +@RunWith(AndroidJUnit4.class) +public class EditTransferMethodTest { + + private static final String ACCOUNT_NUMBER = "8017110254"; + private static final String ROUTING_NUMBER = "211179539"; + + @ClassRule + public static HyperwalletExternalResourceManager sResourceManager = new HyperwalletExternalResourceManager(); + @Rule + public HyperwalletInsightMockRule mHyperwalletInsightMockRule = new HyperwalletInsightMockRule(); + @Rule + public HyperwalletMockWebServer mMockWebServer = new HyperwalletMockWebServer(8080); + @Rule + public ActivityTestRule mActivityTestRule = + new ActivityTestRule<>(ListTransferMethodActivity.class, true, false); + + @Before + public void setup() { + mMockWebServer.mockResponse().withHttpResponseCode(HTTP_OK).withBody(sResourceManager + .getResourceContent("authentication_token_response.json")).mock(); + IdlingRegistry.getInstance().register(EspressoIdlingResource.getIdlingResource()); + } + + @After + public void cleanup() { + TransferMethodRepositoryFactory.clearInstance(); + IdlingRegistry.getInstance().unregister(EspressoIdlingResource.getIdlingResource()); + } + + + @Test + public void testUpdateTransferMethodFragment_verifyUpdateBankAccountTransferMethod() throws InterruptedException { + mMockWebServer.mockResponse().withHttpResponseCode(HTTP_OK).withBody(sResourceManager + .getResourceContent("transfer_method_list_single_bank_account_response.json")).mock(); + mMockWebServer.mockResponse().withHttpResponseCode(HTTP_OK).withBody(sResourceManager + .getResourceContent("transfer_method_update_bankacount_response.json")).mock(); + + + final CountDownLatch gate = new CountDownLatch(1); + final BroadcastReceiver br = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + gate.countDown(); + + StatusTransition statusTransition = intent.getParcelableExtra( + "hyperwallet-local-broadcast-payload"); + assertThat("Transition is not valid", statusTransition.getTransition(), is(DE_ACTIVATED)); + } + }; + + // run test + mActivityTestRule.launchActivity(null); + LocalBroadcastManager.getInstance(mActivityTestRule.getActivity().getApplicationContext()) + .registerReceiver(br, new IntentFilter("ACTION_HYPERWALLET_TRANSFER_METHOD_DEACTIVATED")); + + // assert + onView(withId(R.id.toolbar)).check(matches(isDisplayed())); + onView(withId(R.id.toolbar)) + .check(matches( + hasDescendant(withText(R.string.mobileTransferMethodsHeader)))); + onView(withId(R.id.fab)).check(matches(isDisplayed())); + + onView(withId(R.id.list_transfer_method_item)).check( + matches(atPosition(0, hasDescendant(withText(R.string.bank_account_font_icon))))); + onView(withId(R.id.list_transfer_method_item)).check( + matches(atPosition(0, hasDescendant(withText(R.string.bank_account))))); + onView(withId(R.id.list_transfer_method_item)).check( + matches(atPosition(0, hasDescendant(withText("United States"))))); + onView(withId(R.id.list_transfer_method_item)).check( + matches(atPosition(0, hasDescendant(withText(getEndingIn("1332")))))); + onView(withId(R.id.list_transfer_method_item)).check( + matches(atPosition(0, hasDescendant(withDrawable(R.drawable.ic_three_dots_16dp))))); + + onView(allOf(instanceOf(ImageButton.class), hasSibling(withText(R.string.bank_account)))).perform(click()) + .inRoot(Matchers.instanceOf(MenuItem.class)); + onView(withDrawable(R.drawable.ic_trash)).check(matches(isDisplayed())); + onView(withText(R.string.edit)).check(matches(isDisplayed())).perform(click()); + + onView(withId(R.id.branchIdLabel)).check(matches(isDisplayed())); + + onView(withId(R.id.branchId)).perform(nestedScrollTo()).check(ViewAssertions.matches(ViewMatchers.isDisplayed())) + .check(matches(withText("021000021"))); + + onView(ViewMatchers.withId(R.id.branchIdLabel)) + .check(ViewAssertions.matches(ViewMatchers.isDisplayed())); + + onView(ViewMatchers.withId(R.id.bankAccountId)) + .perform(nestedScrollTo()) + .check(ViewAssertions.matches(ViewMatchers.isDisplayed())) + .check(matches(withText("****"))); + + onView(ViewMatchers.withId(R.id.bankAccountIdLabel)) + .check(ViewAssertions.matches(ViewMatchers.isDisplayed())); + + // ACCOUNT HOLDER INFO + onView( + Matchers.allOf( + ViewMatchers.withId(R.id.section_header_title), + ViewMatchers.withText(R.string.account_holder) + ) + ) + .perform(nestedScrollTo()) + .check(ViewAssertions.matches(ViewMatchers.isDisplayed())); + + onView(ViewMatchers.withId(R.id.firstName)) + .perform(nestedScrollTo()) + .check(ViewAssertions.matches(ViewMatchers.isDisplayed())) + .check(matches(withText("Android Mobile"))); + + onView(ViewMatchers.withId(R.id.firstNameLabel)) + .check(ViewAssertions.matches(ViewMatchers.isDisplayed())); + + onView(ViewMatchers.withId(R.id.middleName)) + .perform(nestedScrollTo()) + .check(ViewAssertions.matches(ViewMatchers.isDisplayed())) + .check(matches(withText("mobile-qa"))); + + onView(ViewMatchers.withId(R.id.middleNameLabel)) + .check(ViewAssertions.matches(ViewMatchers.isDisplayed())); + + onView(ViewMatchers.withId(R.id.lastName)) + .perform(nestedScrollTo()) + .check(ViewAssertions.matches(ViewMatchers.isDisplayed())) + .check(matches(withText("UITest"))); + + onView(ViewMatchers.withId(R.id.lastNameLabel)) + .check(ViewAssertions.matches(ViewMatchers.isDisplayed())); + + // ADDRESS + onView( + Matchers.allOf( + ViewMatchers.withId(R.id.section_header_title), + ViewMatchers.withText(R.string.address) + ) + ).perform(nestedScrollTo()) + .check(ViewAssertions.matches(ViewMatchers.isDisplayed())); + + onView(ViewMatchers.withId(R.id.country)) + .perform(nestedScrollTo()) + .check(ViewAssertions.matches(ViewMatchers.isDisplayed())) + .check(matches(withText("Canada"))); + + onView(ViewMatchers.withId(R.id.countryLabel)) + .check(ViewAssertions.matches(ViewMatchers.isDisplayed())); + + onView(ViewMatchers.withId(R.id.stateProvince)) + .perform(nestedScrollTo()) + .check(ViewAssertions.matches(ViewMatchers.isDisplayed())) + .check(matches(withText("BC"))); + + onView(ViewMatchers.withId(R.id.stateProvinceLabel)) + .check(ViewAssertions.matches(ViewMatchers.isDisplayed())); + + onView(ViewMatchers.withId(R.id.addressLine1)) + .perform(nestedScrollTo()) + .check(ViewAssertions.matches(ViewMatchers.isDisplayed())) + .check(matches(withText("475 howe st"))); + + onView(ViewMatchers.withId(R.id.addressLine1Label)) + .check(ViewAssertions.matches(ViewMatchers.isDisplayed())); + + onView(ViewMatchers.withId(R.id.city)) + .perform(nestedScrollTo()) + .check(matches(withText("vancouver"))) + .check(ViewAssertions.matches(ViewMatchers.isDisplayed())); + + onView(ViewMatchers.withId(R.id.cityLabel)) + .check(ViewAssertions.matches(ViewMatchers.isDisplayed())); + + onView(ViewMatchers.withId(R.id.postalCode)) + .perform(nestedScrollTo()) + .check(ViewAssertions.matches(ViewMatchers.isDisplayed())) + .check(matches(withText("V6Z1L2"))); + + onView(ViewMatchers.withId(R.id.postalCodeLabel)) + .check(ViewAssertions.matches(ViewMatchers.isDisplayed())); + + } + + private String getEndingIn(String ending) { + return String.format(InstrumentationRegistry.getInstrumentation().getTargetContext() + .getString(R.string.endingIn), ending); + } + +} diff --git a/transfermethodui/src/main/AndroidManifest.xml b/transfermethodui/src/main/AndroidManifest.xml index 2a97d4dee..d46f47fb2 100644 --- a/transfermethodui/src/main/AndroidManifest.xml +++ b/transfermethodui/src/main/AndroidManifest.xml @@ -12,6 +12,11 @@ android:theme="@style/AppTheme.NoActionBar" android:windowSoftInputMode="adjustResize"/> + + true screen will be locked to Portrait mode; + * otherwise false screen will follow whatever the + * device orientation is directed. + * @return an Intent with the data necessary to launch the {@link UpdateTransferMethodActivity} + */ + public Intent getIntentUpdateTransferMethodActivity(@NonNull final Context context, + @NonNull final String transferMethodToken, final boolean lockScreenToPortrait) { + Intent intent = new Intent(context, UpdateTransferMethodActivity.class); + intent.putExtra(EXTRA_TRANSFER_METHOD_TOKEN, transferMethodToken); + intent.putExtra(AddTransferMethodActivity.EXTRA_LOCK_SCREEN_ORIENTATION_TO_PORTRAIT, lockScreenToPortrait); + return intent; + } + public static void clearInstance() { sInstance = null; Hyperwallet.clearInstance(); diff --git a/transfermethodui/src/main/java/com/hyperwallet/android/ui/transfermethod/TransferMethodLocalBroadcast.java b/transfermethodui/src/main/java/com/hyperwallet/android/ui/transfermethod/TransferMethodLocalBroadcast.java index f0d88bca0..5d703d328 100644 --- a/transfermethodui/src/main/java/com/hyperwallet/android/ui/transfermethod/TransferMethodLocalBroadcast.java +++ b/transfermethodui/src/main/java/com/hyperwallet/android/ui/transfermethod/TransferMethodLocalBroadcast.java @@ -19,6 +19,7 @@ import static com.hyperwallet.android.ui.common.intent.HyperwalletIntent.HYPERWALLET_LOCAL_BROADCAST_PAYLOAD_KEY; import static com.hyperwallet.android.ui.transfermethod.TransferMethodLocalBroadcast.TransferMethodLocalBroadcastAction.ACTION_HYPERWALLET_TRANSFER_METHOD_ADDED; import static com.hyperwallet.android.ui.transfermethod.TransferMethodLocalBroadcast.TransferMethodLocalBroadcastAction.ACTION_HYPERWALLET_TRANSFER_METHOD_DEACTIVATED; +import static com.hyperwallet.android.ui.transfermethod.TransferMethodLocalBroadcast.TransferMethodLocalBroadcastAction.ACTION_HYPERWALLET_TRANSFER_METHOD_UPDATED; import android.content.Intent; import android.os.Parcelable; @@ -40,6 +41,12 @@ public static Intent createBroadcastIntentTransferMethodAdded( ACTION_HYPERWALLET_TRANSFER_METHOD_ADDED); } + public static Intent createBroadcastIntentTransferMethodUpdated( + @NonNull final TransferMethod transferMethod) { + return createBroadcastIntent(transferMethod, + ACTION_HYPERWALLET_TRANSFER_METHOD_UPDATED); + } + public static Intent createBroadcastIntentTransferMethodDeactivated( @NonNull final StatusTransition StatusTransition) { return createBroadcastIntent(StatusTransition, @@ -57,6 +64,7 @@ private static Intent createBroadcastIntent(@NonNull final Parcelable parcelable @Retention(RetentionPolicy.SOURCE) @StringDef({ ACTION_HYPERWALLET_TRANSFER_METHOD_ADDED, + ACTION_HYPERWALLET_TRANSFER_METHOD_UPDATED, ACTION_HYPERWALLET_TRANSFER_METHOD_DEACTIVATED }) public @interface TransferMethodLocalBroadcastActionType { @@ -69,5 +77,7 @@ private TransferMethodLocalBroadcastAction() {} "ACTION_HYPERWALLET_TRANSFER_METHOD_ADDED"; public static final String ACTION_HYPERWALLET_TRANSFER_METHOD_DEACTIVATED = "ACTION_HYPERWALLET_TRANSFER_METHOD_DEACTIVATED"; + public static final String ACTION_HYPERWALLET_TRANSFER_METHOD_UPDATED = + "ACTION_HYPERWALLET_TRANSFER_METHOD_UPDATED"; } } diff --git a/transfermethodui/src/main/java/com/hyperwallet/android/ui/transfermethod/view/ListTransferMethodActivity.java b/transfermethodui/src/main/java/com/hyperwallet/android/ui/transfermethod/view/ListTransferMethodActivity.java index f81e7a84b..ab11d8e15 100644 --- a/transfermethodui/src/main/java/com/hyperwallet/android/ui/transfermethod/view/ListTransferMethodActivity.java +++ b/transfermethodui/src/main/java/com/hyperwallet/android/ui/transfermethod/view/ListTransferMethodActivity.java @@ -17,6 +17,7 @@ package com.hyperwallet.android.ui.transfermethod.view; import static com.hyperwallet.android.ui.common.intent.HyperwalletIntent.SELECT_TRANSFER_METHOD_REQUEST_CODE; +import static com.hyperwallet.android.ui.common.intent.HyperwalletIntent.UPDATE_TRANSFER_METHOD_REQUEST_CODE; import static com.hyperwallet.android.ui.transfermethod.view.ListTransferMethodFragment.ARGUMENT_IS_TRANSFER_METHODS_RELOAD_NEEDED; import android.content.Intent; @@ -49,7 +50,7 @@ public class ListTransferMethodActivity extends AppCompatActivity implements ListTransferMethodFragment.OnAddNewTransferMethodSelected, ListTransferMethodFragment.OnDeactivateTransferMethodNetworkErrorCallback, ListTransferMethodFragment.OnLoadTransferMethodNetworkErrorCallback, - ListTransferMethodFragment.OnTransferMethodContextMenuDeletionSelected, + ListTransferMethodFragment.OnTransferMethodContextMenuItemSelected, OnTransferMethodDeactivateCallback, OnNetworkErrorCallback { public static final String TAG = "transfer-method:list:list-transfer-methods"; @@ -134,6 +135,9 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { if (fragment != null && fragment.getArguments() != null) { fragment.getArguments().putBoolean(ARGUMENT_IS_TRANSFER_METHODS_RELOAD_NEEDED, true); } + } else if (requestCode == UPDATE_TRANSFER_METHOD_REQUEST_CODE && resultCode == RESULT_OK) { + ActivityUtils.initFragment(this, ListTransferMethodFragment.newInstance(), + R.id.list_transfer_method_fragment); } } @@ -177,6 +181,15 @@ public void showConfirmationDialog(@NonNull TransferMethod transferMethod) { } } + @Override + public void invokeTransferMethodEdit(@NonNull TransferMethod transferMethod) { + String token = transferMethod.getField(TransferMethod.TransferMethodFields.TOKEN); + Intent intent = new Intent(this, UpdateTransferMethodActivity.class); + intent.putExtra(UpdateTransferMethodActivity.EXTRA_TRANSFER_METHOD_TOKEN, token); + intent.putExtra(UpdateTransferMethodActivity.EXTRA_LOCK_SCREEN_ORIENTATION_TO_PORTRAIT, true); + startActivityForResult(intent, UPDATE_TRANSFER_METHOD_REQUEST_CODE); + } + @Override public void confirm() { mRetryCode = RETRY_CONFIRM_DEACTIVATE_TRANSFER_METHOD; diff --git a/transfermethodui/src/main/java/com/hyperwallet/android/ui/transfermethod/view/ListTransferMethodFragment.java b/transfermethodui/src/main/java/com/hyperwallet/android/ui/transfermethod/view/ListTransferMethodFragment.java index 9f4ccb6a8..f5c185311 100644 --- a/transfermethodui/src/main/java/com/hyperwallet/android/ui/transfermethod/view/ListTransferMethodFragment.java +++ b/transfermethodui/src/main/java/com/hyperwallet/android/ui/transfermethod/view/ListTransferMethodFragment.java @@ -69,7 +69,7 @@ public class ListTransferMethodFragment extends Fragment implements ListTransfer private View mProgressBar; private ArrayList mTransferMethodList; private OnAddNewTransferMethodSelected mOnAddNewTransferMethodSelected; - private OnTransferMethodContextMenuDeletionSelected mOnTransferMethodContextMenuDeletionSelected; + private OnTransferMethodContextMenuItemSelected mOnTransferMethodContextMenuItemSelected; private OnDeactivateTransferMethodNetworkErrorCallback mOnDeactivateTransferMethodNetworkErrorCallback; private OnLoadTransferMethodNetworkErrorCallback mOnLoadTransferMethodNetworkErrorCallback; private boolean mIsTransferMethodsReloadNeeded; @@ -105,10 +105,10 @@ public void onAttach(Context context) { } try { - mOnTransferMethodContextMenuDeletionSelected = (OnTransferMethodContextMenuDeletionSelected) context; + mOnTransferMethodContextMenuItemSelected = (OnTransferMethodContextMenuItemSelected) context; } catch (ClassCastException e) { throw new ClassCastException(getActivity().toString() + " must implement " - + OnTransferMethodContextMenuDeletionSelected.class.getCanonicalName()); + + OnTransferMethodContextMenuItemSelected.class.getCanonicalName()); } try { @@ -144,7 +144,7 @@ public void onViewStateRestored(@Nullable Bundle savedInstanceState) { mIsTransferMethodsReloadNeeded = true; } mListTransferMethodAdapter = new ListTransferMethodAdapter(mTransferMethodList, - mOnTransferMethodContextMenuDeletionSelected); + mOnTransferMethodContextMenuItemSelected); recyclerView.setAdapter(mListTransferMethodAdapter); } @@ -187,7 +187,11 @@ public void onActivityCreated(@Nullable Bundle savedInstanceState) { public void onResume() { super.onResume(); mIsTransferMethodsReloadNeeded = getArguments().getBoolean(ARGUMENT_IS_TRANSFER_METHODS_RELOAD_NEEDED, true); - if (mIsTransferMethodsReloadNeeded) { + loadTransferMethodsList(mIsTransferMethodsReloadNeeded); + } + + private void loadTransferMethodsList(boolean shouldReload) { + if (shouldReload) { getArguments().putBoolean(ARGUMENT_IS_TRANSFER_METHODS_RELOAD_NEEDED, false); mPresenter.loadTransferMethods(); } else { @@ -272,9 +276,11 @@ public void loadTransferMethods() { mPresenter.loadTransferMethods(); } - interface OnTransferMethodContextMenuDeletionSelected { + interface OnTransferMethodContextMenuItemSelected { void showConfirmationDialog(@NonNull final TransferMethod transferMethod); + + void invokeTransferMethodEdit(@NonNull final TransferMethod transferMethod); } interface OnAddNewTransferMethodSelected { @@ -294,12 +300,12 @@ interface OnLoadTransferMethodNetworkErrorCallback { private static class ListTransferMethodAdapter extends RecyclerView.Adapter { private List mTransferMethodList; - private OnTransferMethodContextMenuDeletionSelected mOnTransferMethodContextMenuDeletionSelected; + private OnTransferMethodContextMenuItemSelected mOnTransferMethodContextMenuItemSelected; ListTransferMethodAdapter(final List transferMethodList, - final OnTransferMethodContextMenuDeletionSelected onTransferMethodContextMenuSelection) { + final OnTransferMethodContextMenuItemSelected onTransferMethodContextMenuSelection) { mTransferMethodList = transferMethodList; - mOnTransferMethodContextMenuDeletionSelected = onTransferMethodContextMenuSelection; + mOnTransferMethodContextMenuItemSelected = onTransferMethodContextMenuSelection; } @NonNull @@ -388,7 +394,10 @@ public void onClick(View v) { @Override public boolean onMenuItemClick(MenuItem item) { if (item.getItemId() == R.id.remove_account_context_option) { - mOnTransferMethodContextMenuDeletionSelected.showConfirmationDialog(transferMethod); + mOnTransferMethodContextMenuItemSelected.showConfirmationDialog(transferMethod); + return true; + } if (item.getItemId() == R.id.edit_account_context_option) { + mOnTransferMethodContextMenuItemSelected.invokeTransferMethodEdit(transferMethod); return true; } return false; diff --git a/transfermethodui/src/main/java/com/hyperwallet/android/ui/transfermethod/view/UpdateTransferMethodActivity.java b/transfermethodui/src/main/java/com/hyperwallet/android/ui/transfermethod/view/UpdateTransferMethodActivity.java new file mode 100644 index 000000000..7bb47db9c --- /dev/null +++ b/transfermethodui/src/main/java/com/hyperwallet/android/ui/transfermethod/view/UpdateTransferMethodActivity.java @@ -0,0 +1,169 @@ +package com.hyperwallet.android.ui.transfermethod.view; + +import android.content.pm.ActivityInfo; +import android.os.Bundle; +import android.view.View; +import android.view.WindowManager; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; +import androidx.core.content.ContextCompat; +import androidx.fragment.app.FragmentManager; + +import com.google.android.material.appbar.CollapsingToolbarLayout; +import com.hyperwallet.android.model.Error; +import com.hyperwallet.android.ui.R; +import com.hyperwallet.android.ui.common.util.PageGroups; +import com.hyperwallet.android.ui.common.view.ActivityUtils; +import com.hyperwallet.android.ui.common.view.TransferMethodUtils; +import com.hyperwallet.android.ui.common.view.error.OnNetworkErrorCallback; + +import java.util.List; + +public class UpdateTransferMethodActivity extends AppCompatActivity implements + WidgetSelectionDialogFragment.WidgetSelectionItemListener, + UpdateTransferMethodFragment.OnUpdateTransferMethodNetworkErrorCallback, + UpdateTransferMethodFragment.OnLoadTransferMethodConfigurationFieldsNetworkErrorCallback, + OnNetworkErrorCallback, WidgetDateDialogFragment.OnSelectedDateCallback { + + public static final String TAG = "transfer-method:update:collect-transfer-method-information"; + + public static final String EXTRA_TRANSFER_METHOD_TOKEN = "EXTRA_TRANSFER_METHOD_TOKEN"; + private static final String ARGUMENT_RETRY_ACTION = "ARGUMENT_RETRY_ACTION"; + public static final String EXTRA_LOCK_SCREEN_ORIENTATION_TO_PORTRAIT = "EXTRA_LOCK_SCREEN_ORIENTATION_TO_PORTRAIT"; + private static final short RETRY_SHOW_ERROR_UPDATE_TRANSFER_METHOD = 100; + private static final short RETRY_SHOW_ERROR_LOAD_TMC_FIELDS = 101; + + private short mRetryCode; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_update_transfer_method); + + getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE); + + Toolbar toolbar = findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + getSupportActionBar().setDisplayShowHomeEnabled(true); + CollapsingToolbarLayout collapsingToolbar = findViewById(R.id.collapsing_toolbar); + int titleStyleCollapse = TransferMethodUtils.getAdjustCollapseTitleStyle(getTitle().toString()); + collapsingToolbar.setCollapsedTitleTextAppearance(titleStyleCollapse); + int titleStyleExpanded = TransferMethodUtils.getAdjustExpandTitleStyle(getTitle().toString()); + collapsingToolbar.setExpandedTitleTextAppearance(titleStyleExpanded); + getSupportActionBar().setTitle(""); + + toolbar.setNavigationOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + finish(); + } + }); + + if (getIntent().getBooleanExtra(EXTRA_LOCK_SCREEN_ORIENTATION_TO_PORTRAIT, false)) { + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); + } + if (savedInstanceState == null) { + ActivityUtils.initFragment(this, UpdateTransferMethodFragment.newInstance( + getIntent().getStringExtra(EXTRA_TRANSFER_METHOD_TOKEN) + ), R.id.update_transfer_method_fragment); + } else { + mRetryCode = savedInstanceState.getShort(ARGUMENT_RETRY_ACTION); + } + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + outState.putShort(ARGUMENT_RETRY_ACTION, mRetryCode); + super.onSaveInstanceState(outState); + } + + @Override + protected void onRestoreInstanceState(Bundle savedInstanceState) { + super.onRestoreInstanceState(savedInstanceState); + + if (savedInstanceState != null) { + mRetryCode = savedInstanceState.getShort(ARGUMENT_RETRY_ACTION); + } + } + + @Override + public boolean onSupportNavigateUp() { + onBackPressed(); + return true; + } + + @Override + public void onBackPressed() { + getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); + getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); + getWindow().setStatusBarColor(ContextCompat.getColor(this, R.color.colorPrimaryDark)); + getWindow().getDecorView().setSystemUiVisibility(0); + super.onBackPressed(); + } + + @Override + public void retry() { + UpdateTransferMethodFragment fragment = getUpdateTransferFragment(); + switch (mRetryCode) { + case RETRY_SHOW_ERROR_UPDATE_TRANSFER_METHOD: + fragment.retryUpdateTransferMethod(); + break; + case RETRY_SHOW_ERROR_LOAD_TMC_FIELDS: + fragment.reloadTransferMethodConfigurationFields(); + break; + default: // no default action + } + } + + private UpdateTransferMethodFragment getUpdateTransferFragment() { + FragmentManager fragmentManager = getSupportFragmentManager(); + UpdateTransferMethodFragment fragment = (UpdateTransferMethodFragment) + fragmentManager.findFragmentById(R.id.update_transfer_method_fragment); + + if (fragment == null) { + fragment = UpdateTransferMethodFragment.newInstance( + getIntent().getStringExtra(EXTRA_TRANSFER_METHOD_TOKEN)); + } + return fragment; + } + + @Override + public void showErrorsLoadTransferMethodConfigurationFields(@NonNull List errors) { + mRetryCode = RETRY_SHOW_ERROR_LOAD_TMC_FIELDS; + ActivityUtils.showError(this, TAG, PageGroups.TRANSFER_METHOD, errors); + } + + @Override + public void showErrorsUpdateTransferMethod(@NonNull List errors) { + mRetryCode = RETRY_SHOW_ERROR_UPDATE_TRANSFER_METHOD; + ActivityUtils.showError(this, TAG, PageGroups.TRANSFER_METHOD, errors); + } + + @Override + public void setSelectedDateField(@NonNull String fieldName, String selectedValue) { + FragmentManager fragmentManager = getSupportFragmentManager(); + UpdateTransferMethodFragment updateTransferMethodFragment = + (UpdateTransferMethodFragment) fragmentManager.findFragmentById(R.id.update_transfer_method_fragment); + if (updateTransferMethodFragment != null) { + updateTransferMethodFragment.onDateSelected(selectedValue, fieldName); + } + } + + @Override + public void onWidgetSelectionItemClicked(@NonNull String selectedValue, @NonNull String fieldName) { + FragmentManager fragmentManager = getSupportFragmentManager(); + UpdateTransferMethodFragment updateTransferMethodFragment = + (UpdateTransferMethodFragment) fragmentManager.findFragmentById(R.id.update_transfer_method_fragment); + updateTransferMethodFragment.onWidgetSelectionItemClicked(selectedValue, fieldName); + + WidgetSelectionDialogFragment widgetSelectionDialogFragment = + (WidgetSelectionDialogFragment) fragmentManager.findFragmentById(android.R.id.content); + widgetSelectionDialogFragment.dismiss(); + getSupportFragmentManager().popBackStack(WidgetSelectionDialogFragment.TAG, + FragmentManager.POP_BACK_STACK_INCLUSIVE); + } +} diff --git a/transfermethodui/src/main/java/com/hyperwallet/android/ui/transfermethod/view/UpdateTransferMethodContract.java b/transfermethodui/src/main/java/com/hyperwallet/android/ui/transfermethod/view/UpdateTransferMethodContract.java new file mode 100644 index 000000000..b66bdda1f --- /dev/null +++ b/transfermethodui/src/main/java/com/hyperwallet/android/ui/transfermethod/view/UpdateTransferMethodContract.java @@ -0,0 +1,65 @@ +package com.hyperwallet.android.ui.transfermethod.view; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.hyperwallet.android.model.Error; +import com.hyperwallet.android.model.graphql.Fee; +import com.hyperwallet.android.model.graphql.HyperwalletTransferMethodConfigurationField; +import com.hyperwallet.android.model.graphql.ProcessingTime; +import com.hyperwallet.android.model.graphql.field.FieldGroup; +import com.hyperwallet.android.model.transfermethod.TransferMethod; + +import java.util.List; +import java.util.Map; + +/** + * View and Presenter Contract for Updating Transfer Method + */ +public interface UpdateTransferMethodContract { + + interface View { + + void notifyTransferMethodUpdated(@NonNull final TransferMethod transferMethod); + + void showErrorUpdateTransferMethod(@NonNull final List errors); + + void showErrorLoadTransferMethodConfigurationFields(@NonNull final List errors); + + void showTransferMethodFields(@NonNull final HyperwalletTransferMethodConfigurationField field); + + void showTransactionInformation(@NonNull final List fees, + @Nullable final ProcessingTime processingTime); + + void showUpdateButtonProgressBar(); + + void hideUpdateButtonProgressBar(); + + void showProgressBar(); + + void hideProgressBar(); + + void showInputErrors(@NonNull final List errors); + + /** + * Check the state of a View + * + * @return true when View is added to Container + */ + boolean isActive(); + + void retryUpdateTransferMethod(); + + void reloadTransferMethodConfigurationFields(); + } + + interface Presenter { + + void updateTransferMethod(@NonNull TransferMethod transferMethod); + + void loadTransferMethodConfigurationFields(boolean forceUpdate, @NonNull final String transferMethodType, @NonNull final String transferMethodToken); + + void handleUnmappedFieldError(@NonNull final Map fieldSet, + @NonNull final List errors); + } +} diff --git a/transfermethodui/src/main/java/com/hyperwallet/android/ui/transfermethod/view/UpdateTransferMethodFragment.java b/transfermethodui/src/main/java/com/hyperwallet/android/ui/transfermethod/view/UpdateTransferMethodFragment.java new file mode 100644 index 000000000..49ad03ac6 --- /dev/null +++ b/transfermethodui/src/main/java/com/hyperwallet/android/ui/transfermethod/view/UpdateTransferMethodFragment.java @@ -0,0 +1,689 @@ +package com.hyperwallet.android.ui.transfermethod.view; + +import static com.hyperwallet.android.model.transfermethod.TransferMethod.TransferMethodFields.TYPE; +import static com.hyperwallet.android.model.transfermethod.TransferMethod.TransferMethodTypes.BANK_ACCOUNT; +import static com.hyperwallet.android.model.transfermethod.TransferMethod.TransferMethodTypes.BANK_CARD; +import static com.hyperwallet.android.model.transfermethod.TransferMethod.TransferMethodTypes.PAPER_CHECK; +import static com.hyperwallet.android.model.transfermethod.TransferMethod.TransferMethodTypes.PAYPAL_ACCOUNT; +import static com.hyperwallet.android.model.transfermethod.TransferMethod.TransferMethodTypes.VENMO_ACCOUNT; +import static com.hyperwallet.android.model.transfermethod.TransferMethod.TransferMethodTypes.WIRE_ACCOUNT; +import static com.hyperwallet.android.ui.common.intent.HyperwalletIntent.EXTRA_TRANSFER_METHOD_UPDATED; +import static com.hyperwallet.android.ui.transfermethod.TransferMethodLocalBroadcast.TransferMethodLocalBroadcastAction.ACTION_HYPERWALLET_TRANSFER_METHOD_UPDATED; +import static com.hyperwallet.android.ui.transfermethod.view.FeeFormatter.isFeeAvailable; +import static com.hyperwallet.android.ui.transfermethod.view.FeeFormatter.isProcessingTimeAvailable; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.os.Bundle; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.view.inputmethod.InputMethodManager; +import android.widget.Button; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.widget.NestedScrollView; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentTransaction; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; + +import com.hyperwallet.android.exception.HyperwalletException; +import com.hyperwallet.android.model.Error; +import com.hyperwallet.android.model.graphql.Fee; +import com.hyperwallet.android.model.graphql.HyperwalletTransferMethodConfigurationField; +import com.hyperwallet.android.model.graphql.ProcessingTime; +import com.hyperwallet.android.model.graphql.field.Field; +import com.hyperwallet.android.model.graphql.field.FieldGroup; +import com.hyperwallet.android.model.graphql.field.TransferMethodConfiguration; +import com.hyperwallet.android.model.transfermethod.BankAccount; +import com.hyperwallet.android.model.transfermethod.BankCard; +import com.hyperwallet.android.model.transfermethod.PaperCheck; +import com.hyperwallet.android.model.transfermethod.PayPalAccount; +import com.hyperwallet.android.model.transfermethod.TransferMethod; +import com.hyperwallet.android.model.transfermethod.VenmoAccount; +import com.hyperwallet.android.ui.R; +import com.hyperwallet.android.ui.common.insight.HyperwalletInsight; +import com.hyperwallet.android.ui.common.util.ErrorTypes; +import com.hyperwallet.android.ui.common.util.PageGroups; +import com.hyperwallet.android.ui.common.view.TransferMethodUtils; +import com.hyperwallet.android.ui.transfermethod.TransferMethodLocalBroadcast; +import com.hyperwallet.android.ui.transfermethod.repository.TransferMethodRepositoryFactory; +import com.hyperwallet.android.ui.transfermethod.view.widget.AbstractWidget; +import com.hyperwallet.android.ui.transfermethod.view.widget.DateChangedListener; +import com.hyperwallet.android.ui.transfermethod.view.widget.DateWidget; +import com.hyperwallet.android.ui.transfermethod.view.widget.WidgetEventListener; +import com.hyperwallet.android.ui.transfermethod.view.widget.WidgetFactory; +import com.hyperwallet.android.ui.transfermethod.view.widget.WidgetInputState; + +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.TreeMap; + +public class UpdateTransferMethodFragment extends Fragment implements WidgetEventListener, + UpdateTransferMethodContract.View { + + public static final String TAG = UpdateTransferMethodActivity.TAG; + + private static final String ARGUMENT_TRANSFER_METHOD_TOKEN = "ARGUMENT_TRANSFER_METHOD_TOKEN"; + private static final String ARGUMENT_SHOW_UPDATE_PROGRESS_BAR = "ARGUMENT_SHOW_UPDATE_PROGRESS_BAR"; + private static final String ARGUMENT_WIDGET_STATE_MAP = "ARGUMENT_WIDGET_STATE_MAP"; + private static final boolean FORCE_UPDATE = false; + private View mUpdateButtonProgressBar; + private Button mUpdateTransferMethodButton; + private ViewGroup mDynamicContainer; + private NestedScrollView mNestedScrollView; + private OnUpdateTransferMethodNetworkErrorCallback mOnUpdateTransferMethodNetworkErrorCallback; + private OnLoadTransferMethodConfigurationFieldsNetworkErrorCallback + mOnLoadTransferMethodConfigurationFieldsNetworkErrorCallback; + private UpdateTransferMethodContract.Presenter mPresenter; + private View mProgressBar; + private boolean mUpdateProgressBar; + private String mTransferMethodType; + private TransferMethod mTransferMethod; + private String mTransferMethodToken; + private HashMap mWidgetInputStateHashMap; + private boolean isEdited; + + /** + * Please do not use this to have instance of UpdateTransferMethodFragment this is reserved for android framework + */ + public UpdateTransferMethodFragment() { + } + + /** + * Creates new instance of UpdateTransferMethodFragment this is the proper initialization of this class + * since the default constructor is reserved for android framework when lifecycle is triggered. + * The parameters in {@link UpdateTransferMethodFragment#newInstance(String)} is mandatory + * and should be supplied with correct data or this fragment will not initialize properly. + * + * @param transferMethodToken the country selected when creating transfer method + */ + public static UpdateTransferMethodFragment newInstance(@NonNull String transferMethodToken) { + UpdateTransferMethodFragment updateTransferMethodFragment = new UpdateTransferMethodFragment(); + Bundle arguments = new Bundle(); + + updateTransferMethodFragment.mTransferMethodToken = transferMethodToken; + updateTransferMethodFragment.mWidgetInputStateHashMap = new HashMap<>(1); + updateTransferMethodFragment.mTransferMethod = null; + arguments.putString(ARGUMENT_TRANSFER_METHOD_TOKEN, transferMethodToken); + arguments.putSerializable(ARGUMENT_WIDGET_STATE_MAP, updateTransferMethodFragment.mWidgetInputStateHashMap); + updateTransferMethodFragment.setArguments(arguments); + + return updateTransferMethodFragment; + } + + @Override + public void onAttach(Context context) { + super.onAttach(context); + + try { + mOnUpdateTransferMethodNetworkErrorCallback = (OnUpdateTransferMethodNetworkErrorCallback) context; + } catch (ClassCastException e) { + throw new ClassCastException(getActivity().toString() + " must implement " + + OnUpdateTransferMethodNetworkErrorCallback.class.getCanonicalName()); + } + + try { + mOnLoadTransferMethodConfigurationFieldsNetworkErrorCallback = + (OnLoadTransferMethodConfigurationFieldsNetworkErrorCallback) context; + } catch (ClassCastException e) { + throw new ClassCastException(getActivity().toString() + " must implement " + + OnLoadTransferMethodConfigurationFieldsNetworkErrorCallback.class.getCanonicalName()); + } + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setRetainInstance(true); + } + + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_update_transfer_method, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + mDynamicContainer = view.findViewById(R.id.update_transfer_method_dynamic_container); + mNestedScrollView = view.findViewById(R.id.update_transfer_method_scroll_view); + + mUpdateButtonProgressBar = view.findViewById(R.id.update_transfer_method_button_progress_bar); + mProgressBar = view.findViewById(R.id.update_transfer_method_progress_bar_layout); + mUpdateTransferMethodButton = view.findViewById(R.id.update_transfer_method_button); + + mUpdateTransferMethodButton.setBackgroundColor(getResources().getColor(R.color.colorPrimary)); + mUpdateTransferMethodButton.setTextColor(getResources().getColor(R.color.regularColorPrimary)); + mUpdateTransferMethodButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + HyperwalletInsight.getInstance().trackClick(requireContext(), + TAG, PageGroups.TRANSFER_METHOD, + HyperwalletInsight.LINK_SELECT_TRANSFER_METHOD_CREATE, + new HyperwalletInsight.TransferMethodParamsBuilder() + .type(mTransferMethodType) + .build()); + + triggerUpdate(); + } + }); + } + + @Override + public void onActivityCreated(@Nullable Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + TransferMethodRepositoryFactory factory = TransferMethodRepositoryFactory.getInstance(); + mPresenter = new UpdateTransferMethodPresenter(this, + factory.getTransferMethodUpdateConfigurationRepository(), + factory.getTransferMethodRepository()); + } + + @Override + public void onViewStateRestored(@Nullable Bundle savedInstanceState) { + super.onViewStateRestored(savedInstanceState); + + if (savedInstanceState != null) { + mWidgetInputStateHashMap = (HashMap) savedInstanceState.getSerializable(ARGUMENT_WIDGET_STATE_MAP); + mTransferMethodToken = savedInstanceState.getString(ARGUMENT_TRANSFER_METHOD_TOKEN); + mUpdateProgressBar = savedInstanceState.getBoolean(ARGUMENT_SHOW_UPDATE_PROGRESS_BAR); + mTransferMethod = savedInstanceState.getParcelable(ARGUMENT_TRANSFER_METHOD_TOKEN); + } else { // same as UpdateTransferMethodFragment#newInstance + mWidgetInputStateHashMap = (HashMap) getArguments().getSerializable(ARGUMENT_WIDGET_STATE_MAP); + mTransferMethodToken = getArguments().getString(ARGUMENT_TRANSFER_METHOD_TOKEN); + } + } + + @Override + public void onResume() { + super.onResume(); + mPresenter.loadTransferMethodConfigurationFields(FORCE_UPDATE, mTransferMethodType, mTransferMethodToken); + } + + @Override + public void showErrorUpdateTransferMethod(@NonNull final List errors) { + mOnUpdateTransferMethodNetworkErrorCallback.showErrorsUpdateTransferMethod(errors); + } + + @Override + public void onSaveInstanceState(@NonNull Bundle outState) { + outState.putSerializable(ARGUMENT_WIDGET_STATE_MAP, mWidgetInputStateHashMap); + outState.putString(ARGUMENT_TRANSFER_METHOD_TOKEN, mTransferMethodToken); + outState.putBoolean(ARGUMENT_SHOW_UPDATE_PROGRESS_BAR, mUpdateProgressBar); + super.onSaveInstanceState(outState); + } + + private void triggerUpdate() { + hideSoftKeys(); + if (performValidation()) { + switch (mTransferMethodType) { + case BANK_ACCOUNT: + mTransferMethod = new BankAccount.Builder() + .token(mTransferMethodToken) + .build(); + break; + case BANK_CARD: + mTransferMethod = new BankCard.Builder() + .token(mTransferMethodToken) + .build(); + break; + case PAYPAL_ACCOUNT: + mTransferMethod = new PayPalAccount.Builder() + .token(mTransferMethodToken) + .build(); + break; + case WIRE_ACCOUNT: + mTransferMethod = new BankAccount.Builder() + .token(mTransferMethodToken) + .transferMethodType(WIRE_ACCOUNT) + .build(); + break; + case VENMO_ACCOUNT: + mTransferMethod = new VenmoAccount.Builder() + .token(mTransferMethodToken) + .build(); + break; + case PAPER_CHECK: + mTransferMethod = new PaperCheck.Builder() + .token(mTransferMethodToken) + .build(); + break; + default: + mTransferMethod = new TransferMethod(); + mTransferMethod.setField(TYPE, mTransferMethodType); + } + + for (int i = 0; i < mDynamicContainer.getChildCount(); i++) { + View view = mDynamicContainer.getChildAt(i); + if (view.getTag() instanceof AbstractWidget) { + AbstractWidget widget = (AbstractWidget) view.getTag(); + if (widget.isEdited) { + isEdited = true; + mTransferMethod.setField(widget.getName(), widget.getValue()); + } + } + } + + if (isEdited) { + mPresenter.updateTransferMethod(mTransferMethod); + } else { + getActivity().finish(); + } + } + } + + private void hideSoftKeys() { + View view = requireActivity().getCurrentFocus(); + + if (view != null) { + view.clearFocus(); + InputMethodManager inputMethodManager = (InputMethodManager) view.getContext().getSystemService( + Activity.INPUT_METHOD_SERVICE); + inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0); + } + } + + /** + * Use this to perform validation on an entire form, typically used during form submission. + * + * @return true if the form is valid + */ + private boolean performValidation() { + boolean containsInvalidWidget = false; + + // this is added since some phones triggers the create button but the widgets are not yet initialized + boolean hasWidget = false; + Resources resources = requireContext().getResources(); + int pixels = (int) (resources.getDimension(R.dimen.negative_padding) * resources.getDisplayMetrics().density); + + for (int i = 0; i < mDynamicContainer.getChildCount(); i++) { + View currentView = mDynamicContainer.getChildAt(i); + if (currentView.getTag() instanceof AbstractWidget) { + hasWidget = true; + + AbstractWidget widget = (AbstractWidget) currentView.getTag(); + WidgetInputState widgetInputState = mWidgetInputStateHashMap.get(widget.getName()); + widgetInputState.setValue(widget.getValue()); + + if (!isWidgetItemValid(widget) && !containsInvalidWidget) { + containsInvalidWidget = true; + mNestedScrollView.smoothScrollTo(0, currentView.getTop() - pixels); + } + } + } + return hasWidget && !containsInvalidWidget; + } + + /** + * Use this to perform validation on a single widget item, typically used while the user is inputting data. + * + * @param widget the widget to validate + * @return true if the input is valid + */ + private boolean isWidgetItemValid(@NonNull final AbstractWidget widget) { + boolean valid = true; + Context context = requireContext(); + + WidgetInputState widgetInputState = mWidgetInputStateHashMap.get(widget.getName()); + widgetInputState.setValue(widget.getValue()); + if (widget.isValid()) { + if (!widgetInputState.hasApiError()) { + widgetInputState.setErrorMessage(null); + widget.showValidationError(null); + } + } else { + HyperwalletInsight.getInstance().trackError(context, + TAG, PageGroups.TRANSFER_METHOD, + new HyperwalletInsight.ErrorParamsBuilder() + .message(widget.getErrorMessage()) + .fieldName(widget.getName()) + .type(ErrorTypes.FORM_ERROR) + .addAll(new HyperwalletInsight.TransferMethodParamsBuilder() + .type(mTransferMethodType) + .build()) + .build()); + + valid = false; + widget.showValidationError(null); + widgetInputState.setErrorMessage(null); + widget.showValidationError(widget.getErrorMessage()); + widgetInputState.setErrorMessage(widget.getErrorMessage()); + widgetInputState.setHasApiError(false); + } + return valid; + } + + @Override + public void notifyTransferMethodUpdated(@NonNull TransferMethod transferMethod) { + HyperwalletInsight.getInstance().trackImpression(requireContext(), + TAG, PageGroups.TRANSFER_METHOD, + new HyperwalletInsight.TransferMethodParamsBuilder() + .goal(HyperwalletInsight.TRANSFER_METHOD_GOAL) + .type(mTransferMethodType) + .build()); + + Intent intent = TransferMethodLocalBroadcast.createBroadcastIntentTransferMethodUpdated( + transferMethod); + LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent); + + Intent activityResult = new Intent(); + activityResult.setAction(ACTION_HYPERWALLET_TRANSFER_METHOD_UPDATED); + activityResult.putExtra(EXTRA_TRANSFER_METHOD_UPDATED, transferMethod); + getActivity().setResult(Activity.RESULT_OK, activityResult); + getActivity().finish(); + } + + @Override + public void showErrorLoadTransferMethodConfigurationFields(@NonNull List errors) { + mOnLoadTransferMethodConfigurationFieldsNetworkErrorCallback.showErrorsLoadTransferMethodConfigurationFields( + errors); + } + + private String getSectionHeaderText(@NonNull final FieldGroup group, @NonNull final Locale locale, + @NonNull final String currency) { + if (FieldGroup.GroupTypes.ACCOUNT_INFORMATION.equals(group.getGroupName())) { + return requireContext().getString(R.string.account_information, + locale.getDisplayName().toUpperCase(), currency); + } + + return requireContext().getString(requireContext().getResources() + .getIdentifier(group.getGroupName().toLowerCase(Locale.ROOT), "string", + requireContext().getPackageName())); + } + + @Override + public void showTransactionInformation(@NonNull List fees, + @Nullable ProcessingTime processingTime) { + View header = getView().findViewById(R.id.update_transfer_method_static_container_header); + View container = getView().findViewById(R.id.update_transfer_method_static_container); + TextView feeAndProcessingTime = getView().findViewById(R.id.update_transfer_method_information); + + if (isFeeAvailable(fees) && isProcessingTimeAvailable(processingTime)) { + String formattedFee = FeeFormatter.getFormattedFee(header.getContext(), fees); + feeAndProcessingTime.setVisibility(View.VISIBLE); + feeAndProcessingTime.setText( + feeAndProcessingTime.getContext().getString(R.string.feeAndProcessingTimeInformation, formattedFee, + processingTime.getValue())); + } else if (isFeeAvailable(fees) && !isProcessingTimeAvailable(processingTime)) { + String formattedFee = FeeFormatter.getFormattedFee(header.getContext(), fees); + feeAndProcessingTime.setVisibility(View.VISIBLE); + feeAndProcessingTime.setText( + feeAndProcessingTime.getContext().getString(R.string.feeInformation, formattedFee)); + } else if (isProcessingTimeAvailable(processingTime) && !isFeeAvailable(fees)) { + feeAndProcessingTime.setVisibility(View.VISIBLE); + feeAndProcessingTime.setText(processingTime.getValue()); + } else { + feeAndProcessingTime.setVisibility(View.GONE); + } + + if (feeAndProcessingTime.getVisibility() == View.VISIBLE) { + header.setVisibility(View.VISIBLE); + container.setVisibility(View.VISIBLE); + } else { + header.setVisibility(View.GONE); + container.setVisibility(View.GONE); + } + } + + @Override + public void showTransferMethodFields( + @NonNull HyperwalletTransferMethodConfigurationField hyperwalletTransferMethodConfigurationField) { + mDynamicContainer.removeAllViews(); + mUpdateTransferMethodButton.setVisibility(View.VISIBLE); + + try { + TransferMethodConfiguration fields = hyperwalletTransferMethodConfigurationField.getFields(); + Locale locale = new Locale.Builder().setRegion(fields.getCountry()).build(); + mTransferMethodType = fields.getTransferMethodType(); + String transferMethod = TransferMethodUtils.getTransferMethodName(getContext(), mTransferMethodType); + ((UpdateTransferMethodActivity) getActivity()).getSupportActionBar().setTitle(transferMethod); + // group + for (FieldGroup group : fields.getFieldGroups()) { + View sectionHeader = LayoutInflater.from(mDynamicContainer.getContext()) + .inflate(R.layout.item_widget_section_header, mDynamicContainer, false); + TextView sectionTitle = sectionHeader.findViewById(R.id.section_header_title); + sectionTitle.setText(getSectionHeaderText(group, locale, fields.getCurrency())); + sectionHeader.setId(View.generateViewId()); + mDynamicContainer.addView(sectionHeader); + + // group fields + for (final Field field : group.getFields()) { + AbstractWidget widget = WidgetFactory + .newWidget(field, this, mWidgetInputStateHashMap.containsKey(field.getName()) ? + mWidgetInputStateHashMap.get(field.getName()).getValue() : field.getValue(), + mUpdateTransferMethodButton); + if (mWidgetInputStateHashMap.isEmpty() || !mWidgetInputStateHashMap.containsKey(widget.getName())) { + mWidgetInputStateHashMap.put(widget.getName(), widget.getWidgetInputState()); + } + + View widgetView = widget.getView(mDynamicContainer); + widgetView.setTag(widget); + widgetView.setId(View.generateViewId()); + final String error = mWidgetInputStateHashMap.get(widget.getName()).getErrorMessage(); + widget.showValidationError(error); + mDynamicContainer.addView(widgetView); + } + } + + HyperwalletInsight.getInstance().trackImpression(requireContext(), + TAG, PageGroups.TRANSFER_METHOD, + new HyperwalletInsight.TransferMethodParamsBuilder() + .type(mTransferMethodType) + .build()); + + if (mUpdateProgressBar) { + setVisibleAndDisableFields(); + } + } catch (HyperwalletException e) { + throw new IllegalStateException("Widget initialization error: " + e.getMessage()); + } + } + + @Override + public void showUpdateButtonProgressBar() { + mUpdateProgressBar = true; + setVisibleAndDisableFields(); + } + + private void setVisibleAndDisableFields() { + getActivity().getWindow().setFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE, + WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE); + getActivity().getWindow().setFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE); + mUpdateButtonProgressBar.setVisibility(View.VISIBLE); + mUpdateTransferMethodButton.setBackgroundColor(getResources().getColor(R.color.colorSecondaryDark)); + } + + @Override + public void hideUpdateButtonProgressBar() { + mUpdateProgressBar = false; + getActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE); + getActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE); + mUpdateButtonProgressBar.setVisibility(View.GONE); + mUpdateTransferMethodButton.setBackgroundColor(getResources().getColor(R.color.colorPrimary)); + mUpdateTransferMethodButton.setTextColor(getResources().getColor(R.color.regularColorPrimary)); + } + + @Override + public void showProgressBar() { + mProgressBar.setVisibility(View.VISIBLE); + } + + @Override + public void hideProgressBar() { + mProgressBar.setVisibility(View.GONE); + mUpdateTransferMethodButton.setVisibility(View.VISIBLE); + } + + @Override + public void showInputErrors(@NonNull List errors) { + boolean focusSet = false; + Context context = requireContext(); + Resources resources = context.getResources(); + int pixels = (int) (resources.getDimension(R.dimen.negative_padding) * resources.getDisplayMetrics().density); + + for (Error error : errors) { + for (int i = 0; i < mDynamicContainer.getChildCount(); i++) { + View view = mDynamicContainer.getChildAt(i); + if (view.getTag() instanceof AbstractWidget) { + AbstractWidget widget = (AbstractWidget) view.getTag(); + WidgetInputState widgetInputState = mWidgetInputStateHashMap.get(widget.getName()); + if (widget.getName().equals(error.getFieldName())) { + if (!focusSet) { + mNestedScrollView.smoothScrollTo(0, view.getTop() - pixels); + focusSet = true; + } + HyperwalletInsight.getInstance().trackError(context, + TAG, PageGroups.TRANSFER_METHOD, + new HyperwalletInsight.ErrorParamsBuilder() + .code(error.getCode()) + .message(error.getMessage()) + .fieldName(error.getFieldName()) + .type(ErrorTypes.API_ERROR) + .build()); + + widget.showValidationError(null); + widgetInputState.setErrorMessage(null); + widget.showValidationError(error.getMessage()); + widgetInputState.setErrorMessage(error.getMessage()); + widgetInputState.setHasApiError(true); + } else { + widget.showValidationError(null); + widgetInputState.setErrorMessage(null); + } + } + } + } + + mPresenter.handleUnmappedFieldError(mWidgetInputStateHashMap, errors); + } + + @Override + public boolean isActive() { + return isAdded(); + } + + @Override + public void retryUpdateTransferMethod() { + mPresenter.updateTransferMethod(mTransferMethod); + } + + @Override + public void reloadTransferMethodConfigurationFields() { + mPresenter.loadTransferMethodConfigurationFields(FORCE_UPDATE, mTransferMethodType, mTransferMethodToken); + } + + @Override + public void valueChanged(@NonNull AbstractWidget widget) { + isWidgetItemValid(widget); + } + + @Override + public boolean isWidgetSelectionFragmentDialogOpen() { + return getFragmentManager().findFragmentByTag(WidgetSelectionDialogFragment.TAG) != null; + } + + @Override + public void openWidgetSelectionFragmentDialog(@NonNull TreeMap nameValueMap, + @NonNull String selectedName, @NonNull String fieldLabel, @NonNull String fieldName) { + String selectedLabel = selectedName; + if (TextUtils.isEmpty(selectedLabel)) { + selectedLabel = mWidgetInputStateHashMap.get(fieldName).getSelectedName(); + } else { + mWidgetInputStateHashMap.get(fieldName).setSelectedName(selectedLabel); + } + + if (!isWidgetSelectionFragmentDialogOpen()) { + WidgetSelectionDialogFragment widgetSelectionDialogFragment = WidgetSelectionDialogFragment + .newInstance(nameValueMap, selectedLabel, fieldLabel, fieldName); + + FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction(); + fragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); + fragmentTransaction.replace(android.R.id.content, widgetSelectionDialogFragment, + WidgetSelectionDialogFragment.TAG); + fragmentTransaction.addToBackStack(WidgetSelectionDialogFragment.TAG); + fragmentTransaction.commit(); + } + } + + @Override + public void widgetFocused(@NonNull String fieldName) { + WidgetInputState widgetInputState = mWidgetInputStateHashMap.get(fieldName); + widgetInputState.setHasFocused(true); + } + + @Override + public void saveTextChanged(@NonNull String fieldName, @NonNull String value) { + WidgetInputState inputState = mWidgetInputStateHashMap.get(fieldName); + if (inputState.hasApiError()) { + String oldValue = inputState.getValue(); + if (!TextUtils.isEmpty(oldValue) && !oldValue.equals(value)) { + inputState.setHasApiError(false); + } + } + inputState.setValue(value); + } + + void onWidgetSelectionItemClicked(@NonNull final String selectedValue, @NonNull final String fieldName) { + for (int i = 0; i < mDynamicContainer.getChildCount(); i++) { + View view = mDynamicContainer.getChildAt(i); + if (view.getTag() instanceof WidgetSelectionDialogFragment.WidgetSelectionItemType) { + AbstractWidget widget = (AbstractWidget) view.getTag(); + if (fieldName.equals(widget.getName())) { + ((WidgetSelectionDialogFragment.WidgetSelectionItemType) view.getTag()) + .onWidgetSelectionItemClicked(selectedValue); + return; + } + } + } + } + + @Override + public void openWidgetDateDialog(@Nullable String date, @NonNull String fieldName) { + if (getFragmentManager() != null) { + WidgetDateDialogFragment dateDialogFragment = (WidgetDateDialogFragment) + getFragmentManager().findFragmentByTag(WidgetDateDialogFragment.TAG); + + if (dateDialogFragment == null) { + dateDialogFragment = WidgetDateDialogFragment.newInstance(date, fieldName); + } + + if (!dateDialogFragment.isAdded()) { + dateDialogFragment.show(getFragmentManager()); + } + } + } + + void onDateSelected(@NonNull final String selectedValue, @NonNull final String fieldName) { + for (int i = 0; i < mDynamicContainer.getChildCount(); i++) { + View view = mDynamicContainer.getChildAt(i); + if (view.getTag() instanceof DateWidget) { + AbstractWidget widget = (AbstractWidget) view.getTag(); + if (fieldName.equals(widget.getName()) && widget instanceof DateChangedListener) { + ((DateChangedListener) view.getTag()).onUpdate(selectedValue); + return; + } + } + } + } + + interface OnLoadTransferMethodConfigurationFieldsNetworkErrorCallback { + void showErrorsLoadTransferMethodConfigurationFields(@NonNull final List errors); + } + + interface OnUpdateTransferMethodNetworkErrorCallback { + void showErrorsUpdateTransferMethod(@NonNull final List errors); + } +} diff --git a/transfermethodui/src/main/java/com/hyperwallet/android/ui/transfermethod/view/UpdateTransferMethodPresenter.java b/transfermethodui/src/main/java/com/hyperwallet/android/ui/transfermethod/view/UpdateTransferMethodPresenter.java new file mode 100644 index 000000000..3194f6055 --- /dev/null +++ b/transfermethodui/src/main/java/com/hyperwallet/android/ui/transfermethod/view/UpdateTransferMethodPresenter.java @@ -0,0 +1,109 @@ +package com.hyperwallet.android.ui.transfermethod.view; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.hyperwallet.android.model.Error; +import com.hyperwallet.android.model.Errors; +import com.hyperwallet.android.model.graphql.HyperwalletTransferMethodConfigurationField; +import com.hyperwallet.android.model.transfermethod.TransferMethod; +import com.hyperwallet.android.ui.R; +import com.hyperwallet.android.ui.transfermethod.repository.TransferMethodRepository; +import com.hyperwallet.android.ui.transfermethod.repository.TransferMethodUpdateConfigurationRepository; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class UpdateTransferMethodPresenter implements UpdateTransferMethodContract.Presenter { + private static final String ERROR_UNMAPPED_FIELD = "ERROR_UNMAPPED_FIELD"; + private final TransferMethodUpdateConfigurationRepository mTransferMethodUpdateConfigurationRepository; + private final TransferMethodRepository mTransferMethodRepository; + private final UpdateTransferMethodContract.View mView; + + public UpdateTransferMethodPresenter(UpdateTransferMethodContract.View view, + TransferMethodUpdateConfigurationRepository transferMethodUpdateConfigurationRepository, + TransferMethodRepository transferMethodRepository) { + mView = view; + mTransferMethodUpdateConfigurationRepository = transferMethodUpdateConfigurationRepository; + mTransferMethodRepository = transferMethodRepository; + } + + @Override + public void loadTransferMethodConfigurationFields(boolean forceUpdate, @NonNull String transferMethodType, + @NonNull final String transferMethodToken) { + mView.showProgressBar(); + + if (forceUpdate) { + mTransferMethodUpdateConfigurationRepository.refreshFields(); + } + + mTransferMethodUpdateConfigurationRepository.getFields(transferMethodToken, + new TransferMethodUpdateConfigurationRepository.LoadFieldsCallback() { + @Override + public void onFieldsLoaded(@Nullable HyperwalletTransferMethodConfigurationField field) { + if (!mView.isActive()) { + return; + } + + mView.hideProgressBar(); + mView.showTransferMethodFields(field); + // there can be multiple fees when we have flat fee + percentage fees + mView.showTransactionInformation(field.getFees(), field.getProcessingTime()); + } + + @Override + public void onError(@NonNull Errors errors) { + if (!mView.isActive()) { + return; + } + mView.hideProgressBar(); + mView.showErrorLoadTransferMethodConfigurationFields(errors.getErrors()); + } + }); + } + + @Override + public void updateTransferMethod(@NonNull TransferMethod transferMethod) { + mView.showUpdateButtonProgressBar(); + mTransferMethodRepository.updateTransferMethod(transferMethod, + new TransferMethodRepository.LoadTransferMethodCallback() { + @Override + public void onTransferMethodLoaded(TransferMethod transferMethod) { + + if (!mView.isActive()) { + return; + } + mView.hideUpdateButtonProgressBar(); + mView.notifyTransferMethodUpdated(transferMethod); + } + + @Override + public void onError(Errors errors) { + if (!mView.isActive()) { + return; + } + + mView.hideUpdateButtonProgressBar(); + if (errors.containsInputError()) { + mView.showInputErrors(errors.getErrors()); + } else { + mView.showErrorUpdateTransferMethod(errors.getErrors()); + } + } + }); + } + + @Override + public void handleUnmappedFieldError(@NonNull Map fieldSet, @NonNull List errors) { + for (Error error : errors) { + if (fieldSet.get(error.getFieldName()) == null) { + List errorList = new ArrayList() {{ + add(new Error(R.string.error_unmapped_field, ERROR_UNMAPPED_FIELD)); + }}; + mView.showErrorUpdateTransferMethod(errorList); + return; + } + } + } +} diff --git a/transfermethodui/src/main/java/com/hyperwallet/android/ui/transfermethod/view/widget/AbstractMaskedInputWidget.java b/transfermethodui/src/main/java/com/hyperwallet/android/ui/transfermethod/view/widget/AbstractMaskedInputWidget.java index 72cf67afa..87218b628 100644 --- a/transfermethodui/src/main/java/com/hyperwallet/android/ui/transfermethod/view/widget/AbstractMaskedInputWidget.java +++ b/transfermethodui/src/main/java/com/hyperwallet/android/ui/transfermethod/view/widget/AbstractMaskedInputWidget.java @@ -69,7 +69,7 @@ String formatToApi(@NonNull final String displayValue) { * @return a String formatted to the specification in the mask pattern */ String formatToDisplay(@NonNull final String apiValue) { - if (mField != null && mField.getMask() != null) { + if (mField != null && mField.getMask() != null && !mField.isFieldValueMasked()) { // format String pattern = mField.getMask().getPattern(apiValue); if (!TextUtils.isEmpty(pattern)) { diff --git a/transfermethodui/src/main/java/com/hyperwallet/android/ui/transfermethod/view/widget/AbstractWidget.java b/transfermethodui/src/main/java/com/hyperwallet/android/ui/transfermethod/view/widget/AbstractWidget.java index 3db0c5618..0e9cb8311 100644 --- a/transfermethodui/src/main/java/com/hyperwallet/android/ui/transfermethod/view/widget/AbstractWidget.java +++ b/transfermethodui/src/main/java/com/hyperwallet/android/ui/transfermethod/view/widget/AbstractWidget.java @@ -36,6 +36,7 @@ public abstract class AbstractWidget { protected final WidgetEventListener mListener; protected int mBottomViewId = 0; protected WidgetInputState mWidgetInputState; + public boolean isEdited = false; public AbstractWidget(@Nullable Field field, @NonNull WidgetEventListener listener, @Nullable String defaultValue, @NonNull View defaultFocusView) { @@ -64,6 +65,9 @@ public boolean isValid() { if (mField == null) { return true; } + else if(!isEdited && mField.isFieldValueMasked()) { + return true; + } return !isInvalidEmptyValue() && !isInvalidLength() && !isInvalidRegex(); } @@ -163,6 +167,7 @@ public DefaultKeyListener(View focusView, View clearFocusView) { @Override public boolean onKey(View v, int keyCode, KeyEvent event) { + isEdited = true; if (event.getAction() == KeyEvent.ACTION_DOWN) { switch (keyCode) { case KeyEvent.KEYCODE_DPAD_CENTER: diff --git a/transfermethodui/src/main/java/com/hyperwallet/android/ui/transfermethod/view/widget/ExpireDateUtils.java b/transfermethodui/src/main/java/com/hyperwallet/android/ui/transfermethod/view/widget/ExpireDateUtils.java index d26dbd81b..697c890de 100644 --- a/transfermethodui/src/main/java/com/hyperwallet/android/ui/transfermethod/view/widget/ExpireDateUtils.java +++ b/transfermethodui/src/main/java/com/hyperwallet/android/ui/transfermethod/view/widget/ExpireDateUtils.java @@ -154,8 +154,11 @@ private Calendar getInputDate(@NonNull final String input) throws ParseException //get month from server month part private String getMonthFromServer(String[] splitDate) { - return splitDate.length != 2 ? "" : - splitDate[1].length() == 1 && Integer.parseInt(splitDate[1]) > 1 ? - ZERO.concat(splitDate[1]) : splitDate[1]; + if (splitDate.length >= 2) { + return splitDate[1].length() == 1 && Integer.parseInt(splitDate[1]) > 1 ? ZERO.concat(splitDate[1]) + : splitDate[1]; + } else { + return ""; + } } } \ No newline at end of file diff --git a/transfermethodui/src/main/java/com/hyperwallet/android/ui/transfermethod/view/widget/NumberWidget.java b/transfermethodui/src/main/java/com/hyperwallet/android/ui/transfermethod/view/widget/NumberWidget.java index 2b1e5a70e..690347109 100644 --- a/transfermethodui/src/main/java/com/hyperwallet/android/ui/transfermethod/view/widget/NumberWidget.java +++ b/transfermethodui/src/main/java/com/hyperwallet/android/ui/transfermethod/view/widget/NumberWidget.java @@ -57,9 +57,9 @@ public View getView(@NonNull final ViewGroup viewGroup) { editText.setTextColor(viewGroup.getContext().getResources().getColor(R.color.regularColorSecondary)); editText.setEnabled(mField.isEditable()); + editText.setSelectAllOnFocus(mField.isFieldValueMasked()); setIdFromFieldLabel(mTextInputLayout); setIdFromFieldName(editText); - editText.setOnFocusChangeListener(new View.OnFocusChangeListener() { @Override public void onFocusChange(View v, boolean hasFocus) { diff --git a/transfermethodui/src/main/java/com/hyperwallet/android/ui/transfermethod/view/widget/SelectionWidget.java b/transfermethodui/src/main/java/com/hyperwallet/android/ui/transfermethod/view/widget/SelectionWidget.java index 2fe33c069..93510d78f 100644 --- a/transfermethodui/src/main/java/com/hyperwallet/android/ui/transfermethod/view/widget/SelectionWidget.java +++ b/transfermethodui/src/main/java/com/hyperwallet/android/ui/transfermethod/view/widget/SelectionWidget.java @@ -144,6 +144,7 @@ public void onWidgetSelectionItemClicked(@NonNull String selectedValue) { mListener.valueChanged(SelectionWidget.this); mEditText.setText(getKeyFromValue(selectedValue)); mEditText.requestFocus(); + isEdited = true; } private void hideSoftKey(@NonNull View focusedView) { diff --git a/transfermethodui/src/main/java/com/hyperwallet/android/ui/transfermethod/view/widget/TextWidget.java b/transfermethodui/src/main/java/com/hyperwallet/android/ui/transfermethod/view/widget/TextWidget.java index 4e8994718..7b99fbacb 100644 --- a/transfermethodui/src/main/java/com/hyperwallet/android/ui/transfermethod/view/widget/TextWidget.java +++ b/transfermethodui/src/main/java/com/hyperwallet/android/ui/transfermethod/view/widget/TextWidget.java @@ -54,7 +54,7 @@ public View getView(@NonNull final ViewGroup viewGroup) { new ContextThemeWrapper(viewGroup.getContext(), R.style.Widget_Hyperwallet_TextInputEditText)); editText.setTextColor(viewGroup.getContext().getResources().getColor(R.color.regularColorSecondary)); editText.setEnabled(mField.isEditable()); - + editText.setSelectAllOnFocus(mField.isFieldValueMasked()); mTextInputLayout.setHint(mField.getLabel()); setIdFromFieldLabel(mTextInputLayout); setIdFromFieldName(editText); diff --git a/transfermethodui/src/main/res/layout/activity_update_transfer_method.xml b/transfermethodui/src/main/res/layout/activity_update_transfer_method.xml new file mode 100644 index 000000000..35a86b7d2 --- /dev/null +++ b/transfermethodui/src/main/res/layout/activity_update_transfer_method.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/transfermethodui/src/main/res/layout/fragment_update_transfer_method.xml b/transfermethodui/src/main/res/layout/fragment_update_transfer_method.xml new file mode 100644 index 000000000..47ee191f6 --- /dev/null +++ b/transfermethodui/src/main/res/layout/fragment_update_transfer_method.xml @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + +