diff --git a/res/drawable/search_toolbar_shadow.xml b/res/drawable/search_toolbar_shadow.xml new file mode 100644 index 0000000000..5afdc2a2df --- /dev/null +++ b/res/drawable/search_toolbar_shadow.xml @@ -0,0 +1,7 @@ + + + \ No newline at end of file diff --git a/res/layout/conversation_list_activity.xml b/res/layout/conversation_list_activity.xml index 6b9c319930..dcd8957b3d 100644 --- a/res/layout/conversation_list_activity.xml +++ b/res/layout/conversation_list_activity.xml @@ -1,13 +1,14 @@ - + - + + - - - - - + - \ No newline at end of file + \ No newline at end of file diff --git a/res/layout/search_toolbar.xml b/res/layout/search_toolbar.xml new file mode 100644 index 0000000000..5b5a8508e9 --- /dev/null +++ b/res/layout/search_toolbar.xml @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/res/menu/conversation_list.xml b/res/menu/conversation_list.xml deleted file mode 100644 index b6804e62d9..0000000000 --- a/res/menu/conversation_list.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - diff --git a/res/menu/conversation_list_search.xml b/res/menu/conversation_list_search.xml new file mode 100644 index 0000000000..79f229416c --- /dev/null +++ b/res/menu/conversation_list_search.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/res/values/attrs.xml b/res/values/attrs.xml index fb7a552396..689384eb8f 100644 --- a/res/values/attrs.xml +++ b/res/values/attrs.xml @@ -136,6 +136,8 @@ + + diff --git a/res/values/strings.xml b/res/values/strings.xml index 80a839e077..dfd33f171b 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1494,6 +1494,7 @@ Inbox zeeerrro Zip. Zilch. Zero. Nada. You\'re all caught up! No results found for \'%s\' + Search diff --git a/res/values/themes.xml b/res/values/themes.xml index 1371d9c967..24b1c886ea 100644 --- a/res/values/themes.xml +++ b/res/values/themes.xml @@ -224,6 +224,8 @@ @drawable/ic_group_grey600_24dp @style/PreferenceThemeOverlay.Fix + + @color/white diff --git a/src/org/thoughtcrime/securesms/ConversationListActivity.java b/src/org/thoughtcrime/securesms/ConversationListActivity.java index 8f5c97b5fb..b2dff8044e 100644 --- a/src/org/thoughtcrime/securesms/ConversationListActivity.java +++ b/src/org/thoughtcrime/securesms/ConversationListActivity.java @@ -26,21 +26,19 @@ import android.os.AsyncTask; import android.os.Bundle; import android.provider.ContactsContract; import android.support.annotation.NonNull; -import android.support.v4.view.MenuItemCompat; -import android.support.v7.app.ActionBar; -import android.support.v7.widget.SearchView; import android.support.v7.widget.Toolbar; import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; -import android.widget.LinearLayout; +import android.view.View; +import android.widget.ImageView; import android.widget.Toast; import org.thoughtcrime.securesms.components.RatingManager; +import org.thoughtcrime.securesms.components.SearchToolbar; import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.database.DatabaseFactory; -import org.thoughtcrime.securesms.database.MessagingDatabase; import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo; import org.thoughtcrime.securesms.notifications.MarkReadReceiver; import org.thoughtcrime.securesms.notifications.MessageNotifier; @@ -62,8 +60,10 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit private final DynamicLanguage dynamicLanguage = new DynamicLanguage(); private ConversationListFragment fragment; - private ContentObserver observer; - private MasterSecret masterSecret; + private ContentObserver observer; + private MasterSecret masterSecret; + private SearchToolbar searchToolbar; + private ImageView searchAction; @Override protected void onPreCreate() { @@ -80,9 +80,12 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit Toolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); - fragment = initFragment(R.id.fragment_container, new ConversationListFragment(), masterSecret, dynamicLanguage.getCurrentLocale()); + searchToolbar = findViewById(R.id.search_toolbar); + searchAction = findViewById(R.id.search_action); + fragment = initFragment(R.id.fragment_container, new ConversationListFragment(), masterSecret, dynamicLanguage.getCurrentLocale()); initializeContactUpdatesReceiver(); + initializeSearchListener(); RatingManager.showRatingDialogIfNecessary(this); } @@ -109,47 +112,28 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit menu.findItem(R.id.menu_clear_passphrase).setVisible(!TextSecurePreferences.isPasswordDisabled(this)); - inflater.inflate(R.menu.conversation_list, menu); - MenuItem menuItem = menu.findItem(R.id.menu_search); - initializeSearch(menuItem); - super.onPrepareOptionsMenu(menu); return true; } - private void initializeSearch(MenuItem searchViewItem) { - SearchView searchView = (SearchView)MenuItemCompat.getActionView(searchViewItem); - searchView.setQueryHint(getString(R.string.ConversationListActivity_search)); - searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { - @Override - public boolean onQueryTextSubmit(String query) { - if (fragment != null) { - fragment.setQueryFilter(query); - return true; - } - - return false; - } - - @Override - public boolean onQueryTextChange(String newText) { - return onQueryTextSubmit(newText); - } + private void initializeSearchListener() { + searchAction.setOnClickListener(v -> { + searchToolbar.display(searchAction.getX() + (searchAction.getWidth() / 2), searchAction.getY() + (searchAction.getHeight() / 2)); }); - MenuItemCompat.setOnActionExpandListener(searchViewItem, new MenuItemCompat.OnActionExpandListener() { + searchToolbar.setListener(new SearchToolbar.SearchListener() { @Override - public boolean onMenuItemActionExpand(MenuItem menuItem) { - return true; + public void onSearchTextChange(String text) { + if (fragment != null) { + fragment.setQueryFilter(text); + } } @Override - public boolean onMenuItemActionCollapse(MenuItem menuItem) { + public void onSearchReset() { if (fragment != null) { fragment.resetQueryFilter(); } - - return true; } }); } @@ -190,6 +174,12 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit startActivity(intent); } + @Override + public void onBackPressed() { + if (searchToolbar.isVisible()) searchToolbar.collapse(); + else super.onBackPressed(); + } + private void createGroup() { Intent intent = new Intent(this, GroupCreateActivity.class); startActivity(intent); diff --git a/src/org/thoughtcrime/securesms/components/SearchToolbar.java b/src/org/thoughtcrime/securesms/components/SearchToolbar.java new file mode 100644 index 0000000000..a1ef72dc55 --- /dev/null +++ b/src/org/thoughtcrime/securesms/components/SearchToolbar.java @@ -0,0 +1,152 @@ +package org.thoughtcrime.securesms.components; + + +import android.animation.Animator; +import android.content.Context; +import android.graphics.PorterDuff; +import android.os.Build; +import android.support.annotation.MainThread; +import android.support.annotation.Nullable; +import android.support.v7.widget.SearchView; +import android.support.v7.widget.Toolbar; +import android.util.AttributeSet; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewAnimationUtils; +import android.widget.EditText; +import android.widget.LinearLayout; + +import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.animation.AnimationCompleteListener; + +public class SearchToolbar extends LinearLayout { + + private float x, y; + private MenuItem searchItem; + private SearchListener listener; + + public SearchToolbar(Context context) { + super(context); + initialize(); + } + + public SearchToolbar(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + initialize(); + } + + public SearchToolbar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + initialize(); + } + + @SuppressWarnings("ConstantConditions") + private void initialize() { + inflate(getContext(), R.layout.search_toolbar, this); + setOrientation(VERTICAL); + + Toolbar toolbar = findViewById(R.id.toolbar); + + toolbar.setNavigationIcon(R.drawable.ic_arrow_back_white_24dp); + toolbar.getNavigationIcon().setColorFilter(getContext().getResources().getColor(R.color.grey_700), PorterDuff.Mode.SRC_IN); + toolbar.inflateMenu(R.menu.conversation_list_search); + + this.searchItem = toolbar.getMenu().findItem(R.id.action_filter_search); + SearchView searchView = (SearchView) searchItem.getActionView(); + EditText searchText = searchView.findViewById(android.support.v7.appcompat.R.id.search_src_text); + + searchView.setSubmitButtonEnabled(false); + + if (searchText != null) searchText.setHint(R.string.SearchToolbar_search); + else searchView.setQueryHint(getResources().getString(R.string.SearchToolbar_search)); + + searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { + @Override + public boolean onQueryTextSubmit(String query) { + if (listener != null) listener.onSearchTextChange(query); + return true; + } + + @Override + public boolean onQueryTextChange(String newText) { + return onQueryTextSubmit(newText); + } + }); + + searchItem.setOnActionExpandListener(new MenuItem.OnActionExpandListener() { + @Override + public boolean onMenuItemActionExpand(MenuItem item) { + return true; + } + + @Override + public boolean onMenuItemActionCollapse(MenuItem item) { + hide(); + return true; + } + }); + + toolbar.setNavigationOnClickListener(v -> hide()); + } + + @MainThread + public void display(float x, float y) { + if (getVisibility() != View.VISIBLE) { + this.x = x; + this.y = y; + + searchItem.expandActionView(); + + if (Build.VERSION.SDK_INT >= 21) { + Animator animator = ViewAnimationUtils.createCircularReveal(this, (int)x, (int)y, 0, getWidth()); + animator.setDuration(400); + + setVisibility(View.VISIBLE); + animator.start(); + } else { + setVisibility(View.VISIBLE); + } + } + } + + public void collapse() { + searchItem.collapseActionView(); + } + + @MainThread + private void hide() { + if (getVisibility() == View.VISIBLE) { + + if (listener != null) listener.onSearchReset(); + + if (Build.VERSION.SDK_INT >= 21) { + Animator animator = ViewAnimationUtils.createCircularReveal(this, (int)x, (int)y, getWidth(), 0); + animator.setDuration(400); + animator.addListener(new AnimationCompleteListener() { + @Override + public void onAnimationEnd(Animator animation) { + setVisibility(View.INVISIBLE); + } + }); + animator.start(); + } else { + setVisibility(View.INVISIBLE); + } + } + } + + public boolean isVisible() { + return getVisibility() == View.VISIBLE; + } + + @MainThread + public void setListener(SearchListener listener) { + this.listener = listener; + } + + public interface SearchListener { + void onSearchTextChange(String text); + void onSearchReset(); + } + +}