From 4d881e4b22e61913bd2439fba6901bf6cc0e98c5 Mon Sep 17 00:00:00 2001 From: calderwoodra Date: Fri, 12 Jan 2018 12:25:48 -0800 Subject: Added Dialpad to NUI. This change adds the existing dialpad to the new UI and the animation logic required to show/hide it along with the toolbar. A follow up CL will come afterwards to combine these animations with the toolbar expanding/collapsing. known issue: bottom nav appears over the dialpad. Bug: 181512198 Test: MainActivityTest PiperOrigin-RevId: 181777370 Change-Id: Ief1591174ebca3a709df6d1d38c8b8ecbdc1878e --- .../dialer/dialpadview/DialpadFragment.java | 56 +++++++ .../com/android/dialer/main/impl/MainActivity.java | 172 +++++++++++++++++++-- .../main/impl/res/layout/bottom_nav_item.xml | 3 +- .../dialer/main/impl/res/layout/main_activity.xml | 19 ++- .../dialer/main/impl/res/values/strings.xml | 3 + .../android/dialer/main/impl/res/values/styles.xml | 3 + .../dialer/main/impl/toolbar/MainToolbar.java | 39 +++++ 7 files changed, 278 insertions(+), 17 deletions(-) (limited to 'java/com') diff --git a/java/com/android/dialer/dialpadview/DialpadFragment.java b/java/com/android/dialer/dialpadview/DialpadFragment.java index 0418009d7..07e401cab 100644 --- a/java/com/android/dialer/dialpadview/DialpadFragment.java +++ b/java/com/android/dialer/dialpadview/DialpadFragment.java @@ -26,6 +26,7 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.res.Configuration; import android.content.res.Resources; import android.database.Cursor; import android.graphics.Bitmap; @@ -60,6 +61,9 @@ import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import android.view.animation.Animation; +import android.view.animation.Animation.AnimationListener; +import android.view.animation.AnimationUtils; import android.widget.AdapterView; import android.widget.BaseAdapter; import android.widget.EditText; @@ -74,6 +78,7 @@ import com.android.dialer.animation.AnimUtils; import com.android.dialer.callintent.CallInitiationType; import com.android.dialer.callintent.CallIntentBuilder; import com.android.dialer.calllogutils.PhoneAccountUtils; +import com.android.dialer.common.Assert; import com.android.dialer.common.FragmentUtils; import com.android.dialer.common.LogUtil; import com.android.dialer.common.concurrent.DialerExecutor; @@ -89,6 +94,7 @@ import com.android.dialer.proguard.UsedByReflection; import com.android.dialer.telecom.TelecomUtil; import com.android.dialer.util.CallUtil; import com.android.dialer.util.PermissionsUtil; +import com.android.dialer.util.ViewUtil; import com.android.dialer.widget.FloatingActionButtonController; import com.google.common.base.Ascii; import com.google.common.base.Optional; @@ -177,7 +183,11 @@ public class DialpadFragment extends Fragment private boolean firstLaunch = false; private boolean animate = false; + private boolean isLayoutRtl; + private boolean isLandscape; + private DialerExecutor initPhoneNumberFormattingTextWatcherExecutor; + private boolean isDialpadSlideUp; /** * Determines whether an add call operation is requested. @@ -426,6 +436,14 @@ public class DialpadFragment extends Fragment return fragmentView; } + @Override + public void onAttach(Context context) { + super.onAttach(context); + isLayoutRtl = ViewUtil.isRtl(); + isLandscape = + getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE; + } + private String getCurrentCountryIso() { if (currentCountryIsoForTesting.isPresent()) { return currentCountryIsoForTesting.get(); @@ -1508,6 +1526,44 @@ public class DialpadFragment extends Fragment } } + /** Animate the dialpad down off the screen. */ + public void slideDown(boolean animate, AnimationListener listener) { + Assert.checkArgument(isDialpadSlideUp); + isDialpadSlideUp = false; + int animation; + if (isLandscape) { + animation = isLayoutRtl ? R.anim.dialpad_slide_out_left : R.anim.dialpad_slide_out_right; + } else { + animation = R.anim.dialpad_slide_out_bottom; + } + Animation slideDown = AnimationUtils.loadAnimation(getContext(), animation); + slideDown.setInterpolator(AnimUtils.EASE_OUT); + slideDown.setAnimationListener(listener); + slideDown.setDuration(animate ? dialpadSlideInDuration : 0); + getView().startAnimation(slideDown); + } + + /** Animate the dialpad up onto the screen. */ + public void slideUp(boolean animate) { + Assert.checkArgument(!isDialpadSlideUp); + isDialpadSlideUp = true; + int animation; + if (isLandscape) { + animation = isLayoutRtl ? R.anim.dialpad_slide_in_left : R.anim.dialpad_slide_in_right; + } else { + animation = R.anim.dialpad_slide_in_bottom; + } + Animation slideUp = AnimationUtils.loadAnimation(getContext(), animation); + slideUp.setInterpolator(AnimUtils.EASE_IN); + slideUp.setDuration(animate ? dialpadSlideInDuration : 0); + getView().startAnimation(slideUp); + } + + /** Returns the text in the dialpad */ + public String getQuery() { + return digits.getText().toString(); + } + public interface OnDialpadQueryChangedListener { void onDialpadQueryChanged(String query); diff --git a/java/com/android/dialer/main/impl/MainActivity.java b/java/com/android/dialer/main/impl/MainActivity.java index fc7e670b0..8bdb295c7 100644 --- a/java/com/android/dialer/main/impl/MainActivity.java +++ b/java/com/android/dialer/main/impl/MainActivity.java @@ -21,24 +21,35 @@ import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.provider.ContactsContract.QuickContact; +import android.support.design.widget.FloatingActionButton; import android.support.v4.app.FragmentTransaction; import android.support.v7.app.AppCompatActivity; -import android.view.View; +import android.text.TextUtils; +import android.view.animation.Animation; +import android.view.animation.Animation.AnimationListener; import android.widget.ImageView; import com.android.dialer.calllog.ui.NewCallLogFragment; +import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; import com.android.dialer.contactsfragment.ContactsFragment; import com.android.dialer.contactsfragment.ContactsFragment.Header; import com.android.dialer.contactsfragment.ContactsFragment.OnContactSelectedListener; +import com.android.dialer.dialpadview.DialpadFragment; +import com.android.dialer.dialpadview.DialpadFragment.DialpadListener; +import com.android.dialer.dialpadview.DialpadFragment.LastOutgoingCallCallback; +import com.android.dialer.dialpadview.DialpadFragment.OnDialpadQueryChangedListener; import com.android.dialer.main.impl.BottomNavBar.OnBottomNavTabSelectedListener; import com.android.dialer.main.impl.toolbar.MainToolbar; import com.android.dialer.main.impl.toolbar.SearchBarListener; +import com.android.dialer.searchfragment.list.NewSearchFragment; import com.android.dialer.speeddial.SpeedDialFragment; import com.android.dialer.voicemail.listui.NewVoicemailFragment; /** This is the main activity for dialer. It hosts favorites, call log, search, dialpad, etc... */ public final class MainActivity extends AppCompatActivity - implements View.OnClickListener, OnContactSelectedListener { + implements OnContactSelectedListener, OnDialpadQueryChangedListener, DialpadListener { + + private SearchController searchController; /** * @param context Context of the application package implementing MainActivity class. @@ -59,22 +70,20 @@ public final class MainActivity extends AppCompatActivity } private void initLayout() { - findViewById(R.id.fab).setOnClickListener(this); + FloatingActionButton fab = findViewById(R.id.fab); + fab.setOnClickListener(v -> searchController.showDialpad(true)); + MainToolbar toolbar = findViewById(R.id.toolbar); toolbar.setSearchBarListener(new MainSearchBarListener()); - setSupportActionBar(toolbar); + searchController = new SearchController(fab, toolbar); + setSupportActionBar(findViewById(R.id.toolbar)); + BottomNavBar navBar = findViewById(R.id.bottom_nav_bar); navBar.setOnTabSelectedListener(new MainBottomNavBarBottomNavTabListener()); + // TODO(calderwoodra): Implement last tab navBar.selectTab(BottomNavBar.TabIndex.SPEED_DIAL); } - @Override - public void onClick(View v) { - if (v.getId() == R.id.fab) { - // open dialpad search - } - } - @Override public void onContactSelected(ImageView photo, Uri contactUri, long contactId) { // TODO(calderwoodra): Add impression logging @@ -82,6 +91,147 @@ public final class MainActivity extends AppCompatActivity this, photo, contactUri, QuickContact.MODE_LARGE, null /* excludeMimes */); } + @Override // OnDialpadQueryChangedListener + public void onDialpadQueryChanged(String query) { + // TODO(calderwoodra): update search fragment + } + + @Override // DialpadListener + public void getLastOutgoingCall(LastOutgoingCallCallback callback) { + // TODO(calderwoodra): migrate CallLogAsync class outside of dialer/app and call it here. + } + + @Override // DialpadListener + public void onDialpadShown() { + searchController.getDialpadFragment().slideUp(true); + } + + @Override // DialpadListener + public void onCallPlacedFromDialpad() { + // TODO(calderwoodra): logging + } + + @Override + public void onBackPressed() { + if (searchController.onBackPressed()) { + return; + } + super.onBackPressed(); + } + + /** Search controller for handling all the logic related to hiding/showing search and dialpad. */ + private final class SearchController { + + private static final String DIALPAD_FRAGMENT_TAG = "dialpad_fragment_tag"; + private static final String SEARCH_FRAGMENT_TAG = "search_fragment_tag"; + + private final FloatingActionButton fab; + private final MainToolbar toolbar; + + private boolean isDialpadVisible; + private boolean isSearchVisible; + + private SearchController(FloatingActionButton fab, MainToolbar toolbar) { + this.fab = fab; + this.toolbar = toolbar; + } + + /** Shows the dialpad, hides the FAB and slides the toolbar off screen. */ + public void showDialpad(boolean animate) { + Assert.checkArgument(!isDialpadVisible); + isDialpadVisible = true; + isSearchVisible = true; + + fab.hide(); + toolbar.slideUp(animate); + setTitle(R.string.dialpad_activity_title); + + android.app.FragmentTransaction transaction = getFragmentManager().beginTransaction(); + + // Show Search + if (getSearchFragment() == null) { + NewSearchFragment searchFragment = NewSearchFragment.newInstance(false); + transaction.add(R.id.search_fragment_container, searchFragment, SEARCH_FRAGMENT_TAG); + } else if (!isSearchVisible) { + transaction.show(getSearchFragment()); + } + + // Show Dialpad + if (getDialpadFragment() == null) { + DialpadFragment dialpadFragment = new DialpadFragment(); + transaction.add(R.id.search_fragment_container, dialpadFragment, DIALPAD_FRAGMENT_TAG); + } else { + DialpadFragment dialpadFragment = getDialpadFragment(); + transaction.show(dialpadFragment); + } + transaction.commit(); + } + + /** Hides the dialpad, reveals the FAB and slides the toolbar back onto the screen. */ + public void hideDialpad(boolean animate) { + Assert.checkArgument(isDialpadVisible); + isDialpadVisible = false; + + fab.show(); + toolbar.slideDown(animate); + setTitle(R.string.main_activity_label); + + DialpadFragment dialpadFragment = getDialpadFragment(); + dialpadFragment.setAnimate(animate); + dialpadFragment.slideDown( + animate, + new AnimationListener() { + @Override + public void onAnimationStart(Animation animation) {} + + @Override + public void onAnimationEnd(Animation animation) { + if (!(isFinishing() || isDestroyed())) { + getFragmentManager().beginTransaction().remove(dialpadFragment).commit(); + } + } + + @Override + public void onAnimationRepeat(Animation animation) {} + }); + } + + /** + * Should be called when the user presses the back button. + * + * @return true if {@link #onBackPressed()} handled to action. + */ + public boolean onBackPressed() { + if (isDialpadVisible && !TextUtils.isEmpty(getDialpadFragment().getQuery())) { + hideDialpad(true); + return true; + } else if (isSearchVisible) { + closeSearch(true); + return true; + } else { + return false; + } + } + + /** Calls {@link #hideDialpad(boolean)} and removes the search fragment. */ + private void closeSearch(boolean animate) { + Assert.checkArgument(isSearchVisible); + if (isDialpadVisible) { + hideDialpad(animate); + } + getFragmentManager().beginTransaction().remove(getSearchFragment()).commit(); + isSearchVisible = false; + } + + private DialpadFragment getDialpadFragment() { + return (DialpadFragment) getFragmentManager().findFragmentByTag(DIALPAD_FRAGMENT_TAG); + } + + private NewSearchFragment getSearchFragment() { + return (NewSearchFragment) getFragmentManager().findFragmentByTag(SEARCH_FRAGMENT_TAG); + } + } + /** * Implementation of {@link SearchBarListener} that holds the logic for how to handle search bar * events. diff --git a/java/com/android/dialer/main/impl/res/layout/bottom_nav_item.xml b/java/com/android/dialer/main/impl/res/layout/bottom_nav_item.xml index 28ad964dd..f9f2b6102 100644 --- a/java/com/android/dialer/main/impl/res/layout/bottom_nav_item.xml +++ b/java/com/android/dialer/main/impl/res/layout/bottom_nav_item.xml @@ -37,5 +37,6 @@ android:id="@+id/bottom_nav_item_text" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:textSize="12sp"/> + android:textSize="12sp" + android:gravity="center_horizontal"/> \ No newline at end of file diff --git a/java/com/android/dialer/main/impl/res/layout/main_activity.xml b/java/com/android/dialer/main/impl/res/layout/main_activity.xml index 31ff19dd3..0b0652af5 100644 --- a/java/com/android/dialer/main/impl/res/layout/main_activity.xml +++ b/java/com/android/dialer/main/impl/res/layout/main_activity.xml @@ -21,10 +21,7 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - - + + + + + + + + Phone NUI + + Phone Keypad + Call history diff --git a/java/com/android/dialer/main/impl/res/values/styles.xml b/java/com/android/dialer/main/impl/res/values/styles.xml index f94897a8b..26f880dc4 100644 --- a/java/com/android/dialer/main/impl/res/values/styles.xml +++ b/java/com/android/dialer/main/impl/res/values/styles.xml @@ -19,5 +19,8 @@ @color/dialtacts_theme_color @color/dialer_theme_color_dark @color/dialer_secondary_color + + + @style/Dialpad.Light diff --git a/java/com/android/dialer/main/impl/toolbar/MainToolbar.java b/java/com/android/dialer/main/impl/toolbar/MainToolbar.java index 6d9b7da6b..b32217230 100644 --- a/java/com/android/dialer/main/impl/toolbar/MainToolbar.java +++ b/java/com/android/dialer/main/impl/toolbar/MainToolbar.java @@ -17,16 +17,24 @@ package com.android.dialer.main.impl.toolbar; import android.content.Context; +import android.support.annotation.VisibleForTesting; import android.support.v7.widget.PopupMenu.OnMenuItemClickListener; import android.support.v7.widget.Toolbar; import android.util.AttributeSet; import android.view.MenuItem; +import android.view.animation.AccelerateDecelerateInterpolator; import android.widget.ImageButton; +import com.android.dialer.common.Assert; /** Toolbar for {@link com.android.dialer.main.impl.MainActivity}. */ public final class MainToolbar extends Toolbar implements OnMenuItemClickListener { + private static final int SLIDE_DURATION = 300; + private static final AccelerateDecelerateInterpolator SLIDE_INTERPOLATOR = + new AccelerateDecelerateInterpolator(); + private SearchBarListener listener; + private boolean isSlideUp; public MainToolbar(Context context, AttributeSet attrs) { super(context, attrs); @@ -57,4 +65,35 @@ public final class MainToolbar extends Toolbar implements OnMenuItemClickListene this.listener = listener; ((SearchBarView) findViewById(R.id.search_view_container)).setSearchBarListener(listener); } + + /** Slides the toolbar up and off the screen. */ + public void slideUp(boolean animate) { + Assert.checkArgument(!isSlideUp); + isSlideUp = true; + animate() + .translationY(-getHeight()) + .setDuration(animate ? SLIDE_DURATION : 0) + .setInterpolator(SLIDE_INTERPOLATOR) + .start(); + } + + /** + * Slides the toolbar down and back onto the screen. + * + * @param animate + */ + public void slideDown(boolean animate) { + Assert.checkArgument(isSlideUp); + isSlideUp = false; + animate() + .translationY(0) + .setDuration(animate ? SLIDE_DURATION : 0) + .setInterpolator(SLIDE_INTERPOLATOR) + .start(); + } + + @VisibleForTesting + public boolean isSlideUp() { + return isSlideUp; + } } -- cgit v1.2.3