Messaging: Add home categories and shared modal/input chrome
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -23,13 +23,16 @@ import android.view.ActionMode;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import com.android.messaging.R;
|
||||
import com.android.messaging.ui.home.HomeInputFocus;
|
||||
import com.android.messaging.util.BugleActivityUtil;
|
||||
import com.android.messaging.util.ImeUtil;
|
||||
import com.android.messaging.util.LogUtil;
|
||||
@@ -91,6 +94,13 @@ public class BugleActionBarActivity extends AppCompatActivity implements ImeUtil
|
||||
BugleActivityUtil.onActivityResume(this, BugleActionBarActivity.this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dispatchTouchEvent(final MotionEvent ev) {
|
||||
final Window window = getWindow();
|
||||
HomeInputFocus.clearIfOutside(window == null ? null : window.getDecorView(), ev);
|
||||
return super.dispatchTouchEvent(ev);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
|
||||
@@ -221,6 +221,9 @@ public class ComposeMessageView extends LinearLayout
|
||||
mComposeEditText.setOnEditorActionListener(this);
|
||||
mComposeEditText.addTextChangedListener(this);
|
||||
mComposeEditText.setOnFocusChangeListener((v, hasFocus) -> {
|
||||
if (mAttachMediaButton != null) {
|
||||
mAttachMediaButton.setSelected(hasFocus);
|
||||
}
|
||||
if (v == mComposeEditText && hasFocus) {
|
||||
if (mHost != null) {
|
||||
mHost.onComposeEditTextFocused();
|
||||
@@ -320,6 +323,7 @@ public class ComposeMessageView extends LinearLayout
|
||||
}
|
||||
mAttachHost = createComposeAttachHost();
|
||||
mAttachMediaButton.setOnClickListener(clickView -> toggleComposeAttachmentSheet());
|
||||
mAttachMediaButton.setSelected(mComposeEditText.hasFocus());
|
||||
|
||||
final ImageButton emojiButton = findViewById(R.id.home_compose_emoji_button);
|
||||
if (emojiButton != null) {
|
||||
@@ -784,36 +788,22 @@ public class ComposeMessageView extends LinearLayout
|
||||
int sendWidgetMode = SEND_WIDGET_MODE_SELF_AVATAR;
|
||||
if (selfSendButtonUri != null) {
|
||||
if (hasWorkingDraft && isDataLoadedForMessageSend()) {
|
||||
UiUtils.revealOrHideViewWithAnimation(mSendButton, VISIBLE, null);
|
||||
if (isOverriddenAvatarAGroup()) {
|
||||
// If the host has overriden the avatar to show a group avatar where the
|
||||
// send button sits, we have to hide the group avatar because it can be larger
|
||||
// than the send button and pieces of the avatar will stick out from behind
|
||||
// the send button.
|
||||
UiUtils.revealOrHideViewWithAnimation(mSelfSendIcon, GONE, null);
|
||||
}
|
||||
hideSelfSendIcon();
|
||||
mMmsIndicator.setVisibility(draftMessageData.getIsMms() ? VISIBLE : INVISIBLE);
|
||||
sendWidgetMode = SEND_WIDGET_MODE_SEND_BUTTON;
|
||||
} else {
|
||||
mSelfSendIcon.setImageResourceUri(selfSendButtonUri);
|
||||
if (isOverriddenAvatarAGroup()) {
|
||||
UiUtils.revealOrHideViewWithAnimation(mSelfSendIcon, VISIBLE, null);
|
||||
}
|
||||
UiUtils.revealOrHideViewWithAnimation(mSendButton, GONE, null);
|
||||
hideSelfSendIcon();
|
||||
mMmsIndicator.setVisibility(INVISIBLE);
|
||||
if (shouldShowSimSelector(mConversationDataModel.getData())) {
|
||||
sendWidgetMode = SEND_WIDGET_MODE_SIM_SELECTOR;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
mSelfSendIcon.setImageResourceUri(null);
|
||||
hideSelfSendIcon();
|
||||
if (hasWorkingDraft && isDataLoadedForMessageSend()) {
|
||||
UiUtils.revealOrHideViewWithAnimation(mSendButton, VISIBLE, null);
|
||||
UiUtils.revealOrHideViewWithAnimation(mSelfSendIcon, GONE, null);
|
||||
mMmsIndicator.setVisibility(draftMessageData.getIsMms() ? VISIBLE : INVISIBLE);
|
||||
sendWidgetMode = SEND_WIDGET_MODE_SEND_BUTTON;
|
||||
} else {
|
||||
UiUtils.revealOrHideViewWithAnimation(mSendButton, GONE, null);
|
||||
mMmsIndicator.setVisibility(INVISIBLE);
|
||||
}
|
||||
}
|
||||
@@ -898,10 +888,24 @@ public class ComposeMessageView extends LinearLayout
|
||||
if (mVoiceButton == null || mSendColumn == null) {
|
||||
return;
|
||||
}
|
||||
final boolean showSendColumn = sendWidgetMode == SEND_WIDGET_MODE_SEND_BUTTON
|
||||
|| sendWidgetMode == SEND_WIDGET_MODE_SIM_SELECTOR;
|
||||
mSendColumn.setVisibility(showSendColumn ? View.VISIBLE : View.GONE);
|
||||
mVoiceButton.setVisibility(showSendColumn ? View.GONE : View.VISIBLE);
|
||||
final boolean showSend = sendWidgetMode == SEND_WIDGET_MODE_SEND_BUTTON;
|
||||
if (showSend) {
|
||||
mSendColumn.setVisibility(VISIBLE);
|
||||
UiUtils.revealOrHideViewWithAnimation(mSendButton, VISIBLE, null);
|
||||
UiUtils.revealOrHideViewWithAnimation(mVoiceButton, GONE, null);
|
||||
return;
|
||||
}
|
||||
UiUtils.revealOrHideViewWithAnimation(mVoiceButton, VISIBLE, null);
|
||||
if (mSendColumn.getVisibility() == VISIBLE) {
|
||||
UiUtils.revealOrHideViewWithAnimation(mSendButton, GONE, () -> {
|
||||
if (mSendWidgetMode != SEND_WIDGET_MODE_SEND_BUTTON && mSendColumn != null) {
|
||||
mSendColumn.setVisibility(GONE);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
mSendButton.setVisibility(GONE);
|
||||
mSendColumn.setVisibility(GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void setSendButtonAccessibility(final int sendWidgetMode) {
|
||||
@@ -911,7 +915,6 @@ public class ComposeMessageView extends LinearLayout
|
||||
// important for accessibility.
|
||||
mSelfSendIcon.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
|
||||
mSelfSendIcon.setContentDescription(null);
|
||||
mSendButton.setVisibility(View.GONE);
|
||||
setSendWidgetAccessibilityTraversalOrder(SEND_WIDGET_MODE_SELF_AVATAR);
|
||||
break;
|
||||
|
||||
@@ -1078,12 +1081,13 @@ public class ComposeMessageView extends LinearLayout
|
||||
mHost.notifyOfAttachmentLoadFailed();
|
||||
}
|
||||
|
||||
private boolean isOverriddenAvatarAGroup() {
|
||||
final Uri overridenSelfUri = mHost.getSelfSendButtonIconUri();
|
||||
if (overridenSelfUri == null) {
|
||||
return false;
|
||||
private void hideSelfSendIcon() {
|
||||
if (mSelfSendIcon == null) {
|
||||
return;
|
||||
}
|
||||
return AvatarUriUtil.TYPE_GROUP_URI.equals(AvatarUriUtil.getAvatarType(overridenSelfUri));
|
||||
mSelfSendIcon.animate().cancel();
|
||||
mSelfSendIcon.setVisibility(GONE);
|
||||
mSelfSendIcon.setImageResourceUri(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -319,7 +319,7 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
|
||||
mScrollToDismissHandled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private final ActivityResultLauncher<Intent> mLauncher = registerForActivityResult(
|
||||
@@ -968,7 +968,7 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
|
||||
mRecyclerView.post(mAdapter::notifyDataSetChanged);
|
||||
return;
|
||||
}
|
||||
mAdapter.notifyDataSetChanged();
|
||||
mAdapter.notifyDataSetChanged();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1219,8 +1219,8 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
|
||||
mListState = mRecyclerView.getLayoutManager().onSaveInstanceState();
|
||||
|
||||
try {
|
||||
LocalBroadcastManager.getInstance(getActivity())
|
||||
.unregisterReceiver(mConversationSelfIdChangeReceiver);
|
||||
LocalBroadcastManager.getInstance(getActivity())
|
||||
.unregisterReceiver(mConversationSelfIdChangeReceiver);
|
||||
} catch (final IllegalArgumentException ignored) {
|
||||
// onResume may have aborted before the receiver was registered (camera return).
|
||||
}
|
||||
@@ -2421,7 +2421,7 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
|
||||
AccessibilityUtil.getVocalizedPhoneNumber(getResources(), conversationName);
|
||||
titleView.setContentDescription(vocalizedConversationName);
|
||||
if (getActivity() != null) {
|
||||
getActivity().setTitle(conversationName);
|
||||
getActivity().setTitle(conversationName);
|
||||
}
|
||||
} else if (getActivity() != null) {
|
||||
final String appName = getString(R.string.app_name);
|
||||
|
||||
+48
-8
@@ -32,6 +32,8 @@ import androidx.recyclerview.widget.RecyclerView;
|
||||
import com.android.messaging.R;
|
||||
import com.android.messaging.datamodel.data.ConversationListItemData;
|
||||
import com.android.messaging.ui.CursorRecyclerAdapter;
|
||||
import com.android.messaging.ui.home.HomeCategoryClassifier;
|
||||
import com.android.messaging.ui.home.HomeCategoryStore;
|
||||
import com.android.messaging.ui.home.PinnedConversationStore;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -58,6 +60,7 @@ public class ConversationListAdapter
|
||||
private final ConversationListItemView.HostInterface mClivHostInterface;
|
||||
private final ConversationListItemData mFilterProbe = new ConversationListItemData();
|
||||
private int mFilterMode = FILTER_ALL;
|
||||
private String mCategoryId = HomeCategoryStore.ID_ALL;
|
||||
private int[] mDisplayPositions = new int[0];
|
||||
private View mPinnedStripRoot;
|
||||
private boolean mPinnedHeaderVisible;
|
||||
@@ -113,6 +116,46 @@ public class ConversationListAdapter
|
||||
return mFilterMode;
|
||||
}
|
||||
|
||||
public void setCategoryId(final String categoryId) {
|
||||
final String next = categoryId == null ? HomeCategoryStore.ID_ALL : categoryId;
|
||||
if (next.equals(mCategoryId)) {
|
||||
return;
|
||||
}
|
||||
mCategoryId = next;
|
||||
rebuildFilterIndex();
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public String getCategoryId() {
|
||||
return mCategoryId;
|
||||
}
|
||||
|
||||
public void refreshCategoryFilter() {
|
||||
rebuildFilterIndex();
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public List<ConversationListItemData> snapshotConversations() {
|
||||
final Cursor cursor = getCursor();
|
||||
if (cursor == null || cursor.getCount() == 0) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
final List<ConversationListItemData> items = new ArrayList<>(cursor.getCount());
|
||||
final int saved = cursor.getPosition();
|
||||
for (int i = 0; i < cursor.getCount(); i++) {
|
||||
if (!cursor.moveToPosition(i)) {
|
||||
continue;
|
||||
}
|
||||
final ConversationListItemData item = new ConversationListItemData();
|
||||
item.bind(cursor);
|
||||
items.add(item);
|
||||
}
|
||||
if (saved >= 0 && saved < cursor.getCount()) {
|
||||
cursor.moveToPosition(saved);
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
public void notifySelectionChromeChanged() {
|
||||
final int count = getItemCount();
|
||||
if (count > 0) {
|
||||
@@ -245,16 +288,13 @@ public class ConversationListAdapter
|
||||
}
|
||||
|
||||
private boolean passesFilter() {
|
||||
if (mFilterMode == FILTER_ALL) {
|
||||
return true;
|
||||
if (mFilterMode == FILTER_UNREAD && mFilterProbe.getIsRead()) {
|
||||
return false;
|
||||
}
|
||||
if (mFilterMode == FILTER_UNREAD) {
|
||||
return !mFilterProbe.getIsRead();
|
||||
if (mFilterMode == FILTER_GROUPS && mFilterProbe.getParticipantCount() <= 1) {
|
||||
return false;
|
||||
}
|
||||
if (mFilterMode == FILTER_GROUPS) {
|
||||
return mFilterProbe.getParticipantCount() > 1;
|
||||
}
|
||||
return true;
|
||||
return HomeCategoryClassifier.matches(mContext, mCategoryId, mFilterProbe);
|
||||
}
|
||||
|
||||
private void sortPinnedFirst(final ArrayList<Integer> positions, final Cursor cursor) {
|
||||
|
||||
+247
-8
@@ -71,6 +71,11 @@ import com.android.messaging.datamodel.data.ConversationListData;
|
||||
import com.android.messaging.datamodel.data.ConversationListData.ConversationListDataListener;
|
||||
import com.android.messaging.datamodel.data.ConversationListItemData;
|
||||
import com.android.messaging.ui.ContactIconView;
|
||||
import com.android.messaging.ui.home.HomeCategoryAddModal;
|
||||
import com.android.messaging.ui.home.HomeCategoryAssignSheet;
|
||||
import com.android.messaging.ui.home.HomeCategoryClassifier;
|
||||
import com.android.messaging.ui.home.HomeCategoryStore;
|
||||
import com.android.messaging.ui.home.HomeModal;
|
||||
import com.android.messaging.ui.home.HomeEmptyState;
|
||||
import com.android.messaging.ui.SnackBarInteraction;
|
||||
import com.android.messaging.ui.UIIntents;
|
||||
@@ -91,6 +96,7 @@ import com.android.messaging.util.ThreadUtil;
|
||||
import com.android.messaging.util.UiUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
@@ -130,6 +136,10 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
private TextView mHomeReadAllButton;
|
||||
private int mHomeSelectionCount;
|
||||
private View mHomeMessagesTop;
|
||||
private View mHomeCategoryStrip;
|
||||
private ViewGroup mHomeCategoryAll;
|
||||
private View mHomeCategoryScroll;
|
||||
private LinearLayout mHomeCategoryRow;
|
||||
private TextView mHomeTitle;
|
||||
private View mHomeAutomationPane;
|
||||
private View mHomeAutomationCreateButton;
|
||||
@@ -388,6 +398,17 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
}
|
||||
setupHomeChrome(rootView);
|
||||
mHomeMessagesTop = rootView.findViewById(R.id.home_messages_top);
|
||||
mHomeCategoryStrip = rootView.findViewById(R.id.home_category_strip);
|
||||
mHomeCategoryAll = rootView.findViewById(R.id.home_category_all);
|
||||
mHomeCategoryScroll = rootView.findViewById(R.id.home_category_scroll);
|
||||
mHomeCategoryRow = rootView.findViewById(R.id.home_category_row);
|
||||
final View categoryAdd = rootView.findViewById(R.id.home_category_add);
|
||||
if (categoryAdd != null) {
|
||||
categoryAdd.setOnClickListener(v -> HomeCategoryAddModal.show(requireContext(),
|
||||
created -> selectHomeCategory(created.id)));
|
||||
}
|
||||
mAdapter.setCategoryId(HomeCategoryStore.getSelectedId(requireContext()));
|
||||
bindHomeCategoryStrip();
|
||||
mHomeTitle = rootView.findViewById(R.id.home_title);
|
||||
mHomeAutomationPane = rootView.findViewById(R.id.home_automation_pane);
|
||||
mHomeNavDock = rootView.findViewById(R.id.home_nav_dock);
|
||||
@@ -1108,6 +1129,11 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
if (mHomeMessagesTop != null) {
|
||||
mHomeMessagesTop.setVisibility(View.VISIBLE);
|
||||
}
|
||||
if (mHomeCategoryStrip != null) {
|
||||
mHomeCategoryStrip.setVisibility(showMessages ? View.VISIBLE : View.GONE);
|
||||
} else if (mHomeCategoryScroll != null) {
|
||||
mHomeCategoryScroll.setVisibility(showMessages ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
final CharSequence title = getString(showMessages
|
||||
? R.string.home_title : R.string.home_automation_title);
|
||||
if (mHomeTitle != null) {
|
||||
@@ -1470,6 +1496,10 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
dismissHomePopover();
|
||||
toggleConversationMute(data);
|
||||
});
|
||||
content.findViewById(R.id.popover_conversation_category).setOnClickListener(v -> {
|
||||
dismissHomePopover();
|
||||
showAssignHomeCategory(data);
|
||||
});
|
||||
content.findViewById(R.id.popover_conversation_delete).setOnClickListener(v -> {
|
||||
dismissHomePopover();
|
||||
confirmDeleteConversation(data);
|
||||
@@ -1832,21 +1862,224 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
content.findViewById(R.id.popover_edit_pinned).setOnClickListener(v -> {
|
||||
dismissHomePopoverAndThen(() -> setPinnedEditMode(true));
|
||||
});
|
||||
content.findViewById(R.id.popover_name_photo).setOnClickListener(v -> {
|
||||
dismissHomePopover();
|
||||
launchHomeSubScreen(() -> {
|
||||
if (getActivity() instanceof ConversationListActivity) {
|
||||
((ConversationListActivity) getActivity()).onActionBarSettings();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void selectHomeChip(final int filterMode) {
|
||||
mAdapter.setFilterMode(filterMode);
|
||||
bindHomeCategoryStrip();
|
||||
updateEmptyListUi(mAdapter.getConversationItemCount() == 0);
|
||||
}
|
||||
|
||||
private void bindHomeCategoryStrip() {
|
||||
if (mHomeCategoryRow == null || mArchiveMode || mForwardMessageMode) {
|
||||
return;
|
||||
}
|
||||
final Context context = getContext();
|
||||
if (context == null) {
|
||||
return;
|
||||
}
|
||||
final String selectedId = HomeCategoryStore.getSelectedId(context);
|
||||
final List<ConversationListItemData> items = mAdapter.snapshotConversations();
|
||||
final LayoutInflater inflater = LayoutInflater.from(context);
|
||||
if (mHomeCategoryAll != null) {
|
||||
mHomeCategoryAll.removeAllViews();
|
||||
}
|
||||
mHomeCategoryRow.removeAllViews();
|
||||
for (final HomeCategoryStore.Category category : HomeCategoryStore.listTabs(context)) {
|
||||
final boolean pinnedAll = HomeCategoryStore.ID_ALL.equals(category.id);
|
||||
final ViewGroup host = pinnedAll && mHomeCategoryAll != null
|
||||
? mHomeCategoryAll : mHomeCategoryRow;
|
||||
final View tab = inflater.inflate(R.layout.home_category_tab, host, false);
|
||||
bindHomeCategoryTab(tab, category, selectedId, items);
|
||||
host.addView(tab);
|
||||
}
|
||||
}
|
||||
|
||||
private void bindHomeCategoryTab(final View tab, final HomeCategoryStore.Category category,
|
||||
final String selectedId, final List<ConversationListItemData> items) {
|
||||
final Context context = tab.getContext();
|
||||
final TextView label = tab.findViewById(R.id.home_category_tab_label);
|
||||
final View underline = tab.findViewById(R.id.home_category_tab_underline);
|
||||
final View dot = tab.findViewById(R.id.home_category_tab_dot);
|
||||
final boolean selected = selectedId.equals(category.id);
|
||||
if (label != null) {
|
||||
label.setText(category.name);
|
||||
label.setTypeface(null, selected ? android.graphics.Typeface.BOLD
|
||||
: android.graphics.Typeface.NORMAL);
|
||||
label.setTextColor(ContextCompat.getColor(context, selected
|
||||
? R.color.home_accent : R.color.home_text_secondary));
|
||||
}
|
||||
if (underline != null) {
|
||||
underline.setVisibility(selected ? View.VISIBLE : View.INVISIBLE);
|
||||
}
|
||||
if (dot != null) {
|
||||
final boolean showDot = !HomeCategoryStore.ID_ALL.equals(category.id)
|
||||
&& categoryHasUnread(context, category.id, items);
|
||||
dot.setVisibility(showDot ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
tab.setTag(category.id);
|
||||
tab.setOnClickListener(v -> selectHomeCategory(category.id));
|
||||
if (!category.builtIn) {
|
||||
tab.setOnLongClickListener(v -> {
|
||||
confirmDeleteHomeCategory(category);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private boolean categoryHasUnread(final Context context, final String categoryId,
|
||||
final List<ConversationListItemData> items) {
|
||||
for (final ConversationListItemData item : items) {
|
||||
if (!item.getIsRead()
|
||||
&& HomeCategoryClassifier.matches(context, categoryId, item)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void selectHomeCategory(final String categoryId) {
|
||||
final Context context = getContext();
|
||||
if (context == null || mAdapter == null || TextUtils.isEmpty(categoryId)) {
|
||||
return;
|
||||
}
|
||||
final String previous = HomeCategoryStore.getSelectedId(context);
|
||||
if (categoryId.equals(previous) && homeCategoryStripHas(categoryId)) {
|
||||
return;
|
||||
}
|
||||
final boolean fromEnd = homeCategoryIndex(context, categoryId)
|
||||
>= homeCategoryIndex(context, previous);
|
||||
HomeCategoryStore.setSelectedId(context, categoryId);
|
||||
mAdapter.setCategoryId(categoryId);
|
||||
mAdapter.refreshCategoryFilter();
|
||||
if (homeCategoryStripHas(categoryId)) {
|
||||
applyHomeCategorySelection(true);
|
||||
} else {
|
||||
bindHomeCategoryStrip();
|
||||
}
|
||||
updateEmptyListUi(mAdapter.getConversationItemCount() == 0);
|
||||
if (mRecyclerView != null && mRecyclerView.getVisibility() == View.VISIBLE) {
|
||||
HomeUiMotion.categoryPageEnter(mRecyclerView, fromEnd);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean homeCategoryStripHas(final String categoryId) {
|
||||
if (TextUtils.isEmpty(categoryId)) {
|
||||
return false;
|
||||
}
|
||||
return findHomeCategoryTab(categoryId) != null;
|
||||
}
|
||||
|
||||
private View findHomeCategoryTab(final String categoryId) {
|
||||
final View pinned = findHomeCategoryTabIn(mHomeCategoryAll, categoryId);
|
||||
if (pinned != null) {
|
||||
return pinned;
|
||||
}
|
||||
return findHomeCategoryTabIn(mHomeCategoryRow, categoryId);
|
||||
}
|
||||
|
||||
private static View findHomeCategoryTabIn(final ViewGroup host, final String categoryId) {
|
||||
if (host == null) {
|
||||
return null;
|
||||
}
|
||||
for (int i = 0; i < host.getChildCount(); i++) {
|
||||
if (categoryId.equals(host.getChildAt(i).getTag())) {
|
||||
return host.getChildAt(i);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private int homeCategoryIndex(final Context context, final String categoryId) {
|
||||
final List<HomeCategoryStore.Category> tabs = HomeCategoryStore.listTabs(context);
|
||||
for (int i = 0; i < tabs.size(); i++) {
|
||||
if (tabs.get(i).id.equals(categoryId)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private void applyHomeCategorySelection(final boolean animate) {
|
||||
final Context context = getContext();
|
||||
if (context == null) {
|
||||
return;
|
||||
}
|
||||
final String selectedId = HomeCategoryStore.getSelectedId(context);
|
||||
final int selectedColor = ContextCompat.getColor(context, R.color.home_accent);
|
||||
final int idleColor = ContextCompat.getColor(context, R.color.home_text_secondary);
|
||||
View selectedTab = null;
|
||||
selectedTab = applyHomeCategoryHost(mHomeCategoryAll, selectedId, selectedColor, idleColor,
|
||||
animate, selectedTab);
|
||||
selectedTab = applyHomeCategoryHost(mHomeCategoryRow, selectedId, selectedColor, idleColor,
|
||||
animate, selectedTab);
|
||||
if (selectedTab != null
|
||||
&& selectedTab.getParent() == mHomeCategoryRow
|
||||
&& mHomeCategoryScroll instanceof android.widget.HorizontalScrollView) {
|
||||
final View tab = selectedTab;
|
||||
final android.widget.HorizontalScrollView scroller =
|
||||
(android.widget.HorizontalScrollView) mHomeCategoryScroll;
|
||||
scroller.post(() -> scroller.smoothScrollTo(
|
||||
Math.max(tab.getLeft() - tab.getWidth(), 0), 0));
|
||||
}
|
||||
}
|
||||
|
||||
private static View applyHomeCategoryHost(final ViewGroup host, final String selectedId,
|
||||
final int selectedColor, final int idleColor, final boolean animate,
|
||||
View selectedTab) {
|
||||
if (host == null) {
|
||||
return selectedTab;
|
||||
}
|
||||
for (int i = 0; i < host.getChildCount(); i++) {
|
||||
final View tab = host.getChildAt(i);
|
||||
final Object id = tab.getTag();
|
||||
if (!(id instanceof String)) {
|
||||
continue;
|
||||
}
|
||||
final boolean selected = selectedId.equals(id);
|
||||
if (selected) {
|
||||
selectedTab = tab;
|
||||
}
|
||||
HomeUiMotion.applyCategoryTab(
|
||||
tab.findViewById(R.id.home_category_tab_label),
|
||||
tab.findViewById(R.id.home_category_tab_underline),
|
||||
selectedColor, idleColor, selected, animate);
|
||||
}
|
||||
return selectedTab;
|
||||
}
|
||||
|
||||
private void confirmDeleteHomeCategory(final HomeCategoryStore.Category category) {
|
||||
final Context context = getContext();
|
||||
if (context == null || category == null || category.builtIn) {
|
||||
return;
|
||||
}
|
||||
HomeModal.builder(context)
|
||||
.icon(R.drawable.ic_home_category)
|
||||
.title(R.string.home_category_delete_title)
|
||||
.message(R.string.home_category_delete_message)
|
||||
.negative(android.R.string.cancel, null)
|
||||
.positive(R.string.home_conversation_delete, () -> {
|
||||
HomeCategoryStore.removeCustom(context, category.id);
|
||||
selectHomeCategory(HomeCategoryStore.getSelectedId(context));
|
||||
})
|
||||
.show();
|
||||
}
|
||||
|
||||
private void showAssignHomeCategory(final ConversationListItemData data) {
|
||||
final Context context = getContext();
|
||||
if (context == null || data == null || TextUtils.isEmpty(data.getConversationId())) {
|
||||
return;
|
||||
}
|
||||
HomeCategoryAssignSheet.show(context, data, () -> {
|
||||
if (!isAdded() || mAdapter == null) {
|
||||
return;
|
||||
}
|
||||
mAdapter.refreshCategoryFilter();
|
||||
bindHomeCategoryStrip();
|
||||
updateEmptyListUi(mAdapter.getConversationItemCount() == 0);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(@NonNull final Context context) {
|
||||
super.onAttach(context);
|
||||
@@ -1889,6 +2122,7 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
final Cursor oldCursor = mAdapter.swapCursor(cursor);
|
||||
prunePinnedConversations(cursor);
|
||||
refreshPinnedStrip();
|
||||
bindHomeCategoryStrip();
|
||||
updateEmptyListUi(mAdapter.getConversationItemCount() == 0);
|
||||
if (mListState != null && cursor != null && oldCursor == null) {
|
||||
mRecyclerView.getLayoutManager().onRestoreInstanceState(mListState);
|
||||
@@ -2028,6 +2262,11 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
mEmptyListMessageView.setContent(R.drawable.ic_home_menu_archive,
|
||||
R.string.home_empty_archive_title,
|
||||
R.string.home_empty_archive_body);
|
||||
} else if (!HomeCategoryStore.ID_ALL.equals(mAdapter.getCategoryId())
|
||||
|| mAdapter.getFilterMode() != ConversationListAdapter.FILTER_ALL) {
|
||||
mEmptyListMessageView.setContent(R.drawable.ic_home_nav_messages,
|
||||
R.string.home_category_empty_title,
|
||||
R.string.home_category_empty_body);
|
||||
} else {
|
||||
mEmptyListMessageView.setContent(R.drawable.ic_home_nav_messages,
|
||||
R.string.home_empty_messages_title,
|
||||
|
||||
+27
-26
@@ -341,20 +341,32 @@ public class ConversationListItemView extends FrameLayout implements OnClickList
|
||||
|
||||
private void setSnippet() {
|
||||
final String snippetText = ellipsizeSnippetForMaxLines(getSnippetText());
|
||||
final int firstLineMargin = getSnippetLeadingMargin();
|
||||
final CharSequence toSet;
|
||||
if (firstLineMargin > 0 && !TextUtils.isEmpty(snippetText)) {
|
||||
final SpannableString spanned = new SpannableString(snippetText);
|
||||
spanned.setSpan(new LeadingMarginSpan.Standard(firstLineMargin, 0),
|
||||
0, spanned.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
toSet = spanned;
|
||||
} else {
|
||||
toSet = snippetText;
|
||||
}
|
||||
if (TextUtils.equals(toSet, mSnippetTextView.getText())) {
|
||||
final int indent = getSnippetChipIndent();
|
||||
if (indent <= 0) {
|
||||
mSnippetTextView.setText(snippetText);
|
||||
return;
|
||||
}
|
||||
mSnippetTextView.setText(toSet);
|
||||
final SpannableString spanned = new SpannableString(snippetText == null ? "" : snippetText);
|
||||
spanned.setSpan(new LeadingMarginSpan.Standard(indent, 0),
|
||||
0, spanned.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
mSnippetTextView.setText(spanned);
|
||||
}
|
||||
|
||||
private int getSnippetChipIndent() {
|
||||
if (mCategoryChipView == null || mCategoryChipView.getVisibility() != VISIBLE) {
|
||||
return 0;
|
||||
}
|
||||
int width = mCategoryChipView.getWidth();
|
||||
if (width <= 0) {
|
||||
mCategoryChipView.measure(
|
||||
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
|
||||
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
|
||||
width = mCategoryChipView.getMeasuredWidth();
|
||||
}
|
||||
if (width <= 0) {
|
||||
return 0;
|
||||
}
|
||||
return width + getResources().getDimensionPixelSize(R.dimen.looply_space_sm);
|
||||
}
|
||||
|
||||
private String ellipsizeSnippetForMaxLines(final String source) {
|
||||
@@ -367,11 +379,11 @@ public class ConversationListItemView extends FrameLayout implements OnClickList
|
||||
if (maxLines <= 0 || width <= 0) {
|
||||
return source;
|
||||
}
|
||||
final int firstLineMargin = getSnippetLeadingMargin();
|
||||
final int indent = getSnippetChipIndent();
|
||||
final CharSequence measured;
|
||||
if (firstLineMargin > 0) {
|
||||
if (indent > 0) {
|
||||
final SpannableString spanned = new SpannableString(source);
|
||||
spanned.setSpan(new LeadingMarginSpan.Standard(firstLineMargin, 0),
|
||||
spanned.setSpan(new LeadingMarginSpan.Standard(indent, 0),
|
||||
0, spanned.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
measured = spanned;
|
||||
} else {
|
||||
@@ -408,17 +420,6 @@ public class ConversationListItemView extends FrameLayout implements OnClickList
|
||||
return source.substring(0, cut) + "...";
|
||||
}
|
||||
|
||||
private int getSnippetLeadingMargin() {
|
||||
if (mCategoryChipView == null || mCategoryChipView.getVisibility() != VISIBLE) {
|
||||
return 0;
|
||||
}
|
||||
mCategoryChipView.measure(
|
||||
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
|
||||
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
|
||||
return mCategoryChipView.getMeasuredWidth()
|
||||
+ Math.round(6f * getResources().getDisplayMetrics().density);
|
||||
}
|
||||
|
||||
private void updateSelectionIndicator(final boolean selectionMode, final boolean isSelected,
|
||||
final Resources resources) {
|
||||
if (mSelectionIndicatorView == null || mSelectionRingView == null
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
package com.android.messaging.ui.home;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
|
||||
import com.android.messaging.R;
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog;
|
||||
|
||||
/** Default home bottom sheet: handle, close X, optional title, custom body, dismiss on outside tap. */
|
||||
public final class HomeBottomSheet {
|
||||
|
||||
public static final class Builder {
|
||||
private final Context mContext;
|
||||
private CharSequence mTitle;
|
||||
private View mCustom;
|
||||
private boolean mCancelable = true;
|
||||
|
||||
public Builder(@NonNull final Context context) {
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
public Builder title(@StringRes final int titleRes) {
|
||||
mTitle = mContext.getString(titleRes);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder title(final CharSequence title) {
|
||||
mTitle = title;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder custom(final View custom) {
|
||||
mCustom = custom;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder cancelable(final boolean cancelable) {
|
||||
mCancelable = cancelable;
|
||||
return this;
|
||||
}
|
||||
|
||||
public BottomSheetDialog show() {
|
||||
return HomeBottomSheet.show(this);
|
||||
}
|
||||
}
|
||||
|
||||
private HomeBottomSheet() {
|
||||
}
|
||||
|
||||
public static Builder builder(@NonNull final Context context) {
|
||||
return new Builder(context);
|
||||
}
|
||||
|
||||
private static BottomSheetDialog show(final Builder options) {
|
||||
final Context context = options.mContext;
|
||||
final View root = LayoutInflater.from(context).inflate(R.layout.home_bottom_sheet, null, false);
|
||||
final TextView title = root.findViewById(R.id.home_sheet_title);
|
||||
if (title != null) {
|
||||
title.setText(options.mTitle);
|
||||
title.setVisibility(TextUtils.isEmpty(options.mTitle) ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
final FrameLayout custom = root.findViewById(R.id.home_sheet_custom);
|
||||
if (custom != null && options.mCustom != null) {
|
||||
if (options.mCustom.getParent() instanceof ViewGroup) {
|
||||
((ViewGroup) options.mCustom.getParent()).removeView(options.mCustom);
|
||||
}
|
||||
custom.addView(options.mCustom, new FrameLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
}
|
||||
|
||||
final BottomSheetDialog dialog = new BottomSheetDialog(context,
|
||||
R.style.Home_BottomSheetDialog);
|
||||
dialog.setContentView(root);
|
||||
dialog.setCancelable(options.mCancelable);
|
||||
dialog.setCanceledOnTouchOutside(options.mCancelable);
|
||||
final View close = root.findViewById(R.id.home_sheet_close);
|
||||
if (close != null) {
|
||||
close.setOnClickListener(v -> dialog.dismiss());
|
||||
close.setVisibility(options.mCancelable ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
final Window window = dialog.getWindow();
|
||||
if (window != null) {
|
||||
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
|
||||
}
|
||||
dialog.show();
|
||||
HomeInputFocus.install(dialog.getWindow());
|
||||
return dialog;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package com.android.messaging.ui.home;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.messaging.R;
|
||||
import com.android.messaging.util.ImeUtil;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/** Shared Add category name modal. */
|
||||
public final class HomeCategoryAddModal {
|
||||
|
||||
public interface Listener {
|
||||
void onCategoryCreated(HomeCategoryStore.Category created);
|
||||
}
|
||||
|
||||
private HomeCategoryAddModal() {
|
||||
}
|
||||
|
||||
public static void show(@NonNull final Context context, @Nullable final Listener listener) {
|
||||
final View content = LayoutInflater.from(context)
|
||||
.inflate(R.layout.home_category_name_dialog, null, false);
|
||||
final EditText field = content.findViewById(R.id.home_category_name_field);
|
||||
HomeModal.builder(context)
|
||||
.icon(R.drawable.ic_home_category)
|
||||
.title(R.string.home_category_add)
|
||||
.message(R.string.home_category_name_message)
|
||||
.custom(content)
|
||||
.negative(android.R.string.cancel, null)
|
||||
.positive(R.string.home_category_add_new, () -> {
|
||||
final String name = field == null ? "" : field.getText().toString();
|
||||
final HomeCategoryStore.Category created = tryCreate(context, name, "",
|
||||
Collections.emptyList());
|
||||
if (created != null && listener != null) {
|
||||
listener.onCategoryCreated(created);
|
||||
}
|
||||
})
|
||||
.show();
|
||||
if (field != null) {
|
||||
field.requestFocus();
|
||||
ImeUtil.get().showImeKeyboard(context, field);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static HomeCategoryStore.Category tryCreate(@NonNull final Context context,
|
||||
final String name, final String kind, @Nullable final List<String> conversationIds) {
|
||||
final String trimmed = name == null ? "" : name.trim();
|
||||
if (TextUtils.isEmpty(trimmed)) {
|
||||
return null;
|
||||
}
|
||||
if (HomeCategoryStore.hasName(context, trimmed)) {
|
||||
HomeToast.show(context, R.string.home_category_exists_toast);
|
||||
return null;
|
||||
}
|
||||
if (HomeCategoryStore.listCustom(context).size() >= HomeCategoryStore.MAX_CUSTOM) {
|
||||
HomeToast.show(context, R.string.home_category_limit_toast);
|
||||
return null;
|
||||
}
|
||||
final HomeCategoryStore.Category created = HomeCategoryStore.addCustom(context, trimmed,
|
||||
kind);
|
||||
if (created == null) {
|
||||
HomeToast.show(context, R.string.home_category_exists_toast);
|
||||
return null;
|
||||
}
|
||||
if (conversationIds != null && !conversationIds.isEmpty()) {
|
||||
HomeCategoryStore.assignAll(context, conversationIds, created.id);
|
||||
}
|
||||
HomeToast.show(context, R.string.home_category_added_toast);
|
||||
return created;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
package com.android.messaging.ui.home;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.android.messaging.R;
|
||||
import com.android.messaging.datamodel.data.ConversationListItemData;
|
||||
import com.google.android.material.chip.Chip;
|
||||
import com.google.android.material.chip.ChipGroup;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/** Assigns a conversation to categories using the default home modal. */
|
||||
public final class HomeCategoryAssignSheet {
|
||||
|
||||
public interface Listener {
|
||||
void onAssignmentsChanged();
|
||||
}
|
||||
|
||||
private HomeCategoryAssignSheet() {
|
||||
}
|
||||
|
||||
public static void show(@NonNull final Context context,
|
||||
@NonNull final ConversationListItemData data, final Listener listener) {
|
||||
final String conversationId = data.getConversationId();
|
||||
if (TextUtils.isEmpty(conversationId)) {
|
||||
return;
|
||||
}
|
||||
final View custom = LayoutInflater.from(context)
|
||||
.inflate(R.layout.home_category_assign_sheet, null, false);
|
||||
final ChipGroup chips = custom.findViewById(R.id.home_category_assign_chips);
|
||||
final Set<String> selected = new LinkedHashSet<>(
|
||||
HomeCategoryStore.assignedIds(context, conversationId));
|
||||
if (!selected.contains(HomeCategoryStore.ID_PERSONAL)
|
||||
&& !selected.contains(HomeCategoryStore.ID_PROMOTION)) {
|
||||
selected.add(HomeCategoryClassifier.isPromotion(data)
|
||||
? HomeCategoryStore.ID_PROMOTION : HomeCategoryStore.ID_PERSONAL);
|
||||
}
|
||||
|
||||
final Runnable[] bindChips = new Runnable[1];
|
||||
bindChips[0] = () -> {
|
||||
if (chips == null) {
|
||||
return;
|
||||
}
|
||||
chips.removeAllViews();
|
||||
final List<HomeCategoryStore.Category> categories =
|
||||
HomeCategoryStore.listTabs(context);
|
||||
for (final HomeCategoryStore.Category category : categories) {
|
||||
if (HomeCategoryStore.ID_ALL.equals(category.id)) {
|
||||
continue;
|
||||
}
|
||||
final Chip chip = new Chip(context);
|
||||
chip.setText(category.name);
|
||||
chip.setCheckable(true);
|
||||
chip.setCheckedIconVisible(false);
|
||||
chip.setChecked(selected.contains(category.id));
|
||||
styleChip(chip);
|
||||
chip.setOnCheckedChangeListener((button, isChecked) -> {
|
||||
if (isChecked) {
|
||||
selected.add(category.id);
|
||||
} else {
|
||||
selected.remove(category.id);
|
||||
}
|
||||
styleChip(chip);
|
||||
});
|
||||
chips.addView(chip);
|
||||
}
|
||||
final Chip addChip = new Chip(context);
|
||||
addChip.setText(R.string.home_category_add_new);
|
||||
addChip.setCheckable(false);
|
||||
addChip.setCheckedIconVisible(false);
|
||||
styleChip(addChip);
|
||||
addChip.setOnClickListener(v -> HomeCategoryAddModal.show(context, created -> {
|
||||
selected.add(created.id);
|
||||
bindChips[0].run();
|
||||
}));
|
||||
chips.addView(addChip);
|
||||
};
|
||||
|
||||
bindChips[0].run();
|
||||
HomeModal.builder(context)
|
||||
.icon(R.drawable.ic_home_category)
|
||||
.title(R.string.home_category_assign)
|
||||
.message(R.string.home_category_assign_message)
|
||||
.custom(custom)
|
||||
.negative(android.R.string.cancel, null)
|
||||
.positive(R.string.home_category_done, () -> {
|
||||
applyAssignments(context, conversationId, selected);
|
||||
if (listener != null) {
|
||||
listener.onAssignmentsChanged();
|
||||
}
|
||||
HomeToast.show(context, R.string.home_category_updated_toast);
|
||||
})
|
||||
.show();
|
||||
}
|
||||
|
||||
private static void applyAssignments(final Context context, final String conversationId,
|
||||
final Set<String> selected) {
|
||||
final Set<String> current = HomeCategoryStore.assignedIds(context, conversationId);
|
||||
for (final String id : selected) {
|
||||
if (!current.contains(id)) {
|
||||
HomeCategoryStore.assign(context, conversationId, id);
|
||||
}
|
||||
}
|
||||
for (final String id : current) {
|
||||
if (!selected.contains(id)) {
|
||||
HomeCategoryStore.unassign(context, conversationId, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void styleChip(final Chip chip) {
|
||||
final float density = chip.getResources().getDisplayMetrics().density;
|
||||
chip.setChipBackgroundColorResource(R.color.home_category_chip_bg);
|
||||
chip.setChipStrokeColorResource(R.color.home_category_chip_stroke);
|
||||
chip.setChipStrokeWidth(density);
|
||||
chip.setChipMinHeight(36f * density);
|
||||
chip.setChipStartPadding(12f * density);
|
||||
chip.setChipEndPadding(12f * density);
|
||||
chip.setTextSize(14f);
|
||||
chip.setTypeface(chip.getTypeface(), android.graphics.Typeface.BOLD);
|
||||
chip.setTextColor(ContextCompat.getColorStateList(chip.getContext(),
|
||||
R.color.home_category_chip_text));
|
||||
chip.setCheckedIconVisible(false);
|
||||
chip.setEnsureMinTouchTargetSize(false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
package com.android.messaging.ui.home;
|
||||
|
||||
import android.content.Context;
|
||||
import android.telephony.PhoneNumberUtils;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.android.messaging.R;
|
||||
import com.android.messaging.datamodel.data.ConversationListItemData;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/** Personal / promotion split and suggested categories from conversation text. */
|
||||
public final class HomeCategoryClassifier {
|
||||
|
||||
public static final class Suggestion {
|
||||
public final String kind;
|
||||
public final String name;
|
||||
public final List<String> conversationIds = new ArrayList<>();
|
||||
|
||||
Suggestion(final String kind, final String name) {
|
||||
this.kind = kind;
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
private HomeCategoryClassifier() {
|
||||
}
|
||||
|
||||
public static boolean matches(final Context context, final String categoryId,
|
||||
final ConversationListItemData item) {
|
||||
if (item == null || TextUtils.isEmpty(categoryId)
|
||||
|| HomeCategoryStore.ID_ALL.equals(categoryId)) {
|
||||
return true;
|
||||
}
|
||||
if (HomeCategoryStore.ID_PERSONAL.equals(categoryId)) {
|
||||
if (HomeCategoryStore.isAssigned(context, item.getConversationId(),
|
||||
HomeCategoryStore.ID_PERSONAL)) {
|
||||
return true;
|
||||
}
|
||||
if (HomeCategoryStore.isAssigned(context, item.getConversationId(),
|
||||
HomeCategoryStore.ID_PROMOTION)) {
|
||||
return false;
|
||||
}
|
||||
return isPersonal(item);
|
||||
}
|
||||
if (HomeCategoryStore.ID_PROMOTION.equals(categoryId)) {
|
||||
if (HomeCategoryStore.isAssigned(context, item.getConversationId(),
|
||||
HomeCategoryStore.ID_PROMOTION)) {
|
||||
return true;
|
||||
}
|
||||
if (HomeCategoryStore.isAssigned(context, item.getConversationId(),
|
||||
HomeCategoryStore.ID_PERSONAL)) {
|
||||
return false;
|
||||
}
|
||||
return isPromotion(item);
|
||||
}
|
||||
return HomeCategoryStore.isAssigned(context, item.getConversationId(), categoryId);
|
||||
}
|
||||
|
||||
public static boolean isPersonal(final ConversationListItemData item) {
|
||||
return !isPromotion(item);
|
||||
}
|
||||
|
||||
public static boolean isPromotion(final ConversationListItemData item) {
|
||||
if (item == null) {
|
||||
return false;
|
||||
}
|
||||
if (item.getIsGroup() || item.getParticipantContactId() > 0) {
|
||||
return false;
|
||||
}
|
||||
if (looksLikeMobile(item)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean isVerification(final ConversationListItemData item) {
|
||||
return containsAny(haystack(item),
|
||||
"verification", "verify", "otp", "passcode", "auth code",
|
||||
"인증", "인증번호", "인증코드", "인증 번호");
|
||||
}
|
||||
|
||||
public static List<Suggestion> suggestions(final Context context,
|
||||
final List<ConversationListItemData> items) {
|
||||
final List<Suggestion> out = new ArrayList<>();
|
||||
if (HomeCategoryStore.hasKind(context, HomeCategoryStore.KIND_VERIFICATION)) {
|
||||
return out;
|
||||
}
|
||||
final Suggestion verification = new Suggestion(HomeCategoryStore.KIND_VERIFICATION,
|
||||
context.getString(R.string.home_category_suggest_verification));
|
||||
if (items != null) {
|
||||
for (final ConversationListItemData item : items) {
|
||||
if (item != null && isVerification(item)
|
||||
&& !TextUtils.isEmpty(item.getConversationId())) {
|
||||
verification.conversationIds.add(item.getConversationId());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!verification.conversationIds.isEmpty()) {
|
||||
out.add(verification);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private static boolean looksLikeMobile(final ConversationListItemData item) {
|
||||
final String dest = item.getOtherParticipantNormalizedDestination();
|
||||
if (looksLikeMobileNumber(dest)) {
|
||||
return true;
|
||||
}
|
||||
return looksLikeMobileNumber(item.getName());
|
||||
}
|
||||
|
||||
private static boolean looksLikeMobileNumber(final String raw) {
|
||||
if (TextUtils.isEmpty(raw)) {
|
||||
return false;
|
||||
}
|
||||
final String digits = PhoneNumberUtils.normalizeNumber(raw);
|
||||
if (TextUtils.isEmpty(digits)) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < digits.length(); i++) {
|
||||
final char c = digits.charAt(i);
|
||||
if (c != '+' && !Character.isDigit(c)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return digits.length() >= 8;
|
||||
}
|
||||
|
||||
private static String haystack(final ConversationListItemData item) {
|
||||
final String name = item.getName() == null ? "" : item.getName();
|
||||
final String snippet = item.getSnippetText() == null ? "" : item.getSnippetText();
|
||||
return (name + " " + snippet).toLowerCase(Locale.ROOT);
|
||||
}
|
||||
|
||||
private static boolean containsAny(final String haystack, final String... needles) {
|
||||
if (TextUtils.isEmpty(haystack)) {
|
||||
return false;
|
||||
}
|
||||
for (final String needle : needles) {
|
||||
if (haystack.contains(needle)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* Copyright (C) 2026 The LineageOS 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.messaging.ui.home;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.view.ViewParent;
|
||||
import android.widget.HorizontalScrollView;
|
||||
|
||||
import com.android.messaging.R;
|
||||
|
||||
/** Category tab strip that keeps horizontal swipes instead of letting parents steal them. */
|
||||
public final class HomeCategoryScroller extends HorizontalScrollView {
|
||||
|
||||
private final int mTouchSlop;
|
||||
private float mDownX;
|
||||
private float mDownY;
|
||||
private boolean mDragging;
|
||||
|
||||
public HomeCategoryScroller(final Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public HomeCategoryScroller(final Context context, final AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public HomeCategoryScroller(final Context context, final AttributeSet attrs,
|
||||
final int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
|
||||
setHorizontalScrollBarEnabled(false);
|
||||
setHorizontalFadingEdgeEnabled(false);
|
||||
setOverScrollMode(OVER_SCROLL_IF_CONTENT_SCROLLS);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(final boolean changed, final int l, final int t, final int r,
|
||||
final int b) {
|
||||
super.onLayout(changed, l, t, r, b);
|
||||
updateEdgeFades();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onScrollChanged(final int l, final int t, final int oldl, final int oldt) {
|
||||
super.onScrollChanged(l, t, oldl, oldt);
|
||||
updateEdgeFades();
|
||||
}
|
||||
|
||||
private void updateEdgeFades() {
|
||||
final ViewParent parent = getParent();
|
||||
if (!(parent instanceof View)) {
|
||||
return;
|
||||
}
|
||||
final View host = (View) parent;
|
||||
final View start = host.findViewById(R.id.home_category_fade_start);
|
||||
final View end = host.findViewById(R.id.home_category_fade_end);
|
||||
final boolean overflow = canScrollContent();
|
||||
final int startVis = overflow && getScrollX() > 0 ? VISIBLE : GONE;
|
||||
final int endVis = overflow ? VISIBLE : GONE;
|
||||
if (start != null && start.getVisibility() != startVis) {
|
||||
start.setVisibility(startVis);
|
||||
}
|
||||
if (end != null && end.getVisibility() != endVis) {
|
||||
end.setVisibility(endVis);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onInterceptTouchEvent(final MotionEvent ev) {
|
||||
switch (ev.getActionMasked()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
mDownX = ev.getX();
|
||||
mDownY = ev.getY();
|
||||
mDragging = false;
|
||||
disallowParent(canScrollContent());
|
||||
break;
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
if (isHorizontalDrag(ev)) {
|
||||
mDragging = true;
|
||||
disallowParent(true);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case MotionEvent.ACTION_UP:
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
mDragging = false;
|
||||
disallowParent(false);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return super.onInterceptTouchEvent(ev) || mDragging;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(final MotionEvent ev) {
|
||||
final int action = ev.getActionMasked();
|
||||
if (action == MotionEvent.ACTION_DOWN) {
|
||||
mDownX = ev.getX();
|
||||
mDownY = ev.getY();
|
||||
mDragging = false;
|
||||
} else if (action == MotionEvent.ACTION_MOVE && isHorizontalDrag(ev)) {
|
||||
mDragging = true;
|
||||
}
|
||||
if (mDragging || canScrollContent()) {
|
||||
disallowParent(action != MotionEvent.ACTION_UP && action != MotionEvent.ACTION_CANCEL);
|
||||
}
|
||||
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
|
||||
mDragging = false;
|
||||
disallowParent(false);
|
||||
}
|
||||
return super.onTouchEvent(ev);
|
||||
}
|
||||
|
||||
private boolean isHorizontalDrag(final MotionEvent ev) {
|
||||
final float dx = Math.abs(ev.getX() - mDownX);
|
||||
final float dy = Math.abs(ev.getY() - mDownY);
|
||||
return dx > mTouchSlop && dx > dy;
|
||||
}
|
||||
|
||||
private boolean canScrollContent() {
|
||||
if (getChildCount() == 0) {
|
||||
return false;
|
||||
}
|
||||
final View child = getChildAt(0);
|
||||
return child.getWidth() > getWidth() - getPaddingLeft() - getPaddingRight();
|
||||
}
|
||||
|
||||
private void disallowParent(final boolean disallow) {
|
||||
final ViewParent parent = getParent();
|
||||
if (parent != null) {
|
||||
parent.requestDisallowInterceptTouchEvent(disallow);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
package com.android.messaging.ui.home;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.android.messaging.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/** Built-in and user-created conversation categories for the home list. */
|
||||
public final class HomeCategoryStore {
|
||||
|
||||
public static final String ID_ALL = "all";
|
||||
public static final String ID_PERSONAL = "personal";
|
||||
public static final String ID_PROMOTION = "promotion";
|
||||
|
||||
public static final String KIND_VERIFICATION = "verification";
|
||||
|
||||
private static final String PREFS_NAME = "home_categories";
|
||||
private static final String KEY_CUSTOM = "custom";
|
||||
private static final String KEY_ASSIGN = "assign";
|
||||
private static final String KEY_SELECTED = "selected";
|
||||
private static final String ROW = "\n";
|
||||
private static final String COL = "\t";
|
||||
public static final int MAX_CUSTOM = 12;
|
||||
public static final int MAX_NAME = 24;
|
||||
|
||||
public static final class Category {
|
||||
public final String id;
|
||||
public final String name;
|
||||
public final String kind;
|
||||
public final boolean builtIn;
|
||||
|
||||
Category(final String id, final String name, final String kind, final boolean builtIn) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.kind = kind == null ? "" : kind;
|
||||
this.builtIn = builtIn;
|
||||
}
|
||||
}
|
||||
|
||||
private HomeCategoryStore() {
|
||||
}
|
||||
|
||||
public static boolean isBuiltIn(final String id) {
|
||||
return ID_ALL.equals(id) || ID_PERSONAL.equals(id) || ID_PROMOTION.equals(id);
|
||||
}
|
||||
|
||||
public static List<Category> listTabs(final Context context) {
|
||||
final List<Category> tabs = new ArrayList<>();
|
||||
tabs.add(new Category(ID_ALL, context.getString(R.string.home_category_all), "", true));
|
||||
tabs.add(new Category(ID_PERSONAL, context.getString(R.string.home_category_personal),
|
||||
"", true));
|
||||
tabs.add(new Category(ID_PROMOTION, context.getString(R.string.home_category_promotion),
|
||||
"", true));
|
||||
tabs.addAll(listCustom(context));
|
||||
return tabs;
|
||||
}
|
||||
|
||||
public static List<Category> listCustom(final Context context) {
|
||||
final String raw = prefs(context).getString(KEY_CUSTOM, "");
|
||||
if (TextUtils.isEmpty(raw)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
final List<Category> items = new ArrayList<>();
|
||||
for (final String line : raw.split(ROW, -1)) {
|
||||
if (TextUtils.isEmpty(line)) {
|
||||
continue;
|
||||
}
|
||||
final String[] parts = line.split(COL, -1);
|
||||
if (parts.length < 2 || TextUtils.isEmpty(parts[0]) || TextUtils.isEmpty(parts[1])) {
|
||||
continue;
|
||||
}
|
||||
final String kind = parts.length > 2 ? parts[2] : "";
|
||||
items.add(new Category(parts[0], parts[1], kind, false));
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
public static boolean hasKind(final Context context, final String kind) {
|
||||
if (TextUtils.isEmpty(kind)) {
|
||||
return false;
|
||||
}
|
||||
for (final Category category : listCustom(context)) {
|
||||
if (kind.equals(category.kind)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean hasName(final Context context, final String name) {
|
||||
final String trimmed = name == null ? "" : name.trim();
|
||||
if (TextUtils.isEmpty(trimmed)) {
|
||||
return false;
|
||||
}
|
||||
for (final Category category : listTabs(context)) {
|
||||
if (trimmed.equalsIgnoreCase(category.name)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static Category addCustom(final Context context, final String name, final String kind) {
|
||||
String trimmed = name == null ? "" : name.trim();
|
||||
if (TextUtils.isEmpty(trimmed)) {
|
||||
return null;
|
||||
}
|
||||
if (trimmed.length() > MAX_NAME) {
|
||||
trimmed = trimmed.substring(0, MAX_NAME);
|
||||
}
|
||||
if (hasName(context, trimmed)) {
|
||||
return null;
|
||||
}
|
||||
final List<Category> items = new ArrayList<>(listCustom(context));
|
||||
if (items.size() >= MAX_CUSTOM) {
|
||||
return null;
|
||||
}
|
||||
final Category created = new Category("c_" + UUID.randomUUID().toString(), trimmed,
|
||||
kind == null ? "" : kind, false);
|
||||
items.add(created);
|
||||
saveCustom(context, items);
|
||||
return created;
|
||||
}
|
||||
|
||||
public static void removeCustom(final Context context, final String categoryId) {
|
||||
if (isBuiltIn(categoryId)) {
|
||||
return;
|
||||
}
|
||||
final List<Category> items = new ArrayList<>(listCustom(context));
|
||||
boolean removed = false;
|
||||
for (int i = items.size() - 1; i >= 0; i--) {
|
||||
if (items.get(i).id.equals(categoryId)) {
|
||||
items.remove(i);
|
||||
removed = true;
|
||||
}
|
||||
}
|
||||
if (!removed) {
|
||||
return;
|
||||
}
|
||||
saveCustom(context, items);
|
||||
final List<String> kept = new ArrayList<>();
|
||||
for (final String row : assignmentRows(context)) {
|
||||
final String[] parts = row.split(COL, -1);
|
||||
if (parts.length >= 2 && !categoryId.equals(parts[1])) {
|
||||
kept.add(row);
|
||||
}
|
||||
}
|
||||
prefs(context).edit().putString(KEY_ASSIGN, TextUtils.join(ROW, kept)).apply();
|
||||
if (categoryId.equals(getSelectedId(context))) {
|
||||
setSelectedId(context, ID_ALL);
|
||||
}
|
||||
}
|
||||
|
||||
public static void assign(final Context context, final String conversationId,
|
||||
final String categoryId) {
|
||||
if (TextUtils.isEmpty(conversationId) || TextUtils.isEmpty(categoryId)
|
||||
|| ID_ALL.equals(categoryId)) {
|
||||
return;
|
||||
}
|
||||
final LinkedHashSet<String> rows = new LinkedHashSet<>(assignmentRows(context));
|
||||
rows.add(conversationId + COL + categoryId);
|
||||
prefs(context).edit().putString(KEY_ASSIGN, TextUtils.join(ROW, rows)).apply();
|
||||
}
|
||||
|
||||
public static void assignAll(final Context context, final List<String> conversationIds,
|
||||
final String categoryId) {
|
||||
if (conversationIds == null) {
|
||||
return;
|
||||
}
|
||||
for (final String id : conversationIds) {
|
||||
assign(context, id, categoryId);
|
||||
}
|
||||
}
|
||||
|
||||
public static void unassign(final Context context, final String conversationId,
|
||||
final String categoryId) {
|
||||
if (TextUtils.isEmpty(conversationId) || TextUtils.isEmpty(categoryId)) {
|
||||
return;
|
||||
}
|
||||
final String needle = conversationId + COL + categoryId;
|
||||
final List<String> rows = assignmentRows(context);
|
||||
if (!rows.remove(needle)) {
|
||||
return;
|
||||
}
|
||||
prefs(context).edit().putString(KEY_ASSIGN, TextUtils.join(ROW, rows)).apply();
|
||||
}
|
||||
|
||||
public static Set<String> assignedIds(final Context context,
|
||||
final String conversationId) {
|
||||
final LinkedHashSet<String> ids = new LinkedHashSet<>();
|
||||
if (TextUtils.isEmpty(conversationId)) {
|
||||
return ids;
|
||||
}
|
||||
for (final String row : assignmentRows(context)) {
|
||||
final String[] parts = row.split(COL, -1);
|
||||
if (parts.length >= 2 && conversationId.equals(parts[0])) {
|
||||
ids.add(parts[1]);
|
||||
}
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
public static boolean isAssigned(final Context context, final String conversationId,
|
||||
final String categoryId) {
|
||||
if (TextUtils.isEmpty(conversationId) || TextUtils.isEmpty(categoryId)) {
|
||||
return false;
|
||||
}
|
||||
final String needle = conversationId + COL + categoryId;
|
||||
return assignmentRows(context).contains(needle);
|
||||
}
|
||||
|
||||
public static String getSelectedId(final Context context) {
|
||||
final String selected = prefs(context).getString(KEY_SELECTED, ID_ALL);
|
||||
return TextUtils.isEmpty(selected) ? ID_ALL : selected;
|
||||
}
|
||||
|
||||
public static void setSelectedId(final Context context, final String categoryId) {
|
||||
prefs(context).edit().putString(KEY_SELECTED,
|
||||
TextUtils.isEmpty(categoryId) ? ID_ALL : categoryId).apply();
|
||||
}
|
||||
|
||||
private static List<String> assignmentRows(final Context context) {
|
||||
final String raw = prefs(context).getString(KEY_ASSIGN, "");
|
||||
if (TextUtils.isEmpty(raw)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
final List<String> rows = new ArrayList<>();
|
||||
for (final String line : raw.split(ROW, -1)) {
|
||||
if (!TextUtils.isEmpty(line)) {
|
||||
rows.add(line);
|
||||
}
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
private static void saveCustom(final Context context, final List<Category> items) {
|
||||
final List<String> rows = new ArrayList<>(items.size());
|
||||
for (final Category item : items) {
|
||||
rows.add(item.id + COL + item.name + COL + item.kind);
|
||||
}
|
||||
prefs(context).edit().putString(KEY_CUSTOM, TextUtils.join(ROW, rows)).apply();
|
||||
}
|
||||
|
||||
private static SharedPreferences prefs(final Context context) {
|
||||
return context.getApplicationContext().getSharedPreferences(PREFS_NAME,
|
||||
Context.MODE_PRIVATE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright (C) 2026 The LineageOS 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.messaging.ui.home;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import com.android.messaging.R;
|
||||
|
||||
/**
|
||||
* Category tabs + add button. Tabs stay left of the add control; the add button
|
||||
* follows the last tab and never crosses the strip's end padding.
|
||||
*/
|
||||
public final class HomeCategoryStrip extends LinearLayout {
|
||||
|
||||
public HomeCategoryStrip(final Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public HomeCategoryStrip(final Context context, final AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public HomeCategoryStrip(final Context context, final AttributeSet attrs,
|
||||
final int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
setOrientation(HORIZONTAL);
|
||||
setClipToPadding(true);
|
||||
setClipChildren(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
|
||||
final View all = findViewById(R.id.home_category_all);
|
||||
final View add = findViewById(R.id.home_category_add);
|
||||
final View scroll = findViewById(R.id.home_category_scroll_host);
|
||||
int pinned = 0;
|
||||
int contentHeight = 0;
|
||||
if (all != null && all.getVisibility() != GONE) {
|
||||
measureChildWithMargins(all, widthMeasureSpec, 0, heightMeasureSpec, 0);
|
||||
pinned += measuredWidthWithMargins(all);
|
||||
contentHeight = Math.max(contentHeight, measuredHeightWithMargins(all));
|
||||
}
|
||||
if (add != null && add.getVisibility() != GONE) {
|
||||
measureChildWithMargins(add, widthMeasureSpec, pinned, heightMeasureSpec, 0);
|
||||
pinned += measuredWidthWithMargins(add);
|
||||
contentHeight = Math.max(contentHeight, measuredHeightWithMargins(add));
|
||||
}
|
||||
int contentWidth = pinned;
|
||||
if (scroll != null && scroll.getVisibility() != GONE) {
|
||||
measureChildWithMargins(scroll, widthMeasureSpec, pinned, heightMeasureSpec, 0);
|
||||
contentWidth += measuredWidthWithMargins(scroll);
|
||||
contentHeight = Math.max(contentHeight, measuredHeightWithMargins(scroll));
|
||||
}
|
||||
setMeasuredDimension(
|
||||
resolveSize(contentWidth + getPaddingLeft() + getPaddingRight(),
|
||||
widthMeasureSpec),
|
||||
resolveSize(contentHeight + getPaddingTop() + getPaddingBottom(),
|
||||
heightMeasureSpec));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(final boolean changed, final int left, final int top, final int right,
|
||||
final int bottom) {
|
||||
final int innerTop = getPaddingTop();
|
||||
final int innerBottom = bottom - top - getPaddingBottom();
|
||||
int x = getPaddingLeft();
|
||||
x = layoutChild(findViewById(R.id.home_category_all), x, innerTop, innerBottom);
|
||||
x = layoutChild(findViewById(R.id.home_category_scroll_host), x, innerTop, innerBottom);
|
||||
layoutChild(findViewById(R.id.home_category_add), x, innerTop, innerBottom);
|
||||
}
|
||||
|
||||
private int layoutChild(final View child, final int x, final int innerTop,
|
||||
final int innerBottom) {
|
||||
if (child == null || child.getVisibility() == GONE) {
|
||||
return x;
|
||||
}
|
||||
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
|
||||
final int height = child.getMeasuredHeight();
|
||||
final int childTop = innerTop + lp.topMargin
|
||||
+ Math.max(0, (innerBottom - innerTop - height - lp.topMargin - lp.bottomMargin) / 2);
|
||||
final int childLeft = x + lp.leftMargin;
|
||||
child.layout(childLeft, childTop, childLeft + child.getMeasuredWidth(),
|
||||
childTop + height);
|
||||
return childLeft + child.getMeasuredWidth() + lp.rightMargin;
|
||||
}
|
||||
|
||||
private static int measuredWidthWithMargins(final View child) {
|
||||
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
|
||||
return child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
|
||||
}
|
||||
|
||||
private static int measuredHeightWithMargins(final View child) {
|
||||
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
|
||||
return child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
|
||||
}
|
||||
}
|
||||
@@ -89,6 +89,16 @@ public class HomeComposeActivity extends BugleActionBarActivity
|
||||
mNextButton.setOnClickListener(v -> openGroupNameStep());
|
||||
|
||||
mRecipientField = findViewById(R.id.home_compose_recipient);
|
||||
final View recipientBox = findViewById(R.id.home_compose_recipient_box);
|
||||
if (recipientBox != null) {
|
||||
recipientBox.setOnClickListener(v -> {
|
||||
if (mRecipientField.getVisibility() != View.VISIBLE) {
|
||||
return;
|
||||
}
|
||||
mRecipientField.requestFocus();
|
||||
ImeUtil.get().showImeKeyboard(this, mRecipientField);
|
||||
});
|
||||
}
|
||||
mRecipientField.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(final CharSequence s, final int start, final int count,
|
||||
|
||||
@@ -89,6 +89,5 @@ public final class HomeDeleteAlertDialog {
|
||||
WindowManager.LayoutParams.WRAP_CONTENT);
|
||||
window.getDecorView().setPadding(margin, 0, margin, 0);
|
||||
}
|
||||
HomeUiMotion.dialogEnter(content);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
package com.android.messaging.ui.home;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewParent;
|
||||
import android.view.Window;
|
||||
import android.widget.EditText;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.view.WindowCallbackWrapper;
|
||||
|
||||
import com.android.messaging.util.ImeUtil;
|
||||
|
||||
/** Shared rule: tap outside an input box clears focus and hides the keyboard. */
|
||||
public final class HomeInputFocus {
|
||||
|
||||
private HomeInputFocus() {
|
||||
}
|
||||
|
||||
public static void install(@Nullable final Window window) {
|
||||
if (window == null) {
|
||||
return;
|
||||
}
|
||||
final Window.Callback current = window.getCallback();
|
||||
if (current instanceof Installed) {
|
||||
return;
|
||||
}
|
||||
window.setCallback(new Installed(current, window));
|
||||
}
|
||||
|
||||
public static boolean clearIfOutside(@Nullable final View root, @Nullable final MotionEvent ev) {
|
||||
if (root == null || ev == null || ev.getAction() != MotionEvent.ACTION_DOWN) {
|
||||
return false;
|
||||
}
|
||||
final View focused = root.findFocus();
|
||||
if (!(focused instanceof EditText)) {
|
||||
return false;
|
||||
}
|
||||
final View box = findBox(focused);
|
||||
if (isInside(box, ev)) {
|
||||
return false;
|
||||
}
|
||||
root.setFocusable(true);
|
||||
root.setFocusableInTouchMode(true);
|
||||
focused.clearFocus();
|
||||
root.requestFocus();
|
||||
ImeUtil.get().hideImeKeyboard(focused.getContext(), focused);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static View findBox(final View focused) {
|
||||
ViewParent parent = focused.getParent();
|
||||
while (parent instanceof ViewGroup) {
|
||||
final ViewGroup group = (ViewGroup) parent;
|
||||
if (group.addStatesFromChildren()) {
|
||||
return group;
|
||||
}
|
||||
parent = group.getParent();
|
||||
}
|
||||
return focused;
|
||||
}
|
||||
|
||||
private static boolean isInside(final View view, final MotionEvent ev) {
|
||||
final Rect rect = new Rect();
|
||||
if (!view.getGlobalVisibleRect(rect)) {
|
||||
return false;
|
||||
}
|
||||
return rect.contains(Math.round(ev.getRawX()), Math.round(ev.getRawY()));
|
||||
}
|
||||
|
||||
private static final class Installed extends WindowCallbackWrapper {
|
||||
private final Window mWindow;
|
||||
|
||||
Installed(final Window.Callback wrapped, final Window window) {
|
||||
super(wrapped);
|
||||
mWindow = window;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dispatchTouchEvent(@NonNull final MotionEvent event) {
|
||||
clearIfOutside(mWindow.getDecorView(), event);
|
||||
return super.dispatchTouchEvent(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
package com.android.messaging.ui.home;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import com.android.messaging.R;
|
||||
|
||||
/**
|
||||
* Default home modal: centered icon on a soft well, title, body, optional
|
||||
* custom content, and stacked pill actions.
|
||||
*/
|
||||
public final class HomeModal {
|
||||
|
||||
public interface Action {
|
||||
void onClick();
|
||||
}
|
||||
|
||||
public static final class Handle {
|
||||
public final AlertDialog dialog;
|
||||
public final TextView title;
|
||||
public final TextView message;
|
||||
public final TextView positive;
|
||||
public final TextView negative;
|
||||
public final FrameLayout custom;
|
||||
|
||||
Handle(final AlertDialog dialog, final TextView title, final TextView message,
|
||||
final TextView positive, final TextView negative, final FrameLayout custom) {
|
||||
this.dialog = dialog;
|
||||
this.title = title;
|
||||
this.message = message;
|
||||
this.positive = positive;
|
||||
this.negative = negative;
|
||||
this.custom = custom;
|
||||
}
|
||||
|
||||
public void setTitle(final CharSequence text) {
|
||||
if (title != null) {
|
||||
title.setText(text);
|
||||
title.setVisibility(TextUtils.isEmpty(text) ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
public void setMessage(final CharSequence text) {
|
||||
if (message != null) {
|
||||
message.setText(text);
|
||||
message.setVisibility(TextUtils.isEmpty(text) ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
public void setPositive(final CharSequence label, @Nullable final Action action,
|
||||
final boolean dismiss) {
|
||||
bindButton(positive, label, action, dismiss);
|
||||
}
|
||||
|
||||
public void setNegative(final CharSequence label, @Nullable final Action action,
|
||||
final boolean dismiss) {
|
||||
bindButton(negative, label, action, dismiss);
|
||||
}
|
||||
|
||||
private void bindButton(final TextView button, final CharSequence label,
|
||||
final Action action, final boolean dismiss) {
|
||||
if (button == null) {
|
||||
return;
|
||||
}
|
||||
button.setText(label);
|
||||
button.setVisibility(TextUtils.isEmpty(label) ? View.GONE : View.VISIBLE);
|
||||
button.setOnClickListener(v -> {
|
||||
if (dismiss) {
|
||||
dialog.dismiss();
|
||||
}
|
||||
if (action != null) {
|
||||
action.onClick();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
private final Context mContext;
|
||||
@DrawableRes
|
||||
private int mIconRes;
|
||||
private CharSequence mTitle;
|
||||
private CharSequence mMessage;
|
||||
private CharSequence mPositive;
|
||||
private CharSequence mNegative;
|
||||
private Action mOnPositive;
|
||||
private Action mOnNegative;
|
||||
private View mCustom;
|
||||
private boolean mCancelable = true;
|
||||
|
||||
public Builder(@NonNull final Context context) {
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
public Builder icon(@DrawableRes final int iconRes) {
|
||||
mIconRes = iconRes;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder title(@StringRes final int titleRes) {
|
||||
mTitle = mContext.getString(titleRes);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder title(final CharSequence title) {
|
||||
mTitle = title;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder message(@StringRes final int messageRes) {
|
||||
mMessage = mContext.getString(messageRes);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder message(final CharSequence message) {
|
||||
mMessage = message;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder custom(final View custom) {
|
||||
mCustom = custom;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder positive(@StringRes final int labelRes, @Nullable final Action action) {
|
||||
mPositive = mContext.getString(labelRes);
|
||||
mOnPositive = action;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder positive(final CharSequence label, @Nullable final Action action) {
|
||||
mPositive = label;
|
||||
mOnPositive = action;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder negative(@StringRes final int labelRes, @Nullable final Action action) {
|
||||
mNegative = mContext.getString(labelRes);
|
||||
mOnNegative = action;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder negative(final CharSequence label, @Nullable final Action action) {
|
||||
mNegative = label;
|
||||
mOnNegative = action;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder cancelable(final boolean cancelable) {
|
||||
mCancelable = cancelable;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Handle show() {
|
||||
return HomeModal.show(this);
|
||||
}
|
||||
}
|
||||
|
||||
private HomeModal() {
|
||||
}
|
||||
|
||||
public static Builder builder(@NonNull final Context context) {
|
||||
return new Builder(context);
|
||||
}
|
||||
|
||||
private static Handle show(final Builder options) {
|
||||
final Context context = options.mContext;
|
||||
final View content = LayoutInflater.from(context).inflate(R.layout.home_modal, null, false);
|
||||
bind(content, options);
|
||||
|
||||
final AlertDialog dialog = new AlertDialog.Builder(context, R.style.Home_AlertDialog)
|
||||
.setView(content)
|
||||
.setCancelable(options.mCancelable)
|
||||
.create();
|
||||
dialog.setCanceledOnTouchOutside(options.mCancelable);
|
||||
final View close = content.findViewById(R.id.home_modal_close);
|
||||
if (close != null) {
|
||||
close.setVisibility(options.mCancelable ? View.VISIBLE : View.GONE);
|
||||
close.setOnClickListener(v -> dialog.dismiss());
|
||||
}
|
||||
|
||||
final TextView positive = content.findViewById(R.id.home_modal_positive);
|
||||
final TextView negative = content.findViewById(R.id.home_modal_negative);
|
||||
bindButton(negative, options.mNegative, options.mOnNegative, dialog, true);
|
||||
bindButton(positive, options.mPositive, options.mOnPositive, dialog, true);
|
||||
|
||||
dialog.show();
|
||||
|
||||
final Window window = dialog.getWindow();
|
||||
if (window != null) {
|
||||
window.setBackgroundDrawable(new ColorDrawable(android.graphics.Color.TRANSPARENT));
|
||||
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
|
||||
final int margin = context.getResources().getDimensionPixelSize(
|
||||
R.dimen.home_alert_dialog_horizontal_margin);
|
||||
window.setLayout(WindowManager.LayoutParams.MATCH_PARENT,
|
||||
WindowManager.LayoutParams.WRAP_CONTENT);
|
||||
window.getDecorView().setPadding(margin, 0, margin, 0);
|
||||
HomeInputFocus.install(window);
|
||||
}
|
||||
return new Handle(dialog, content.findViewById(R.id.home_modal_title),
|
||||
content.findViewById(R.id.home_modal_message), positive, negative,
|
||||
content.findViewById(R.id.home_modal_custom));
|
||||
}
|
||||
|
||||
private static void bind(final View content, final Builder options) {
|
||||
final View iconWell = content.findViewById(R.id.home_modal_icon_well);
|
||||
final ImageView icon = content.findViewById(R.id.home_modal_icon);
|
||||
if (options.mIconRes != 0 && icon != null) {
|
||||
icon.setImageResource(options.mIconRes);
|
||||
if (iconWell != null) {
|
||||
iconWell.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
icon.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
final TextView title = content.findViewById(R.id.home_modal_title);
|
||||
if (title != null) {
|
||||
title.setText(options.mTitle);
|
||||
title.setVisibility(TextUtils.isEmpty(options.mTitle) ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
|
||||
final TextView message = content.findViewById(R.id.home_modal_message);
|
||||
if (message != null) {
|
||||
message.setText(options.mMessage);
|
||||
message.setVisibility(TextUtils.isEmpty(options.mMessage) ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
|
||||
final FrameLayout custom = content.findViewById(R.id.home_modal_custom);
|
||||
if (custom != null && options.mCustom != null) {
|
||||
if (options.mCustom.getParent() instanceof ViewGroup) {
|
||||
((ViewGroup) options.mCustom.getParent()).removeView(options.mCustom);
|
||||
}
|
||||
custom.addView(options.mCustom, new FrameLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
custom.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
private static void bindButton(final TextView button, final CharSequence label,
|
||||
final Action action, final AlertDialog dialog, final boolean dismiss) {
|
||||
if (button == null || TextUtils.isEmpty(label)) {
|
||||
return;
|
||||
}
|
||||
button.setText(label);
|
||||
button.setVisibility(View.VISIBLE);
|
||||
button.setOnClickListener(v -> {
|
||||
if (dismiss) {
|
||||
dialog.dismiss();
|
||||
}
|
||||
if (action != null) {
|
||||
action.onClick();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -16,12 +16,14 @@
|
||||
|
||||
package com.android.messaging.ui.home;
|
||||
|
||||
import android.animation.ArgbEvaluator;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.View.MeasureSpec;
|
||||
import android.view.animation.PathInterpolator;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
|
||||
|
||||
@@ -679,19 +681,52 @@ public final class HomeUiMotion {
|
||||
return;
|
||||
}
|
||||
content.setAlpha(0f);
|
||||
content.setScaleX(0.94f);
|
||||
content.setScaleY(0.94f);
|
||||
content.setScaleX(0.92f);
|
||||
content.setScaleY(0.92f);
|
||||
beginLayer(content);
|
||||
content.animate()
|
||||
.alpha(1f)
|
||||
.scaleX(1f)
|
||||
.scaleY(1f)
|
||||
.setDuration(300)
|
||||
.setDuration(280)
|
||||
.setInterpolator(EASE)
|
||||
.withEndAction(() -> endLayer(content))
|
||||
.start();
|
||||
}
|
||||
|
||||
public static void dialogExit(final View content, final Runnable endAction) {
|
||||
if (content == null) {
|
||||
if (endAction != null) {
|
||||
endAction.run();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!enabled()) {
|
||||
if (endAction != null) {
|
||||
endAction.run();
|
||||
}
|
||||
return;
|
||||
}
|
||||
beginLayer(content);
|
||||
content.animate().cancel();
|
||||
content.animate()
|
||||
.alpha(0f)
|
||||
.scaleX(0.94f)
|
||||
.scaleY(0.94f)
|
||||
.setDuration(180)
|
||||
.setInterpolator(EASE)
|
||||
.withEndAction(() -> {
|
||||
endLayer(content);
|
||||
content.setAlpha(1f);
|
||||
content.setScaleX(1f);
|
||||
content.setScaleY(1f);
|
||||
if (endAction != null) {
|
||||
endAction.run();
|
||||
}
|
||||
})
|
||||
.start();
|
||||
}
|
||||
|
||||
private static void applyBottomDockStateInstant(final View mainNav, final View selectionBar,
|
||||
final boolean selectionActive) {
|
||||
if (mainNav != null) {
|
||||
@@ -907,7 +942,7 @@ public final class HomeUiMotion {
|
||||
if (attachButton == null) {
|
||||
return;
|
||||
}
|
||||
attachButton.setImageResource(R.drawable.ic_home_compose_plus);
|
||||
attachButton.setImageResource(R.drawable.ic_home_automation_plus);
|
||||
attachButton.setRotation(0f);
|
||||
}
|
||||
|
||||
@@ -1070,4 +1105,107 @@ public final class HomeUiMotion {
|
||||
})
|
||||
.start();
|
||||
}
|
||||
|
||||
/** Selects a category tab: accent color plus a scale-in underline. */
|
||||
public static void applyCategoryTab(final TextView label, final View underline,
|
||||
final int selectedColor, final int idleColor, final boolean selected,
|
||||
final boolean animate) {
|
||||
if (label != null) {
|
||||
label.setTypeface(null, selected ? android.graphics.Typeface.BOLD
|
||||
: android.graphics.Typeface.NORMAL);
|
||||
final Object running = label.getTag(R.id.home_category_tab_label);
|
||||
if (running instanceof ValueAnimator) {
|
||||
((ValueAnimator) running).cancel();
|
||||
label.setTag(R.id.home_category_tab_label, null);
|
||||
}
|
||||
if (!enabled() || !animate) {
|
||||
label.setTextColor(selected ? selectedColor : idleColor);
|
||||
} else {
|
||||
final int from = label.getCurrentTextColor();
|
||||
final int to = selected ? selectedColor : idleColor;
|
||||
if (from != to) {
|
||||
final ValueAnimator color = ValueAnimator.ofObject(new ArgbEvaluator(), from, to);
|
||||
color.setDuration(HOME_TAB_MS);
|
||||
color.setInterpolator(EASE);
|
||||
color.addUpdateListener(animation ->
|
||||
label.setTextColor((Integer) animation.getAnimatedValue()));
|
||||
color.addListener(new android.animation.AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(final android.animation.Animator animation) {
|
||||
if (label.getTag(R.id.home_category_tab_label) == color) {
|
||||
label.setTag(R.id.home_category_tab_label, null);
|
||||
}
|
||||
}
|
||||
});
|
||||
label.setTag(R.id.home_category_tab_label, color);
|
||||
color.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (underline == null) {
|
||||
return;
|
||||
}
|
||||
underline.animate().cancel();
|
||||
underline.setPivotX(Math.max(underline.getWidth(), 1) / 2f);
|
||||
underline.setPivotY(underline.getHeight());
|
||||
if (selected) {
|
||||
underline.setVisibility(View.VISIBLE);
|
||||
if (!enabled() || !animate) {
|
||||
underline.setScaleX(1f);
|
||||
underline.setAlpha(1f);
|
||||
return;
|
||||
}
|
||||
underline.setScaleX(0.35f);
|
||||
underline.setAlpha(0f);
|
||||
underline.animate()
|
||||
.scaleX(1f)
|
||||
.alpha(1f)
|
||||
.setDuration(HOME_TAB_MS)
|
||||
.setInterpolator(EASE)
|
||||
.start();
|
||||
return;
|
||||
}
|
||||
if (!enabled() || !animate || underline.getVisibility() != View.VISIBLE) {
|
||||
underline.setScaleX(1f);
|
||||
underline.setAlpha(1f);
|
||||
underline.setVisibility(View.INVISIBLE);
|
||||
return;
|
||||
}
|
||||
underline.animate()
|
||||
.scaleX(0.35f)
|
||||
.alpha(0f)
|
||||
.setDuration(HOME_TAB_MS)
|
||||
.setInterpolator(EASE)
|
||||
.withEndAction(() -> {
|
||||
underline.setVisibility(View.INVISIBLE);
|
||||
underline.setScaleX(1f);
|
||||
underline.setAlpha(1f);
|
||||
})
|
||||
.start();
|
||||
}
|
||||
|
||||
/** Short fade + horizontal slide after a category list swap. */
|
||||
public static void categoryPageEnter(final View list, final boolean fromEnd) {
|
||||
if (list == null) {
|
||||
return;
|
||||
}
|
||||
list.animate().cancel();
|
||||
if (!enabled()) {
|
||||
list.setAlpha(1f);
|
||||
list.setTranslationX(0f);
|
||||
return;
|
||||
}
|
||||
final int slide = Math.max(list.getWidth() / 12,
|
||||
list.getResources().getDimensionPixelSize(R.dimen.looply_space_xl));
|
||||
list.setAlpha(0.2f);
|
||||
list.setTranslationX(fromEnd ? slide : -slide);
|
||||
beginLayer(list);
|
||||
list.animate()
|
||||
.alpha(1f)
|
||||
.translationX(0f)
|
||||
.setDuration(HOME_TAB_MS)
|
||||
.setInterpolator(EASE)
|
||||
.withEndAction(() -> endLayer(list))
|
||||
.start();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:interpolator="@android:interpolator/fast_out_slow_in"
|
||||
android:shareInterpolator="true">
|
||||
<alpha
|
||||
android:duration="220"
|
||||
android:fromAlpha="0"
|
||||
android:toAlpha="1" />
|
||||
<scale
|
||||
android:duration="280"
|
||||
android:fromXScale="0.92"
|
||||
android:fromYScale="0.92"
|
||||
android:pivotX="50%"
|
||||
android:pivotY="50%"
|
||||
android:toXScale="1"
|
||||
android:toYScale="1" />
|
||||
</set>
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:interpolator="@android:interpolator/fast_out_slow_in"
|
||||
android:shareInterpolator="true">
|
||||
<alpha
|
||||
android:duration="180"
|
||||
android:fromAlpha="1"
|
||||
android:toAlpha="0" />
|
||||
<scale
|
||||
android:duration="180"
|
||||
android:fromXScale="1"
|
||||
android:fromYScale="1"
|
||||
android:pivotX="50%"
|
||||
android:pivotY="50%"
|
||||
android:toXScale="0.94"
|
||||
android:toYScale="0.94" />
|
||||
</set>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:color="@color/home_accent_soft"
|
||||
android:state_selected="true" />
|
||||
<item android:color="@color/home_accent" />
|
||||
</selector>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="@color/home_accent" android:state_checked="true" />
|
||||
<item android:color="@color/home_row_bg" />
|
||||
</selector>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="@color/home_accent" android:state_checked="true" />
|
||||
<item android:color="@color/home_button_stroke" />
|
||||
</selector>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="@android:color/white" android:state_checked="true" />
|
||||
<item android:color="@color/home_text_primary" />
|
||||
</selector>
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="@color/home_accent_soft">
|
||||
<item>
|
||||
<selector>
|
||||
<item
|
||||
android:drawable="@drawable/home_bg_add_circle_focused"
|
||||
android:state_selected="true" />
|
||||
<item android:drawable="@drawable/home_bg_add_circle_idle" />
|
||||
</selector>
|
||||
</item>
|
||||
</ripple>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval">
|
||||
<solid android:color="@color/home_accent" />
|
||||
</shape>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval">
|
||||
<solid android:color="@color/home_accent_soft" />
|
||||
</shape>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<gradient
|
||||
android:angle="0"
|
||||
android:endColor="@color/home_bg"
|
||||
android:startColor="#00F4F1FB" />
|
||||
</shape>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<gradient
|
||||
android:angle="0"
|
||||
android:endColor="#00F4F1FB"
|
||||
android:startColor="@color/home_bg" />
|
||||
</shape>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="@color/home_accent_soft">
|
||||
<item>
|
||||
<shape android:shape="oval">
|
||||
<solid android:color="@color/home_accent_soft" />
|
||||
</shape>
|
||||
</item>
|
||||
</ripple>
|
||||
@@ -1,6 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<corners android:radius="@dimen/looply_radius_pill" />
|
||||
<solid android:color="@color/home_compose_pill_bg" />
|
||||
</shape>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:drawable="@drawable/home_bg_search_field_focused"
|
||||
android:state_focused="true" />
|
||||
<item android:drawable="@drawable/home_bg_search_field_idle" />
|
||||
</selector>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<corners android:radius="22dp" />
|
||||
<solid android:color="@color/home_bg" />
|
||||
<solid android:color="@color/home_date_pill_blur" />
|
||||
<stroke
|
||||
android:width="0.5dp"
|
||||
android:color="@color/home_button_stroke" />
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval">
|
||||
<solid android:color="@color/home_bg" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="@color/home_divider" />
|
||||
<solid android:color="@color/home_accent" />
|
||||
</shape>
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/home_row_bg" />
|
||||
<corners android:radius="@dimen/looply_radius_xl" />
|
||||
</shape>
|
||||
@@ -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">
|
||||
<solid android:color="@color/home_surface" />
|
||||
<corners android:radius="@dimen/looply_radius_pill" />
|
||||
</shape>
|
||||
</item>
|
||||
</ripple>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/home_accent_soft" />
|
||||
<corners android:radius="@dimen/looply_radius_md" />
|
||||
</shape>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:drawable="@drawable/home_bg_modal_input_focused"
|
||||
android:state_focused="true" />
|
||||
<item android:drawable="@drawable/home_bg_modal_input_idle" />
|
||||
</selector>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/home_accent_soft" />
|
||||
<corners android:radius="@dimen/looply_radius_lg" />
|
||||
<stroke
|
||||
android:width="1.5dp"
|
||||
android:color="@color/home_accent" />
|
||||
</shape>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/home_surface" />
|
||||
<corners android:radius="@dimen/looply_radius_lg" />
|
||||
<stroke
|
||||
android:width="1.5dp"
|
||||
android:color="@color/home_divider" />
|
||||
</shape>
|
||||
@@ -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_accent_soft">
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="@color/home_accent" />
|
||||
<corners android:radius="@dimen/looply_radius_pill" />
|
||||
</shape>
|
||||
</item>
|
||||
</ripple>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:drawable="@drawable/home_bg_search_field_focused"
|
||||
android:state_focused="true" />
|
||||
<item android:drawable="@drawable/home_bg_search_field_idle" />
|
||||
</selector>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/home_accent_soft" />
|
||||
<corners android:radius="@dimen/looply_radius_pill" />
|
||||
<stroke
|
||||
android:width="1.5dp"
|
||||
android:color="@color/home_accent" />
|
||||
</shape>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/home_search_field_bg" />
|
||||
<corners android:radius="@dimen/looply_radius_pill" />
|
||||
<stroke
|
||||
android:width="1.5dp"
|
||||
android:color="@color/home_divider" />
|
||||
</shape>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<size android:width="2dp" />
|
||||
<solid android:color="@color/home_accent" />
|
||||
</shape>
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/transparent"
|
||||
android:pathData="M4.5,6.5h8.2l6.3,5.5 -6.3,5.5H4.5V6.5z"
|
||||
android:strokeWidth="1.8"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeLineJoin="round" />
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M9.35,12 A1.15,1.15 0 1 1 6.9,12 A1.15,1.15 0 1 1 9.35,12 Z" />
|
||||
</vector>
|
||||
@@ -5,9 +5,9 @@
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#8E8E93"
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M12,5l5,5H7z" />
|
||||
<path
|
||||
android:fillColor="#8E8E93"
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M12,19l-5,-5h10z" />
|
||||
</vector>
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/transparent"
|
||||
android:pathData="M23,12A11,11 0,1 1,1 12A11,11 0,1 1,23 12"
|
||||
android:strokeWidth="1.8"
|
||||
android:strokeColor="#FFFFFF"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" />
|
||||
<path
|
||||
android:fillColor="@android:color/transparent"
|
||||
android:pathData="M12,17.6L12,6.4M12,6.4L15.2,9.6M12,6.4L8.8,9.6"
|
||||
android:strokeWidth="1.8"
|
||||
android:strokeColor="#FFFFFF"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" />
|
||||
</vector>
|
||||
@@ -242,6 +242,8 @@
|
||||
|
||||
android:layout_height="@dimen/home_conversation_compose_pill_height"
|
||||
|
||||
android:addStatesFromChildren="true"
|
||||
|
||||
android:background="@drawable/home_bg_compose_pill"
|
||||
|
||||
android:gravity="center_vertical"
|
||||
@@ -254,37 +256,21 @@
|
||||
|
||||
|
||||
|
||||
<FrameLayout
|
||||
<ImageButton
|
||||
|
||||
android:layout_width="@dimen/home_compose_attach_size"
|
||||
android:id="@+id/attach_media_button"
|
||||
|
||||
android:layout_height="@dimen/home_compose_attach_size"
|
||||
style="@style/Home.AddButton"
|
||||
|
||||
android:layout_marginStart="@dimen/looply_space_xs">
|
||||
android:layout_marginStart="@dimen/looply_space_xs"
|
||||
|
||||
android:contentDescription="@string/attachMediaButtonContentDescription"
|
||||
|
||||
android:focusable="false"
|
||||
|
||||
<ImageButton
|
||||
app:srcCompat="@drawable/ic_home_automation_plus"
|
||||
|
||||
android:id="@+id/attach_media_button"
|
||||
|
||||
android:layout_width="@dimen/home_compose_attach_size"
|
||||
|
||||
android:layout_height="@dimen/home_compose_attach_size"
|
||||
|
||||
android:background="@drawable/home_bg_compose_attach_circle"
|
||||
|
||||
android:contentDescription="@string/attachMediaButtonContentDescription"
|
||||
|
||||
android:padding="@dimen/home_compose_attach_icon_padding"
|
||||
|
||||
android:scaleType="centerInside"
|
||||
|
||||
app:srcCompat="@drawable/ic_home_compose_plus"
|
||||
|
||||
app:tint="@color/home_accent" />
|
||||
|
||||
</FrameLayout>
|
||||
app:tint="@color/home_add_icon_tint" />
|
||||
|
||||
|
||||
|
||||
@@ -326,6 +312,8 @@
|
||||
|
||||
android:textColorHint="@color/home_text_secondary"
|
||||
|
||||
android:textCursorDrawable="@drawable/home_cursor"
|
||||
|
||||
android:textSize="16sp" />
|
||||
|
||||
|
||||
@@ -342,6 +330,8 @@
|
||||
|
||||
android:contentDescription="@string/home_compose_emoji_content_description"
|
||||
|
||||
android:focusable="false"
|
||||
|
||||
android:padding="6dp"
|
||||
|
||||
android:scaleType="centerInside"
|
||||
@@ -366,6 +356,8 @@
|
||||
|
||||
android:contentDescription="@string/home_compose_gallery_content_description"
|
||||
|
||||
android:focusable="false"
|
||||
|
||||
android:padding="6dp"
|
||||
|
||||
android:scaleType="centerInside"
|
||||
@@ -452,9 +444,9 @@
|
||||
|
||||
android:id="@+id/home_conversation_send_button_container"
|
||||
|
||||
android:layout_width="@dimen/home_conversation_compose_send_size"
|
||||
android:layout_width="@dimen/home_conversation_compose_voice_size"
|
||||
|
||||
android:layout_height="@dimen/home_conversation_compose_send_size"
|
||||
android:layout_height="@dimen/home_conversation_compose_voice_size"
|
||||
|
||||
android:layout_gravity="center">
|
||||
|
||||
@@ -472,6 +464,8 @@
|
||||
|
||||
android:importantForAccessibility="no"
|
||||
|
||||
android:visibility="gone"
|
||||
|
||||
app:contactIconSize="small"
|
||||
|
||||
app:reveal="true" />
|
||||
@@ -490,15 +484,15 @@
|
||||
|
||||
android:contentDescription="@string/sendButtonContentDescription"
|
||||
|
||||
android:padding="10dp"
|
||||
android:padding="5dp"
|
||||
|
||||
android:scaleType="centerInside"
|
||||
android:scaleType="fitCenter"
|
||||
|
||||
android:src="@drawable/ic_send_light"
|
||||
android:src="@drawable/ic_home_send_up"
|
||||
|
||||
android:visibility="gone"
|
||||
|
||||
app:tint="@android:color/white" />
|
||||
app:tint="@color/home_accent_soft" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
|
||||
@@ -197,6 +197,72 @@
|
||||
app:srcCompat="@drawable/ic_checkmark_circle_primary" />
|
||||
</FrameLayout>
|
||||
|
||||
<com.android.messaging.ui.home.HomeCategoryStrip
|
||||
android:id="@+id/home_category_strip"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="@dimen/looply_screen_padding"
|
||||
android:paddingTop="@dimen/looply_space_md"
|
||||
android:paddingEnd="12dp"
|
||||
android:paddingBottom="@dimen/looply_space_md">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/home_category_all"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/home_category_scroll_host"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipChildren="true"
|
||||
android:clipToPadding="true">
|
||||
|
||||
<com.android.messaging.ui.home.HomeCategoryScroller
|
||||
android:id="@+id/home_category_scroll"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipToPadding="true"
|
||||
android:fillViewport="false"
|
||||
android:overScrollMode="ifContentScrolls"
|
||||
android:scrollbars="none">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/home_category_row"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal" />
|
||||
</com.android.messaging.ui.home.HomeCategoryScroller>
|
||||
|
||||
<View
|
||||
android:id="@+id/home_category_fade_start"
|
||||
android:layout_width="@dimen/home_category_fade_width"
|
||||
android:layout_height="36dp"
|
||||
android:layout_gravity="start|center_vertical"
|
||||
android:background="@drawable/home_bg_category_fade_start"
|
||||
android:clickable="false"
|
||||
android:focusable="false"
|
||||
android:visibility="gone" />
|
||||
|
||||
<View
|
||||
android:id="@+id/home_category_fade_end"
|
||||
android:layout_width="@dimen/home_category_fade_width"
|
||||
android:layout_height="36dp"
|
||||
android:layout_gravity="end|center_vertical"
|
||||
android:background="@drawable/home_bg_category_fade_end"
|
||||
android:clickable="false"
|
||||
android:focusable="false"
|
||||
android:visibility="gone" />
|
||||
</FrameLayout>
|
||||
|
||||
<include
|
||||
android:id="@+id/home_category_add"
|
||||
layout="@layout/home_category_add_tab"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp" />
|
||||
</com.android.messaging.ui.home.HomeCategoryStrip>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<FrameLayout
|
||||
|
||||
@@ -274,8 +274,7 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="top|start"
|
||||
android:text="@string/home_chip_personal"
|
||||
android:textSize="11sp"
|
||||
android:layout_marginTop="2dp"
|
||||
android:visibility="gone" />
|
||||
</FrameLayout>
|
||||
</LinearLayout>
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Circled plus used by home chrome, matching Home.CloseButton. -->
|
||||
<ImageButton xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
style="@style/Home.AddButton"
|
||||
android:contentDescription="@string/home_category_add_content_description"
|
||||
app:srcCompat="@drawable/ic_home_automation_plus"
|
||||
app:tint="@color/home_add_icon_tint" />
|
||||
@@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/home_bg_bottom_sheet"
|
||||
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_xl">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="36dp">
|
||||
|
||||
<View
|
||||
android:layout_width="@dimen/home_gallery_handle_width"
|
||||
android:layout_height="@dimen/home_gallery_handle_height"
|
||||
android:layout_gravity="center"
|
||||
android:background="@drawable/home_bg_gallery_handle" />
|
||||
|
||||
<include
|
||||
android:id="@+id/home_sheet_close"
|
||||
layout="@layout/home_close_button"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_gravity="end|center_vertical" />
|
||||
</FrameLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/home_sheet_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/looply_space_sm"
|
||||
android:includeFontPadding="false"
|
||||
android:textColor="@color/home_text_primary"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
android:visibility="gone" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/home_sheet_custom"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/looply_space_md" />
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="@dimen/looply_space_lg"
|
||||
android:paddingTop="@dimen/looply_space_sm"
|
||||
android:paddingEnd="@dimen/looply_space_lg">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/home_category_add_subtitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/home_category_suggested_subtitle"
|
||||
android:textColor="@color/home_text_secondary"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/home_category_suggest_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/looply_space_md"
|
||||
android:orientation="vertical" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/home_category_add_manually"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:gravity="center"
|
||||
android:paddingTop="@dimen/looply_space_lg"
|
||||
android:paddingBottom="@dimen/looply_space_sm"
|
||||
android:text="@string/home_category_add_manually"
|
||||
android:textColor="@color/home_text_primary"
|
||||
android:textSize="16sp" />
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,111 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="@dimen/looply_space_xl"
|
||||
android:paddingTop="@dimen/looply_space_sm"
|
||||
android:paddingEnd="@dimen/looply_space_xl"
|
||||
android:paddingBottom="@dimen/looply_space_lg">
|
||||
|
||||
<View
|
||||
android:layout_width="@dimen/home_gallery_handle_width"
|
||||
android:layout_height="@dimen/home_gallery_handle_height"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginBottom="@dimen/looply_space_md"
|
||||
android:background="@drawable/home_bg_gallery_handle" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/home_category_sheet_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:includeFontPadding="false"
|
||||
android:text="@string/home_category_add"
|
||||
android:textColor="@color/home_text_primary"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/home_category_sheet_suggest_page"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/home_category_add_subtitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/looply_space_sm"
|
||||
android:text="@string/home_category_suggested_subtitle"
|
||||
android:textColor="@color/home_text_secondary"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/home_category_suggest_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/looply_space_md"
|
||||
android:orientation="vertical" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/home_category_add_manually"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:gravity="center"
|
||||
android:paddingTop="@dimen/looply_space_lg"
|
||||
android:paddingBottom="@dimen/looply_space_sm"
|
||||
android:text="@string/home_category_add_manually"
|
||||
android:textColor="@color/home_accent"
|
||||
android:textSize="16sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/home_category_sheet_name_page"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/home_category_name_field"
|
||||
style="@style/Home.ModalInput"
|
||||
android:layout_marginTop="@dimen/looply_space_lg"
|
||||
android:hint="@string/home_category_name_hint"
|
||||
android:imeOptions="actionDone"
|
||||
android:inputType="textCapWords"
|
||||
android:maxLength="24"
|
||||
android:maxLines="1" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/looply_space_md"
|
||||
android:gravity="end"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/home_category_sheet_cancel"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:padding="@dimen/looply_space_sm"
|
||||
android:text="@android:string/cancel"
|
||||
android:textAllCaps="false"
|
||||
android:textColor="@color/home_text_secondary"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/home_category_sheet_add"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/looply_space_md"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:padding="@dimen/looply_space_sm"
|
||||
android:text="@string/home_category_add_action"
|
||||
android:textColor="@color/home_accent"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ImageButton xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
style="@style/Home.AddButton"
|
||||
android:layout_marginTop="6dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:contentDescription="@string/home_category_add_content_description"
|
||||
app:srcCompat="@drawable/ic_home_automation_plus"
|
||||
app:tint="@color/home_add_icon_tint" />
|
||||
@@ -0,0 +1,24 @@
|
||||
<?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="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/home_category_assign_page"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.chip.ChipGroup
|
||||
android:id="@+id/home_category_assign_chips"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
app:chipSpacingHorizontal="@dimen/looply_space_sm"
|
||||
app:chipSpacingVertical="@dimen/looply_space_sm"
|
||||
app:singleSelection="false" />
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/home_category_name_field"
|
||||
style="@style/Home.ModalInput"
|
||||
android:hint="@string/home_category_name_hint"
|
||||
android:imeOptions="actionDone"
|
||||
android:inputType="textCapWords"
|
||||
android:maxLength="24"
|
||||
android:maxLines="1" />
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/home_category_suggest_radio"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:buttonTint="@color/home_accent"
|
||||
android:clickable="false"
|
||||
android:focusable="false" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/home_category_suggest_label"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/looply_space_sm"
|
||||
android:layout_weight="1"
|
||||
android:includeFontPadding="false"
|
||||
android:textColor="@color/home_text_primary"
|
||||
android:textSize="16sp" />
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:gravity="center_horizontal"
|
||||
android:minHeight="36dp"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="2dp"
|
||||
android:paddingEnd="14dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/home_category_tab_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:includeFontPadding="false"
|
||||
android:textColor="@color/home_text_primary"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<View
|
||||
android:id="@+id/home_category_tab_dot"
|
||||
android:layout_width="6dp"
|
||||
android:layout_height="6dp"
|
||||
android:layout_marginStart="5dp"
|
||||
android:background="@drawable/home_bg_unread_dot"
|
||||
android:visibility="gone" />
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:id="@+id/home_category_tab_underline"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="2dp"
|
||||
android:background="@color/home_accent"
|
||||
android:visibility="invisible" />
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Circled X used by home modal, bottom sheet, and sheet-style headers. -->
|
||||
<ImageButton xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
style="@style/Home.CloseButton"
|
||||
android:contentDescription="@string/home_sub_header_close"
|
||||
app:srcCompat="@drawable/ic_home_close"
|
||||
app:tint="@color/home_accent" />
|
||||
@@ -19,12 +19,14 @@
|
||||
<include layout="@layout/home_sub_header" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/home_compose_recipient_box"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
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_input_field"
|
||||
android:addStatesFromChildren="true"
|
||||
android:background="@drawable/home_bg_search_field_box"
|
||||
android:gravity="center_vertical"
|
||||
android:minHeight="@dimen/home_compose_field_height"
|
||||
android:orientation="horizontal"
|
||||
@@ -34,6 +36,7 @@
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:focusable="false"
|
||||
android:includeFontPadding="false"
|
||||
android:text="@string/home_compose_recipient_label"
|
||||
android:textColor="@color/home_text_secondary"
|
||||
@@ -41,6 +44,7 @@
|
||||
|
||||
<EditText
|
||||
android:id="@+id/home_compose_recipient"
|
||||
style="@style/Home.EditText"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="@dimen/home_compose_field_height"
|
||||
android:layout_marginStart="@dimen/looply_space_sm"
|
||||
@@ -61,6 +65,8 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/looply_space_sm"
|
||||
android:layout_weight="1"
|
||||
android:focusable="false"
|
||||
android:focusableInTouchMode="false"
|
||||
android:paddingTop="@dimen/looply_space_xs"
|
||||
android:paddingBottom="@dimen/looply_space_xs"
|
||||
android:scrollbars="none"
|
||||
@@ -79,6 +85,7 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/looply_space_sm"
|
||||
android:focusable="false"
|
||||
android:includeFontPadding="false"
|
||||
android:text="@string/home_compose_add_people"
|
||||
android:textColor="@color/home_search_hint"
|
||||
|
||||
@@ -4,19 +4,21 @@
|
||||
android:id="@+id/home_compose_attach_compose"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/home_bg"
|
||||
android:background="@android:color/transparent"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginBottom="@dimen/looply_space_sm"
|
||||
android:paddingStart="@dimen/looply_space_md"
|
||||
android:paddingTop="@dimen/looply_space_sm"
|
||||
android:paddingEnd="@dimen/looply_space_md"
|
||||
android:paddingBottom="@dimen/looply_space_md"
|
||||
android:paddingBottom="@dimen/looply_space_xl"
|
||||
android:visibility="gone">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="@dimen/home_conversation_compose_pill_height"
|
||||
android:layout_weight="1"
|
||||
android:addStatesFromChildren="true"
|
||||
android:background="@drawable/home_bg_compose_pill"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
@@ -29,6 +31,7 @@
|
||||
android:layout_height="36dp"
|
||||
android:background="@android:color/transparent"
|
||||
android:contentDescription="@string/home_compose_emoji_content_description"
|
||||
android:focusable="false"
|
||||
android:padding="6dp"
|
||||
android:scaleType="centerInside"
|
||||
app:srcCompat="@drawable/ic_home_emoji"
|
||||
@@ -51,6 +54,7 @@
|
||||
android:paddingEnd="@dimen/looply_space_xs"
|
||||
android:textColor="@color/home_text_primary"
|
||||
android:textColorHint="@color/home_text_secondary"
|
||||
android:textCursorDrawable="@drawable/home_cursor"
|
||||
android:textSize="16sp" />
|
||||
</LinearLayout>
|
||||
|
||||
@@ -61,8 +65,8 @@
|
||||
android:layout_marginStart="@dimen/looply_space_sm"
|
||||
android:background="@drawable/home_bg_compose_send_circle"
|
||||
android:contentDescription="@string/sendButtonContentDescription"
|
||||
android:padding="10dp"
|
||||
android:scaleType="centerInside"
|
||||
android:src="@drawable/ic_send_light"
|
||||
app:tint="@android:color/white" />
|
||||
android:padding="4dp"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/ic_home_send_up"
|
||||
app:tint="@color/home_accent_soft" />
|
||||
</LinearLayout>
|
||||
|
||||
@@ -8,14 +8,16 @@
|
||||
android:alpha="0"
|
||||
android:background="@drawable/home_bg_gallery_date_pill"
|
||||
android:clickable="true"
|
||||
android:clipToOutline="true"
|
||||
android:elevation="8dp"
|
||||
android:focusable="true"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:outlineAmbientShadowColor="@color/home_pill_shadow_ambient"
|
||||
android:outlineProvider="background"
|
||||
android:outlineSpotShadowColor="@color/home_pill_shadow_spot"
|
||||
android:paddingStart="14dp"
|
||||
android:paddingEnd="6dp"
|
||||
android:paddingEnd="2dp"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
@@ -28,11 +30,12 @@
|
||||
android:textStyle="bold" />
|
||||
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginStart="6dp"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:background="@drawable/home_bg_gallery_fast_scroll"
|
||||
android:contentDescription="@null"
|
||||
android:padding="4dp"
|
||||
android:src="@drawable/ic_home_scroll_handle" />
|
||||
android:padding="6dp"
|
||||
android:src="@drawable/ic_home_scroll_handle"
|
||||
android:tint="@color/home_accent_soft" />
|
||||
</LinearLayout>
|
||||
|
||||
@@ -15,7 +15,8 @@
|
||||
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:addStatesFromChildren="true"
|
||||
android:background="@drawable/home_bg_search_field_box"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="@dimen/looply_space_md"
|
||||
@@ -44,6 +45,7 @@
|
||||
android:maxLines="1"
|
||||
android:textColor="@color/home_text_primary"
|
||||
android:textColorHint="@color/home_search_hint"
|
||||
android:textCursorDrawable="@drawable/home_cursor"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<ImageButton
|
||||
@@ -52,6 +54,7 @@
|
||||
android:layout_height="32dp"
|
||||
android:background="@android:color/transparent"
|
||||
android:contentDescription="@string/home_search_clear"
|
||||
android:focusable="false"
|
||||
android:padding="6dp"
|
||||
android:scaleType="centerInside"
|
||||
android:visibility="gone"
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/home_bg_modal"
|
||||
android:orientation="vertical">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingEnd="12dp">
|
||||
|
||||
<include
|
||||
android:id="@+id/home_modal_close"
|
||||
layout="@layout/home_close_button"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_gravity="end" />
|
||||
</FrameLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_horizontal"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="@dimen/looply_space_xl"
|
||||
android:paddingTop="@dimen/looply_space_md"
|
||||
android:paddingEnd="@dimen/looply_space_xl"
|
||||
android:paddingBottom="@dimen/looply_space_xl">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/home_modal_icon_well"
|
||||
android:layout_width="@dimen/home_modal_icon_well"
|
||||
android:layout_height="@dimen/home_modal_icon_well"
|
||||
android:layout_marginBottom="@dimen/looply_space_lg"
|
||||
android:background="@drawable/home_bg_modal_icon"
|
||||
android:visibility="gone">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/home_modal_icon"
|
||||
android:layout_width="@dimen/home_modal_icon_size"
|
||||
android:layout_height="@dimen/home_modal_icon_size"
|
||||
android:layout_gravity="center"
|
||||
android:contentDescription="@null"
|
||||
android:scaleType="fitCenter"
|
||||
android:tint="@color/home_accent" />
|
||||
</FrameLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/home_modal_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:includeFontPadding="false"
|
||||
android:textColor="@color/home_text_primary"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/home_modal_message"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/looply_space_sm"
|
||||
android:gravity="center"
|
||||
android:includeFontPadding="false"
|
||||
android:lineSpacingExtra="3dp"
|
||||
android:textColor="@color/home_text_secondary"
|
||||
android:textSize="14sp"
|
||||
android:visibility="gone" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/home_modal_custom"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/looply_space_lg"
|
||||
android:visibility="gone" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/home_modal_actions"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/looply_space_xl"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/home_modal_positive"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/home_modal_button_height"
|
||||
android:background="@drawable/home_bg_modal_primary"
|
||||
android:gravity="center"
|
||||
android:includeFontPadding="false"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="15sp"
|
||||
android:textStyle="bold"
|
||||
android:visibility="gone" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/home_modal_negative"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/home_modal_button_height"
|
||||
android:layout_marginTop="@dimen/looply_space_sm"
|
||||
android:background="@drawable/home_bg_modal_cancel"
|
||||
android:gravity="center"
|
||||
android:includeFontPadding="false"
|
||||
android:textColor="@color/home_text_primary"
|
||||
android:textSize="15sp"
|
||||
android:visibility="gone" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="260dp"
|
||||
android:layout_width="@dimen/home_popover_width"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/home_bg_popover"
|
||||
android:elevation="12dp"
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="248dp"
|
||||
android:layout_width="@dimen/home_popover_width"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:background="@drawable/home_bg_popover"
|
||||
@@ -113,6 +113,29 @@
|
||||
android:text="@string/home_conversation_hide_alerts" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/popover_conversation_category"
|
||||
style="@style/Home.PopoverItem"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="44dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="22dp"
|
||||
android:layout_height="22dp"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_home_category"
|
||||
android:tint="@color/home_text_primary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/popover_conversation_category_label"
|
||||
style="@style/Home.PopoverLabel"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/home_category_assign" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/popover_conversation_delete"
|
||||
style="@style/Home.PopoverItem"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="220dp"
|
||||
android:layout_width="@dimen/home_popover_width"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/home_bg_popover"
|
||||
android:elevation="12dp"
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Edit popover (Select Messages / Edit Pinned / Name and Photo) -->
|
||||
<!-- Edit popover (Select Messages / Edit Pinned) -->
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="260dp"
|
||||
android:layout_width="@dimen/home_popover_width"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/home_bg_popover"
|
||||
android:elevation="12dp"
|
||||
@@ -53,26 +53,4 @@
|
||||
android:layout_weight="1"
|
||||
android:text="@string/home_popover_edit_pinned" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/popover_name_photo"
|
||||
style="@style/Home.PopoverItem"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="44dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="22dp"
|
||||
android:layout_height="22dp"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_home_menu_person"
|
||||
android:tint="@color/home_text_primary" />
|
||||
|
||||
<TextView
|
||||
style="@style/Home.PopoverLabel"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/home_popover_name_photo" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<!-- Filter popover (Messages / Spam / Archive + Filter By) -->
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="260dp"
|
||||
android:layout_width="@dimen/home_popover_width"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/home_bg_popover"
|
||||
android:elevation="12dp"
|
||||
|
||||
@@ -29,8 +29,8 @@
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/home_search_field_height"
|
||||
android:background="@drawable/home_bg_search_field_inner"
|
||||
android:elevation="2dp"
|
||||
android:addStatesFromChildren="true"
|
||||
android:background="@drawable/home_bg_search_field_box"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="@dimen/looply_space_md"
|
||||
@@ -59,6 +59,7 @@
|
||||
android:maxLines="1"
|
||||
android:textColor="@color/home_text_primary"
|
||||
android:textColorHint="@color/home_search_hint"
|
||||
android:textCursorDrawable="@drawable/home_cursor"
|
||||
android:textSize="15sp" />
|
||||
|
||||
<ImageButton
|
||||
@@ -67,6 +68,7 @@
|
||||
android:layout_height="36dp"
|
||||
android:background="@android:color/transparent"
|
||||
android:contentDescription="@string/home_search_clear"
|
||||
android:focusable="false"
|
||||
android:padding="8dp"
|
||||
android:scaleType="centerInside"
|
||||
android:src="@drawable/ic_home_close"
|
||||
@@ -79,6 +81,7 @@
|
||||
android:layout_height="36dp"
|
||||
android:background="@android:color/transparent"
|
||||
android:contentDescription="@string/home_search_voice"
|
||||
android:focusable="false"
|
||||
android:padding="6dp"
|
||||
android:scaleType="centerInside"
|
||||
android:src="@drawable/ic_home_mic"
|
||||
|
||||
@@ -18,17 +18,13 @@
|
||||
style="@style/Home.SubHeaderTitle" />
|
||||
|
||||
<!-- Shown instead of the back arrow when the screen is presented as a sheet. -->
|
||||
<ImageButton
|
||||
<include
|
||||
android:id="@+id/home_sub_header_close"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:background="@android:color/transparent"
|
||||
android:contentDescription="@string/home_sub_header_close"
|
||||
android:padding="9dp"
|
||||
android:scaleType="centerInside"
|
||||
android:visibility="gone"
|
||||
app:srcCompat="@drawable/ic_home_close"
|
||||
app:tint="@color/home_text_primary" />
|
||||
layout="@layout/home_close_button"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_marginStart="4dp"
|
||||
android:visibility="gone" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/home_sub_header_overflow"
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
<color name="home_surface">#F2F2F7</color>
|
||||
<color name="home_surface_elevated">#E5E5EA</color>
|
||||
<color name="home_surface_translucent">#E8FFFFFF</color>
|
||||
<color name="home_date_pill_blur">#CCFFFFFF</color>
|
||||
<color name="home_popover_bg">#F2FFFFFF</color>
|
||||
<color name="home_button_stroke">#14000000</color>
|
||||
<!-- Elevation + drawable spread (keep alphas low for a soft float) -->
|
||||
|
||||
@@ -10,6 +10,36 @@
|
||||
<item name="android:scaleType">centerInside</item>
|
||||
</style>
|
||||
|
||||
<style name="Home.EditText" parent="Widget.AppCompat.EditText">
|
||||
<item name="android:textCursorDrawable">@drawable/home_cursor</item>
|
||||
</style>
|
||||
|
||||
<style name="Home.ModalInput" parent="Home.EditText">
|
||||
<item name="android:layout_width">match_parent</item>
|
||||
<item name="android:layout_height">@dimen/looply_input_height</item>
|
||||
<item name="android:background">@drawable/home_bg_modal_input</item>
|
||||
<item name="android:gravity">center_vertical</item>
|
||||
<item name="android:includeFontPadding">false</item>
|
||||
<item name="android:paddingStart">@dimen/looply_space_lg</item>
|
||||
<item name="android:paddingEnd">@dimen/looply_space_lg</item>
|
||||
<item name="android:textColor">@color/home_text_primary</item>
|
||||
<item name="android:textColorHint">@color/home_text_secondary</item>
|
||||
<item name="android:textSize">16sp</item>
|
||||
</style>
|
||||
|
||||
<style name="Home.CloseButton">
|
||||
<item name="android:layout_width">32dp</item>
|
||||
<item name="android:layout_height">32dp</item>
|
||||
<item name="android:background">@drawable/home_bg_close_circle</item>
|
||||
<item name="android:padding">8dp</item>
|
||||
<item name="android:scaleType">centerInside</item>
|
||||
</style>
|
||||
|
||||
<style name="Home.AddButton" parent="Home.CloseButton">
|
||||
<item name="android:background">@drawable/home_bg_add_circle</item>
|
||||
<item name="android:tint">@color/home_add_icon_tint</item>
|
||||
</style>
|
||||
|
||||
<style name="Home.SubHeaderTitle">
|
||||
<item name="android:layout_width">0dp</item>
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
@@ -76,8 +106,19 @@
|
||||
<style name="Home.AlertDialog" parent="@style/ThemeOverlay.MaterialComponents.Dialog.Alert">
|
||||
<item name="android:windowBackground">@android:color/transparent</item>
|
||||
<item name="android:backgroundDimAmount">0.35</item>
|
||||
<item name="android:windowCloseOnTouchOutside">true</item>
|
||||
<item name="android:windowAnimationStyle">@style/Home.AlertDialog.Animation</item>
|
||||
<item name="editTextStyle">@style/Home.EditText</item>
|
||||
<item name="android:editTextStyle">@style/Home.EditText</item>
|
||||
</style>
|
||||
|
||||
<style name="Home.AlertDialog.Animation">
|
||||
<item name="android:windowEnterAnimation">@anim/home_modal_enter</item>
|
||||
<item name="android:windowExitAnimation">@anim/home_modal_exit</item>
|
||||
</style>
|
||||
<style name="Home.SubScreen" parent="@style/BugleTheme">
|
||||
<item name="editTextStyle">@style/Home.EditText</item>
|
||||
<item name="android:editTextStyle">@style/Home.EditText</item>
|
||||
<item name="windowActionBar">false</item>
|
||||
<item name="windowNoTitle">true</item>
|
||||
<item name="android:windowBackground">@color/home_bg</item>
|
||||
@@ -141,12 +182,51 @@
|
||||
<item name="dialogCornerRadius">28dp</item>
|
||||
</style>
|
||||
|
||||
<style name="Home.BottomSheetDialog" parent="@style/ThemeOverlay.MaterialComponents.BottomSheetDialog">
|
||||
<item name="colorPrimary">@color/home_accent</item>
|
||||
<item name="colorAccent">@color/home_accent</item>
|
||||
<item name="android:textColorPrimary">@color/home_text_primary</item>
|
||||
<item name="android:textColorSecondary">@color/home_text_secondary</item>
|
||||
<item name="bottomSheetStyle">@style/Home.ModalBottomSheet</item>
|
||||
<item name="android:windowCloseOnTouchOutside">true</item>
|
||||
</style>
|
||||
|
||||
<style name="Home.ModalBottomSheet" parent="Widget.MaterialComponents.BottomSheet.Modal">
|
||||
<item name="backgroundTint">@color/home_bg</item>
|
||||
<item name="shapeAppearanceOverlay">@style/Home.SheetCorners</item>
|
||||
</style>
|
||||
|
||||
<style name="Home.SheetCorners">
|
||||
<item name="cornerFamily">rounded</item>
|
||||
<item name="cornerSizeTopLeft">@dimen/looply_radius_xl</item>
|
||||
<item name="cornerSizeTopRight">@dimen/looply_radius_xl</item>
|
||||
<item name="cornerSizeBottomLeft">0dp</item>
|
||||
<item name="cornerSizeBottomRight">0dp</item>
|
||||
</style>
|
||||
|
||||
<style name="Home.CategoryChip" parent="Widget.MaterialComponents.Chip.Filter">
|
||||
<item name="chipBackgroundColor">@color/home_category_chip_bg</item>
|
||||
<item name="chipStrokeColor">@color/home_category_chip_stroke</item>
|
||||
<item name="chipStrokeWidth">1dp</item>
|
||||
<item name="android:textColor">@color/home_category_chip_text</item>
|
||||
<item name="checkedIconVisible">false</item>
|
||||
<item name="chipMinHeight">36dp</item>
|
||||
<item name="chipStartPadding">12dp</item>
|
||||
<item name="chipEndPadding">12dp</item>
|
||||
<item name="android:textSize">14sp</item>
|
||||
<item name="android:textStyle">bold</item>
|
||||
</style>
|
||||
|
||||
<style name="Home.SimBadge" parent="Home.Chip">
|
||||
<item name="android:background">@drawable/home_bg_sim_badge</item>
|
||||
<item name="android:textColor">@color/home_sim_badge_text</item>
|
||||
<item name="android:minHeight">18dp</item>
|
||||
<item name="android:paddingTop">3dp</item>
|
||||
<item name="android:paddingBottom">3dp</item>
|
||||
<item name="android:includeFontPadding">true</item>
|
||||
<item name="android:textSize">9sp</item>
|
||||
<item name="android:minHeight">14dp</item>
|
||||
<item name="android:paddingStart">4dp</item>
|
||||
<item name="android:paddingEnd">4dp</item>
|
||||
<item name="android:paddingTop">0dp</item>
|
||||
<item name="android:paddingBottom">0dp</item>
|
||||
<item name="android:includeFontPadding">false</item>
|
||||
<item name="android:gravity">center</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
<dimen name="looply_space_xl">20dp</dimen>
|
||||
<dimen name="looply_space_xxl">24dp</dimen>
|
||||
<dimen name="looply_screen_padding">20dp</dimen>
|
||||
<dimen name="home_popover_width">260dp</dimen>
|
||||
<dimen name="home_category_fade_width">32dp</dimen>
|
||||
<dimen name="home_empty_dock_padding">104dp</dimen>
|
||||
|
||||
<!-- Components -->
|
||||
@@ -106,6 +108,9 @@
|
||||
<dimen name="home_nav_item_padding">8dp</dimen>
|
||||
<dimen name="home_nav_dock_padding_bottom">14dp</dimen>
|
||||
<dimen name="home_alert_dialog_horizontal_margin">28dp</dimen>
|
||||
<dimen name="home_modal_icon_well">56dp</dimen>
|
||||
<dimen name="home_modal_icon_size">24dp</dimen>
|
||||
<dimen name="home_modal_button_height">48dp</dimen>
|
||||
<dimen name="home_search_filter_height">88dp</dimen>
|
||||
<dimen name="home_search_filter_icon_size">22dp</dimen>
|
||||
<dimen name="home_search_field_height">48dp</dimen>
|
||||
|
||||
@@ -143,7 +143,6 @@
|
||||
<string name="home_popover_settings" translatable="false">Settings</string>
|
||||
<string name="home_popover_select_messages" translatable="false">Select Messages</string>
|
||||
<string name="home_popover_edit_pinned" translatable="false">Edit Pinned</string>
|
||||
<string name="home_popover_name_photo" translatable="false">Name and Photo Settings</string>
|
||||
<string name="home_popover_coming_soon" translatable="false">Coming soon</string>
|
||||
|
||||
<string name="home_pinned_dialog_title" translatable="false">Pinned Conversations</string>
|
||||
@@ -300,4 +299,35 @@
|
||||
<string name="home_requires_default_sms_app" translatable="false">Set Messages as the default SMS app</string>
|
||||
<string name="home_requires_default_sms_app_to_send" translatable="false">To send, set Messages as the default SMS app</string>
|
||||
<string name="home_requires_default_sms_change" translatable="false">Change</string>
|
||||
|
||||
<!-- Home conversation categories -->
|
||||
<string name="home_category_all" translatable="false">All</string>
|
||||
<string name="home_category_personal" translatable="false">Personal</string>
|
||||
<string name="home_category_promotion" translatable="false">Promotion</string>
|
||||
<string name="home_category_add" translatable="false">Add category</string>
|
||||
<string name="home_category_add_new" translatable="false">+ Add new</string>
|
||||
<string name="home_category_add_content_description" translatable="false">Add category</string>
|
||||
<string name="home_category_suggested_subtitle" translatable="false">Suggested categories based on your conversations</string>
|
||||
<string name="home_category_add_manually" translatable="false">Add manually</string>
|
||||
<string name="home_category_name_hint" translatable="false">Category name</string>
|
||||
<string name="home_category_add_action" translatable="false">Add</string>
|
||||
<string name="home_category_suggest_verification" translatable="false">Verification</string>
|
||||
<string name="home_category_suggest_count" translatable="false">%1$s (%2$d)</string>
|
||||
<string name="home_category_empty_title" translatable="false">No conversations</string>
|
||||
<string name="home_category_empty_body" translatable="false">Long-press a chat and choose Add to category.</string>
|
||||
<string name="home_category_added_toast" translatable="false">Category added</string>
|
||||
<string name="home_category_exists_toast" translatable="false">That category already exists</string>
|
||||
<string name="home_category_limit_toast" translatable="false">You can add up to 12 categories</string>
|
||||
<string name="home_category_delete_title" translatable="false">Delete category?</string>
|
||||
<string name="home_category_delete_message" translatable="false">Conversations stay in your inbox. Only this category is removed.</string>
|
||||
<string name="home_category_assign" translatable="false">Add to category</string>
|
||||
<string name="home_category_assign_message" translatable="false">Select one or more categories.</string>
|
||||
<string name="home_category_name_message" translatable="false">Enter a category name.</string>
|
||||
<string name="home_category_done" translatable="false">Confirm</string>
|
||||
<string name="home_category_updated_toast" translatable="false">Categories updated</string>
|
||||
<string name="home_category_assigned_toast" translatable="false">Added to %1$s</string>
|
||||
<string name="home_category_already_assigned_toast" translatable="false">Already in %1$s</string>
|
||||
<string name="home_category_none_toast" translatable="false">Add a category first</string>
|
||||
<string name="home_category_remove_from" translatable="false">Remove from %1$s</string>
|
||||
<string name="home_category_removed_toast" translatable="false">Removed from %1$s</string>
|
||||
</resources>
|
||||
|
||||
@@ -58,6 +58,9 @@
|
||||
<item name="android:navigationBarColor">@color/home_bg</item>
|
||||
<item name="android:windowLightNavigationBar">true</item>
|
||||
<item name="android:forceDarkAllowed">false</item>
|
||||
<item name="editTextStyle">@style/Home.EditText</item>
|
||||
<item name="android:editTextStyle">@style/Home.EditText</item>
|
||||
<item name="colorAccent">@color/home_accent</item>
|
||||
</style>
|
||||
|
||||
<style name="BugleTheme.ConversationActivityBase" parent="BugleTheme">
|
||||
|
||||
Reference in New Issue
Block a user