diff options
7 files changed, 281 insertions, 31 deletions
diff --git a/java/com/android/dialer/main/impl/MainActivity.java b/java/com/android/dialer/main/impl/MainActivity.java index 0884c117b..fc7e670b0 100644 --- a/java/com/android/dialer/main/impl/MainActivity.java +++ b/java/com/android/dialer/main/impl/MainActivity.java @@ -31,6 +31,8 @@ import com.android.dialer.contactsfragment.ContactsFragment; import com.android.dialer.contactsfragment.ContactsFragment.Header; import com.android.dialer.contactsfragment.ContactsFragment.OnContactSelectedListener; 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.speeddial.SpeedDialFragment; import com.android.dialer.voicemail.listui.NewVoicemailFragment; @@ -58,7 +60,9 @@ public final class MainActivity extends AppCompatActivity private void initLayout() { findViewById(R.id.fab).setOnClickListener(this); - setSupportActionBar(findViewById(R.id.toolbar)); + MainToolbar toolbar = findViewById(R.id.toolbar); + toolbar.setSearchBarListener(new MainSearchBarListener()); + setSupportActionBar(toolbar); BottomNavBar navBar = findViewById(R.id.bottom_nav_bar); navBar.setOnTabSelectedListener(new MainBottomNavBarBottomNavTabListener()); navBar.selectTab(BottomNavBar.TabIndex.SPEED_DIAL); @@ -79,6 +83,28 @@ public final class MainActivity extends AppCompatActivity } /** + * Implementation of {@link SearchBarListener} that holds the logic for how to handle search bar + * events. + */ + private static final class MainSearchBarListener implements SearchBarListener { + + @Override + public void onSearchQueryUpdated(String query) {} + + @Override + public void onSearchBackButtonClicked() {} + + @Override + public void onVoiceButtonClicked(VoiceSearchResultCallback voiceSearchResultCallback) {} + + @Override + public void openSettings() {} + + @Override + public void sendFeedback() {} + } + + /** * Implementation of {@link OnBottomNavTabSelectedListener} that handles logic for showing each of * the main tabs. */ diff --git a/java/com/android/dialer/main/impl/toolbar/MainToolbar.java b/java/com/android/dialer/main/impl/toolbar/MainToolbar.java index 19c763cf6..6d9b7da6b 100644 --- a/java/com/android/dialer/main/impl/toolbar/MainToolbar.java +++ b/java/com/android/dialer/main/impl/toolbar/MainToolbar.java @@ -22,11 +22,12 @@ import android.support.v7.widget.Toolbar; import android.util.AttributeSet; import android.view.MenuItem; import android.widget.ImageButton; -import android.widget.Toast; /** Toolbar for {@link com.android.dialer.main.impl.MainActivity}. */ public final class MainToolbar extends Toolbar implements OnMenuItemClickListener { + private SearchBarListener listener; + public MainToolbar(Context context, AttributeSet attrs) { super(context, attrs); } @@ -40,23 +41,20 @@ public final class MainToolbar extends Toolbar implements OnMenuItemClickListene overflowMenu.setOnMenuItemClickListener(this); optionsMenuButton.setOnClickListener(v -> overflowMenu.show()); optionsMenuButton.setOnTouchListener(overflowMenu.getDragToOpenListener()); - - findViewById(R.id.voice_search_button).setOnClickListener(v -> onVoiceIconClicked()); - findViewById(R.id.search_box_collapsed).setOnClickListener(v -> onSearchBarClicked()); } @Override public boolean onMenuItemClick(MenuItem menuItem) { - Toast.makeText(getContext(), "Not yet implemented", Toast.LENGTH_SHORT).show(); - // TODO(calderwoodra): implement menu item clicks + if (menuItem.getItemId() == R.id.settings) { + listener.openSettings(); + } else if (menuItem.getItemId() == R.id.feedback) { + listener.sendFeedback(); + } return false; } - private void onVoiceIconClicked() { - // TODO(calderwoodra): take voice input - } - - private void onSearchBarClicked() { - // TODO(calderwoodra): open search UI + public void setSearchBarListener(SearchBarListener listener) { + this.listener = listener; + ((SearchBarView) findViewById(R.id.search_view_container)).setSearchBarListener(listener); } } diff --git a/java/com/android/dialer/main/impl/toolbar/SearchBarListener.java b/java/com/android/dialer/main/impl/toolbar/SearchBarListener.java new file mode 100644 index 000000000..32258d95b --- /dev/null +++ b/java/com/android/dialer/main/impl/toolbar/SearchBarListener.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.dialer.main.impl.toolbar; + +/** Useful callback for {@link SearchBarView} listeners. */ +public interface SearchBarListener { + + /** Called when the search query updates. */ + void onSearchQueryUpdated(String query); + + /** Called when the back button is clicked in the search bar. */ + void onSearchBackButtonClicked(); + + /** Called when the voice search button is clicked. */ + void onVoiceButtonClicked(VoiceSearchResultCallback voiceSearchResultCallback); + + /** Called when the settings option is selected from the search menu. */ + void openSettings(); + + /** Called when send feedback is selected from the search menu. */ + void sendFeedback(); + + /** Interface for returning voice results to the search bar. */ + interface VoiceSearchResultCallback { + + /** Sets the voice results in the search bar and expands the search UI. */ + void setResult(String result); + } +} diff --git a/java/com/android/dialer/main/impl/toolbar/SearchBarView.java b/java/com/android/dialer/main/impl/toolbar/SearchBarView.java index b3b27efc2..35c3cee16 100644 --- a/java/com/android/dialer/main/impl/toolbar/SearchBarView.java +++ b/java/com/android/dialer/main/impl/toolbar/SearchBarView.java @@ -16,16 +16,180 @@ package com.android.dialer.main.impl.toolbar; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; import android.content.Context; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.text.Editable; +import android.text.TextUtils; +import android.text.TextWatcher; import android.util.AttributeSet; +import android.view.View; +import android.widget.EditText; import android.widget.FrameLayout; +import com.android.dialer.animation.AnimUtils; +import com.android.dialer.util.DialerUtils; +import com.google.common.base.Optional; -/** */ +/** Search bar for {@link MainToolbar}. Mostly used to handle expand and collapse animation. */ final class SearchBarView extends FrameLayout { + private static final int ANIMATION_DURATION = 200; + private static final float EXPAND_MARGIN_FRACTION_START = 0.8f; + + private final float margin; + private final float animationEndHeight; + + private SearchBarListener listener; + private EditText searchBox; + + private int initialHeight; + private boolean isExpanded; + private View searchBoxCollapsed; + private View searchBoxExpanded; + private View clearButton; + public SearchBarView(@NonNull Context context, @Nullable AttributeSet attrs) { super(context, attrs); + margin = getContext().getResources().getDimension(R.dimen.search_bar_margin); + animationEndHeight = + getContext().getResources().getDimension(R.dimen.expanded_search_bar_height); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + clearButton = findViewById(R.id.search_clear_button); + searchBox = findViewById(R.id.search_view); + searchBoxCollapsed = findViewById(R.id.search_box_collapsed); + searchBoxExpanded = findViewById(R.id.search_box_expanded); + + setOnClickListener(v -> expand(true, Optional.absent())); + findViewById(R.id.voice_search_button).setOnClickListener(v -> voiceSearchClicked()); + findViewById(R.id.search_back_button).setOnClickListener(v -> onSearchBackButtonClicked()); + clearButton.setOnClickListener(v -> onSearchClearButtonClicked()); + searchBox.addTextChangedListener(new SearchBoxTextWatcher()); + } + + private void onSearchClearButtonClicked() { + searchBox.setText(""); + } + + private void onSearchBackButtonClicked() { + listener.onSearchBackButtonClicked(); + collapse(true); + } + + private void voiceSearchClicked() { + listener.onVoiceButtonClicked( + result -> { + if (!TextUtils.isEmpty(result)) { + expand(true, Optional.of(result)); + } + }); + } + + /** Expand the search bar and populate it with text if any exists. */ + private void expand(boolean animate, Optional<String> text) { + if (isExpanded) { + return; + } + initialHeight = getHeight(); + + int duration = animate ? ANIMATION_DURATION : 0; + searchBoxExpanded.setVisibility(VISIBLE); + AnimUtils.crossFadeViews(searchBoxExpanded, searchBoxCollapsed, duration); + ValueAnimator animator = ValueAnimator.ofFloat(EXPAND_MARGIN_FRACTION_START, 0f); + animator.addUpdateListener(animation -> setMargins((Float) animation.getAnimatedValue())); + animator.setDuration(duration); + animator.addListener( + new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + super.onAnimationStart(animation); + DialerUtils.showInputMethod(searchBox); + isExpanded = true; + } + + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + if (text.isPresent()) { + searchBox.setText(text.get()); + } + searchBox.requestFocus(); + } + }); + animator.start(); + } + + /** Collapse the search bar and clear it's text. */ + private void collapse(boolean animate) { + if (!isExpanded) { + return; + } + + int duration = animate ? ANIMATION_DURATION : 0; + AnimUtils.crossFadeViews(searchBoxCollapsed, searchBoxExpanded, duration); + ValueAnimator animator = ValueAnimator.ofFloat(0f, EXPAND_MARGIN_FRACTION_START); + animator.addUpdateListener(animation -> setMargins((Float) animation.getAnimatedValue())); + animator.setDuration(duration); + + animator.addListener( + new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + super.onAnimationStart(animation); + DialerUtils.hideInputMethod(searchBox); + isExpanded = false; + } + + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + searchBox.setText(""); + searchBoxExpanded.setVisibility(INVISIBLE); + } + }); + animator.start(); + } + + /** + * Assigns margins to the search box as a fraction of its maximum margin size + * + * @param fraction How large the margins should be as a fraction of their full size + */ + private void setMargins(float fraction) { + int margin = (int) (this.margin * fraction); + MarginLayoutParams params = (MarginLayoutParams) getLayoutParams(); + params.topMargin = margin; + params.bottomMargin = margin; + params.leftMargin = margin; + params.rightMargin = margin; + searchBoxExpanded.getLayoutParams().height = + (int) (animationEndHeight - (animationEndHeight - initialHeight) * fraction); + requestLayout(); + } + + public void setSearchBarListener(SearchBarListener listener) { + this.listener = listener; + } + + /** Handles logic for text changes in the search box. */ + private class SearchBoxTextWatcher implements TextWatcher { + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) {} + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) {} + + @Override + public void afterTextChanged(Editable s) { + clearButton.setVisibility(TextUtils.isEmpty(s) ? GONE : VISIBLE); + listener.onSearchQueryUpdated(s.toString()); + } } } diff --git a/java/com/android/dialer/main/impl/toolbar/res/layout/expanded_search_bar.xml b/java/com/android/dialer/main/impl/toolbar/res/layout/expanded_search_bar.xml index f814a766d..4dbd4bf22 100644 --- a/java/com/android/dialer/main/impl/toolbar/res/layout/expanded_search_bar.xml +++ b/java/com/android/dialer/main/impl/toolbar/res/layout/expanded_search_bar.xml @@ -17,14 +17,14 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/search_box_expanded" android:layout_width="match_parent" - android:layout_height="match_parent" - android:visibility="gone"> + android:layout_height="wrap_content" + android:visibility="invisible"> <ImageButton android:id="@+id/search_back_button" android:layout_width="48dp" android:layout_height="48dp" - android:layout_marginStart="16dp" + android:layout_marginStart="8dp" android:layout_centerVertical="true" android:background="?attr/selectableItemBackgroundBorderless" android:contentDescription="@string/action_menu_back_from_search" @@ -39,17 +39,19 @@ android:layout_toStartOf="@+id/search_close_button" android:layout_centerVertical="true" android:layout_marginStart="8dp" + android:minHeight="48dp" android:background="@null" android:imeOptions="flagNoExtractUi" android:inputType="textFilter" android:maxLines="1" + android:hint="@string/dialer_hint_find_contact" android:textColor="@color/dialer_secondary_text_color" android:textColorHint="@color/dialer_edit_text_hint_color" android:textCursorDrawable="@drawable/custom_cursor" android:textSize="16sp"/> <ImageView - android:id="@+id/search_close_button" + android:id="@+id/search_clear_button" android:layout_width="48dp" android:layout_height="48dp" android:layout_alignParentEnd="true" @@ -58,5 +60,6 @@ android:background="?attr/selectableItemBackgroundBorderless" android:contentDescription="@string/description_clear_search" android:src="@drawable/quantum_ic_close_vd_theme_24" - android:tint="@color/dialer_secondary_text_color"/> + android:tint="@color/dialer_secondary_text_color" + android:visibility="gone"/> </RelativeLayout>
\ No newline at end of file diff --git a/java/com/android/dialer/main/impl/toolbar/res/layout/toolbar_layout.xml b/java/com/android/dialer/main/impl/toolbar/res/layout/toolbar_layout.xml index 27b37e80f..9b0086b38 100644 --- a/java/com/android/dialer/main/impl/toolbar/res/layout/toolbar_layout.xml +++ b/java/com/android/dialer/main/impl/toolbar/res/layout/toolbar_layout.xml @@ -22,25 +22,21 @@ android:minHeight="?attr/actionBarSize" android:background="@color/dialer_theme_color" app:contentInsetStart="0dp" - app:contentInsetEnd="0dp" - app:theme="@style/ThemeOverlay.AppCompat.Light"> + app:contentInsetEnd="0dp"> <com.android.dialer.main.impl.toolbar.SearchBarView android:id="@+id/search_view_container" android:layout_width="match_parent" - android:layout_height="48dp" - android:layout_marginStart="8dp" - android:layout_marginTop="8dp" - android:layout_marginBottom="8dp" - android:layout_marginEnd="8dp" + android:layout_height="wrap_content" + android:layout_margin="@dimen/search_bar_margin" android:background="@drawable/rounded_corner" - android:elevation="4dp" - android:theme="@style/Theme.AppCompat.Light"> + android:elevation="4dp"> <RelativeLayout android:id="@+id/search_box_collapsed" android:layout_width="match_parent" - android:layout_height="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" android:background="?android:selectableItemBackground" android:gravity="center_vertical"> @@ -58,12 +54,12 @@ <TextView android:id="@+id/search_box_start_search" android:layout_width="wrap_content" - android:layout_height="match_parent" + android:layout_height="wrap_content" android:layout_toEndOf="@+id/search_magnifying_glass" android:layout_toStartOf="@+id/voice_search_button" android:layout_marginStart="8dp" + android:layout_centerVertical="true" android:fontFamily="sans-serif" - android:gravity="center_vertical" android:hint="@string/dialer_hint_find_contact" android:textColorHint="@color/dialer_secondary_text_color" android:textSize="16dp"/> diff --git a/java/com/android/dialer/main/impl/toolbar/res/values/dimens.xml b/java/com/android/dialer/main/impl/toolbar/res/values/dimens.xml new file mode 100644 index 000000000..f54f053da --- /dev/null +++ b/java/com/android/dialer/main/impl/toolbar/res/values/dimens.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2018 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<resources> + <dimen name="search_bar_margin">8dp</dimen> + <dimen name="expanded_search_bar_height">60dp</dimen> +</resources>
\ No newline at end of file |