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/test/java/com/hyperwallet/android/ui/transfermethod/repository/TransferMethodUpdateConfigurationRepositoryImplTest.java b/transfermethodrepository/src/test/java/com/hyperwallet/android/ui/transfermethod/repository/TransferMethodUpdateConfigurationRepositoryImplTest.java index bd55524c5..264e1d22c 100644 --- 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 @@ -150,6 +150,21 @@ public Object answer(InvocationOnMock invocation) { 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() { + }); + + FieldMapKey fieldMapKey = new FieldMapKey(TRANSFER_METHOD_TYPE); + when(mFieldsMap.get(fieldMapKey)).thenReturn(result); + + mTransferMethodUpdateConfigurationRepositoryImplMock.getFields(TRANSFER_METHOD_TYPE,TRANSFER_TOKEN, loadFieldsCallback); + verify(loadFieldsCallback, never()).onError(any(Errors.class)); + } + @Test public void testRefreshFields_clearsFieldMapWhenNotEmpty() throws Exception { String responseBody = externalResourceManager.getResourceContent( 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..d2fedfc65 --- /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 class 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..7b920f6b6 --- /dev/null +++ b/transfermethodui/src/main/java/com/hyperwallet/android/ui/transfermethod/view/UpdateTransferMethodPresenter.java @@ -0,0 +1,110 @@ +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(transferMethodType, + 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 d0fb1c01e..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,7 +36,7 @@ public abstract class AbstractWidget { protected final WidgetEventListener mListener; protected int mBottomViewId = 0; protected WidgetInputState mWidgetInputState; - public Boolean isEdited = false; + public boolean isEdited = false; public AbstractWidget(@Nullable Field field, @NonNull WidgetEventListener listener, @Nullable String defaultValue, @NonNull View defaultFocusView) { @@ -65,6 +65,9 @@ public boolean isValid() { if (mField == null) { return true; } + else if(!isEdited && mField.isFieldValueMasked()) { + return true; + } return !isInvalidEmptyValue() && !isInvalidLength() && !isInvalidRegex(); } @@ -164,8 +167,8 @@ public DefaultKeyListener(View focusView, View clearFocusView) { @Override public boolean onKey(View v, int keyCode, KeyEvent event) { + isEdited = true; if (event.getAction() == KeyEvent.ACTION_DOWN) { - isEdited = true; switch (keyCode) { case KeyEvent.KEYCODE_DPAD_CENTER: case KeyEvent.KEYCODE_ENTER: 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 5e872b011..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,6 +57,7 @@ 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() { 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 12f496f0f..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,6 +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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + +