Messaging: Looply search hub with frequent contacts and nav inset

Redesign Search around frequent contacts, category counts, and recent chips, and keep the Messages/Automation pill on the left guideline without moving compose.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-26 05:36:08 +09:00
co-authored by Cursor
parent 88529c3634
commit a32250fe34
13 changed files with 762 additions and 163 deletions
@@ -138,6 +138,7 @@ public class ConversationListFragment extends Fragment implements ConversationLi
private TextView mHomeEditButton; private TextView mHomeEditButton;
private ImageButton mHomePinnedDoneButton; private ImageButton mHomePinnedDoneButton;
private boolean mHomePinnedEditMode; private boolean mHomePinnedEditMode;
private View mHomeNavDock;
private View mHomeNavMessages; private View mHomeNavMessages;
private View mHomeNavAutomate; private View mHomeNavAutomate;
private View mHomeNavTabGap; private View mHomeNavTabGap;
@@ -389,6 +390,7 @@ public class ConversationListFragment extends Fragment implements ConversationLi
mHomeMessagesTop = rootView.findViewById(R.id.home_messages_top); mHomeMessagesTop = rootView.findViewById(R.id.home_messages_top);
mHomeTitle = rootView.findViewById(R.id.home_title); mHomeTitle = rootView.findViewById(R.id.home_title);
mHomeAutomationPane = rootView.findViewById(R.id.home_automation_pane); mHomeAutomationPane = rootView.findViewById(R.id.home_automation_pane);
mHomeNavDock = rootView.findViewById(R.id.home_nav_dock);
mHomeNavMessages = rootView.findViewById(R.id.home_nav_messages); mHomeNavMessages = rootView.findViewById(R.id.home_nav_messages);
bindHomeNavTab(mHomeNavMessages, R.drawable.ic_home_nav_messages, bindHomeNavTab(mHomeNavMessages, R.drawable.ic_home_nav_messages,
R.string.home_nav_messages); R.string.home_nav_messages);
@@ -453,6 +455,23 @@ public class ConversationListFragment extends Fragment implements ConversationLi
} }
applyHomeNavTabActiveSize(mHomeNavMessages, mHomeNavMessagesNaturalWidth); applyHomeNavTabActiveSize(mHomeNavMessages, mHomeNavMessagesNaturalWidth);
applyHomeNavTabActiveSize(mHomeNavAutomate, mHomeNavAutomateNaturalWidth); applyHomeNavTabActiveSize(mHomeNavAutomate, mHomeNavAutomateNaturalWidth);
if (mHomeNavDock != null) {
mHomeNavDock.post(this::alignHomeNavDockToGuideline);
}
}
/** Slide only the Messages/Automation pill to the 20dp screen guideline; compose stays put. */
private void alignHomeNavDockToGuideline() {
if (mHomeNavDock == null || mRootView == null || mHomeNavDock.getWidth() <= 0) {
return;
}
final int guideline = getResources().getDimensionPixelSize(R.dimen.looply_screen_padding);
mHomeNavDock.setTranslationX(0f);
final int[] dockLoc = new int[2];
final int[] rootLoc = new int[2];
mHomeNavDock.getLocationOnScreen(dockLoc);
mRootView.getLocationOnScreen(rootLoc);
mHomeNavDock.setTranslationX(guideline - (dockLoc[0] - rootLoc[0]));
} }
private void applyHomeNavTabActiveSize(final View tab, final int natural) { private void applyHomeNavTabActiveSize(final View tab, final int natural) {
@@ -16,29 +16,58 @@
package com.android.messaging.ui.home; package com.android.messaging.ui.home;
import android.content.Intent;
import android.content.res.ColorStateList;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.speech.RecognizerIntent;
import android.text.Editable; import android.text.Editable;
import android.text.TextUtils; import android.text.TextUtils;
import android.text.TextWatcher; import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.widget.EditText; import android.widget.EditText;
import android.widget.ImageButton; import android.widget.ImageButton;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.DrawableRes; import androidx.annotation.DrawableRes;
import androidx.annotation.IdRes; import androidx.annotation.IdRes;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes; import androidx.annotation.StringRes;
import androidx.core.content.ContextCompat;
import com.android.messaging.R; import com.android.messaging.R;
import com.android.messaging.datamodel.DatabaseHelper.ConversationColumns;
import com.android.messaging.datamodel.MessagingContentProvider;
import com.android.messaging.datamodel.data.ConversationListItemData;
import com.android.messaging.ui.BugleActionBarActivity; import com.android.messaging.ui.BugleActionBarActivity;
import com.android.messaging.ui.ContactIconView;
import com.android.messaging.ui.UIIntents;
import com.android.messaging.util.AvatarUriUtil;
import com.android.messaging.util.ImeUtil; import com.android.messaging.util.ImeUtil;
import com.android.messaging.util.UriUtil;
import com.google.android.material.chip.Chip;
import com.google.android.material.chip.ChipGroup;
/** Home-style message search entry point with quick filter tiles. */ import java.util.ArrayList;
import java.util.List;
/** Home-style message search hub with frequent contacts, categories, and recent queries. */
public class HomeSearchActivity extends BugleActionBarActivity { public class HomeSearchActivity extends BugleActionBarActivity {
private static final int FREQUENT_LIMIT = 4;
private static final int VOICE_SEARCH_REQUEST = 71;
private EditText mSearchField; private EditText mSearchField;
private ImageButton mClearButton; private ImageButton mClearButton;
private ImageButton mVoiceButton;
private LinearLayout mFrequentSection;
private LinearLayout mFrequentRow;
private View mRecentSection;
private ChipGroup mRecentGroup;
@Override @Override
protected void onCreate(final Bundle savedInstanceState) { protected void onCreate(final Bundle savedInstanceState) {
@@ -49,12 +78,40 @@ public class HomeSearchActivity extends BugleActionBarActivity {
} }
HomeSubHeader.bind(this, R.string.home_search_title, v -> finish()); HomeSubHeader.bind(this, R.string.home_search_title, v -> finish());
HomeSubHeader.bindAction(this, R.string.home_search_reset, v -> resetSearch());
mSearchField = findViewById(R.id.home_search_field); mSearchField = findViewById(R.id.home_search_field);
mClearButton = findViewById(R.id.home_search_clear); mClearButton = findViewById(R.id.home_search_clear);
mVoiceButton = findViewById(R.id.home_search_voice);
mFrequentSection = findViewById(R.id.home_search_frequent_section);
mFrequentRow = findViewById(R.id.home_search_frequent_row);
mRecentSection = findViewById(R.id.home_search_recent_section);
mRecentGroup = findViewById(R.id.home_search_recent_group);
if (mClearButton != null) { if (mClearButton != null) {
mClearButton.setOnClickListener(v -> mSearchField.setText("")); mClearButton.setOnClickListener(v -> mSearchField.setText(""));
} }
if (mVoiceButton != null) {
mVoiceButton.setOnClickListener(v -> startVoiceSearch());
}
final View seeAll = findViewById(R.id.home_search_frequent_see_all);
if (seeAll != null) {
seeAll.setOnClickListener(v -> HomeToast.show(this,
getString(R.string.home_search_coming_soon,
getString(R.string.home_search_frequent_title))));
}
final View clearHistory = findViewById(R.id.home_search_clear_history);
if (clearHistory != null) {
clearHistory.setOnClickListener(v -> {
HomeSearchHistoryStore.clear(this);
bindRecentSearches();
});
}
final TextView categoryCount = findViewById(R.id.home_search_categories_count);
if (categoryCount != null) {
categoryCount.setText(getString(R.string.home_search_categories_count, 8));
}
if (mSearchField != null) { if (mSearchField != null) {
mSearchField.addTextChangedListener(new TextWatcher() { mSearchField.addTextChangedListener(new TextWatcher() {
@Override @Override
@@ -69,7 +126,7 @@ public class HomeSearchActivity extends BugleActionBarActivity {
@Override @Override
public void afterTextChanged(final Editable s) { public void afterTextChanged(final Editable s) {
updateClearButton(); updateFieldActions();
} }
}); });
mSearchField.setOnEditorActionListener((v, actionId, event) -> { mSearchField.setOnEditorActionListener((v, actionId, event) -> {
@@ -79,28 +136,31 @@ public class HomeSearchActivity extends BugleActionBarActivity {
mSearchField.requestFocus(); mSearchField.requestFocus();
mSearchField.post(() -> ImeUtil.get().showImeKeyboard(this, mSearchField)); mSearchField.post(() -> ImeUtil.get().showImeKeyboard(this, mSearchField));
} }
updateClearButton(); updateFieldActions();
bindFilter(R.id.home_search_filter_unread, R.drawable.ic_home_menu_unread, bindFilter(R.id.home_search_filter_unread, R.drawable.ic_home_menu_unread,
R.string.home_search_filter_unread); R.string.home_search_filter_unread, 0);
bindFilter(R.id.home_search_filter_saved, R.drawable.ic_home_menu_person, bindFilter(R.id.home_search_filter_saved, R.drawable.ic_home_menu_person,
R.string.home_search_filter_saved); R.string.home_search_filter_saved, 0);
bindFilter(R.id.home_search_filter_unknown, R.drawable.ic_home_menu_spam, bindFilter(R.id.home_search_filter_unknown, R.drawable.ic_home_menu_spam,
R.string.home_search_filter_unknown); R.string.home_search_filter_unknown, 0);
bindFilter(R.id.home_search_filter_starred, R.drawable.ic_home_star_line, bindFilter(R.id.home_search_filter_starred, R.drawable.ic_home_star_line,
R.string.home_search_filter_starred); R.string.home_search_filter_starred, StarredMessagesStore.count(this));
bindFilter(R.id.home_search_filter_images, R.drawable.ic_home_image_line, bindFilter(R.id.home_search_filter_images, R.drawable.ic_home_image_line,
R.string.home_search_filter_images); R.string.home_search_filter_images, 0);
bindFilter(R.id.home_search_filter_videos, R.drawable.ic_home_video_line, bindFilter(R.id.home_search_filter_videos, R.drawable.ic_home_video_line,
R.string.home_search_filter_videos); R.string.home_search_filter_videos, 0);
bindFilter(R.id.home_search_filter_places, R.drawable.ic_home_place_line, bindFilter(R.id.home_search_filter_places, R.drawable.ic_home_place_line,
R.string.home_search_filter_places); R.string.home_search_filter_places, 0);
bindFilter(R.id.home_search_filter_links, R.drawable.ic_home_link_line, bindFilter(R.id.home_search_filter_links, R.drawable.ic_home_link_line,
R.string.home_search_filter_links); R.string.home_search_filter_links, 0);
bindRecentSearches();
loadConversationStats();
} }
private void bindFilter(@IdRes final int tileId, @DrawableRes final int iconRes, private void bindFilter(@IdRes final int tileId, @DrawableRes final int iconRes,
@StringRes final int labelRes) { @StringRes final int labelRes, final int count) {
final View tile = findViewById(tileId); final View tile = findViewById(tileId);
if (tile == null) { if (tile == null) {
return; return;
@@ -113,25 +173,232 @@ public class HomeSearchActivity extends BugleActionBarActivity {
if (label != null) { if (label != null) {
label.setText(labelRes); label.setText(labelRes);
} }
setFilterCount(tile, count);
tile.setOnClickListener(v -> HomeToast.show(this, tile.setOnClickListener(v -> HomeToast.show(this,
getString(R.string.home_search_coming_soon, getString(labelRes)))); getString(R.string.home_search_coming_soon, getString(labelRes))));
} }
private void updateClearButton() { private void setFilterCount(final View tile, final int count) {
if (mClearButton == null || mSearchField == null) { final TextView countView = tile.findViewById(R.id.home_search_filter_count);
if (countView == null) {
return; return;
} }
mClearButton.setVisibility( countView.setText(String.valueOf(Math.max(0, count)));
TextUtils.isEmpty(mSearchField.getText()) ? View.GONE : View.VISIBLE); countView.setTextColor(ContextCompat.getColor(this,
count > 0 ? R.color.home_accent : R.color.home_text_secondary));
}
private void loadConversationStats() {
new Thread(() -> {
int unread = 0;
int saved = 0;
int unknown = 0;
final List<ConversationListItemData> frequent = new ArrayList<>();
Cursor cursor = null;
try {
cursor = getContentResolver().query(
MessagingContentProvider.CONVERSATIONS_URI, null, null, null,
ConversationColumns.SORT_TIMESTAMP + " DESC");
if (cursor != null) {
while (cursor.moveToNext()) {
final ConversationListItemData item = new ConversationListItemData();
item.bind(cursor);
if (item.getIsArchived()) {
continue;
}
if (!item.getIsRead()) {
unread++;
}
if (!item.getIsGroup()) {
if (item.getParticipantContactId() > 0) {
saved++;
} else {
unknown++;
}
}
if (frequent.size() < FREQUENT_LIMIT) {
frequent.add(item);
}
}
}
} catch (final Exception ignored) {
} finally {
if (cursor != null) {
cursor.close();
}
}
final int unreadCount = unread;
final int savedCount = saved;
final int unknownCount = unknown;
runOnUiThread(() -> {
if (isFinishing()) {
return;
}
applyFilterCount(R.id.home_search_filter_unread, unreadCount);
applyFilterCount(R.id.home_search_filter_saved, savedCount);
applyFilterCount(R.id.home_search_filter_unknown, unknownCount);
bindFrequentContacts(frequent);
});
}, "home-search-stats").start();
}
private void applyFilterCount(@IdRes final int tileId, final int count) {
final View tile = findViewById(tileId);
if (tile != null) {
setFilterCount(tile, count);
}
}
private void bindFrequentContacts(final List<ConversationListItemData> items) {
if (mFrequentSection == null || mFrequentRow == null) {
return;
}
mFrequentRow.removeAllViews();
if (items == null || items.isEmpty()) {
mFrequentSection.setVisibility(View.GONE);
return;
}
mFrequentSection.setVisibility(View.VISIBLE);
final LayoutInflater inflater = getLayoutInflater();
for (final ConversationListItemData item : items) {
final View cell = inflater.inflate(R.layout.home_search_frequent_item, mFrequentRow,
false);
final ContactIconView avatar = cell.findViewById(R.id.home_search_frequent_avatar);
final TextView name = cell.findViewById(R.id.home_search_frequent_name);
if (avatar != null) {
avatar.setImageResourceUri(frequentAvatarUri(item),
item.getParticipantContactId(),
item.getParticipantLookupKey(),
item.getOtherParticipantNormalizedDestination());
avatar.setImageClickHandlerDisabled(true);
}
if (name != null) {
name.setText(item.getName());
}
cell.setOnClickListener(v -> UIIntents.get().launchConversationActivity(
this, item.getConversationId(), null));
mFrequentRow.addView(cell);
}
}
private void bindRecentSearches() {
if (mRecentSection == null || mRecentGroup == null) {
return;
}
mRecentGroup.removeAllViews();
final List<String> items = HomeSearchHistoryStore.list(this);
if (items.isEmpty()) {
mRecentSection.setVisibility(View.GONE);
return;
}
mRecentSection.setVisibility(View.VISIBLE);
final int bg = ContextCompat.getColor(this, R.color.home_row_bg);
final int stroke = ContextCompat.getColor(this, R.color.home_divider);
final int text = ContextCompat.getColor(this, R.color.home_text_primary);
final int close = ContextCompat.getColor(this, R.color.home_text_secondary);
for (final String query : items) {
final Chip chip = new Chip(this, null, com.google.android.material.R.attr.chipStyle);
chip.setText(query);
chip.setCheckable(false);
chip.setClickable(true);
chip.setCloseIconVisible(true);
chip.setChipBackgroundColor(ColorStateList.valueOf(bg));
chip.setChipStrokeColor(ColorStateList.valueOf(stroke));
chip.setChipStrokeWidth(getResources().getDisplayMetrics().density);
chip.setChipCornerRadius(999f);
chip.setTextColor(text);
chip.setCloseIconTint(ColorStateList.valueOf(close));
chip.setEnsureMinTouchTargetSize(false);
chip.setOnClickListener(v -> {
mSearchField.setText(query);
mSearchField.setSelection(query.length());
submitSearch();
});
chip.setOnCloseIconClickListener(v -> {
HomeSearchHistoryStore.remove(this, query);
bindRecentSearches();
});
mRecentGroup.addView(chip);
}
}
private static Uri frequentAvatarUri(final ConversationListItemData item) {
Uri stored = null;
if (!TextUtils.isEmpty(item.getIcon())) {
try {
stored = Uri.parse(item.getIcon());
} catch (final RuntimeException ignored) {
stored = null;
}
}
if (stored != null && AvatarUriUtil.isAvatarUri(stored)) {
return stored;
}
final Uri photo = (stored != null && UriUtil.isLocalResourceUri(stored)) ? stored : null;
return AvatarUriUtil.createAvatarUri(photo, item.getName(),
item.getOtherParticipantNormalizedDestination(),
item.getParticipantLookupKey());
}
private void updateFieldActions() {
final boolean empty = mSearchField == null || TextUtils.isEmpty(mSearchField.getText());
if (mClearButton != null) {
mClearButton.setVisibility(empty ? View.GONE : View.VISIBLE);
}
if (mVoiceButton != null) {
mVoiceButton.setVisibility(empty ? View.VISIBLE : View.GONE);
}
} }
private void submitSearch() { private void submitSearch() {
if (mSearchField == null || TextUtils.isEmpty(mSearchField.getText())) { if (mSearchField == null || TextUtils.isEmpty(mSearchField.getText())) {
return; return;
} }
final String query = mSearchField.getText().toString().trim();
HomeSearchHistoryStore.add(this, query);
bindRecentSearches();
ImeUtil.get().hideImeKeyboard(this, mSearchField); ImeUtil.get().hideImeKeyboard(this, mSearchField);
HomeToast.show(this, getString(R.string.home_search_coming_soon, query));
}
private void resetSearch() {
if (mSearchField != null) {
mSearchField.setText("");
}
HomeSearchHistoryStore.clear(this);
bindRecentSearches();
updateFieldActions();
}
private void startVoiceSearch() {
final Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
intent.putExtra(RecognizerIntent.EXTRA_PROMPT, getString(R.string.home_search_field_hint));
try {
startActivityForResult(intent, VOICE_SEARCH_REQUEST);
} catch (final Exception ignored) {
HomeToast.show(this, getString(R.string.home_search_coming_soon, HomeToast.show(this, getString(R.string.home_search_coming_soon,
mSearchField.getText().toString())); getString(R.string.home_search_voice)));
}
}
@Override
protected void onActivityResult(final int requestCode, final int resultCode,
@Nullable final Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode != VOICE_SEARCH_REQUEST || resultCode != RESULT_OK || data == null
|| mSearchField == null) {
return;
}
final ArrayList<String> results =
data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
if (results == null || results.isEmpty()) {
return;
}
mSearchField.setText(results.get(0));
mSearchField.setSelection(mSearchField.getText().length());
submitSearch();
} }
@Override @Override
@@ -0,0 +1,69 @@
package com.android.messaging.ui.home;
import android.content.Context;
import android.content.SharedPreferences;
import android.text.TextUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/** Recent search queries for the home search hub. */
public final class HomeSearchHistoryStore {
private static final String PREFS_NAME = "home_search_history";
private static final String KEY_QUERIES = "queries";
private static final String SEPARATOR = "\n";
private static final int MAX_ITEMS = 10;
private HomeSearchHistoryStore() {
}
public static List<String> list(final Context context) {
final String raw = prefs(context).getString(KEY_QUERIES, "");
if (TextUtils.isEmpty(raw)) {
return Collections.emptyList();
}
final List<String> items = new ArrayList<>();
for (final String part : raw.split(SEPARATOR, -1)) {
if (!TextUtils.isEmpty(part)) {
items.add(part);
}
}
return items;
}
public static void add(final Context context, final String query) {
final String trimmed = query == null ? "" : query.trim();
if (TextUtils.isEmpty(trimmed)) {
return;
}
final List<String> items = new ArrayList<>(list(context));
items.remove(trimmed);
items.add(0, trimmed);
while (items.size() > MAX_ITEMS) {
items.remove(items.size() - 1);
}
save(context, items);
}
public static void remove(final Context context, final String query) {
final List<String> items = new ArrayList<>(list(context));
if (items.remove(query)) {
save(context, items);
}
}
public static void clear(final Context context) {
prefs(context).edit().remove(KEY_QUERIES).apply();
}
private static void save(final Context context, final List<String> items) {
prefs(context).edit().putString(KEY_QUERIES, TextUtils.join(SEPARATOR, items)).apply();
}
private static SharedPreferences prefs(final Context context) {
return context.getApplicationContext().getSharedPreferences(PREFS_NAME,
Context.MODE_PRIVATE);
}
}
@@ -48,6 +48,17 @@ public final class HomeSubHeader {
} }
} }
public static void bindAction(final Activity activity, @StringRes final int actionResId,
final View.OnClickListener onAction) {
final TextView action = activity.findViewById(R.id.home_sub_header_action);
if (action == null) {
return;
}
action.setText(actionResId);
action.setVisibility(View.VISIBLE);
action.setOnClickListener(onAction);
}
public static void bindBackOnly(final Activity activity, public static void bindBackOnly(final Activity activity,
final View.OnClickListener onBack) { final View.OnClickListener onBack) {
final ImageButton back = activity.findViewById(R.id.home_sub_header_back); final ImageButton back = activity.findViewById(R.id.home_sub_header_back);
@@ -32,6 +32,10 @@ public final class StarredMessagesStore {
private StarredMessagesStore() { private StarredMessagesStore() {
} }
public static int count(final Context context) {
return ids(context).size();
}
public static boolean isStarred(final Context context, final String messageId) { public static boolean isStarred(final Context context, final String messageId) {
if (TextUtils.isEmpty(messageId)) { if (TextUtils.isEmpty(messageId)) {
return false; return false;
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/home_surface_elevated">
<item>
<shape android:shape="rectangle">
<corners android:radius="@dimen/looply_radius_xl" />
<solid android:color="@color/home_row_bg" />
</shape>
</item>
</ripple>
@@ -277,6 +277,15 @@
android:text="@string/home_search_field_hint" android:text="@string/home_search_field_hint"
android:textColor="@color/home_search_hint" android:textColor="@color/home_search_hint"
android:textSize="16sp" /> android:textSize="16sp" />
<ImageView
android:id="@+id/home_search_bar_mic"
android:layout_width="22dp"
android:layout_height="22dp"
android:contentDescription="@string/home_search_voice"
android:scaleType="fitCenter"
android:src="@drawable/ic_home_mic"
android:tint="@color/home_text_secondary" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
+155 -13
View File
@@ -9,17 +9,32 @@
<include layout="@layout/home_sub_header" /> <include layout="@layout/home_sub_header" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:clipToPadding="false"
android:overScrollMode="ifContentScrolls"
android:scrollbars="none">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingStart="@dimen/looply_screen_padding"
android:paddingTop="@dimen/looply_space_sm"
android:paddingEnd="@dimen/looply_screen_padding"
android:paddingBottom="@dimen/looply_space_xxl">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="@dimen/home_search_field_height" android:layout_height="@dimen/home_search_field_height"
android:layout_marginStart="@dimen/looply_space_md"
android:layout_marginTop="@dimen/looply_space_sm"
android:layout_marginEnd="@dimen/looply_space_md"
android:background="@drawable/home_bg_search_field_inner" android:background="@drawable/home_bg_search_field_inner"
android:elevation="2dp"
android:gravity="center_vertical" android:gravity="center_vertical"
android:orientation="horizontal" android:orientation="horizontal"
android:paddingStart="@dimen/looply_space_md" android:paddingStart="@dimen/looply_space_md"
android:paddingEnd="@dimen/looply_space_md"> android:paddingEnd="@dimen/looply_space_sm">
<ImageView <ImageView
android:layout_width="18dp" android:layout_width="18dp"
@@ -33,8 +48,8 @@
android:id="@+id/home_search_field" android:id="@+id/home_search_field"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_weight="1"
android:layout_marginStart="@dimen/looply_space_sm" android:layout_marginStart="@dimen/looply_space_sm"
android:layout_weight="1"
android:background="@android:color/transparent" android:background="@android:color/transparent"
android:gravity="center_vertical|start" android:gravity="center_vertical|start"
android:hint="@string/home_search_field_hint" android:hint="@string/home_search_field_hint"
@@ -44,28 +59,108 @@
android:maxLines="1" android:maxLines="1"
android:textColor="@color/home_text_primary" android:textColor="@color/home_text_primary"
android:textColorHint="@color/home_search_hint" android:textColorHint="@color/home_search_hint"
android:textSize="16sp" /> android:textSize="15sp" />
<ImageButton <ImageButton
android:id="@+id/home_search_clear" android:id="@+id/home_search_clear"
android:layout_width="32dp" android:layout_width="36dp"
android:layout_height="32dp" android:layout_height="36dp"
android:background="@android:color/transparent" android:background="@android:color/transparent"
android:contentDescription="@string/home_search_clear" android:contentDescription="@string/home_search_clear"
android:padding="8dp"
android:scaleType="centerInside"
android:src="@drawable/ic_home_close"
android:tint="@color/home_text_secondary"
android:visibility="gone" />
<ImageButton
android:id="@+id/home_search_voice"
android:layout_width="36dp"
android:layout_height="36dp"
android:background="@android:color/transparent"
android:contentDescription="@string/home_search_voice"
android:padding="6dp" android:padding="6dp"
android:scaleType="centerInside" android:scaleType="centerInside"
android:visibility="gone" android:src="@drawable/ic_home_mic"
app:srcCompat="@drawable/ic_home_close" android:tint="@color/home_text_secondary"
app:tint="@color/home_text_secondary" /> android:visibility="visible" />
</LinearLayout>
<LinearLayout
android:id="@+id/home_search_frequent_section"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/looply_space_xl"
android:orientation="vertical"
android:visibility="gone">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:includeFontPadding="false"
android:text="@string/home_search_frequent_title"
android:textColor="@color/home_text_primary"
android:textSize="15sp"
android:textStyle="bold" />
<TextView
android:id="@+id/home_search_frequent_see_all"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackgroundBorderless"
android:includeFontPadding="false"
android:padding="@dimen/looply_space_xs"
android:text="@string/home_search_see_all"
android:textColor="@color/home_accent"
android:textSize="14sp" />
</LinearLayout>
<LinearLayout
android:id="@+id/home_search_frequent_row"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/looply_space_md"
android:orientation="horizontal" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/looply_space_xl"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:includeFontPadding="false"
android:text="@string/home_search_categories_title"
android:textColor="@color/home_text_primary"
android:textSize="15sp"
android:textStyle="bold" />
<TextView
android:id="@+id/home_search_categories_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:includeFontPadding="false"
android:textColor="@color/home_text_secondary"
android:textSize="13sp" />
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout
android:id="@+id/home_search_filters" android:id="@+id/home_search_filters"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="@dimen/looply_space_md"
android:layout_marginTop="@dimen/looply_space_md" android:layout_marginTop="@dimen/looply_space_md"
android:layout_marginEnd="@dimen/looply_space_md"
android:orientation="vertical"> android:orientation="vertical">
<LinearLayout <LinearLayout
@@ -155,4 +250,51 @@
android:layout_weight="1" /> android:layout_weight="1" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
<LinearLayout
android:id="@+id/home_search_recent_section"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/looply_space_xl"
android:orientation="vertical"
android:visibility="gone">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:includeFontPadding="false"
android:text="@string/home_search_recent_title"
android:textColor="@color/home_text_primary"
android:textSize="15sp"
android:textStyle="bold" />
<TextView
android:id="@+id/home_search_clear_history"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackgroundBorderless"
android:includeFontPadding="false"
android:padding="@dimen/looply_space_xs"
android:text="@string/home_search_clear_history"
android:textColor="@color/home_accent"
android:textSize="14sp" />
</LinearLayout>
<com.google.android.material.chip.ChipGroup
android:id="@+id/home_search_recent_group"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/looply_space_md"
app:chipSpacingHorizontal="8dp"
app:chipSpacingVertical="8dp" />
</LinearLayout>
</LinearLayout>
</ScrollView>
</LinearLayout> </LinearLayout>
@@ -1,20 +1,23 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- One filter tile in the message search grid (icon + label). -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="@dimen/home_search_filter_height" android:layout_height="@dimen/home_search_filter_height"
android:background="@drawable/home_bg_sheet_action" android:background="@drawable/home_bg_search_card"
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
android:gravity="center_vertical" android:gravity="center_vertical"
android:orientation="horizontal" android:orientation="horizontal"
android:paddingStart="@dimen/looply_space_lg" android:paddingStart="@dimen/looply_space_lg"
android:paddingEnd="@dimen/looply_space_md"> android:paddingTop="@dimen/looply_space_md"
android:paddingEnd="@dimen/looply_space_lg"
android:paddingBottom="@dimen/looply_space_md">
<ImageView <ImageView
android:id="@+id/home_search_filter_icon" android:id="@+id/home_search_filter_icon"
android:layout_width="@dimen/home_search_filter_icon_size" android:layout_width="@dimen/home_search_filter_icon_size"
android:layout_height="@dimen/home_search_filter_icon_size" android:layout_height="@dimen/home_search_filter_icon_size"
android:layout_gravity="top"
android:layout_marginTop="2dp"
android:contentDescription="@null" android:contentDescription="@null"
android:scaleType="fitCenter" android:scaleType="fitCenter"
android:tint="@color/home_text_primary" /> android:tint="@color/home_text_primary" />
@@ -27,7 +30,17 @@
android:layout_weight="1" android:layout_weight="1"
android:ellipsize="end" android:ellipsize="end"
android:includeFontPadding="false" android:includeFontPadding="false"
android:maxLines="1" android:maxLines="2"
android:textColor="@color/home_text_primary" android:textColor="@color/home_text_primary"
android:textSize="15sp" /> android:textSize="15sp" />
<TextView
android:id="@+id/home_search_filter_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_marginStart="@dimen/looply_space_sm"
android:includeFontPadding="false"
android:textColor="@color/home_text_secondary"
android:textSize="14sp" />
</LinearLayout> </LinearLayout>
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:clickable="true"
android:focusable="true"
android:gravity="center_horizontal"
android:orientation="vertical"
android:paddingStart="2dp"
android:paddingEnd="2dp">
<com.android.messaging.ui.ContactIconView
android:id="@+id/home_search_frequent_avatar"
android:layout_width="@dimen/home_search_frequent_avatar"
android:layout_height="@dimen/home_search_frequent_avatar"
android:contentDescription="@null"
app:contactIconSize="normal" />
<TextView
android:id="@+id/home_search_frequent_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:ellipsize="end"
android:gravity="center_horizontal"
android:includeFontPadding="false"
android:maxLines="2"
android:textColor="@color/home_text_primary"
android:textSize="12sp" />
</LinearLayout>
@@ -41,4 +41,18 @@
android:visibility="gone" android:visibility="gone"
app:srcCompat="@drawable/ic_looply_more" app:srcCompat="@drawable/ic_looply_more"
app:tint="@color/home_text_primary" /> app:tint="@color/home_text_primary" />
<TextView
android:id="@+id/home_sub_header_action"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackgroundBorderless"
android:includeFontPadding="false"
android:paddingStart="@dimen/looply_space_sm"
android:paddingTop="@dimen/looply_space_sm"
android:paddingEnd="@dimen/looply_space_xs"
android:paddingBottom="@dimen/looply_space_sm"
android:textColor="@color/home_accent"
android:textSize="16sp"
android:visibility="gone" />
</LinearLayout> </LinearLayout>
+3 -2
View File
@@ -106,9 +106,10 @@
<dimen name="home_nav_item_padding">8dp</dimen> <dimen name="home_nav_item_padding">8dp</dimen>
<dimen name="home_nav_dock_padding_bottom">14dp</dimen> <dimen name="home_nav_dock_padding_bottom">14dp</dimen>
<dimen name="home_alert_dialog_horizontal_margin">28dp</dimen> <dimen name="home_alert_dialog_horizontal_margin">28dp</dimen>
<dimen name="home_search_filter_height">56dp</dimen> <dimen name="home_search_filter_height">88dp</dimen>
<dimen name="home_search_filter_icon_size">22dp</dimen> <dimen name="home_search_filter_icon_size">22dp</dimen>
<dimen name="home_search_field_height">52dp</dimen> <dimen name="home_search_field_height">48dp</dimen>
<dimen name="home_search_frequent_avatar">48dp</dimen>
<dimen name="home_compose_field_height">52dp</dimen> <dimen name="home_compose_field_height">52dp</dimen>
<dimen name="home_compose_group_button_height">52dp</dimen> <dimen name="home_compose_group_button_height">52dp</dimen>
<dimen name="home_selection_dock_height">55dp</dimen> <dimen name="home_selection_dock_height">55dp</dimen>
+9 -1
View File
@@ -155,8 +155,16 @@
<!-- Message search screen --> <!-- Message search screen -->
<string name="home_search_title" translatable="false">Search</string> <string name="home_search_title" translatable="false">Search</string>
<string name="home_search_field_hint" translatable="false">Search messages</string> <string name="home_search_field_hint" translatable="false">Search messages or contacts</string>
<string name="home_search_clear" translatable="false">Clear</string> <string name="home_search_clear" translatable="false">Clear</string>
<string name="home_search_reset" translatable="false">Reset</string>
<string name="home_search_voice" translatable="false">Voice search</string>
<string name="home_search_frequent_title" translatable="false">Frequent contacts</string>
<string name="home_search_see_all" translatable="false">See all</string>
<string name="home_search_categories_title" translatable="false">Categories</string>
<string name="home_search_categories_count" translatable="false">%1$d items</string>
<string name="home_search_recent_title" translatable="false">Recent searches</string>
<string name="home_search_clear_history" translatable="false">Clear history</string>
<string name="home_search_filter_unread" translatable="false">Unread</string> <string name="home_search_filter_unread" translatable="false">Unread</string>
<string name="home_search_filter_saved" translatable="false">Saved contacts</string> <string name="home_search_filter_saved" translatable="false">Saved contacts</string>
<string name="home_search_filter_unknown" translatable="false">Unknown contacts</string> <string name="home_search_filter_unknown" translatable="false">Unknown contacts</string>