Messaging: Split trash from archive and match home selection chrome
Inbox delete now parks conversations in a 30-day trash, and spam/archive/trash lists share the home select/search chrome. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -533,6 +533,7 @@ public class BugleDatabaseOperations {
|
||||
// the sender is blocked, or we have been told to keep it archived.
|
||||
if (!keepArchived) {
|
||||
values.put(ConversationColumns.ARCHIVE_STATUS, 0);
|
||||
values.put(ConversationColumns.TRASH_TIMESTAMP, 0);
|
||||
}
|
||||
|
||||
final MessageData message = readMessage(dbWrapper, messageId);
|
||||
@@ -575,6 +576,20 @@ public class BugleDatabaseOperations {
|
||||
updateConversationRowIfExists(dbWrapper, conversationId, values);
|
||||
}
|
||||
|
||||
@DoesNotRunOnMainThread
|
||||
public static void updateConversationTrashTimestampInTransaction(
|
||||
final DatabaseWrapper dbWrapper, final String conversationId,
|
||||
final long trashTimestamp) {
|
||||
Assert.isNotMainThread();
|
||||
Assert.isTrue(dbWrapper.getDatabase().inTransaction());
|
||||
final ContentValues values = new ContentValues();
|
||||
values.put(ConversationColumns.TRASH_TIMESTAMP, trashTimestamp);
|
||||
if (trashTimestamp > 0) {
|
||||
values.put(ConversationColumns.ARCHIVE_STATUS, 0);
|
||||
}
|
||||
updateConversationRowIfExists(dbWrapper, conversationId, values);
|
||||
}
|
||||
|
||||
static void addSnippetTextAndPreviewToContentValues(final MessageData message,
|
||||
final boolean showDraft, final ContentValues values) {
|
||||
values.put(ConversationColumns.SHOW_DRAFT, showDraft ? 1 : 0);
|
||||
|
||||
@@ -539,7 +539,10 @@ public class BugleNotifications {
|
||||
private static void createMessageNotification(final boolean silent,
|
||||
final String conversationId) {
|
||||
final NotificationState state = MessageNotificationState.getNotificationState();
|
||||
final boolean softSound = DataModel.get().isNewMessageObservable(conversationId);
|
||||
final boolean muted = MutedConversationStore.isMuted(
|
||||
Factory.get().getApplicationContext(), conversationId);
|
||||
final boolean softSound = !muted
|
||||
&& DataModel.get().isNewMessageObservable(conversationId);
|
||||
if (state == null) {
|
||||
cancel(PendingIntentConstants.SMS_NOTIFICATION_ID);
|
||||
if (softSound && !TextUtils.isEmpty(conversationId)) {
|
||||
|
||||
@@ -68,8 +68,14 @@ public abstract class DataModel {
|
||||
get().getActionService().scheduleAction(action, code, delayMs);
|
||||
}
|
||||
|
||||
public ConversationListData createConversationListData(final Context context,
|
||||
final ConversationListDataListener listener, final boolean archivedMode) {
|
||||
return createConversationListData(context, listener, archivedMode, false);
|
||||
}
|
||||
|
||||
public abstract ConversationListData createConversationListData(final Context context,
|
||||
final ConversationListDataListener listener, final boolean archivedMode);
|
||||
final ConversationListDataListener listener, final boolean archivedMode,
|
||||
final boolean trashMode);
|
||||
|
||||
public abstract ConversationData createConversationData(final Context context,
|
||||
final ConversationDataListener listener, final String conversationId);
|
||||
|
||||
@@ -26,6 +26,7 @@ import com.android.messaging.datamodel.action.ActionService;
|
||||
import com.android.messaging.datamodel.action.BackgroundWorker;
|
||||
import com.android.messaging.datamodel.action.FixupMessageStatusOnStartupAction;
|
||||
import com.android.messaging.datamodel.action.ProcessPendingMessagesAction;
|
||||
import com.android.messaging.datamodel.action.PurgeExpiredTrashAction;
|
||||
import com.android.messaging.datamodel.data.BlockedParticipantsData;
|
||||
import com.android.messaging.datamodel.data.BlockedParticipantsData.BlockedParticipantsDataListener;
|
||||
import com.android.messaging.datamodel.data.ConversationSearchData;
|
||||
@@ -81,8 +82,9 @@ public class DataModelImpl extends DataModel {
|
||||
|
||||
@Override
|
||||
public ConversationListData createConversationListData(final Context context,
|
||||
final ConversationListDataListener listener, final boolean archivedMode) {
|
||||
return new ConversationListData(context, listener, archivedMode);
|
||||
final ConversationListDataListener listener, final boolean archivedMode,
|
||||
final boolean trashMode) {
|
||||
return new ConversationListData(context, listener, archivedMode, trashMode);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -211,6 +213,7 @@ public class DataModelImpl extends DataModel {
|
||||
createConnectivityUtilForEachActiveSubscription();
|
||||
|
||||
FixupMessageStatusOnStartupAction.fixupMessageStatus();
|
||||
PurgeExpiredTrashAction.purgeExpired();
|
||||
ProcessPendingMessagesAction.processFirstPendingMessage();
|
||||
SyncManager.immediateSync();
|
||||
|
||||
|
||||
@@ -101,6 +101,9 @@ public class DatabaseHelper extends SQLiteOpenHelper {
|
||||
/* If this conversation is archived */
|
||||
public static final String ARCHIVE_STATUS = "archive_status";
|
||||
|
||||
/* When the conversation was moved to Trash; 0 if not in Trash */
|
||||
public static final String TRASH_TIMESTAMP = "trash_timestamp";
|
||||
|
||||
/* Timestamp for sorting purposes */
|
||||
public static final String SORT_TIMESTAMP = "sort_timestamp";
|
||||
|
||||
@@ -159,6 +162,7 @@ public class DatabaseHelper extends SQLiteOpenHelper {
|
||||
+ ConversationColumns.DRAFT_PREVIEW_URI + " TEXT, "
|
||||
+ ConversationColumns.DRAFT_PREVIEW_CONTENT_TYPE + " TEXT, "
|
||||
+ ConversationColumns.ARCHIVE_STATUS + " INT DEFAULT(0), "
|
||||
+ ConversationColumns.TRASH_TIMESTAMP + " INT DEFAULT(0), "
|
||||
+ ConversationColumns.SORT_TIMESTAMP + " INT DEFAULT(0), "
|
||||
+ ConversationColumns.LAST_READ_TIMESTAMP + " INT DEFAULT(0), "
|
||||
+ ConversationColumns.ICON + " TEXT, "
|
||||
@@ -183,6 +187,11 @@ public class DatabaseHelper extends SQLiteOpenHelper {
|
||||
+ " ON " + CONVERSATIONS_TABLE
|
||||
+ "(" + ConversationColumns.ARCHIVE_STATUS + ")";
|
||||
|
||||
private static final String CONVERSATIONS_TABLE_TRASH_TIMESTAMP_INDEX_SQL =
|
||||
"CREATE INDEX index_" + CONVERSATIONS_TABLE + "_" + ConversationColumns.TRASH_TIMESTAMP
|
||||
+ " ON " + CONVERSATIONS_TABLE
|
||||
+ "(" + ConversationColumns.TRASH_TIMESTAMP + ")";
|
||||
|
||||
private static final String CONVERSATIONS_TABLE_SORT_TIMESTAMP_INDEX_SQL =
|
||||
"CREATE INDEX index_" + CONVERSATIONS_TABLE + "_" + ConversationColumns.SORT_TIMESTAMP
|
||||
+ " ON " + CONVERSATIONS_TABLE
|
||||
@@ -525,6 +534,7 @@ public class DatabaseHelper extends SQLiteOpenHelper {
|
||||
private static final String[] CREATE_INDEX_SQLS = new String[] {
|
||||
CONVERSATIONS_TABLE_SMS_THREAD_ID_INDEX_SQL,
|
||||
CONVERSATIONS_TABLE_ARCHIVE_STATUS_INDEX_SQL,
|
||||
CONVERSATIONS_TABLE_TRASH_TIMESTAMP_INDEX_SQL,
|
||||
CONVERSATIONS_TABLE_SORT_TIMESTAMP_INDEX_SQL,
|
||||
MESSAGES_TABLE_SORT_INDEX_SQL,
|
||||
MESSAGES_TABLE_STATUS_SEEN_INDEX_SQL,
|
||||
|
||||
@@ -48,6 +48,9 @@ public class DatabaseUpgradeHelper {
|
||||
if (currentVersion < 2) {
|
||||
currentVersion = upgradeToVersion2(db);
|
||||
}
|
||||
if (currentVersion < 3) {
|
||||
currentVersion = upgradeToVersion3(db);
|
||||
}
|
||||
// Rebuild all the views
|
||||
final Context context = Factory.get().getApplicationContext();
|
||||
DatabaseHelper.dropAllViews(db);
|
||||
@@ -63,6 +66,17 @@ public class DatabaseUpgradeHelper {
|
||||
return 2;
|
||||
}
|
||||
|
||||
private int upgradeToVersion3(final SQLiteDatabase db) {
|
||||
db.execSQL("ALTER TABLE " + DatabaseHelper.CONVERSATIONS_TABLE + " ADD COLUMN " +
|
||||
DatabaseHelper.ConversationColumns.TRASH_TIMESTAMP + " INT DEFAULT(0)");
|
||||
db.execSQL("CREATE INDEX IF NOT EXISTS index_" + DatabaseHelper.CONVERSATIONS_TABLE
|
||||
+ "_" + DatabaseHelper.ConversationColumns.TRASH_TIMESTAMP
|
||||
+ " ON " + DatabaseHelper.CONVERSATIONS_TABLE
|
||||
+ "(" + DatabaseHelper.ConversationColumns.TRASH_TIMESTAMP + ")");
|
||||
LogUtil.i(TAG, "Upgraded database to version 3");
|
||||
return 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks db version correctness at the end of each milestone release. If target database
|
||||
* version lies beyond the version range that the current release may handle, we snap the
|
||||
|
||||
@@ -51,6 +51,7 @@ import com.android.messaging.datamodel.data.MessagePartData;
|
||||
import com.android.messaging.datamodel.data.ParticipantData;
|
||||
import com.android.messaging.sms.MmsUtils;
|
||||
import com.android.messaging.ui.UIIntents;
|
||||
import com.android.messaging.ui.home.MutedConversationStore;
|
||||
import com.android.messaging.util.Assert;
|
||||
import com.android.messaging.util.AvatarUriUtil;
|
||||
import com.android.messaging.util.BugleGservicesKeys;
|
||||
@@ -802,6 +803,9 @@ public abstract class MessageNotificationState extends NotificationState {
|
||||
final String messageText = convMessageData.getText();
|
||||
|
||||
final String convId = convMessageData.getConversationId();
|
||||
if (MutedConversationStore.isMuted(context, convId)) {
|
||||
continue;
|
||||
}
|
||||
final String messageId = convMessageData.getMessageId();
|
||||
|
||||
CharSequence text = messageText;
|
||||
@@ -818,6 +822,9 @@ public abstract class MessageNotificationState extends NotificationState {
|
||||
if (currConvInfo == null) {
|
||||
final ConversationListItemData convData =
|
||||
ConversationListItemData.getExistingConversation(db, convId);
|
||||
if (convData == null || convData.getIsTrashed()) {
|
||||
continue;
|
||||
}
|
||||
final int subId = BugleDatabaseOperations.getSelfSubscriptionId(db,
|
||||
convData.getSelfId());
|
||||
groupConversationName = convData.getName();
|
||||
|
||||
@@ -132,6 +132,13 @@ public class InsertNewMessageAction extends Action implements Parcelable {
|
||||
final DatabaseWrapper db = DataModel.get().getDatabase();
|
||||
final String conversationId = message.getConversationId();
|
||||
|
||||
final ConversationListItemData conversation =
|
||||
ConversationListItemData.getExistingConversation(db, conversationId);
|
||||
if (conversation != null && conversation.getIsTrashed()) {
|
||||
LogUtil.w(TAG, "InsertNewMessageAction: conversation is in trash");
|
||||
return null;
|
||||
}
|
||||
|
||||
final ParticipantData self = getSelf(db, conversationId, message);
|
||||
if (self == null) {
|
||||
return null;
|
||||
@@ -190,6 +197,7 @@ public class InsertNewMessageAction extends Action implements Parcelable {
|
||||
messageToSend, BugleDatabaseOperations.UPDATE_MODE_CLEAR_DRAFT);
|
||||
}
|
||||
MessagingContentProvider.notifyConversationListChanged();
|
||||
MessagingContentProvider.notifyConversationMetadataChanged(conversationId);
|
||||
ProcessPendingMessagesAction.scheduleProcessPendingMessagesAction(false, this);
|
||||
|
||||
return message;
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
package com.android.messaging.datamodel.action;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.messaging.datamodel.DataModel;
|
||||
import com.android.messaging.datamodel.DatabaseHelper;
|
||||
import com.android.messaging.datamodel.DatabaseHelper.ConversationColumns;
|
||||
import com.android.messaging.datamodel.DatabaseWrapper;
|
||||
import com.android.messaging.util.LogUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/** Permanently deletes conversations that have been in Trash longer than 30 days. */
|
||||
public class PurgeExpiredTrashAction extends Action {
|
||||
|
||||
private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
|
||||
|
||||
public static void purgeExpired() {
|
||||
new PurgeExpiredTrashAction().start();
|
||||
}
|
||||
|
||||
private PurgeExpiredTrashAction() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object executeAction() {
|
||||
final long cutoff = System.currentTimeMillis()
|
||||
- UpdateConversationTrashStatusAction.RETENTION_MS;
|
||||
final DatabaseWrapper db = DataModel.get().getDatabase();
|
||||
final ArrayList<String> ids = new ArrayList<>();
|
||||
final ArrayList<Long> timestamps = new ArrayList<>();
|
||||
try (Cursor cursor = db.query(DatabaseHelper.CONVERSATIONS_TABLE,
|
||||
new String[] { ConversationColumns._ID, ConversationColumns.SORT_TIMESTAMP },
|
||||
ConversationColumns.TRASH_TIMESTAMP + " > 0 AND "
|
||||
+ ConversationColumns.TRASH_TIMESTAMP + " < ?",
|
||||
new String[] { Long.toString(cutoff) },
|
||||
null, null, null)) {
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
ids.add(cursor.getString(0));
|
||||
timestamps.add(cursor.getLong(1));
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < ids.size(); i++) {
|
||||
DeleteConversationAction.deleteConversation(ids.get(i), timestamps.get(i));
|
||||
}
|
||||
if (!ids.isEmpty()) {
|
||||
LogUtil.i(TAG, "Purged " + ids.size() + " expired trash conversations");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private PurgeExpiredTrashAction(final Parcel in) {
|
||||
super(in);
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<PurgeExpiredTrashAction> CREATOR
|
||||
= new Parcelable.Creator<>() {
|
||||
@Override
|
||||
public PurgeExpiredTrashAction createFromParcel(final Parcel in) {
|
||||
return new PurgeExpiredTrashAction(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PurgeExpiredTrashAction[] newArray(final int size) {
|
||||
return new PurgeExpiredTrashAction[size];
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull final Parcel parcel, final int flags) {
|
||||
writeActionToParcel(parcel, flags);
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@ import com.android.messaging.datamodel.DataModel;
|
||||
import com.android.messaging.datamodel.DatabaseHelper.MessageColumns;
|
||||
import com.android.messaging.datamodel.DatabaseWrapper;
|
||||
import com.android.messaging.datamodel.MessagingContentProvider;
|
||||
import com.android.messaging.datamodel.data.ConversationListItemData;
|
||||
import com.android.messaging.datamodel.data.MessageData;
|
||||
import com.android.messaging.util.LogUtil;
|
||||
|
||||
@@ -70,6 +71,12 @@ public class ResendMessageAction extends Action implements Parcelable {
|
||||
final MessageData message = BugleDatabaseOperations.readMessage(db, messageId);
|
||||
// Check message can be resent
|
||||
if (message != null && message.canResendMessage()) {
|
||||
final ConversationListItemData conversation =
|
||||
ConversationListItemData.getExistingConversation(db, message.getConversationId());
|
||||
if (conversation != null && conversation.getIsTrashed()) {
|
||||
LogUtil.w(TAG, "ResendMessageAction: conversation is in trash");
|
||||
return null;
|
||||
}
|
||||
final boolean isMms = message.getIsMms();
|
||||
long timestamp = System.currentTimeMillis();
|
||||
if (isMms) {
|
||||
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
package com.android.messaging.datamodel.action;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.messaging.Factory;
|
||||
import com.android.messaging.datamodel.BugleDatabaseOperations;
|
||||
import com.android.messaging.datamodel.DataModel;
|
||||
import com.android.messaging.datamodel.DatabaseWrapper;
|
||||
import com.android.messaging.datamodel.MessagingContentProvider;
|
||||
import com.android.messaging.ui.home.PinnedConversationStore;
|
||||
import com.android.messaging.util.Assert;
|
||||
|
||||
public class UpdateConversationTrashStatusAction extends Action {
|
||||
|
||||
public static final long RETENTION_MS = 30L * 24 * 60 * 60 * 1000;
|
||||
|
||||
public static void moveToTrash(final String conversationId) {
|
||||
final UpdateConversationTrashStatusAction action =
|
||||
new UpdateConversationTrashStatusAction(conversationId, true);
|
||||
action.start();
|
||||
}
|
||||
|
||||
public static void restoreFromTrash(final String conversationId) {
|
||||
final UpdateConversationTrashStatusAction action =
|
||||
new UpdateConversationTrashStatusAction(conversationId, false);
|
||||
action.start();
|
||||
}
|
||||
|
||||
private static final String KEY_CONVERSATION_ID = "conversation_id";
|
||||
private static final String KEY_TO_TRASH = "to_trash";
|
||||
|
||||
private UpdateConversationTrashStatusAction(final String conversationId,
|
||||
final boolean toTrash) {
|
||||
Assert.isTrue(!TextUtils.isEmpty(conversationId));
|
||||
actionParameters.putString(KEY_CONVERSATION_ID, conversationId);
|
||||
actionParameters.putBoolean(KEY_TO_TRASH, toTrash);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object executeAction() {
|
||||
final String conversationId = actionParameters.getString(KEY_CONVERSATION_ID);
|
||||
final boolean toTrash = actionParameters.getBoolean(KEY_TO_TRASH);
|
||||
final DatabaseWrapper db = DataModel.get().getDatabase();
|
||||
db.beginTransaction();
|
||||
try {
|
||||
BugleDatabaseOperations.updateConversationTrashTimestampInTransaction(
|
||||
db, conversationId, toTrash ? System.currentTimeMillis() : 0L);
|
||||
db.setTransactionSuccessful();
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
}
|
||||
if (toTrash) {
|
||||
PinnedConversationStore.unpin(Factory.get().getApplicationContext(), conversationId);
|
||||
UpdateMessageNotificationAction.updateMessageNotification();
|
||||
}
|
||||
MessagingContentProvider.notifyConversationListChanged();
|
||||
MessagingContentProvider.notifyConversationMetadataChanged(conversationId);
|
||||
return null;
|
||||
}
|
||||
|
||||
private UpdateConversationTrashStatusAction(final Parcel in) {
|
||||
super(in);
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<UpdateConversationTrashStatusAction> CREATOR
|
||||
= new Parcelable.Creator<>() {
|
||||
@Override
|
||||
public UpdateConversationTrashStatusAction createFromParcel(final Parcel in) {
|
||||
return new UpdateConversationTrashStatusAction(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpdateConversationTrashStatusAction[] newArray(final int size) {
|
||||
return new UpdateConversationTrashStatusAction[size];
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull final Parcel parcel, final int flags) {
|
||||
writeActionToParcel(parcel, flags);
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,7 @@ import com.android.messaging.datamodel.action.InsertNewMessageAction;
|
||||
import com.android.messaging.datamodel.action.RedownloadMmsAction;
|
||||
import com.android.messaging.datamodel.action.ResendMessageAction;
|
||||
import com.android.messaging.datamodel.action.UpdateConversationArchiveStatusAction;
|
||||
import com.android.messaging.datamodel.action.UpdateConversationTrashStatusAction;
|
||||
import com.android.messaging.datamodel.binding.BindableData;
|
||||
import com.android.messaging.datamodel.binding.Binding;
|
||||
import com.android.messaging.datamodel.binding.BindingBase;
|
||||
@@ -460,7 +461,11 @@ public class ConversationData extends BindableData {
|
||||
}
|
||||
|
||||
public boolean getIsArchived() {
|
||||
return mConversationMetadata.getIsArchived();
|
||||
return mConversationMetadata != null && mConversationMetadata.getIsArchived();
|
||||
}
|
||||
|
||||
public boolean getIsTrashed() {
|
||||
return mConversationMetadata != null && mConversationMetadata.getIsTrashed();
|
||||
}
|
||||
|
||||
public String getIcon() {
|
||||
@@ -569,6 +574,12 @@ public class ConversationData extends BindableData {
|
||||
final MessageData message) {
|
||||
Assert.isTrue(TextUtils.equals(mConversationId, message.getConversationId()));
|
||||
Assert.isTrue(binding.getData() == this);
|
||||
if (getIsTrashed()) {
|
||||
return;
|
||||
}
|
||||
if (getIsArchived()) {
|
||||
unarchiveConversation(binding);
|
||||
}
|
||||
|
||||
if (message.getSelfId() == null) {
|
||||
InsertNewMessageAction.insertNewMessage(message);
|
||||
@@ -612,6 +623,12 @@ public class ConversationData extends BindableData {
|
||||
public void resendMessage(final BindingBase<ConversationData> binding, final String messageId) {
|
||||
Assert.isTrue(binding.getData() == this);
|
||||
Assert.notNull(messageId);
|
||||
if (getIsTrashed()) {
|
||||
return;
|
||||
}
|
||||
if (getIsArchived()) {
|
||||
unarchiveConversation(binding);
|
||||
}
|
||||
ResendMessageAction.resendMessage(messageId);
|
||||
}
|
||||
|
||||
@@ -642,6 +659,16 @@ public class ConversationData extends BindableData {
|
||||
UpdateConversationArchiveStatusAction.unarchiveConversation(mConversationId);
|
||||
}
|
||||
|
||||
public void moveConversationToTrash(final BindingBase<ConversationData> binding) {
|
||||
Assert.isTrue(binding.getData() == this);
|
||||
UpdateConversationTrashStatusAction.moveToTrash(mConversationId);
|
||||
}
|
||||
|
||||
public void restoreConversationFromTrash(final BindingBase<ConversationData> binding) {
|
||||
Assert.isTrue(binding.getData() == this);
|
||||
UpdateConversationTrashStatusAction.restoreFromTrash(mConversationId);
|
||||
}
|
||||
|
||||
public ConversationParticipantsData getParticipants() {
|
||||
return mParticipantData;
|
||||
}
|
||||
|
||||
@@ -52,6 +52,12 @@ public class ConversationListData extends BindableData
|
||||
"(" + ConversationListViewColumns.ARCHIVE_STATUS + " = 1)";
|
||||
public static final String WHERE_NOT_ARCHIVED =
|
||||
"(" + ConversationListViewColumns.ARCHIVE_STATUS + " = 0)";
|
||||
public static final String WHERE_NOT_TRASHED =
|
||||
"(" + ConversationListViewColumns.TRASH_TIMESTAMP + " = 0)";
|
||||
public static final String WHERE_TRASHED =
|
||||
"(" + ConversationListViewColumns.TRASH_TIMESTAMP + " > 0)";
|
||||
public static final String WHERE_INBOX = WHERE_NOT_ARCHIVED + " AND " + WHERE_NOT_TRASHED;
|
||||
public static final String WHERE_ARCHIVED_VISIBLE = WHERE_ARCHIVED + " AND " + WHERE_NOT_TRASHED;
|
||||
|
||||
public interface ConversationListDataListener {
|
||||
void onConversationListCursorUpdated(ConversationListData data, Cursor cursor);
|
||||
@@ -61,13 +67,20 @@ public class ConversationListData extends BindableData
|
||||
private ConversationListDataListener mListener;
|
||||
private final Context mContext;
|
||||
private final boolean mArchivedMode;
|
||||
private final boolean mTrashMode;
|
||||
private LoaderManager mLoaderManager;
|
||||
|
||||
public ConversationListData(final Context context, final ConversationListDataListener listener,
|
||||
final boolean archivedMode) {
|
||||
this(context, listener, archivedMode, false);
|
||||
}
|
||||
|
||||
public ConversationListData(final Context context, final ConversationListDataListener listener,
|
||||
final boolean archivedMode, final boolean trashMode) {
|
||||
mListener = listener;
|
||||
mContext = context;
|
||||
mArchivedMode = archivedMode;
|
||||
mTrashMode = trashMode;
|
||||
mArchivedMode = archivedMode && !trashMode;
|
||||
}
|
||||
|
||||
private static final int CONVERSATION_LIST_LOADER = 1;
|
||||
@@ -101,7 +114,7 @@ public class ConversationListData extends BindableData
|
||||
loader = new BoundCursorLoader(bindingId, mContext,
|
||||
MessagingContentProvider.CONVERSATIONS_URI,
|
||||
ConversationListItemData.PROJECTION,
|
||||
mArchivedMode ? WHERE_ARCHIVED : WHERE_NOT_ARCHIVED,
|
||||
listSelection(),
|
||||
null, // selection args
|
||||
SORT_ORDER);
|
||||
break;
|
||||
@@ -212,4 +225,18 @@ public class ConversationListData extends BindableData
|
||||
public HashSet<String> getBlockedParticipants() {
|
||||
return mBlockedParticipants;
|
||||
}
|
||||
|
||||
public boolean isTrashMode() {
|
||||
return mTrashMode;
|
||||
}
|
||||
|
||||
private String listSelection() {
|
||||
if (mTrashMode) {
|
||||
return WHERE_TRASHED;
|
||||
}
|
||||
if (mArchivedMode) {
|
||||
return WHERE_ARCHIVED_VISIBLE;
|
||||
}
|
||||
return WHERE_INBOX;
|
||||
}
|
||||
}
|
||||
|
||||
+22
-1
@@ -28,6 +28,7 @@ import com.android.messaging.datamodel.DatabaseHelper.MessageColumns;
|
||||
import com.android.messaging.datamodel.DatabaseHelper.ParticipantColumns;
|
||||
import com.android.messaging.datamodel.DatabaseWrapper;
|
||||
import com.android.messaging.datamodel.action.DeleteConversationAction;
|
||||
import com.android.messaging.datamodel.action.UpdateConversationTrashStatusAction;
|
||||
import com.android.messaging.util.Assert;
|
||||
import com.android.messaging.util.ContactUtil;
|
||||
import com.android.messaging.util.Dates;
|
||||
@@ -61,6 +62,7 @@ public class ConversationListItemData {
|
||||
private String mDraftPreviewContentType;
|
||||
private String mDraftSnippetText;
|
||||
private boolean mIsArchived;
|
||||
private long mTrashTimestamp;
|
||||
private String mSubject;
|
||||
private String mDraftSubject;
|
||||
private String mSnippetSenderFirstName;
|
||||
@@ -110,6 +112,8 @@ public class ConversationListItemData {
|
||||
}
|
||||
|
||||
mIsArchived = cursor.getInt(INDEX_ARCHIVE_STATUS) == 1;
|
||||
final int trashIndex = cursor.getColumnIndex(ConversationListViewColumns.TRASH_TIMESTAMP);
|
||||
mTrashTimestamp = trashIndex >= 0 ? cursor.getLong(trashIndex) : 0;
|
||||
mSubject = cursor.getString(INDEX_SUBJECT_TEXT);
|
||||
mSnippetSenderFirstName = cursor.getString(INDEX_SNIPPET_SENDER_FIRST_NAME);
|
||||
mSnippetSenderDisplayDestination =
|
||||
@@ -240,6 +244,14 @@ public class ConversationListItemData {
|
||||
return mIsArchived;
|
||||
}
|
||||
|
||||
public boolean getIsTrashed() {
|
||||
return mTrashTimestamp > 0;
|
||||
}
|
||||
|
||||
public long getTrashTimestamp() {
|
||||
return mTrashTimestamp;
|
||||
}
|
||||
|
||||
public String getSubject() {
|
||||
return mSubject;
|
||||
}
|
||||
@@ -256,7 +268,11 @@ public class ConversationListItemData {
|
||||
}
|
||||
|
||||
public void deleteConversation() {
|
||||
DeleteConversationAction.deleteConversation(mConversationId, mTimestamp);
|
||||
if (getIsTrashed()) {
|
||||
DeleteConversationAction.deleteConversation(mConversationId, mTimestamp);
|
||||
} else {
|
||||
UpdateConversationTrashStatusAction.moveToTrash(mConversationId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -281,6 +297,8 @@ public class ConversationListItemData {
|
||||
+ " as " + ConversationListViewColumns.CURRENT_SELF_ID + ", "
|
||||
+ DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.ARCHIVE_STATUS
|
||||
+ " as " + ConversationListViewColumns.ARCHIVE_STATUS + ", "
|
||||
+ DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.TRASH_TIMESTAMP
|
||||
+ " as " + ConversationListViewColumns.TRASH_TIMESTAMP + ", "
|
||||
+ DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.READ
|
||||
+ " as " + ConversationListViewColumns.READ + ", "
|
||||
+ DatabaseHelper.CONVERSATIONS_TABLE + '.' + ConversationColumns.ICON
|
||||
@@ -354,6 +372,7 @@ public class ConversationListItemData {
|
||||
public static final String _ID = ConversationColumns._ID;
|
||||
static final String NAME = ConversationColumns.NAME;
|
||||
static final String ARCHIVE_STATUS = ConversationColumns.ARCHIVE_STATUS;
|
||||
static final String TRASH_TIMESTAMP = ConversationColumns.TRASH_TIMESTAMP;
|
||||
static final String READ = MessageColumns.READ;
|
||||
static final String SORT_TIMESTAMP = ConversationColumns.SORT_TIMESTAMP;
|
||||
static final String PREVIEW_URI = ConversationColumns.PREVIEW_URI;
|
||||
@@ -412,6 +431,7 @@ public class ConversationListItemData {
|
||||
ConversationListViewColumns.SNIPPET_SENDER_FIRST_NAME,
|
||||
ConversationListViewColumns.SNIPPET_SENDER_DISPLAY_DESTINATION,
|
||||
ConversationListViewColumns.IS_ENTERPRISE,
|
||||
ConversationListViewColumns.TRASH_TIMESTAMP,
|
||||
};
|
||||
|
||||
private static final int INDEX_ID = 0;
|
||||
@@ -441,6 +461,7 @@ public class ConversationListItemData {
|
||||
private static final int INDEX_SNIPPET_SENDER_FIRST_NAME = 24;
|
||||
private static final int INDEX_SNIPPET_SENDER_DISPLAY_DESTINATION = 25;
|
||||
private static final int INDEX_IS_ENTERPRISE = 26;
|
||||
private static final int INDEX_TRASH_TIMESTAMP = 27;
|
||||
|
||||
private static final String DIVIDER_TEXT = ", ";
|
||||
|
||||
|
||||
@@ -87,6 +87,11 @@ public class ParticipantListItemData extends PersonItemData {
|
||||
return mNormalizedDestination;
|
||||
}
|
||||
|
||||
public void unblockQuietly(final Context context) {
|
||||
UpdateDestinationBlockedAction.updateDestinationBlocked(
|
||||
mNormalizedDestination, false, null, (action, success, block, destination) -> { });
|
||||
}
|
||||
|
||||
public void unblock(final Context context) {
|
||||
UpdateDestinationBlockedAction.updateDestinationBlocked(
|
||||
mNormalizedDestination, false, null,
|
||||
|
||||
@@ -346,6 +346,9 @@ public class BugleActionBarActivity extends AppCompatActivity implements ImeUtil
|
||||
}
|
||||
|
||||
public void updateActionBar(final ActionBar actionBar) {
|
||||
if (actionBar == null) {
|
||||
return;
|
||||
}
|
||||
if (!BugleActionBarActivity.this.shouldShowContextualActionBar(mCallback)) {
|
||||
actionBar.hide();
|
||||
return;
|
||||
|
||||
@@ -30,6 +30,7 @@ import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.android.messaging.Factory;
|
||||
import com.android.messaging.datamodel.data.ConversationMessageData;
|
||||
import com.android.messaging.datamodel.data.MessageData;
|
||||
import com.android.messaging.util.ConversationIdSet;
|
||||
|
||||
@@ -183,6 +184,12 @@ public abstract class UIIntents {
|
||||
public abstract void launchHomeConversationSearchActivity(final Context context,
|
||||
final String conversationId);
|
||||
|
||||
/**
|
||||
* Launch home UI for a single message's details.
|
||||
*/
|
||||
public abstract void launchHomeMessageDetailsActivity(final Context context,
|
||||
final ConversationMessageData data);
|
||||
|
||||
/**
|
||||
* Launch an external activity to handle a phone call
|
||||
* @param phoneNumber the phone number to call
|
||||
@@ -196,6 +203,11 @@ public abstract class UIIntents {
|
||||
*/
|
||||
public abstract void launchArchivedConversationsActivity(final Context context);
|
||||
|
||||
/**
|
||||
* Launch an activity to show conversations in Trash.
|
||||
*/
|
||||
public abstract void launchRecentlyDeletedConversationsActivity(final Context context);
|
||||
|
||||
/**
|
||||
* Launch an activity to show blocked participants.
|
||||
*/
|
||||
|
||||
@@ -45,6 +45,7 @@ import com.android.messaging.R;
|
||||
import com.android.messaging.datamodel.ConversationImagePartsView;
|
||||
import com.android.messaging.datamodel.MediaScratchFileProvider;
|
||||
import com.android.messaging.datamodel.MessagingContentProvider;
|
||||
import com.android.messaging.datamodel.data.ConversationMessageData;
|
||||
import com.android.messaging.datamodel.data.MessageData;
|
||||
import com.android.messaging.datamodel.data.MessagePartData;
|
||||
import com.android.messaging.datamodel.data.ParticipantData;
|
||||
@@ -56,8 +57,10 @@ import com.android.messaging.ui.appsettings.SettingsActivity;
|
||||
import com.android.messaging.ui.conversation.ConversationActivity;
|
||||
import com.android.messaging.ui.conversation.LaunchConversationActivity;
|
||||
import com.android.messaging.ui.conversationlist.ArchivedConversationListActivity;
|
||||
import com.android.messaging.ui.conversationlist.RecentlyDeletedConversationListActivity;
|
||||
import com.android.messaging.ui.conversationlist.ConversationListActivity;
|
||||
import com.android.messaging.ui.conversationlist.ForwardMessageActivity;
|
||||
import com.android.messaging.ui.home.HomeMessageDetailsActivity;
|
||||
import com.android.messaging.ui.photoviewer.BuglePhotoViewActivity;
|
||||
import com.android.messaging.util.Assert;
|
||||
import com.android.messaging.util.ConversationIdSet;
|
||||
@@ -208,6 +211,12 @@ public class UIIntentsImpl extends UIIntents {
|
||||
context.startActivity(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void launchRecentlyDeletedConversationsActivity(final Context context) {
|
||||
final Intent intent = new Intent(context, RecentlyDeletedConversationListActivity.class);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void launchBlockedParticipantsActivity(final Context context) {
|
||||
final Intent intent = new Intent(context, BlockedParticipantsActivity.class);
|
||||
@@ -280,6 +289,16 @@ public class UIIntentsImpl extends UIIntents {
|
||||
context.startActivity(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void launchHomeMessageDetailsActivity(final Context context,
|
||||
final ConversationMessageData data) {
|
||||
HomeMessageDetailsActivity.start(context, data);
|
||||
if (context instanceof Activity) {
|
||||
((Activity) context).overridePendingTransition(
|
||||
R.anim.home_activity_open, R.anim.home_activity_hold);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void launchPhoneCallActivity(final Context context, final String phoneNumber,
|
||||
final Point clickPosition) {
|
||||
|
||||
+153
-45
@@ -115,6 +115,8 @@ import com.android.messaging.ui.home.HomeBottomSheet;
|
||||
import com.android.messaging.ui.home.HomeComposeAttachPanel;
|
||||
import com.android.messaging.ui.home.HomeEmptyState;
|
||||
import com.android.messaging.ui.home.HomeGallerySheet;
|
||||
import com.android.messaging.ui.home.HomeModal;
|
||||
import com.android.messaging.ui.home.HomeToast;
|
||||
import com.android.messaging.ui.home.HomeUiMotion;
|
||||
import com.android.messaging.ui.home.StarredMessagesStore;
|
||||
import com.android.messaging.ui.home.UnknownSenderPromptStore;
|
||||
@@ -193,6 +195,7 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
|
||||
private TextView mMessageSelectionTitle;
|
||||
private View mComposeContainer;
|
||||
private View mComposeStage;
|
||||
private View mTrashBar;
|
||||
private View mMessageActionBar;
|
||||
private View mMessageActionCopy;
|
||||
private View mMessageActionForward;
|
||||
@@ -354,7 +357,8 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
|
||||
result -> {
|
||||
if (result.getResultCode() == Activity.RESULT_OK
|
||||
&& result.getData() != null
|
||||
&& mComposeMessageView != null) {
|
||||
&& mComposeMessageView != null
|
||||
&& !isConversationTrashed()) {
|
||||
mComposeMessageView.applySharedLocation(result.getData());
|
||||
}
|
||||
});
|
||||
@@ -371,7 +375,7 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
|
||||
public void onCreate(final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
mGalleryPicker = new DocumentImagePicker(this, pendingItem -> {
|
||||
if (mComposeMessageView != null) {
|
||||
if (mComposeMessageView != null && !isConversationTrashed()) {
|
||||
mComposeMessageView.onPendingAttachmentAdded(pendingItem);
|
||||
}
|
||||
});
|
||||
@@ -467,6 +471,7 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
|
||||
mEmptyContainer = view.findViewById(R.id.home_conversation_empty);
|
||||
setupConversationHeader(view);
|
||||
setupUnknownSenderBanner(view);
|
||||
setupTrashComposeBar(view);
|
||||
setupMessageSelectionChrome(view);
|
||||
bindConversationEdgeFade(view);
|
||||
final LinearLayoutManager manager = new LinearLayoutManager(getActivity());
|
||||
@@ -673,7 +678,6 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
|
||||
final View copy = content.findViewById(R.id.home_message_menu_copy);
|
||||
final View star = content.findViewById(R.id.home_message_menu_star);
|
||||
final TextView starLabel = content.findViewById(R.id.home_message_menu_star_label);
|
||||
final View delete = content.findViewById(R.id.home_message_menu_delete);
|
||||
final View selectMore = content.findViewById(R.id.home_message_menu_select_more);
|
||||
final View details = content.findViewById(R.id.home_message_menu_details);
|
||||
|
||||
@@ -699,18 +703,16 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
|
||||
dismissMessageActionPopup();
|
||||
dismissMessageSelection();
|
||||
});
|
||||
delete.setOnClickListener(v -> {
|
||||
dismissMessageActionPopup();
|
||||
performSelectedMessageAction(R.id.home_message_action_delete);
|
||||
});
|
||||
selectMore.setOnClickListener(v -> {
|
||||
mClearSelectionOnPopupDismiss = false;
|
||||
dismissMessageActionPopup();
|
||||
selectMessage(messageView, mSelectedAttachment, false /* showPopup */);
|
||||
});
|
||||
details.setOnClickListener(v -> {
|
||||
mClearSelectionOnPopupDismiss = false;
|
||||
dismissMessageActionPopup();
|
||||
performSelectedMessageAction(R.id.home_message_action_details);
|
||||
UIIntents.get().launchHomeMessageDetailsActivity(getActivity(), data);
|
||||
dismissMessageSelection();
|
||||
});
|
||||
|
||||
content.setLayoutParams(new ViewGroup.LayoutParams(
|
||||
@@ -964,7 +966,7 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
|
||||
// On resume, check if there's a pending request for resuming message compose. This
|
||||
// may happen when the user commits the contact selection for a group conversation and
|
||||
// goes from compose back to the conversation fragment.
|
||||
if (mHost.shouldResumeComposeMessage()) {
|
||||
if (!isConversationTrashed() && mHost.shouldResumeComposeMessage()) {
|
||||
mComposeMessageView.resumeComposeMessage();
|
||||
}
|
||||
|
||||
@@ -974,10 +976,8 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
|
||||
if (mConversationHeader != null) {
|
||||
mConversationHeader.setVisibility(View.VISIBLE);
|
||||
}
|
||||
if (mComposeContainer != null) {
|
||||
mComposeContainer.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
updateTrashComposeLock();
|
||||
if (mHost != null) {
|
||||
mHost.invalidateActionBar();
|
||||
}
|
||||
@@ -1030,9 +1030,10 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
|
||||
menu.findItem(R.id.action_add_contact).setVisible(addContactActionVisible);
|
||||
|
||||
// See if we should show archive or unarchive.
|
||||
final boolean isTrashed = data.getIsTrashed();
|
||||
final boolean isArchived = data.getIsArchived();
|
||||
menu.findItem(R.id.action_archive).setVisible(!isArchived);
|
||||
menu.findItem(R.id.action_unarchive).setVisible(isArchived);
|
||||
menu.findItem(R.id.action_archive).setVisible(!isArchived && !isTrashed);
|
||||
menu.findItem(R.id.action_unarchive).setVisible(isArchived && !isTrashed);
|
||||
|
||||
// Call lives on the header pill; keep it out of the overflow.
|
||||
menu.findItem(R.id.action_call).setVisible(false);
|
||||
@@ -1069,18 +1070,7 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
|
||||
(new AddContactsConfirmationDialog(getActivity(), avatarUri, destination)).show();
|
||||
return true;
|
||||
} else if (itemId == R.id.action_delete) {
|
||||
if (isReadyForDeleteAction()) {
|
||||
new AlertDialog.Builder(getActivity(), R.style.AlertDialogTheme)
|
||||
.setTitle(getResources().getQuantityString(
|
||||
R.plurals.delete_conversations_confirmation_dialog_title, 1))
|
||||
.setPositiveButton(R.string.delete_conversation_confirmation_button,
|
||||
(dialog, button) -> deleteConversation())
|
||||
.setNegativeButton(R.string.delete_conversation_decline_button, null)
|
||||
.show();
|
||||
} else {
|
||||
warnOfMissingActionConditions(false /*sending*/,
|
||||
null /*commandToRunAfterActionConditionResolved*/);
|
||||
}
|
||||
confirmDeleteConversation();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
@@ -1196,6 +1186,7 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
|
||||
mAdapter.notifyDataSetChanged();
|
||||
updateHomeEmptyState();
|
||||
updateUnknownSenderBanner();
|
||||
updateTrashComposeLock();
|
||||
mHost.invalidateActionBar();
|
||||
}
|
||||
|
||||
@@ -1280,6 +1271,9 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
|
||||
|
||||
@Override
|
||||
public void sendMessage(final MessageData message) {
|
||||
if (isConversationTrashed()) {
|
||||
return;
|
||||
}
|
||||
if (isReadyForAction()) {
|
||||
if (ensureKnownRecipients()) {
|
||||
// Merge the caption text from attachments into the text body of the messages
|
||||
@@ -1381,6 +1375,9 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
|
||||
}
|
||||
|
||||
public void retrySend(final String messageId) {
|
||||
if (isConversationTrashed()) {
|
||||
return;
|
||||
}
|
||||
if (isReadyForAction()) {
|
||||
if (ensureKnownRecipients()) {
|
||||
showSendingStatus(messageId);
|
||||
@@ -1427,8 +1424,14 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
|
||||
|
||||
public void deleteConversation() {
|
||||
if (isReadyForDeleteAction()) {
|
||||
final Context context = getActivity();
|
||||
mBinding.getData().deleteConversation(mBinding);
|
||||
if (mBinding.getData().getIsTrashed()) {
|
||||
mBinding.getData().deleteConversation(mBinding);
|
||||
} else {
|
||||
mBinding.getData().moveConversationToTrash(mBinding);
|
||||
if (getActivity() != null) {
|
||||
HomeToast.show(getActivity(), R.string.home_trash_moved_toast_one);
|
||||
}
|
||||
}
|
||||
closeConversation(mConversationId);
|
||||
} else {
|
||||
warnOfMissingActionConditions(false /*sending*/,
|
||||
@@ -1458,6 +1461,7 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
|
||||
|
||||
mRecyclerView.setVisibility(View.VISIBLE);
|
||||
updateUnknownSenderBanner();
|
||||
updateTrashComposeLock();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1934,6 +1938,34 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
|
||||
.show();
|
||||
}
|
||||
|
||||
private void confirmReportConversationSpam() {
|
||||
if (getActivity() == null || !mBinding.isBound()) {
|
||||
return;
|
||||
}
|
||||
final ParticipantData other = mBinding.getData().getOtherParticipant();
|
||||
if (other == null) {
|
||||
HomeToast.show(getActivity(), R.string.home_report_spam_groups_only);
|
||||
return;
|
||||
}
|
||||
HomeModal.builder(getActivity())
|
||||
.icon(R.drawable.ic_home_menu_spam)
|
||||
.title(R.string.home_report_spam_title)
|
||||
.message(R.string.home_report_spam_message_one)
|
||||
.negative(android.R.string.cancel, null)
|
||||
.positive(R.string.home_unknown_sender_report_spam, () -> {
|
||||
if (!mBinding.isBound()) {
|
||||
return;
|
||||
}
|
||||
UpdateDestinationBlockedAction.updateDestinationBlocked(
|
||||
other.getNormalizedDestination(), true, mConversationId,
|
||||
BugleActionToasts.makeUpdateDestinationBlockedActionListener(
|
||||
getActivity()));
|
||||
HomeToast.show(getActivity(), R.string.home_report_spam_toast_one);
|
||||
closeConversation(mConversationId);
|
||||
})
|
||||
.show();
|
||||
}
|
||||
|
||||
private void updateUnknownSenderBanner() {
|
||||
if (mUnknownSenderBanner == null || !mBinding.isBound()) {
|
||||
return;
|
||||
@@ -1962,6 +1994,54 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
|
||||
HomeUiMotion.unknownSenderBannerEnter(mUnknownSenderBanner);
|
||||
}
|
||||
|
||||
private void setupTrashComposeBar(final View root) {
|
||||
mTrashBar = root.findViewById(R.id.home_conversation_trash_bar);
|
||||
final View restore = root.findViewById(R.id.home_conversation_trash_restore);
|
||||
if (restore != null) {
|
||||
restore.setOnClickListener(v -> restoreConversationFromTrash());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isConversationTrashed() {
|
||||
return mBinding.isBound() && mBinding.getData().getIsTrashed();
|
||||
}
|
||||
|
||||
private void restoreConversationFromTrash() {
|
||||
if (!isConversationTrashed()) {
|
||||
return;
|
||||
}
|
||||
mBinding.getData().restoreConversationFromTrash(mBinding);
|
||||
if (getActivity() != null) {
|
||||
HomeToast.show(getActivity(), R.string.home_trash_restored_toast);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateTrashComposeLock() {
|
||||
final boolean trashed = isConversationTrashed() && mSelectedMessage == null;
|
||||
if (mTrashBar != null) {
|
||||
mTrashBar.setVisibility(trashed ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
if (mComposeContainer != null) {
|
||||
mComposeContainer.setVisibility(trashed ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
if (trashed) {
|
||||
HomeComposeAttachPanel.dismissIfShowing(getActivity());
|
||||
HomeGallerySheet.dismissIfShowing(getActivity());
|
||||
if (mComposeMessageView != null) {
|
||||
mComposeMessageView.hideAllComposeInputs(false /* animate */);
|
||||
if (getActivity() != null) {
|
||||
ImeUtil.get().hideImeKeyboard(getActivity(), mComposeMessageView);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (getView() != null) {
|
||||
getView().post(() -> applyConversationEdgeFade(
|
||||
getView().findViewById(R.id.conversation_fade_top),
|
||||
getView().findViewById(R.id.conversation_fade_bottom),
|
||||
getResources().getDimensionPixelSize(R.dimen.home_conversation_edge_fade)));
|
||||
}
|
||||
}
|
||||
|
||||
private void hideUnknownSenderBanner(final boolean animate) {
|
||||
if (mUnknownSenderBanner == null) {
|
||||
return;
|
||||
@@ -2205,6 +2285,11 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
|
||||
getResources().getDimensionPixelSize(R.dimen.home_conversation_edge_fade)));
|
||||
}
|
||||
HomeUiMotion.swapBottomDock(mComposeContainer, mMessageActionBar, active, null);
|
||||
if (!active) {
|
||||
updateTrashComposeLock();
|
||||
} else if (mTrashBar != null) {
|
||||
mTrashBar.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void refreshMessageActionButtons() {
|
||||
@@ -2218,7 +2303,8 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
|
||||
setMessageActionVisible(mMessageActionShare, canForward);
|
||||
setMessageActionVisible(mMessageActionDetails, true);
|
||||
setMessageActionVisible(mMessageActionSave, mSelectedAttachment != null);
|
||||
setMessageActionVisible(mMessageActionSend, data.getShowResendMessage());
|
||||
setMessageActionVisible(mMessageActionSend,
|
||||
data.getShowResendMessage() && !isConversationTrashed());
|
||||
setMessageActionVisible(mMessageActionDownload, data.getShowDownloadMessage());
|
||||
setMessageActionVisible(mMessageActionDelete, true);
|
||||
}
|
||||
@@ -2261,9 +2347,7 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
|
||||
ClipData.newPlainText(null /* label */, data.getText()));
|
||||
dismissMessageSelection();
|
||||
} else if (actionId == R.id.home_message_action_details) {
|
||||
MessageDetailsDialog.show(
|
||||
getActivity(), data, mBinding.getData().getParticipants(),
|
||||
mBinding.getData().getSelfParticipantById(data.getSelfParticipantId()));
|
||||
UIIntents.get().launchHomeMessageDetailsActivity(getActivity(), data);
|
||||
dismissMessageSelection();
|
||||
} else if (actionId == R.id.home_message_action_share) {
|
||||
shareSelectedMessage(data);
|
||||
@@ -2349,20 +2433,37 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
|
||||
UIIntents.get().launchHomeConversationSearchActivity(getActivity(), mConversationId);
|
||||
});
|
||||
|
||||
final boolean trashed = data.getIsTrashed();
|
||||
final boolean archived = data.getIsArchived();
|
||||
final TextView archiveLabel = content.findViewById(R.id.popover_overflow_archive_label);
|
||||
archiveLabel.setText(archived
|
||||
? R.string.home_profile_option_unarchive
|
||||
: R.string.home_profile_option_archive);
|
||||
archiveLabel.setText(trashed
|
||||
? R.string.home_archive_restore
|
||||
: (archived
|
||||
? R.string.home_profile_option_unarchive
|
||||
: R.string.home_profile_option_archive));
|
||||
content.findViewById(R.id.popover_overflow_archive).setOnClickListener(v -> {
|
||||
dismissConversationOverflowPopover();
|
||||
if (archived) {
|
||||
if (trashed) {
|
||||
mBinding.getData().restoreConversationFromTrash(mBinding);
|
||||
if (getActivity() != null) {
|
||||
HomeToast.show(getActivity(), R.string.home_trash_restored_toast);
|
||||
}
|
||||
} else if (archived) {
|
||||
mBinding.getData().unarchiveConversation(mBinding);
|
||||
} else {
|
||||
mBinding.getData().archiveConversation(mBinding);
|
||||
closeConversation(mConversationId);
|
||||
}
|
||||
});
|
||||
final View spamRow = content.findViewById(R.id.popover_overflow_spam);
|
||||
if (spamRow != null) {
|
||||
final boolean canReportSpam = !trashed && participant != null;
|
||||
spamRow.setVisibility(canReportSpam ? View.VISIBLE : View.GONE);
|
||||
spamRow.setOnClickListener(v -> {
|
||||
dismissConversationOverflowPopover();
|
||||
confirmReportConversationSpam();
|
||||
});
|
||||
}
|
||||
content.findViewById(R.id.popover_overflow_delete).setOnClickListener(v -> {
|
||||
dismissConversationOverflowPopover();
|
||||
confirmDeleteConversation();
|
||||
@@ -2371,8 +2472,9 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
|
||||
content.measure(
|
||||
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
|
||||
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
|
||||
final int width = content.getMeasuredWidth();
|
||||
final PopupWindow popup = new PopupWindow(content,
|
||||
content.getMeasuredWidth(),
|
||||
width,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
true);
|
||||
popup.setOutsideTouchable(true);
|
||||
@@ -2396,7 +2498,7 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
|
||||
} else {
|
||||
windowRight = getResources().getDisplayMetrics().widthPixels;
|
||||
}
|
||||
final int desiredLeft = windowRight - endInset - content.getMeasuredWidth();
|
||||
final int desiredLeft = windowRight - endInset - width;
|
||||
final int xOff = desiredLeft - anchorLoc[0];
|
||||
popup.showAsDropDown(anchor, xOff, 8);
|
||||
HomeUiMotion.popoverEnter(content, true);
|
||||
@@ -2428,16 +2530,22 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
|
||||
}
|
||||
|
||||
private void confirmDeleteConversation() {
|
||||
if (getActivity() == null) {
|
||||
if (getActivity() == null || !mBinding.isBound()) {
|
||||
return;
|
||||
}
|
||||
if (isReadyForDeleteAction()) {
|
||||
new AlertDialog.Builder(getActivity(), R.style.Home_Dialog)
|
||||
.setTitle(getResources().getQuantityString(
|
||||
R.plurals.delete_conversations_confirmation_dialog_title, 1))
|
||||
.setPositiveButton(R.string.delete_conversation_confirmation_button,
|
||||
(dialog, button) -> deleteConversation())
|
||||
.setNegativeButton(R.string.delete_conversation_decline_button, null)
|
||||
final boolean trashed = mBinding.getData().getIsTrashed();
|
||||
HomeModal.builder(getActivity())
|
||||
.icon(R.drawable.ic_home_menu_trash)
|
||||
.title(trashed
|
||||
? getString(R.string.home_trash_permanent_title)
|
||||
: getResources().getQuantityString(
|
||||
R.plurals.delete_conversations_confirmation_dialog_title, 1))
|
||||
.message(trashed
|
||||
? getString(R.string.home_trash_permanent_message_one)
|
||||
: getString(R.string.home_delete_alert_message_one))
|
||||
.negative(R.string.delete_conversation_decline_button, null)
|
||||
.positive(R.string.home_delete_confirm, this::deleteConversation)
|
||||
.show();
|
||||
} else {
|
||||
warnOfMissingActionConditions(false /*sending*/,
|
||||
|
||||
+292
-50
@@ -25,6 +25,7 @@ import android.content.Intent;
|
||||
import android.content.res.Resources;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.ActionMode;
|
||||
import android.view.View;
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
@@ -35,6 +36,7 @@ import androidx.appcompat.app.ActionBar;
|
||||
import com.android.messaging.R;
|
||||
import com.android.messaging.datamodel.action.DeleteConversationAction;
|
||||
import com.android.messaging.datamodel.action.UpdateConversationArchiveStatusAction;
|
||||
import com.android.messaging.datamodel.action.UpdateConversationTrashStatusAction;
|
||||
import com.android.messaging.datamodel.action.UpdateDestinationBlockedAction;
|
||||
import com.android.messaging.datamodel.data.ConversationListData;
|
||||
import com.android.messaging.datamodel.data.ConversationListItemData;
|
||||
@@ -45,6 +47,8 @@ import com.android.messaging.datamodel.action.MarkAsUnreadAction;
|
||||
import com.android.messaging.ui.UIIntents;
|
||||
import com.android.messaging.ui.contact.AddContactsConfirmationDialog;
|
||||
import com.android.messaging.ui.home.HomeDeleteAlertDialog;
|
||||
import com.android.messaging.ui.home.HomeInboxMenu;
|
||||
import com.android.messaging.ui.home.HomeModal;
|
||||
import com.android.messaging.ui.home.HomeToast;
|
||||
import com.android.messaging.ui.conversationlist.ConversationListFragment.ConversationListFragmentHost;
|
||||
import com.android.messaging.ui.conversationlist.MultiSelectActionModeCallback.SelectedConversation;
|
||||
@@ -96,7 +100,123 @@ public abstract class AbstractConversationListActivity extends BugleActionBarAct
|
||||
return false;
|
||||
}
|
||||
|
||||
protected boolean isTrashList() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean shouldShowContextualActionBar(final ActionMode.Callback callback) {
|
||||
if (useHomeSelectionChrome() && callback instanceof MultiSelectActionModeCallback) {
|
||||
return !((MultiSelectActionModeCallback) callback).useHomeSelectionChrome();
|
||||
}
|
||||
return super.shouldShowContextualActionBar(callback);
|
||||
}
|
||||
|
||||
public void enterHomeEditMode() {
|
||||
if (!hasVisibleConversations()) {
|
||||
HomeToast.show(this, R.string.home_toast_nothing_to_select);
|
||||
return;
|
||||
}
|
||||
if (!isInConversationListSelectMode()) {
|
||||
startMultiSelectActionMode();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasVisibleConversations() {
|
||||
return mConversationListFragment != null
|
||||
&& mConversationListFragment.getVisibleConversationCount() > 0;
|
||||
}
|
||||
|
||||
public void onSubListSelectionMode(final boolean enabled) {
|
||||
onSubListSelectionCount(0);
|
||||
HomeInboxMenu.setSelecting(this, enabled);
|
||||
}
|
||||
|
||||
public void onSubListSelectionCount(final int count) {
|
||||
final int visible = mConversationListFragment == null
|
||||
? 0 : mConversationListFragment.getVisibleConversationCount();
|
||||
final boolean hasWork = count > 0 || visible > 0;
|
||||
if (isTrashList()) {
|
||||
HomeInboxMenu.setSelectAction(this,
|
||||
count > 0 ? R.drawable.ic_delete_small_dark : R.drawable.ic_home_menu_trash,
|
||||
count > 0 ? R.string.home_delete_confirm : R.string.home_empty_trash_action,
|
||||
hasWork, true);
|
||||
} else {
|
||||
HomeInboxMenu.setSelectAction(this, R.drawable.ic_home_menu_archive,
|
||||
count > 0 ? R.string.home_sub_unarchive : R.string.home_sub_unarchive_all,
|
||||
hasWork, false);
|
||||
}
|
||||
}
|
||||
|
||||
public void onSubListPrimaryAction() {
|
||||
if (isTrashList()) {
|
||||
emptyTrashOrDeleteSelected();
|
||||
} else {
|
||||
unarchiveAllOrSelected();
|
||||
}
|
||||
}
|
||||
|
||||
private void unarchiveAllOrSelected() {
|
||||
final Collection<SelectedConversation> selected = getSelectedConversationsIfAny();
|
||||
if (selected != null && !selected.isEmpty()) {
|
||||
restoreSelectedConversations();
|
||||
return;
|
||||
}
|
||||
if (mConversationListFragment == null) {
|
||||
return;
|
||||
}
|
||||
final List<ConversationListItemData> items =
|
||||
mConversationListFragment.snapshotVisibleConversations();
|
||||
if (items.isEmpty()) {
|
||||
HomeToast.show(this, R.string.home_toast_nothing_to_unarchive);
|
||||
return;
|
||||
}
|
||||
for (final ConversationListItemData item : items) {
|
||||
UpdateConversationArchiveStatusAction.unarchiveConversation(item.getConversationId());
|
||||
}
|
||||
if (items.size() == 1) {
|
||||
HomeToast.show(this, R.string.home_profile_unarchived_toast);
|
||||
} else {
|
||||
HomeToast.show(this, getString(R.string.home_sub_unarchived_toast_many, items.size()));
|
||||
}
|
||||
exitMultiSelectState();
|
||||
}
|
||||
|
||||
private void emptyTrashOrDeleteSelected() {
|
||||
final Collection<SelectedConversation> selected = getSelectedConversationsIfAny();
|
||||
if (selected != null && !selected.isEmpty()) {
|
||||
confirmPermanentDelete(selected);
|
||||
return;
|
||||
}
|
||||
if (mConversationListFragment == null) {
|
||||
return;
|
||||
}
|
||||
final List<ConversationListItemData> items =
|
||||
mConversationListFragment.snapshotVisibleConversations();
|
||||
if (items.isEmpty()) {
|
||||
HomeToast.show(this, R.string.home_toast_nothing_to_empty);
|
||||
return;
|
||||
}
|
||||
final ArrayList<SelectedConversation> all = new ArrayList<>(items.size());
|
||||
for (final ConversationListItemData item : items) {
|
||||
all.add(new SelectedConversation(item));
|
||||
}
|
||||
confirmEmptyAll(all);
|
||||
}
|
||||
|
||||
private Collection<SelectedConversation> getSelectedConversationsIfAny() {
|
||||
if (!isInConversationListSelectMode()) {
|
||||
return null;
|
||||
}
|
||||
final MultiSelectActionModeCallback callback =
|
||||
(MultiSelectActionModeCallback) getActionModeCallback();
|
||||
return callback.getSelectedConversations();
|
||||
}
|
||||
|
||||
protected void startMultiSelectActionMode() {
|
||||
if (mConversationListFragment == null) {
|
||||
return;
|
||||
}
|
||||
final boolean homeChrome = useHomeSelectionChrome();
|
||||
if (homeChrome) {
|
||||
hideHomeContextualActionBar();
|
||||
@@ -112,6 +232,9 @@ public abstract class AbstractConversationListActivity extends BugleActionBarAct
|
||||
|
||||
public void exitMultiSelectState() {
|
||||
dismissActionMode();
|
||||
if (mConversationListFragment == null) {
|
||||
return;
|
||||
}
|
||||
if (useHomeSelectionChrome()) {
|
||||
mConversationListFragment.setHomeSelectionMode(false);
|
||||
} else {
|
||||
@@ -138,6 +261,10 @@ public abstract class AbstractConversationListActivity extends BugleActionBarAct
|
||||
if (conversations.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (isTrashList()) {
|
||||
confirmPermanentDelete(conversations);
|
||||
return;
|
||||
}
|
||||
if (useHomeSelectionChrome()) {
|
||||
showHomeDeleteAlert(conversations);
|
||||
} else {
|
||||
@@ -149,8 +276,13 @@ public abstract class AbstractConversationListActivity extends BugleActionBarAct
|
||||
if (data == null) {
|
||||
return;
|
||||
}
|
||||
showHomeDeleteAlert(java.util.Collections.singletonList(
|
||||
new MultiSelectActionModeCallback.SelectedConversation(data)));
|
||||
final java.util.List<SelectedConversation> one = java.util.Collections.singletonList(
|
||||
new MultiSelectActionModeCallback.SelectedConversation(data));
|
||||
if (isTrashList()) {
|
||||
confirmPermanentDelete(one);
|
||||
return;
|
||||
}
|
||||
showHomeDeleteAlert(one);
|
||||
}
|
||||
|
||||
public void markSelectedConversationsUnread() {
|
||||
@@ -166,6 +298,109 @@ public abstract class AbstractConversationListActivity extends BugleActionBarAct
|
||||
exitMultiSelectState();
|
||||
}
|
||||
|
||||
public void restoreSelectedConversations() {
|
||||
if (!isInConversationListSelectMode()) {
|
||||
return;
|
||||
}
|
||||
final MultiSelectActionModeCallback callback =
|
||||
(MultiSelectActionModeCallback) getActionModeCallback();
|
||||
final Collection<SelectedConversation> conversations =
|
||||
callback.getSelectedConversations();
|
||||
if (conversations.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
for (final SelectedConversation conversation : conversations) {
|
||||
if (isTrashList()) {
|
||||
UpdateConversationTrashStatusAction.restoreFromTrash(conversation.conversationId);
|
||||
} else {
|
||||
UpdateConversationArchiveStatusAction.unarchiveConversation(
|
||||
conversation.conversationId);
|
||||
}
|
||||
}
|
||||
HomeToast.show(this, isTrashList()
|
||||
? R.string.home_trash_restored_toast
|
||||
: R.string.home_profile_unarchived_toast);
|
||||
exitMultiSelectState();
|
||||
}
|
||||
|
||||
public void archiveSelectedConversations() {
|
||||
if (!isInConversationListSelectMode()) {
|
||||
return;
|
||||
}
|
||||
final MultiSelectActionModeCallback callback =
|
||||
(MultiSelectActionModeCallback) getActionModeCallback();
|
||||
final Collection<SelectedConversation> conversations =
|
||||
callback.getSelectedConversations();
|
||||
if (conversations.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
int count = 0;
|
||||
for (final SelectedConversation conversation : conversations) {
|
||||
UpdateConversationArchiveStatusAction.archiveConversation(
|
||||
conversation.conversationId);
|
||||
count++;
|
||||
}
|
||||
if (count == 1) {
|
||||
HomeToast.show(this, R.string.home_profile_archived_toast);
|
||||
} else {
|
||||
HomeToast.show(this, getString(R.string.home_selection_archived_toast_many, count));
|
||||
}
|
||||
exitMultiSelectState();
|
||||
}
|
||||
|
||||
public void reportSelectedConversationsSpam() {
|
||||
if (!isInConversationListSelectMode()) {
|
||||
return;
|
||||
}
|
||||
final MultiSelectActionModeCallback callback =
|
||||
(MultiSelectActionModeCallback) getActionModeCallback();
|
||||
final Collection<SelectedConversation> conversations = new ArrayList<>(
|
||||
callback.getSelectedConversations());
|
||||
int reportableCount = 0;
|
||||
for (final SelectedConversation conversation : conversations) {
|
||||
if (!conversation.isGroup
|
||||
&& conversation.otherParticipantNormalizedDestination != null) {
|
||||
reportableCount++;
|
||||
}
|
||||
}
|
||||
if (reportableCount == 0) {
|
||||
HomeToast.show(this, R.string.home_report_spam_groups_only);
|
||||
return;
|
||||
}
|
||||
final int reportable = reportableCount;
|
||||
final String message = reportable == 1
|
||||
? getString(R.string.home_report_spam_message_one)
|
||||
: getString(R.string.home_report_spam_message_many, reportable);
|
||||
HomeModal.builder(this)
|
||||
.icon(R.drawable.ic_home_menu_spam)
|
||||
.title(R.string.home_report_spam_title)
|
||||
.message(message)
|
||||
.negative(android.R.string.cancel, null)
|
||||
.positive(R.string.home_unknown_sender_report_spam, () ->
|
||||
performReportSelectedSpam(conversations, reportable))
|
||||
.show();
|
||||
}
|
||||
|
||||
private void performReportSelectedSpam(
|
||||
final Collection<SelectedConversation> conversations, final int reportable) {
|
||||
for (final SelectedConversation conversation : conversations) {
|
||||
if (conversation.isGroup
|
||||
|| conversation.otherParticipantNormalizedDestination == null) {
|
||||
continue;
|
||||
}
|
||||
UpdateDestinationBlockedAction.updateDestinationBlocked(
|
||||
conversation.otherParticipantNormalizedDestination, true,
|
||||
conversation.conversationId,
|
||||
NOOP_BLOCK_LISTENER);
|
||||
}
|
||||
if (reportable == 1) {
|
||||
HomeToast.show(this, R.string.home_report_spam_toast_one);
|
||||
} else {
|
||||
HomeToast.show(this, getString(R.string.home_report_spam_toast_many, reportable));
|
||||
}
|
||||
exitMultiSelectState();
|
||||
}
|
||||
|
||||
protected boolean isInConversationListSelectMode() {
|
||||
return getActionModeCallback() instanceof MultiSelectActionModeCallback;
|
||||
}
|
||||
@@ -181,17 +416,7 @@ public abstract class AbstractConversationListActivity extends BugleActionBarAct
|
||||
return;
|
||||
}
|
||||
HomeDeleteAlertDialog.show(this, conversations.size(),
|
||||
new HomeDeleteAlertDialog.Listener() {
|
||||
@Override
|
||||
public void onDelete() {
|
||||
performDeleteConversations(conversations);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeleteAndRegisterSpam() {
|
||||
performDeleteAndRegisterSpam(conversations);
|
||||
}
|
||||
});
|
||||
() -> performDeleteConversations(conversations));
|
||||
}
|
||||
|
||||
private boolean ensureDefaultSmsAppForDelete() {
|
||||
@@ -217,50 +442,67 @@ public abstract class AbstractConversationListActivity extends BugleActionBarAct
|
||||
|
||||
private void performDeleteConversations(
|
||||
final Collection<SelectedConversation> conversations) {
|
||||
performDeleteConversations(conversations, false /* reportSpam */);
|
||||
}
|
||||
|
||||
private void performDeleteConversations(
|
||||
final Collection<SelectedConversation> conversations, final boolean reportSpam) {
|
||||
final int count = conversations.size();
|
||||
for (final SelectedConversation conversation : conversations) {
|
||||
DeleteConversationAction.deleteConversation(
|
||||
conversation.conversationId,
|
||||
conversation.timestamp);
|
||||
if (isTrashList()) {
|
||||
for (final SelectedConversation conversation : conversations) {
|
||||
DeleteConversationAction.deleteConversation(
|
||||
conversation.conversationId,
|
||||
conversation.timestamp);
|
||||
}
|
||||
showDeleteCompletedToast(count);
|
||||
} else {
|
||||
for (final SelectedConversation conversation : conversations) {
|
||||
UpdateConversationTrashStatusAction.moveToTrash(conversation.conversationId);
|
||||
}
|
||||
if (useHomeSelectionChrome() && count > 0) {
|
||||
HomeToast.show(this, count == 1
|
||||
? getString(R.string.home_trash_moved_toast_one)
|
||||
: getString(R.string.home_trash_moved_toast_many, count));
|
||||
}
|
||||
}
|
||||
if (isInConversationListSelectMode()) {
|
||||
exitMultiSelectState();
|
||||
}
|
||||
showDeleteCompletedToast(count, reportSpam);
|
||||
exitMultiSelectState();
|
||||
}
|
||||
|
||||
private void showDeleteCompletedToast(final int count, final boolean reportSpam) {
|
||||
private void confirmPermanentDelete(final Collection<SelectedConversation> conversations) {
|
||||
if (!ensureDefaultSmsAppForDelete()) {
|
||||
return;
|
||||
}
|
||||
final int count = conversations.size();
|
||||
HomeModal.builder(this)
|
||||
.icon(R.drawable.ic_home_menu_trash)
|
||||
.title(R.string.home_trash_permanent_title)
|
||||
.message(count == 1
|
||||
? getString(R.string.home_trash_permanent_message_one)
|
||||
: getString(R.string.home_trash_permanent_message_many, count))
|
||||
.negative(android.R.string.cancel, null)
|
||||
.positive(R.string.home_delete_confirm, () ->
|
||||
performDeleteConversations(conversations))
|
||||
.show();
|
||||
}
|
||||
|
||||
private void confirmEmptyAll(final Collection<SelectedConversation> conversations) {
|
||||
if (!ensureDefaultSmsAppForDelete()) {
|
||||
return;
|
||||
}
|
||||
HomeModal.builder(this)
|
||||
.icon(R.drawable.ic_home_menu_trash)
|
||||
.title(R.string.home_empty_all_title)
|
||||
.message(R.string.home_empty_all_message)
|
||||
.negative(android.R.string.cancel, null)
|
||||
.positive(R.string.home_empty_all_confirm, () ->
|
||||
performDeleteConversations(conversations))
|
||||
.show();
|
||||
}
|
||||
|
||||
private void showDeleteCompletedToast(final int count) {
|
||||
if (!useHomeSelectionChrome() || count <= 0) {
|
||||
return;
|
||||
}
|
||||
final String message;
|
||||
if (reportSpam) {
|
||||
message = count == 1
|
||||
? getString(R.string.home_delete_spam_toast_one)
|
||||
: getString(R.string.home_delete_spam_toast_many, count);
|
||||
} else {
|
||||
message = count == 1
|
||||
? getString(R.string.home_delete_toast_one)
|
||||
: getString(R.string.home_delete_toast_many, count);
|
||||
}
|
||||
HomeToast.show(this, message);
|
||||
}
|
||||
|
||||
private void performDeleteAndRegisterSpam(
|
||||
final Collection<SelectedConversation> conversations) {
|
||||
for (final SelectedConversation conversation : conversations) {
|
||||
if (!conversation.isGroup
|
||||
&& conversation.otherParticipantNormalizedDestination != null) {
|
||||
UpdateDestinationBlockedAction.updateDestinationBlocked(
|
||||
conversation.otherParticipantNormalizedDestination, true,
|
||||
conversation.conversationId,
|
||||
NOOP_BLOCK_LISTENER);
|
||||
}
|
||||
}
|
||||
performDeleteConversations(conversations, true /* registerSpam */);
|
||||
HomeToast.show(this, count == 1
|
||||
? getString(R.string.home_delete_toast_one)
|
||||
: getString(R.string.home_delete_toast_many, count));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+4
-2
@@ -22,6 +22,7 @@ import android.view.MenuItem;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
|
||||
import com.android.messaging.R;
|
||||
import com.android.messaging.ui.home.HomeInboxMenu;
|
||||
import com.android.messaging.ui.home.HomeSubHeader;
|
||||
|
||||
public class ArchivedConversationListActivity extends AbstractConversationListActivity {
|
||||
@@ -33,7 +34,8 @@ public class ArchivedConversationListActivity extends AbstractConversationListAc
|
||||
if (getSupportActionBar() != null) {
|
||||
getSupportActionBar().hide();
|
||||
}
|
||||
HomeSubHeader.bind(this, R.string.home_recently_deleted_title, v -> onActionBarHome());
|
||||
HomeSubHeader.bind(this, R.string.home_archived_title, v -> onActionBarHome());
|
||||
HomeInboxMenu.bind(this, HomeInboxMenu.Screen.ARCHIVE);
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
mConversationListFragment =
|
||||
@@ -87,7 +89,7 @@ public class ArchivedConversationListActivity extends AbstractConversationListAc
|
||||
|
||||
@Override
|
||||
public boolean isSwipeAnimatable() {
|
||||
return false;
|
||||
return !isInConversationListSelectMode();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
-15
@@ -18,7 +18,6 @@
|
||||
package com.android.messaging.ui.conversationlist;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.ActionMode;
|
||||
import android.window.SplashScreenView;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
@@ -52,14 +51,6 @@ public class ConversationListActivity extends AbstractConversationListActivity {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean shouldShowContextualActionBar(final ActionMode.Callback callback) {
|
||||
if (callback instanceof MultiSelectActionModeCallback) {
|
||||
return !((MultiSelectActionModeCallback) callback).useHomeSelectionChrome();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateActionBar(final ActionBar actionBar) {
|
||||
if (actionBar == null) {
|
||||
@@ -152,12 +143,6 @@ public class ConversationListActivity extends AbstractConversationListActivity {
|
||||
UIIntents.get().launchHomeAutomationActivity(this);
|
||||
}
|
||||
|
||||
public void enterHomeEditMode() {
|
||||
if (!isInConversationListSelectMode()) {
|
||||
startMultiSelectActionMode();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSwipeAnimatable() {
|
||||
return !isInConversationListSelectMode();
|
||||
|
||||
+57
@@ -34,6 +34,8 @@ 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.HomeInboxFilter;
|
||||
import com.android.messaging.ui.home.HomeSubListSearch;
|
||||
import com.android.messaging.ui.home.PinnedConversationStore;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -51,6 +53,8 @@ public class ConversationListAdapter
|
||||
public static final int FILTER_ALL = 0;
|
||||
public static final int FILTER_UNREAD = 1;
|
||||
public static final int FILTER_GROUPS = 2;
|
||||
public static final int FILTER_WORK = 3;
|
||||
public static final int FILTER_PERSONAL = 4;
|
||||
|
||||
private static final int VIEW_TYPE_PINNED_HEADER = 1;
|
||||
private static final int VIEW_TYPE_CONVERSATION = 2;
|
||||
@@ -61,6 +65,7 @@ public class ConversationListAdapter
|
||||
private final ConversationListItemData mFilterProbe = new ConversationListItemData();
|
||||
private int mFilterMode = FILTER_ALL;
|
||||
private String mCategoryId = HomeCategoryStore.ID_ALL;
|
||||
private String mSearchQuery = "";
|
||||
private int[] mDisplayPositions = new int[0];
|
||||
private View mPinnedStripRoot;
|
||||
private boolean mPinnedHeaderVisible;
|
||||
@@ -116,6 +121,24 @@ public class ConversationListAdapter
|
||||
return mFilterMode;
|
||||
}
|
||||
|
||||
public void setSearchQuery(final String query) {
|
||||
final String next = query == null ? "" : query.trim();
|
||||
if (next.equals(mSearchQuery)) {
|
||||
return;
|
||||
}
|
||||
mSearchQuery = next;
|
||||
rebuildFilterIndex();
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public String getSearchQuery() {
|
||||
return mSearchQuery;
|
||||
}
|
||||
|
||||
public boolean hasSearchQuery() {
|
||||
return !mSearchQuery.isEmpty();
|
||||
}
|
||||
|
||||
public void setCategoryId(final String categoryId) {
|
||||
final String next = categoryId == null ? HomeCategoryStore.ID_ALL : categoryId;
|
||||
if (next.equals(mCategoryId)) {
|
||||
@@ -156,6 +179,28 @@ public class ConversationListAdapter
|
||||
return items;
|
||||
}
|
||||
|
||||
/** Visible (filtered) conversation rows, not the raw cursor. */
|
||||
public List<ConversationListItemData> snapshotVisibleConversations() {
|
||||
final Cursor cursor = getCursor();
|
||||
if (cursor == null || mDisplayPositions.length == 0) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
final List<ConversationListItemData> items = new ArrayList<>(mDisplayPositions.length);
|
||||
final int saved = cursor.getPosition();
|
||||
for (final int position : mDisplayPositions) {
|
||||
if (!cursor.moveToPosition(position)) {
|
||||
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) {
|
||||
@@ -294,6 +339,18 @@ public class ConversationListAdapter
|
||||
if (mFilterMode == FILTER_GROUPS && mFilterProbe.getParticipantCount() <= 1) {
|
||||
return false;
|
||||
}
|
||||
if (mFilterMode == FILTER_WORK && !HomeInboxFilter.matchesWork(mContext, mFilterProbe)) {
|
||||
return false;
|
||||
}
|
||||
if (mFilterMode == FILTER_PERSONAL
|
||||
&& !HomeInboxFilter.matchesPersonal(mContext, mFilterProbe)) {
|
||||
return false;
|
||||
}
|
||||
if (!HomeSubListSearch.matches(mSearchQuery,
|
||||
mFilterProbe.getName(), mFilterProbe.getSnippetText(),
|
||||
mFilterProbe.getSnippetSenderName())) {
|
||||
return false;
|
||||
}
|
||||
return HomeCategoryClassifier.matches(mContext, mCategoryId, mFilterProbe);
|
||||
}
|
||||
|
||||
|
||||
+519
-55
@@ -28,6 +28,7 @@ import android.graphics.Rect;
|
||||
import android.graphics.RenderEffect;
|
||||
import android.graphics.Shader;
|
||||
import android.telephony.PhoneNumberUtils;
|
||||
import android.telephony.SubscriptionInfo;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
@@ -37,6 +38,7 @@ import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.Gravity;
|
||||
import android.view.HapticFeedbackConstants;
|
||||
import android.view.View;
|
||||
@@ -66,6 +68,10 @@ import com.android.messaging.datamodel.MessagingContentProvider;
|
||||
import com.android.messaging.datamodel.data.ConversationMessageData;
|
||||
import com.android.messaging.datamodel.action.MarkAsReadAction;
|
||||
import com.android.messaging.datamodel.action.MarkAsUnreadAction;
|
||||
import com.android.messaging.datamodel.action.UpdateConversationArchiveStatusAction;
|
||||
import com.android.messaging.datamodel.action.UpdateConversationTrashStatusAction;
|
||||
import com.android.messaging.datamodel.action.UpdateDestinationBlockedAction;
|
||||
import com.android.messaging.datamodel.action.UpdateMessageNotificationAction;
|
||||
import com.android.messaging.datamodel.binding.Binding;
|
||||
import com.android.messaging.datamodel.binding.BindingBase;
|
||||
import com.android.messaging.datamodel.data.ConversationListData;
|
||||
@@ -76,6 +82,7 @@ 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.HomeInboxFilter;
|
||||
import com.android.messaging.ui.home.HomeModal;
|
||||
import com.android.messaging.ui.home.HomeEmptyState;
|
||||
import com.android.messaging.ui.SnackBarInteraction;
|
||||
@@ -86,6 +93,7 @@ import com.android.messaging.util.ContactUtil;
|
||||
import com.android.messaging.util.Dates;
|
||||
import com.android.messaging.util.PhoneUtils;
|
||||
import com.android.messaging.ui.home.HomePinnedStrip;
|
||||
import com.android.messaging.ui.home.HomeSubListSearch;
|
||||
import com.android.messaging.ui.home.MutedConversationStore;
|
||||
import com.android.messaging.ui.home.HomeToast;
|
||||
import com.android.messaging.ui.home.HomeUiMotion;
|
||||
@@ -108,10 +116,12 @@ import java.util.Set;
|
||||
public class ConversationListFragment extends Fragment implements ConversationListDataListener,
|
||||
ConversationListItemView.HostInterface {
|
||||
private static final String BUNDLE_ARCHIVED_MODE = "archived_mode";
|
||||
private static final String BUNDLE_TRASH_MODE = "trash_mode";
|
||||
private static final String BUNDLE_FORWARD_MESSAGE_MODE = "forward_message_mode";
|
||||
|
||||
private MenuItem mShowBlockedMenuItem;
|
||||
private boolean mArchiveMode;
|
||||
private boolean mTrashMode;
|
||||
private boolean mBlockedAvailable;
|
||||
private boolean mForwardMessageMode;
|
||||
|
||||
@@ -134,7 +144,16 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
private View mHomeBottomNav;
|
||||
private View mHomeSelectionBar;
|
||||
private ImageButton mHomeSelectionDelete;
|
||||
private TextView mHomeReadAllButton;
|
||||
private View mHomeSelectionUnread;
|
||||
private TextView mHomeSelectionUnreadLabel;
|
||||
private View mHomeSelectionSpam;
|
||||
private View mHomeSelectionArchive;
|
||||
private View mHomeSelectionDock;
|
||||
private View mHomeSelectionTabs;
|
||||
private View mHomeSelectionIndicator;
|
||||
private int mHomeSelectionUnreadNaturalWidth;
|
||||
private int mHomeSelectionSpamNaturalWidth;
|
||||
private int mHomeSelectionArchiveNaturalWidth;
|
||||
private int mHomeSelectionCount;
|
||||
private View mHomeMessagesTop;
|
||||
private View mHomeCategoryCollapse;
|
||||
@@ -149,6 +168,7 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
private View mHomePinnedStrip;
|
||||
private TextView mHomeEditButton;
|
||||
private ImageButton mHomePinnedDoneButton;
|
||||
private View mHomeSelectionDone;
|
||||
private boolean mHomePinnedEditMode;
|
||||
private View mHomeNavDock;
|
||||
private View mHomeNavMessages;
|
||||
@@ -189,6 +209,7 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
"homeSearchBarExpanded";
|
||||
private static final String SAVED_INSTANCE_STATE_HOME_TAB = "homeTab";
|
||||
private static final String SAVED_INSTANCE_STATE_PINNED_EDIT = "homePinnedEdit";
|
||||
private static final String SAVED_INSTANCE_STATE_FILTER = "homeFilter";
|
||||
private static final int HOME_TAB_MESSAGES = 0;
|
||||
private static final int HOME_TAB_AUTOMATE = 1;
|
||||
private static final int SEARCH_NEWEST_EDGE_SLOP_DP = 48;
|
||||
@@ -236,10 +257,18 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
return createConversationListFragment(BUNDLE_ARCHIVED_MODE);
|
||||
}
|
||||
|
||||
public static ConversationListFragment createTrashConversationListFragment() {
|
||||
return createConversationListFragment(BUNDLE_TRASH_MODE);
|
||||
}
|
||||
|
||||
public static ConversationListFragment createForwardMessageConversationListFragment() {
|
||||
return createConversationListFragment(BUNDLE_FORWARD_MESSAGE_MODE);
|
||||
}
|
||||
|
||||
private boolean isSubList() {
|
||||
return mArchiveMode || mTrashMode || mForwardMessageMode;
|
||||
}
|
||||
|
||||
public static ConversationListFragment createConversationListFragment(String modeKeyName) {
|
||||
final ConversationListFragment fragment = new ConversationListFragment();
|
||||
if (modeKeyName != null) {
|
||||
@@ -258,6 +287,10 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
super.onCreate(bundle);
|
||||
mListBinding.getData().init(LoaderManager.getInstance(this), mListBinding);
|
||||
mAdapter = new ConversationListAdapter(getActivity(), null, this);
|
||||
if (bundle != null) {
|
||||
mAdapter.setFilterMode(bundle.getInt(SAVED_INSTANCE_STATE_FILTER,
|
||||
ConversationListAdapter.FILTER_ALL));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -275,8 +308,7 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
}
|
||||
|
||||
public void setScrolledToNewestConversationIfNeeded() {
|
||||
if (!mArchiveMode
|
||||
&& !mForwardMessageMode
|
||||
if (!isSubList()
|
||||
&& isScrolledToFirstConversation()
|
||||
&& mHost.hasWindowFocus()) {
|
||||
mListBinding.getData().setScrolledToNewestConversation(true);
|
||||
@@ -320,9 +352,7 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
}
|
||||
|
||||
private void updateListTopFade() {
|
||||
final boolean listActive = mHomeTab == HOME_TAB_MESSAGES
|
||||
&& !mArchiveMode
|
||||
&& !mForwardMessageMode;
|
||||
final boolean listActive = isSubList() || mHomeTab == HOME_TAB_MESSAGES;
|
||||
setFadeVisible(mListTopFade, listActive && !isListFlushAtTop());
|
||||
setFadeVisible(mListBottomFade, listActive && !isListFlushAtBottom());
|
||||
}
|
||||
@@ -397,7 +427,7 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
mListBinding.getData().setScrolledToNewestConversation(false);
|
||||
}
|
||||
|
||||
if (!mArchiveMode && !mForwardMessageMode) {
|
||||
if (!isSubList()) {
|
||||
handleSearchBarByScrollOffset(dy);
|
||||
}
|
||||
updateListTopFade();
|
||||
@@ -424,12 +454,33 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
mHomeBottomNav = rootView.findViewById(R.id.home_bottom_nav);
|
||||
mHomeSelectionBar = rootView.findViewById(R.id.home_selection_bar);
|
||||
mHomeSelectionDelete = rootView.findViewById(R.id.home_selection_delete);
|
||||
mHomeReadAllButton = rootView.findViewById(R.id.home_read_all_button);
|
||||
mHomeSelectionUnread = rootView.findViewById(R.id.home_selection_unread);
|
||||
mHomeSelectionSpam = rootView.findViewById(R.id.home_selection_spam);
|
||||
mHomeSelectionArchive = rootView.findViewById(R.id.home_selection_archive);
|
||||
mHomeSelectionDock = rootView.findViewById(R.id.home_selection_actions);
|
||||
mHomeSelectionTabs = rootView.findViewById(R.id.home_selection_tabs);
|
||||
mHomeSelectionIndicator = rootView.findViewById(R.id.home_selection_indicator);
|
||||
if (mHomeSelectionUnread != null) {
|
||||
mHomeSelectionUnreadLabel = mHomeSelectionUnread.findViewById(R.id.home_nav_tab_label);
|
||||
bindHomeSelectionTab(mHomeSelectionUnread, R.drawable.ic_home_menu_unread,
|
||||
R.string.home_read_all, true);
|
||||
}
|
||||
if (mHomeSelectionSpam != null) {
|
||||
bindHomeSelectionTab(mHomeSelectionSpam, R.drawable.ic_home_menu_spam,
|
||||
R.string.home_selection_spam, false);
|
||||
}
|
||||
if (mHomeSelectionArchive != null) {
|
||||
bindHomeSelectionTab(mHomeSelectionArchive, R.drawable.ic_home_menu_archive,
|
||||
R.string.home_selection_archive, false);
|
||||
}
|
||||
applyHomeSelectionTabEnabled(mHomeSelectionSpam, false);
|
||||
applyHomeSelectionTabEnabled(mHomeSelectionArchive, false);
|
||||
bindHomeSelectionDockMetrics();
|
||||
|
||||
mHomeComposeContainer = rootView.findViewById(R.id.home_compose_container);
|
||||
mStartNewConversationButton = rootView.findViewById(R.id.home_nav_search);
|
||||
mHomeAutomationCreateButton = rootView.findViewById(R.id.home_nav_automation_create);
|
||||
if (mArchiveMode || mForwardMessageMode) {
|
||||
if (isSubList()) {
|
||||
rootView.setFitsSystemWindows(false);
|
||||
if (mHomeComposeContainer != null) {
|
||||
mHomeComposeContainer.setVisibility(View.GONE);
|
||||
@@ -496,6 +547,8 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
updateHomeEditButtonForPinnedMode();
|
||||
}
|
||||
|
||||
bindSubListSearch();
|
||||
|
||||
// The root view has a non-null background, which by default is deemed by the framework
|
||||
// to be a "transition group," where all child views are animated together during an
|
||||
// activity transition. However, we want each individual items in the recycler view to
|
||||
@@ -506,6 +559,45 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
return rootView;
|
||||
}
|
||||
|
||||
private void bindHomeSelectionDockMetrics() {
|
||||
if (mHomeSelectionUnread == null) {
|
||||
return;
|
||||
}
|
||||
mHomeSelectionUnread.addOnLayoutChangeListener((v, l, t, r, b, ol, ot, or, ob) ->
|
||||
applyHomeSelectionDockMetrics());
|
||||
mHomeSelectionUnread.post(this::applyHomeSelectionDockMetrics);
|
||||
}
|
||||
|
||||
private void applyHomeSelectionDockMetrics() {
|
||||
if (mHomeSelectionUnreadNaturalWidth <= 0 && mHomeSelectionUnread != null) {
|
||||
mHomeSelectionUnreadNaturalWidth = measureNavTabNaturalWidth(mHomeSelectionUnread);
|
||||
}
|
||||
if (mHomeSelectionSpamNaturalWidth <= 0 && mHomeSelectionSpam != null) {
|
||||
mHomeSelectionSpamNaturalWidth = measureNavTabNaturalWidth(mHomeSelectionSpam);
|
||||
}
|
||||
if (mHomeSelectionArchiveNaturalWidth <= 0 && mHomeSelectionArchive != null) {
|
||||
mHomeSelectionArchiveNaturalWidth = measureNavTabNaturalWidth(mHomeSelectionArchive);
|
||||
}
|
||||
applyHomeNavTabActiveSize(mHomeSelectionUnread, mHomeSelectionUnreadNaturalWidth);
|
||||
applyHomeNavTabActiveSize(mHomeSelectionSpam, mHomeSelectionSpamNaturalWidth);
|
||||
applyHomeNavTabActiveSize(mHomeSelectionArchive, mHomeSelectionArchiveNaturalWidth);
|
||||
slideHomeSelectionIndicator();
|
||||
if (mHomeSelectionDock != null) {
|
||||
mHomeSelectionDock.post(() -> alignDockToGuideline(mHomeSelectionDock));
|
||||
}
|
||||
}
|
||||
|
||||
private void slideHomeSelectionIndicator() {
|
||||
if (mHomeSelectionUnread == null || mHomeSelectionIndicator == null
|
||||
|| mHomeSelectionTabs == null || mHomeSelectionUnread.getWidth() <= 0) {
|
||||
return;
|
||||
}
|
||||
final float x = mHomeSelectionTabs.getLeft() + mHomeSelectionUnread.getLeft();
|
||||
final float y = mHomeSelectionTabs.getTop() + mHomeSelectionUnread.getTop();
|
||||
HomeUiMotion.slideNavIndicator(mHomeSelectionIndicator, x, y,
|
||||
mHomeSelectionUnread.getWidth(), mHomeSelectionUnread.getHeight(), false);
|
||||
}
|
||||
|
||||
private void bindHomeNavMetrics(final View gap) {
|
||||
mHomeNavTabGap = gap;
|
||||
if (mHomeNavMessages == null) {
|
||||
@@ -536,22 +628,26 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
applyHomeNavTabActiveSize(mHomeNavMessages, mHomeNavMessagesNaturalWidth);
|
||||
applyHomeNavTabActiveSize(mHomeNavAutomate, mHomeNavAutomateNaturalWidth);
|
||||
if (mHomeNavDock != null) {
|
||||
mHomeNavDock.post(this::alignHomeNavDockToGuideline);
|
||||
mHomeNavDock.post(() -> alignDockToGuideline(mHomeNavDock));
|
||||
}
|
||||
}
|
||||
|
||||
/** Slide only the Messages/Automation pill to the 20dp screen guideline; compose stays put. */
|
||||
private void alignHomeNavDockToGuideline() {
|
||||
if (mHomeNavDock == null || mRootView == null || mHomeNavDock.getWidth() <= 0) {
|
||||
alignDockToGuideline(mHomeNavDock);
|
||||
}
|
||||
|
||||
private void alignDockToGuideline(final View dock) {
|
||||
if (dock == null || mRootView == null || dock.getWidth() <= 0) {
|
||||
return;
|
||||
}
|
||||
final int guideline = getResources().getDimensionPixelSize(R.dimen.looply_screen_padding);
|
||||
mHomeNavDock.setTranslationX(0f);
|
||||
dock.setTranslationX(0f);
|
||||
final int[] dockLoc = new int[2];
|
||||
final int[] rootLoc = new int[2];
|
||||
mHomeNavDock.getLocationOnScreen(dockLoc);
|
||||
dock.getLocationOnScreen(dockLoc);
|
||||
mRootView.getLocationOnScreen(rootLoc);
|
||||
mHomeNavDock.setTranslationX(guideline - (dockLoc[0] - rootLoc[0]));
|
||||
dock.setTranslationX(guideline - (dockLoc[0] - rootLoc[0]));
|
||||
}
|
||||
|
||||
private void applyHomeNavTabActiveSize(final View tab, final int natural) {
|
||||
@@ -629,9 +725,28 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
|
||||
private void hideHomeChrome(final View rootView) {
|
||||
setGone(rootView, R.id.home_messages_top);
|
||||
setGone(rootView, R.id.home_category_collapse);
|
||||
setGone(rootView, R.id.home_search_bar_container);
|
||||
setGone(rootView, R.id.home_bottom_nav);
|
||||
setGone(rootView, R.id.home_selection_bar);
|
||||
setGone(rootView, R.id.home_automation_pane);
|
||||
setGone(rootView, R.id.home_compose_container);
|
||||
if (rootView instanceof ViewGroup && ((ViewGroup) rootView).getChildCount() > 0) {
|
||||
((ViewGroup) rootView).getChildAt(0).setFitsSystemWindows(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void bindSubListSearch() {
|
||||
if (!isSubList() || getActivity() == null) {
|
||||
return;
|
||||
}
|
||||
HomeSubListSearch.bind(requireActivity(), mRecyclerView, query -> {
|
||||
if (mAdapter == null) {
|
||||
return;
|
||||
}
|
||||
mAdapter.setSearchQuery(query);
|
||||
updateEmptyListUi(mAdapter.getConversationItemCount() == 0);
|
||||
});
|
||||
}
|
||||
|
||||
private static void setGone(final View root, final int id) {
|
||||
@@ -849,16 +964,34 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
navAutomate.setOnClickListener(v -> selectHomeTab(HOME_TAB_AUTOMATE));
|
||||
}
|
||||
|
||||
final View selectionDone = rootView.findViewById(R.id.home_selection_done);
|
||||
if (selectionDone != null) {
|
||||
selectionDone.setOnClickListener(v -> {
|
||||
mHomeSelectionDone = rootView.findViewById(R.id.home_selection_done);
|
||||
if (mHomeSelectionDone != null) {
|
||||
mHomeSelectionDone.setOnClickListener(v -> {
|
||||
if (getActivity() instanceof AbstractConversationListActivity) {
|
||||
((AbstractConversationListActivity) getActivity()).exitMultiSelectState();
|
||||
}
|
||||
});
|
||||
}
|
||||
if (mHomeReadAllButton != null) {
|
||||
mHomeReadAllButton.setOnClickListener(v -> onHomeReadAllOrUnreadClick());
|
||||
if (mHomeSelectionUnread != null) {
|
||||
mHomeSelectionUnread.setOnClickListener(v -> onHomeReadAllOrUnreadClick());
|
||||
}
|
||||
if (mHomeSelectionSpam != null) {
|
||||
mHomeSelectionSpam.setOnClickListener(v -> {
|
||||
if (mHomeSelectionCount > 0
|
||||
&& getActivity() instanceof AbstractConversationListActivity) {
|
||||
((AbstractConversationListActivity) getActivity())
|
||||
.reportSelectedConversationsSpam();
|
||||
}
|
||||
});
|
||||
}
|
||||
if (mHomeSelectionArchive != null) {
|
||||
mHomeSelectionArchive.setOnClickListener(v -> {
|
||||
if (mHomeSelectionCount > 0
|
||||
&& getActivity() instanceof AbstractConversationListActivity) {
|
||||
((AbstractConversationListActivity) getActivity())
|
||||
.archiveSelectedConversations();
|
||||
}
|
||||
});
|
||||
}
|
||||
if (mHomeSelectionDelete != null) {
|
||||
mHomeSelectionDelete.setOnClickListener(v -> {
|
||||
@@ -915,7 +1048,7 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
|
||||
private void showHomeListProfilePopup(final ConversationListItemData data,
|
||||
final View anchorView) {
|
||||
if (mHomeListProfilePopup == null || data == null || mArchiveMode || mForwardMessageMode
|
||||
if (mHomeListProfilePopup == null || data == null || isSubList()
|
||||
|| isSelectionMode()) {
|
||||
return;
|
||||
}
|
||||
@@ -1230,7 +1363,20 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
}
|
||||
|
||||
public void setHomeSelectionMode(final boolean enabled) {
|
||||
if (mArchiveMode || mForwardMessageMode || mRootView == null) {
|
||||
if (mRootView == null) {
|
||||
return;
|
||||
}
|
||||
if (isSubList()) {
|
||||
if (getActivity() instanceof AbstractConversationListActivity) {
|
||||
((AbstractConversationListActivity) getActivity()).onSubListSelectionMode(enabled);
|
||||
}
|
||||
mRootView.post(() -> {
|
||||
if (!isAdded()) {
|
||||
return;
|
||||
}
|
||||
applySubListSelectPadding(enabled);
|
||||
refreshListSelectionChrome();
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (enabled) {
|
||||
@@ -1244,9 +1390,20 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
HomeUiMotion.replaceHeaderInstant(
|
||||
enabled ? mHomeHeader : mHomeSelectionHeader,
|
||||
enabled ? mHomeSelectionHeader : mHomeHeader);
|
||||
if (mHomeSelectionDone != null) {
|
||||
if (enabled) {
|
||||
HomeUiMotion.swapOverlayChip(null, mHomeSelectionDone);
|
||||
} else {
|
||||
mHomeSelectionDone.setScaleX(1f);
|
||||
mHomeSelectionDone.setScaleY(1f);
|
||||
mHomeSelectionDone.setAlpha(1f);
|
||||
}
|
||||
}
|
||||
refreshListSelectionChrome();
|
||||
HomeUiMotion.swapBottomDock(mHomeBottomNav, mHomeSelectionBar, enabled, () -> {
|
||||
if (!enabled) {
|
||||
if (enabled) {
|
||||
applyHomeSelectionDockMetrics();
|
||||
} else {
|
||||
updateHomeMainNavVisibility(false);
|
||||
}
|
||||
});
|
||||
@@ -1261,7 +1418,7 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
}
|
||||
|
||||
private void selectHomeTab(final int tab) {
|
||||
if (mArchiveMode || mForwardMessageMode || mRootView == null) {
|
||||
if (isSubList() || mRootView == null) {
|
||||
return;
|
||||
}
|
||||
if (isSelectionMode() && tab != HOME_TAB_MESSAGES) {
|
||||
@@ -1283,8 +1440,8 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
} else {
|
||||
setCategoryStripShown(false, false);
|
||||
}
|
||||
final CharSequence title = getString(showMessages
|
||||
? R.string.home_title : R.string.home_automation_title);
|
||||
final CharSequence title = showMessages
|
||||
? homeMessagesTitle() : getString(R.string.home_automation_title);
|
||||
if (mHomeTitle != null) {
|
||||
if (animate) {
|
||||
HomeUiMotion.fadeSwapText(mHomeTitle, title);
|
||||
@@ -1343,7 +1500,7 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
* action mode alone
|
||||
*/
|
||||
private void updateHomeMainNavVisibility(final boolean homeSelectionChromeActive) {
|
||||
if (mHomeBottomNav == null || mArchiveMode || mForwardMessageMode) {
|
||||
if (mHomeBottomNav == null || isSubList()) {
|
||||
return;
|
||||
}
|
||||
final boolean hideForSelection = homeSelectionChromeActive || isSelectionMode();
|
||||
@@ -1360,7 +1517,7 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
}
|
||||
|
||||
private void updateHomeComposeButtonVisibility(final boolean animate) {
|
||||
if (mArchiveMode || mForwardMessageMode) {
|
||||
if (isSubList()) {
|
||||
return;
|
||||
}
|
||||
final boolean showDock = isResumed() && !isSelectionMode();
|
||||
@@ -1447,16 +1604,29 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
}
|
||||
|
||||
public void updateHomeSelectionBar(final int selectedCount) {
|
||||
if (isSubList()) {
|
||||
if (getActivity() instanceof AbstractConversationListActivity) {
|
||||
((AbstractConversationListActivity) getActivity())
|
||||
.onSubListSelectionCount(selectedCount);
|
||||
}
|
||||
return;
|
||||
}
|
||||
mHomeSelectionCount = selectedCount;
|
||||
if (mHomeReadAllButton != null) {
|
||||
mHomeReadAllButton.setText(selectedCount > 0
|
||||
if (mHomeSelectionUnreadLabel != null) {
|
||||
mHomeSelectionUnreadLabel.setText(selectedCount > 0
|
||||
? R.string.home_mark_unread
|
||||
: R.string.home_read_all);
|
||||
mHomeSelectionUnreadNaturalWidth = 0;
|
||||
if (mHomeSelectionUnread != null) {
|
||||
mHomeSelectionUnread.post(this::applyHomeSelectionDockMetrics);
|
||||
}
|
||||
}
|
||||
final boolean enabled = selectedCount > 0;
|
||||
applyHomeSelectionTabEnabled(mHomeSelectionSpam, enabled);
|
||||
applyHomeSelectionTabEnabled(mHomeSelectionArchive, enabled);
|
||||
if (mHomeSelectionDelete == null) {
|
||||
return;
|
||||
}
|
||||
final boolean enabled = selectedCount > 0;
|
||||
mHomeSelectionDelete.setEnabled(enabled);
|
||||
mHomeSelectionDelete.setAlpha(1f);
|
||||
mHomeSelectionDelete.setImageAlpha(enabled ? 255 : 102);
|
||||
@@ -1466,6 +1636,49 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
android.content.res.ColorStateList.valueOf(tintColor));
|
||||
}
|
||||
|
||||
private void bindHomeSelectionTab(final View tab, final int iconRes, final int labelRes,
|
||||
final boolean selected) {
|
||||
if (tab == null) {
|
||||
return;
|
||||
}
|
||||
final ImageView icon = tab.findViewById(R.id.home_nav_tab_icon);
|
||||
final TextView label = tab.findViewById(R.id.home_nav_tab_label);
|
||||
if (icon != null) {
|
||||
icon.setImageResource(iconRes);
|
||||
}
|
||||
if (label != null) {
|
||||
label.setText(labelRes);
|
||||
}
|
||||
applyHomeSelectionTabStyle(tab, selected);
|
||||
}
|
||||
|
||||
private void applyHomeSelectionTabStyle(final View tab, final boolean selected) {
|
||||
if (tab == null) {
|
||||
return;
|
||||
}
|
||||
tab.setBackgroundResource(android.R.color.transparent);
|
||||
final int color = ContextCompat.getColor(requireContext(),
|
||||
selected ? R.color.home_nav_active_content : R.color.home_nav_inactive);
|
||||
final ImageView icon = tab.findViewById(R.id.home_nav_tab_icon);
|
||||
final TextView label = tab.findViewById(R.id.home_nav_tab_label);
|
||||
if (icon != null) {
|
||||
icon.setImageTintList(android.content.res.ColorStateList.valueOf(color));
|
||||
}
|
||||
if (label != null) {
|
||||
label.setTextColor(color);
|
||||
label.setTypeface(null, selected ? android.graphics.Typeface.BOLD
|
||||
: android.graphics.Typeface.NORMAL);
|
||||
}
|
||||
}
|
||||
|
||||
private void applyHomeSelectionTabEnabled(final View tab, final boolean enabled) {
|
||||
if (tab == null) {
|
||||
return;
|
||||
}
|
||||
tab.setEnabled(enabled);
|
||||
tab.setAlpha(enabled ? 1f : 0.4f);
|
||||
}
|
||||
|
||||
private void onHomeReadAllOrUnreadClick() {
|
||||
if (mHomeSelectionCount > 0
|
||||
&& getActivity() instanceof AbstractConversationListActivity) {
|
||||
@@ -1475,6 +1688,30 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
markAllVisibleConversationsRead();
|
||||
}
|
||||
|
||||
private void applySubListSelectPadding(final boolean enabled) {
|
||||
if (mRecyclerView == null || !isSubList()) {
|
||||
return;
|
||||
}
|
||||
mRecyclerView.setPadding(
|
||||
mRecyclerView.getPaddingLeft(),
|
||||
mRecyclerView.getPaddingTop(),
|
||||
mRecyclerView.getPaddingRight(),
|
||||
getResources().getDimensionPixelSize(enabled
|
||||
? R.dimen.home_sub_select_list_padding
|
||||
: R.dimen.looply_space_lg));
|
||||
}
|
||||
|
||||
public List<ConversationListItemData> snapshotVisibleConversations() {
|
||||
if (mAdapter == null) {
|
||||
return java.util.Collections.emptyList();
|
||||
}
|
||||
return mAdapter.snapshotVisibleConversations();
|
||||
}
|
||||
|
||||
public int getVisibleConversationCount() {
|
||||
return mAdapter == null ? 0 : mAdapter.getConversationItemCount();
|
||||
}
|
||||
|
||||
private void markAllVisibleConversationsRead() {
|
||||
mAdapter.forEachVisibleConversationId(MarkAsReadAction::markAsRead);
|
||||
}
|
||||
@@ -1596,6 +1833,39 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
final boolean muted = MutedConversationStore.isMuted(requireContext(),
|
||||
data.getConversationId());
|
||||
final boolean read = data.getIsRead();
|
||||
final View restoreRow = content.findViewById(R.id.popover_conversation_restore);
|
||||
if (restoreRow != null) {
|
||||
restoreRow.setVisibility((mArchiveMode || mTrashMode) ? View.VISIBLE : View.GONE);
|
||||
restoreRow.setOnClickListener(v -> {
|
||||
dismissHomePopover();
|
||||
if (mTrashMode) {
|
||||
UpdateConversationTrashStatusAction.restoreFromTrash(data.getConversationId());
|
||||
HomeToast.show(requireContext(), R.string.home_trash_restored_toast);
|
||||
} else {
|
||||
UpdateConversationArchiveStatusAction.unarchiveConversation(
|
||||
data.getConversationId());
|
||||
HomeToast.show(requireContext(), R.string.home_profile_unarchived_toast);
|
||||
}
|
||||
});
|
||||
}
|
||||
final View pinRow = content.findViewById(R.id.popover_conversation_pin);
|
||||
if (pinRow != null) {
|
||||
pinRow.setVisibility(isSubList() ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
final View categoryRow = content.findViewById(R.id.popover_conversation_category);
|
||||
if (categoryRow != null) {
|
||||
categoryRow.setVisibility(isSubList() ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
final View archiveRow = content.findViewById(R.id.popover_conversation_archive);
|
||||
if (archiveRow != null) {
|
||||
archiveRow.setVisibility((mArchiveMode || mTrashMode) ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
final View spamRow = content.findViewById(R.id.popover_conversation_spam);
|
||||
if (spamRow != null) {
|
||||
final boolean canReportSpam = !mTrashMode && !data.getIsGroup()
|
||||
&& !TextUtils.isEmpty(data.getOtherParticipantNormalizedDestination());
|
||||
spamRow.setVisibility(canReportSpam ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
final TextView pinLabel = content.findViewById(R.id.popover_conversation_pin_label);
|
||||
if (pinLabel != null) {
|
||||
@@ -1621,6 +1891,10 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
? R.drawable.ic_home_bell
|
||||
: R.drawable.ic_home_bell_off);
|
||||
}
|
||||
final View muteRow = content.findViewById(R.id.popover_conversation_mute);
|
||||
if (muteRow != null) {
|
||||
muteRow.setVisibility(mTrashMode ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
|
||||
content.findViewById(R.id.popover_conversation_pin).setOnClickListener(v -> {
|
||||
dismissHomePopover();
|
||||
@@ -1650,6 +1924,14 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
dismissHomePopover();
|
||||
showAssignHomeCategory(data);
|
||||
});
|
||||
content.findViewById(R.id.popover_conversation_archive).setOnClickListener(v -> {
|
||||
dismissHomePopover();
|
||||
archiveConversationFromPopover(data);
|
||||
});
|
||||
content.findViewById(R.id.popover_conversation_spam).setOnClickListener(v -> {
|
||||
dismissHomePopover();
|
||||
confirmReportConversationSpam(data);
|
||||
});
|
||||
content.findViewById(R.id.popover_conversation_delete).setOnClickListener(v -> {
|
||||
dismissHomePopover();
|
||||
confirmDeleteConversation(data);
|
||||
@@ -1664,6 +1946,14 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
HomeToast.show(requireContext(), muted
|
||||
? R.string.home_conversation_muted_toast
|
||||
: R.string.home_conversation_unmuted_toast);
|
||||
if (mAdapter != null) {
|
||||
final int adapterPosition =
|
||||
mAdapter.getAdapterPositionForConversationId(data.getConversationId());
|
||||
if (adapterPosition >= 0) {
|
||||
mAdapter.notifyItemChanged(adapterPosition);
|
||||
}
|
||||
}
|
||||
UpdateMessageNotificationAction.updateMessageNotification();
|
||||
}
|
||||
|
||||
private void confirmDeleteConversation(final ConversationListItemData data) {
|
||||
@@ -1672,6 +1962,37 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
}
|
||||
}
|
||||
|
||||
private void archiveConversationFromPopover(final ConversationListItemData data) {
|
||||
if (data.getIsArchived()) {
|
||||
UpdateConversationArchiveStatusAction.unarchiveConversation(data.getConversationId());
|
||||
HomeToast.show(requireContext(), R.string.home_profile_unarchived_toast);
|
||||
return;
|
||||
}
|
||||
UpdateConversationArchiveStatusAction.archiveConversation(data.getConversationId());
|
||||
HomeToast.show(requireContext(), R.string.home_profile_archived_toast);
|
||||
}
|
||||
|
||||
private void confirmReportConversationSpam(final ConversationListItemData data) {
|
||||
if (data.getIsGroup()
|
||||
|| TextUtils.isEmpty(data.getOtherParticipantNormalizedDestination())) {
|
||||
HomeToast.show(requireContext(), R.string.home_report_spam_groups_only);
|
||||
return;
|
||||
}
|
||||
HomeModal.builder(requireContext())
|
||||
.icon(R.drawable.ic_home_menu_spam)
|
||||
.title(R.string.home_report_spam_title)
|
||||
.message(R.string.home_report_spam_message_one)
|
||||
.negative(android.R.string.cancel, null)
|
||||
.positive(R.string.home_unknown_sender_report_spam, () -> {
|
||||
UpdateDestinationBlockedAction.updateDestinationBlocked(
|
||||
data.getOtherParticipantNormalizedDestination(), true,
|
||||
data.getConversationId(),
|
||||
(action, success, block, destination) -> { });
|
||||
HomeToast.show(requireContext(), R.string.home_report_spam_toast_one);
|
||||
})
|
||||
.show();
|
||||
}
|
||||
|
||||
private static final int PREVIEW_MESSAGE_LIMIT = 3;
|
||||
|
||||
private static final class PreviewMessage {
|
||||
@@ -1813,6 +2134,16 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
}
|
||||
host.addView(content, new FrameLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
content.setOnTouchListener((v, event) -> {
|
||||
if (event.getActionMasked() != MotionEvent.ACTION_DOWN) {
|
||||
return false;
|
||||
}
|
||||
if (isPeekTouchOnCard(content, event)) {
|
||||
return false;
|
||||
}
|
||||
dismissHomePopover();
|
||||
return true;
|
||||
});
|
||||
final PopupWindow popup = new PopupWindow(host,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
@@ -1918,16 +2249,38 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
content.setY(y);
|
||||
}
|
||||
|
||||
private static boolean isPeekTouchOnCard(final View content, final MotionEvent event) {
|
||||
final View preview = content.findViewById(R.id.popover_preview_card);
|
||||
final View menu = content.findViewById(R.id.popover_menu_card);
|
||||
if (preview != null || menu != null) {
|
||||
return isEventInside(preview, event) || isEventInside(menu, event);
|
||||
}
|
||||
return isEventInside(content, event);
|
||||
}
|
||||
|
||||
private static boolean isEventInside(final View view, final MotionEvent event) {
|
||||
if (view == null || view.getVisibility() != View.VISIBLE) {
|
||||
return false;
|
||||
}
|
||||
final int[] loc = new int[2];
|
||||
view.getLocationOnScreen(loc);
|
||||
final float x = event.getRawX();
|
||||
final float y = event.getRawY();
|
||||
return x >= loc[0] && x < loc[0] + view.getWidth()
|
||||
&& y >= loc[1] && y < loc[1] + view.getHeight();
|
||||
}
|
||||
|
||||
private PopupWindow showHomePopover(final View anchor, final int layoutResId,
|
||||
final boolean alignEnd) {
|
||||
dismissHomePopover();
|
||||
final View content = LayoutInflater.from(requireContext())
|
||||
.inflate(layoutResId, null, false);
|
||||
final int width = getResources().getDimensionPixelSize(R.dimen.home_popover_width);
|
||||
content.measure(
|
||||
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
|
||||
View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
|
||||
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
|
||||
final PopupWindow popup = new PopupWindow(content,
|
||||
content.getMeasuredWidth(),
|
||||
width,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
true);
|
||||
popup.setOutsideTouchable(true);
|
||||
@@ -1954,9 +2307,9 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
windowRight = getResources().getDisplayMetrics().widthPixels;
|
||||
}
|
||||
int xOff = alignEnd
|
||||
? windowRight - endInset - content.getMeasuredWidth() - anchorLoc[0]
|
||||
? windowRight - endInset - width - anchorLoc[0]
|
||||
: 0;
|
||||
final int overflow = (anchorLoc[0] + xOff + content.getMeasuredWidth())
|
||||
final int overflow = (anchorLoc[0] + xOff + width)
|
||||
- (windowRight - endInset);
|
||||
if (overflow > 0) {
|
||||
xOff -= overflow;
|
||||
@@ -1970,6 +2323,8 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
private void showHomeFilterPopover(final View anchor) {
|
||||
final PopupWindow popup = showHomePopover(anchor, R.layout.home_popover_filter, true);
|
||||
final View content = popup.getContentView();
|
||||
bindHomeFilterPopoverSims(content);
|
||||
bindHomeFilterPopoverChecks(content);
|
||||
content.findViewById(R.id.popover_messages).setOnClickListener(v -> {
|
||||
selectHomeChip(ConversationListAdapter.FILTER_ALL);
|
||||
dismissHomePopover();
|
||||
@@ -1978,21 +2333,26 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
dismissHomePopover();
|
||||
launchHomeSubScreen(() -> UIIntents.get().launchHomeSpamActivity(requireActivity()));
|
||||
});
|
||||
content.findViewById(R.id.popover_recently_deleted).setOnClickListener(v -> {
|
||||
content.findViewById(R.id.popover_archive).setOnClickListener(v -> {
|
||||
dismissHomePopover();
|
||||
launchHomeSubScreen(() -> UIIntents.get().launchArchivedConversationsActivity(
|
||||
requireActivity()));
|
||||
});
|
||||
content.findViewById(R.id.popover_recently_deleted).setOnClickListener(v -> {
|
||||
dismissHomePopover();
|
||||
launchHomeSubScreen(() -> UIIntents.get().launchRecentlyDeletedConversationsActivity(
|
||||
requireActivity()));
|
||||
});
|
||||
content.findViewById(R.id.popover_unread).setOnClickListener(v -> {
|
||||
selectHomeChip(ConversationListAdapter.FILTER_UNREAD);
|
||||
dismissHomePopover();
|
||||
});
|
||||
content.findViewById(R.id.popover_work).setOnClickListener(v -> {
|
||||
selectHomeChip(ConversationListAdapter.FILTER_WORK);
|
||||
dismissHomePopover();
|
||||
HomeToast.show(requireContext(), R.string.home_popover_coming_soon);
|
||||
});
|
||||
content.findViewById(R.id.popover_personal).setOnClickListener(v -> {
|
||||
selectHomeChip(ConversationListAdapter.FILTER_ALL);
|
||||
selectHomeChip(ConversationListAdapter.FILTER_PERSONAL);
|
||||
dismissHomePopover();
|
||||
});
|
||||
content.findViewById(R.id.popover_share_app).setOnClickListener(v -> {
|
||||
@@ -2009,6 +2369,54 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
});
|
||||
}
|
||||
|
||||
private void bindHomeFilterPopoverSims(final View content) {
|
||||
final Context context = content.getContext();
|
||||
final SubscriptionInfo work = HomeInboxFilter.workSubscription(context);
|
||||
final SubscriptionInfo personal = HomeInboxFilter.personalSubscription(context);
|
||||
final boolean distinctWork = work != null && personal != null
|
||||
&& work.getSubscriptionId() != personal.getSubscriptionId();
|
||||
final View workRow = content.findViewById(R.id.popover_work);
|
||||
if (workRow != null) {
|
||||
workRow.setVisibility(distinctWork ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
setHomeFilterText(content, R.id.popover_work_badge, HomeInboxFilter.workSimName(context));
|
||||
setHomeFilterText(content, R.id.popover_work_label, HomeInboxFilter.workSimName(context));
|
||||
setHomeFilterText(content, R.id.popover_personal_badge,
|
||||
HomeInboxFilter.personalSimName(context));
|
||||
setHomeFilterText(content, R.id.popover_personal_label,
|
||||
HomeInboxFilter.personalSimName(context));
|
||||
}
|
||||
|
||||
private static void setHomeFilterText(final View content, final int id, final String text) {
|
||||
final View view = content.findViewById(id);
|
||||
if (view instanceof TextView) {
|
||||
((TextView) view).setText(text);
|
||||
}
|
||||
}
|
||||
|
||||
private void bindHomeFilterPopoverChecks(final View content) {
|
||||
final int mode = mAdapter == null
|
||||
? ConversationListAdapter.FILTER_ALL : mAdapter.getFilterMode();
|
||||
setHomeFilterCheck(content, R.id.popover_messages_check,
|
||||
mode == ConversationListAdapter.FILTER_ALL);
|
||||
setHomeFilterCheck(content, R.id.popover_unread_check,
|
||||
mode == ConversationListAdapter.FILTER_UNREAD);
|
||||
setHomeFilterCheck(content, R.id.popover_work_check,
|
||||
mode == ConversationListAdapter.FILTER_WORK);
|
||||
setHomeFilterCheck(content, R.id.popover_personal_check,
|
||||
mode == ConversationListAdapter.FILTER_PERSONAL);
|
||||
setHomeFilterCheck(content, R.id.popover_spam_check, false);
|
||||
setHomeFilterCheck(content, R.id.popover_archive_check, false);
|
||||
setHomeFilterCheck(content, R.id.popover_recently_deleted_check, false);
|
||||
}
|
||||
|
||||
private static void setHomeFilterCheck(final View content, final int id, final boolean on) {
|
||||
final View check = content.findViewById(id);
|
||||
if (check != null) {
|
||||
check.setVisibility(on ? View.VISIBLE : View.INVISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
private void shareThisApp() {
|
||||
final Context context = getContext();
|
||||
if (context == null) {
|
||||
@@ -2023,12 +2431,16 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
}
|
||||
|
||||
private void showHomeEditPopover(final View anchor) {
|
||||
if (getVisibleConversationCount() == 0) {
|
||||
HomeToast.show(requireContext(), R.string.home_toast_nothing_to_edit);
|
||||
return;
|
||||
}
|
||||
final PopupWindow popup = showHomePopover(anchor, R.layout.home_popover_edit, true);
|
||||
final View content = popup.getContentView();
|
||||
content.findViewById(R.id.popover_select_messages).setOnClickListener(v -> {
|
||||
dismissHomePopoverImmediate();
|
||||
if (getActivity() instanceof ConversationListActivity) {
|
||||
((ConversationListActivity) getActivity()).enterHomeEditMode();
|
||||
if (getActivity() instanceof AbstractConversationListActivity) {
|
||||
((AbstractConversationListActivity) getActivity()).enterHomeEditMode();
|
||||
}
|
||||
});
|
||||
content.findViewById(R.id.popover_edit_pinned).setOnClickListener(v -> {
|
||||
@@ -2037,13 +2449,42 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
}
|
||||
|
||||
private void selectHomeChip(final int filterMode) {
|
||||
mAdapter.setFilterMode(filterMode);
|
||||
if (mHomeTab != HOME_TAB_MESSAGES) {
|
||||
selectHomeTab(HOME_TAB_MESSAGES);
|
||||
}
|
||||
if (mAdapter != null) {
|
||||
mAdapter.setFilterMode(filterMode);
|
||||
}
|
||||
updateHomeFilterTitle();
|
||||
bindHomeCategoryStrip();
|
||||
updateEmptyListUi(mAdapter.getConversationItemCount() == 0);
|
||||
updateEmptyListUi(mAdapter != null && mAdapter.getConversationItemCount() == 0);
|
||||
}
|
||||
|
||||
private CharSequence homeMessagesTitle() {
|
||||
if (mAdapter == null) {
|
||||
return getString(R.string.home_title);
|
||||
}
|
||||
switch (mAdapter.getFilterMode()) {
|
||||
case ConversationListAdapter.FILTER_UNREAD:
|
||||
return getString(R.string.home_popover_unread);
|
||||
case ConversationListAdapter.FILTER_WORK:
|
||||
return HomeInboxFilter.workSimName(requireContext());
|
||||
case ConversationListAdapter.FILTER_PERSONAL:
|
||||
return HomeInboxFilter.personalSimName(requireContext());
|
||||
default:
|
||||
return getString(R.string.home_title);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateHomeFilterTitle() {
|
||||
if (mHomeTitle == null || mHomeTab != HOME_TAB_MESSAGES || isSubList()) {
|
||||
return;
|
||||
}
|
||||
mHomeTitle.setText(homeMessagesTitle());
|
||||
}
|
||||
|
||||
private void bindHomeCategoryStrip() {
|
||||
if (mHomeCategoryRow == null || mArchiveMode || mForwardMessageMode) {
|
||||
if (mHomeCategoryRow == null || isSubList()) {
|
||||
return;
|
||||
}
|
||||
final Context context = getContext();
|
||||
@@ -2259,9 +2700,11 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
final Bundle arguments = getArguments();
|
||||
if (arguments != null) {
|
||||
mArchiveMode = arguments.getBoolean(BUNDLE_ARCHIVED_MODE, false);
|
||||
mTrashMode = arguments.getBoolean(BUNDLE_TRASH_MODE, false);
|
||||
mForwardMessageMode = arguments.getBoolean(BUNDLE_FORWARD_MESSAGE_MODE, false);
|
||||
}
|
||||
mListBinding.bind(DataModel.get().createConversationListData(context, this, mArchiveMode));
|
||||
mListBinding.bind(DataModel.get().createConversationListData(
|
||||
context, this, mArchiveMode, mTrashMode));
|
||||
}
|
||||
|
||||
|
||||
@@ -2274,6 +2717,9 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
outState.putBoolean(SAVED_INSTANCE_STATE_SEARCH_BAR_EXPANDED, mSearchBarShown);
|
||||
outState.putInt(SAVED_INSTANCE_STATE_HOME_TAB, mHomeTab);
|
||||
outState.putBoolean(SAVED_INSTANCE_STATE_PINNED_EDIT, mHomePinnedEditMode);
|
||||
if (mAdapter != null) {
|
||||
outState.putInt(SAVED_INSTANCE_STATE_FILTER, mAdapter.getFilterMode());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -2281,7 +2727,7 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
super.onPause();
|
||||
dismissHomeListProfilePopup(false);
|
||||
dismissHomePopover();
|
||||
if (mHomeBottomNav != null && !mArchiveMode && !mForwardMessageMode) {
|
||||
if (mHomeBottomNav != null && !isSubList()) {
|
||||
mHomeBottomNav.setVisibility(View.GONE);
|
||||
}
|
||||
mListState = mRecyclerView.getLayoutManager().onSaveInstanceState();
|
||||
@@ -2392,7 +2838,7 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
|
||||
@Override
|
||||
public boolean shouldShowConversationPinButton() {
|
||||
return mHomePinnedEditMode && !mArchiveMode && !mForwardMessageMode
|
||||
return mHomePinnedEditMode && !isSubList()
|
||||
&& mHomeTab == HOME_TAB_MESSAGES;
|
||||
}
|
||||
|
||||
@@ -2434,10 +2880,30 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
mEmptyListMessageView.setContent(R.drawable.ic_home_nav_messages,
|
||||
R.string.home_empty_messages_sync_title,
|
||||
R.string.home_empty_messages_sync_body);
|
||||
} else if (mAdapter.hasSearchQuery()) {
|
||||
mEmptyListMessageView.setContent(R.drawable.ic_home_search_line,
|
||||
getString(R.string.home_empty_sub_search_title),
|
||||
getString(R.string.home_empty_sub_search_body, mAdapter.getSearchQuery()));
|
||||
} else if (mArchiveMode) {
|
||||
mEmptyListMessageView.setContent(R.drawable.ic_home_menu_archive,
|
||||
R.string.home_empty_archive_title,
|
||||
R.string.home_empty_archive_body);
|
||||
} else if (mTrashMode) {
|
||||
mEmptyListMessageView.setContent(R.drawable.ic_home_menu_trash,
|
||||
R.string.home_empty_trash_title,
|
||||
R.string.home_empty_trash_body);
|
||||
} else if (mAdapter.getFilterMode() == ConversationListAdapter.FILTER_UNREAD) {
|
||||
mEmptyListMessageView.setContent(R.drawable.ic_home_menu_unread,
|
||||
R.string.home_empty_unread_title,
|
||||
R.string.home_empty_unread_body);
|
||||
} else if (mAdapter.getFilterMode() == ConversationListAdapter.FILTER_WORK) {
|
||||
mEmptyListMessageView.setContent(R.drawable.ic_home_nav_messages,
|
||||
R.string.home_empty_work_title,
|
||||
R.string.home_empty_work_body);
|
||||
} else if (mAdapter.getFilterMode() == ConversationListAdapter.FILTER_PERSONAL) {
|
||||
mEmptyListMessageView.setContent(R.drawable.ic_home_nav_messages,
|
||||
R.string.home_empty_personal_title,
|
||||
R.string.home_empty_personal_body);
|
||||
} else if (!HomeCategoryStore.ID_ALL.equals(mAdapter.getCategoryId())
|
||||
|| mAdapter.getFilterMode() != ConversationListAdapter.FILTER_ALL) {
|
||||
mEmptyListMessageView.setContent(R.drawable.ic_home_nav_messages,
|
||||
@@ -2448,7 +2914,7 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
R.string.home_empty_messages_title,
|
||||
R.string.home_empty_messages_body);
|
||||
}
|
||||
mEmptyListMessageView.setReserveDock(!mArchiveMode && !mForwardMessageMode);
|
||||
mEmptyListMessageView.setReserveDock(!isSubList());
|
||||
mEmptyListMessageView.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
mEmptyListMessageView.setVisibility(View.GONE);
|
||||
@@ -2527,7 +2993,7 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
}
|
||||
|
||||
private void setPinnedEditMode(final boolean enabled, final boolean refreshList) {
|
||||
if (mArchiveMode || mForwardMessageMode) {
|
||||
if (isSubList()) {
|
||||
return;
|
||||
}
|
||||
if (enabled) {
|
||||
@@ -2570,23 +3036,21 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
return;
|
||||
}
|
||||
if (mHomePinnedEditMode) {
|
||||
mHomeEditButton.setVisibility(View.GONE);
|
||||
HomeUiMotion.swapOverlayChip(mHomeEditButton, mHomePinnedDoneButton);
|
||||
if (mHomePinnedDoneButton != null) {
|
||||
mHomePinnedDoneButton.setVisibility(View.VISIBLE);
|
||||
mHomePinnedDoneButton.setOnClickListener(v -> setPinnedEditMode(false));
|
||||
}
|
||||
} else {
|
||||
mHomeEditButton.setVisibility(View.VISIBLE);
|
||||
mHomeEditButton.setText(R.string.home_edit);
|
||||
mHomeEditButton.setOnClickListener(this::showHomeEditPopover);
|
||||
if (mHomePinnedDoneButton != null) {
|
||||
mHomePinnedDoneButton.setVisibility(View.GONE);
|
||||
HomeUiMotion.swapOverlayChip(mHomePinnedDoneButton, mHomeEditButton);
|
||||
if (mHomeEditButton != null) {
|
||||
mHomeEditButton.setText(R.string.home_edit);
|
||||
mHomeEditButton.setOnClickListener(this::showHomeEditPopover);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void refreshPinnedStrip() {
|
||||
if (mHomePinnedStrip == null || mArchiveMode || mForwardMessageMode
|
||||
if (mHomePinnedStrip == null || isSubList()
|
||||
|| mHomeTab != HOME_TAB_MESSAGES) {
|
||||
mAdapter.setPinnedHeaderVisible(false);
|
||||
return;
|
||||
|
||||
+56
-2
@@ -53,6 +53,7 @@ import com.android.messaging.Factory;
|
||||
import com.android.messaging.R;
|
||||
import com.android.messaging.datamodel.MessagingContentProvider;
|
||||
import com.android.messaging.datamodel.action.UpdateConversationArchiveStatusAction;
|
||||
import com.android.messaging.datamodel.action.UpdateConversationTrashStatusAction;
|
||||
import com.android.messaging.datamodel.data.ConversationListItemData;
|
||||
import com.android.messaging.datamodel.data.ConversationMessageData;
|
||||
import com.android.messaging.datamodel.data.MessageData;
|
||||
@@ -63,6 +64,8 @@ import com.android.messaging.ui.AudioAttachmentView;
|
||||
import com.android.messaging.ui.ContactIconView;
|
||||
import com.android.messaging.ui.SnackBar;
|
||||
import com.android.messaging.ui.SnackBarInteraction;
|
||||
import com.android.messaging.ui.home.HomeToast;
|
||||
import com.android.messaging.ui.home.MutedConversationStore;
|
||||
import com.android.messaging.util.Assert;
|
||||
import com.android.messaging.util.ImageUtils;
|
||||
import com.android.messaging.util.PhoneUtils;
|
||||
@@ -155,6 +158,7 @@ public class ConversationListItemView extends FrameLayout implements OnClickList
|
||||
private ContactIconView mContactIconView;
|
||||
private ImageView mContactCheckmarkView;
|
||||
private ImageView mFailedStatusIconView;
|
||||
private ImageView mMuteBadgeView;
|
||||
private ImageView mCrossSwipeArchiveLeftImageView;
|
||||
private View mCrossSwipeRightActions;
|
||||
private ImageView mCrossSwipeMuteView;
|
||||
@@ -196,6 +200,7 @@ public class ConversationListItemView extends FrameLayout implements OnClickList
|
||||
mContactIconView = findViewById(R.id.conversation_icon);
|
||||
mContactCheckmarkView = findViewById(R.id.conversation_checkmark);
|
||||
mFailedStatusIconView = findViewById(R.id.conversation_failed_status_icon);
|
||||
mMuteBadgeView = findViewById(R.id.conversation_mute_badge);
|
||||
mCrossSwipeArchiveLeftImageView = findViewById(R.id.crossSwipeArchiveIconLeft);
|
||||
mCrossSwipeRightActions = findViewById(R.id.cross_swipe_right_actions);
|
||||
mCrossSwipeMuteView = findViewById(R.id.cross_swipe_mute);
|
||||
@@ -813,6 +818,15 @@ public class ConversationListItemView extends FrameLayout implements OnClickList
|
||||
|
||||
mContactCheckmarkView.setVisibility(checkmarkVisiblity);
|
||||
mFailedStatusIconView.setVisibility(failStatusVisiblity);
|
||||
final boolean muted = MutedConversationStore.isMuted(getContext(),
|
||||
mData.getConversationId());
|
||||
final boolean trashed = mData.getIsTrashed();
|
||||
if (mMuteBadgeView != null) {
|
||||
mMuteBadgeView.setVisibility(
|
||||
muted && !trashed && failStatusVisiblity != VISIBLE && !isSelected
|
||||
? VISIBLE : GONE);
|
||||
}
|
||||
updateSwipeMuteButton(muted, trashed);
|
||||
|
||||
updatePinButton(selectionMode, resources);
|
||||
updateListDividerInset(selectionMode);
|
||||
@@ -931,6 +945,31 @@ public class ConversationListItemView extends FrameLayout implements OnClickList
|
||||
&& screenY <= actionLoc[1] + mCrossSwipeRightActions.getHeight();
|
||||
}
|
||||
|
||||
private void updateSwipeMuteButton(final boolean muted, final boolean trashed) {
|
||||
if (mCrossSwipeMuteView == null) {
|
||||
return;
|
||||
}
|
||||
mCrossSwipeMuteView.setVisibility(trashed ? GONE : VISIBLE);
|
||||
if (mCrossSwipeDeleteView != null) {
|
||||
final ViewGroup.MarginLayoutParams lp =
|
||||
(ViewGroup.MarginLayoutParams) mCrossSwipeDeleteView.getLayoutParams();
|
||||
if (lp != null) {
|
||||
lp.setMarginStart(trashed ? 0
|
||||
: getResources().getDimensionPixelSize(R.dimen.looply_space_sm));
|
||||
mCrossSwipeDeleteView.setLayoutParams(lp);
|
||||
}
|
||||
}
|
||||
if (trashed) {
|
||||
return;
|
||||
}
|
||||
mCrossSwipeMuteView.setImageResource(muted
|
||||
? R.drawable.ic_home_bell
|
||||
: R.drawable.ic_home_bell_off);
|
||||
mCrossSwipeMuteView.setContentDescription(getResources().getString(muted
|
||||
? R.string.home_swipe_unmute
|
||||
: R.string.home_swipe_mute));
|
||||
}
|
||||
|
||||
public void setSwipeTranslationX(final float translationX) {
|
||||
mSwipeableContainer.setTranslationX(translationX);
|
||||
if (translationX == 0) {
|
||||
@@ -967,9 +1006,24 @@ public class ConversationListItemView extends FrameLayout implements OnClickList
|
||||
final String conversationId = mData.getConversationId();
|
||||
if (PrefsUtils.isSwipeRightToDeleteEnabled()
|
||||
&& swipeDirection == ConversationListSwipeHelper.SWIPE_DIRECTION_RIGHT) {
|
||||
final boolean alreadyTrashed = mData.getIsTrashed();
|
||||
mData.deleteConversation();
|
||||
UiUtils.showSnackBar(getContext(), getRootView(),
|
||||
getResources().getString(R.string.conversation_deleted));
|
||||
if (alreadyTrashed) {
|
||||
UiUtils.showSnackBar(getContext(), getRootView(),
|
||||
getResources().getString(R.string.conversation_deleted));
|
||||
} else {
|
||||
HomeToast.show(getContext(), R.string.home_trash_moved_toast_one);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (mData.getIsTrashed()) {
|
||||
UpdateConversationTrashStatusAction.restoreFromTrash(conversationId);
|
||||
HomeToast.show(getContext(), R.string.home_trash_restored_toast);
|
||||
return;
|
||||
}
|
||||
if (mData.getIsArchived()) {
|
||||
UpdateConversationArchiveStatusAction.unarchiveConversation(conversationId);
|
||||
HomeToast.show(getContext(), R.string.home_profile_unarchived_toast);
|
||||
return;
|
||||
}
|
||||
UpdateConversationArchiveStatusAction.archiveConversation(conversationId);
|
||||
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
* Copyright (C) 2024-2025 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.conversationlist;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
|
||||
import com.android.messaging.R;
|
||||
import com.android.messaging.ui.home.HomeInboxMenu;
|
||||
import com.android.messaging.ui.home.HomeSubHeader;
|
||||
|
||||
public class RecentlyDeletedConversationListActivity extends AbstractConversationListActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.home_archived_activity);
|
||||
if (getSupportActionBar() != null) {
|
||||
getSupportActionBar().hide();
|
||||
}
|
||||
HomeSubHeader.bind(this, R.string.home_recently_deleted_title, v -> onActionBarHome());
|
||||
HomeInboxMenu.bind(this, HomeInboxMenu.Screen.TRASH);
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
mConversationListFragment =
|
||||
ConversationListFragment.createTrashConversationListFragment();
|
||||
getSupportFragmentManager()
|
||||
.beginTransaction()
|
||||
.replace(R.id.home_archived_list_host, mConversationListFragment)
|
||||
.commit();
|
||||
} else {
|
||||
mConversationListFragment = (ConversationListFragment) getSupportFragmentManager()
|
||||
.findFragmentById(R.id.home_archived_list_host);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean useHomeSelectionChrome() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isTrashList() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateActionBar(final ActionBar actionBar) {
|
||||
if (actionBar == null) {
|
||||
return;
|
||||
}
|
||||
actionBar.hide();
|
||||
super.updateActionBar(actionBar);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (isInConversationListSelectMode()) {
|
||||
exitMultiSelectState();
|
||||
} else {
|
||||
super.onBackPressed();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(final MenuItem menuItem) {
|
||||
if (menuItem.getItemId() == android.R.id.home) {
|
||||
onActionBarHome();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(menuItem);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActionBarHome() {
|
||||
onBackPressed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSwipeAnimatable() {
|
||||
return !isInConversationListSelectMode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish() {
|
||||
super.finish();
|
||||
overridePendingTransition(R.anim.home_activity_hold, R.anim.home_activity_close);
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import android.text.Editable;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
import android.text.method.PasswordTransformationMethod;
|
||||
import android.view.Gravity;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@@ -13,6 +14,7 @@ import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.EditText;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
@@ -70,9 +72,15 @@ public class HomeAppLockActivity extends BugleActionBarActivity {
|
||||
if (isPinMode()) {
|
||||
inflater.inflate(R.layout.home_lock_pin_body, body, true);
|
||||
bindPinPad(body);
|
||||
if (mMode != HomeAppLock.MODE_UNLOCK) {
|
||||
relocateLockPrompt(body);
|
||||
}
|
||||
} else if (isPatternMode()) {
|
||||
inflater.inflate(R.layout.home_lock_pattern_body, body, true);
|
||||
bindPattern(body);
|
||||
if (mMode != HomeAppLock.MODE_UNLOCK) {
|
||||
relocateLockPrompt(body);
|
||||
}
|
||||
} else {
|
||||
inflater.inflate(R.layout.home_lock_password_body, body, true);
|
||||
bindPassword(body);
|
||||
@@ -108,6 +116,35 @@ public class HomeAppLockActivity extends BugleActionBarActivity {
|
||||
}
|
||||
}
|
||||
|
||||
private void relocateLockPrompt(final View host) {
|
||||
final ViewGroup prompt = host.findViewById(R.id.home_lock_prompt);
|
||||
if (prompt == null) {
|
||||
return;
|
||||
}
|
||||
if (mSubtitle != null) {
|
||||
mSubtitle.setGravity(Gravity.CENTER);
|
||||
mSubtitle.setTextAlignment(View.TEXT_ALIGNMENT_CENTER);
|
||||
final LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
lp.bottomMargin = getResources().getDimensionPixelSize(R.dimen.looply_space_sm);
|
||||
mSubtitle.setLayoutParams(lp);
|
||||
moveChild(prompt, mSubtitle, 0);
|
||||
}
|
||||
if (mError != null) {
|
||||
moveChild(prompt, mError, mSubtitle == null ? 0 : 1);
|
||||
}
|
||||
}
|
||||
|
||||
private static void moveChild(final ViewGroup parent, final View child, final int index) {
|
||||
if (parent == null || child == null) {
|
||||
return;
|
||||
}
|
||||
if (child.getParent() instanceof ViewGroup) {
|
||||
((ViewGroup) child.getParent()).removeView(child);
|
||||
}
|
||||
parent.addView(child, Math.min(index, parent.getChildCount()));
|
||||
}
|
||||
|
||||
private void bindPinPad(final View host) {
|
||||
mPinDots = host.findViewById(R.id.home_lock_pin_dots);
|
||||
final ViewGroup keypad = host.findViewById(R.id.home_lock_pin_keypad);
|
||||
|
||||
@@ -17,25 +17,16 @@
|
||||
package com.android.messaging.ui.home;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import com.android.messaging.R;
|
||||
|
||||
/** Centered iOS-style alert for delete / delete-and-register-spam from home selection mode. */
|
||||
/** Home modal for moving conversations to Trash from the conversation list. */
|
||||
public final class HomeDeleteAlertDialog {
|
||||
|
||||
public interface Listener {
|
||||
void onDelete();
|
||||
|
||||
void onDeleteAndRegisterSpam();
|
||||
}
|
||||
|
||||
private HomeDeleteAlertDialog() {
|
||||
@@ -43,51 +34,17 @@ public final class HomeDeleteAlertDialog {
|
||||
|
||||
public static void show(@NonNull final Context context, final int conversationCount,
|
||||
@NonNull final Listener listener) {
|
||||
final View content = LayoutInflater.from(context).inflate(
|
||||
R.layout.home_delete_alert_dialog, null);
|
||||
|
||||
final TextView message = content.findViewById(R.id.home_delete_alert_message);
|
||||
if (message != null) {
|
||||
if (conversationCount == 1) {
|
||||
message.setText(R.string.home_delete_alert_message_one);
|
||||
} else {
|
||||
message.setText(context.getString(
|
||||
R.string.home_delete_alert_message_many, conversationCount));
|
||||
}
|
||||
}
|
||||
|
||||
final AlertDialog dialog = new AlertDialog.Builder(context, R.style.Home_AlertDialog)
|
||||
.setView(content)
|
||||
.setCancelable(true)
|
||||
.create();
|
||||
|
||||
final View delete = content.findViewById(R.id.home_delete_alert_delete);
|
||||
if (delete != null) {
|
||||
delete.setOnClickListener(v -> {
|
||||
dialog.dismiss();
|
||||
listener.onDelete();
|
||||
});
|
||||
}
|
||||
|
||||
final View deleteSpam = content.findViewById(R.id.home_delete_alert_delete_spam);
|
||||
if (deleteSpam != null) {
|
||||
deleteSpam.setOnClickListener(v -> {
|
||||
dialog.dismiss();
|
||||
listener.onDeleteAndRegisterSpam();
|
||||
});
|
||||
}
|
||||
|
||||
dialog.show();
|
||||
|
||||
final Window window = dialog.getWindow();
|
||||
if (window != null) {
|
||||
window.setBackgroundDrawable(new ColorDrawable(android.graphics.Color.TRANSPARENT));
|
||||
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);
|
||||
}
|
||||
final String title = context.getResources().getQuantityString(
|
||||
R.plurals.delete_conversations_confirmation_dialog_title, conversationCount);
|
||||
final String message = conversationCount == 1
|
||||
? context.getString(R.string.home_delete_alert_message_one)
|
||||
: context.getString(R.string.home_delete_alert_message_many, conversationCount);
|
||||
HomeModal.builder(context)
|
||||
.icon(R.drawable.ic_home_menu_trash)
|
||||
.title(title)
|
||||
.message(message)
|
||||
.positive(R.string.home_delete_confirm, listener::onDelete)
|
||||
.negative(R.string.delete_conversation_decline_button, null)
|
||||
.show();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
package com.android.messaging.ui.home;
|
||||
|
||||
import android.content.Context;
|
||||
import android.telephony.SubscriptionInfo;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.android.messaging.R;
|
||||
import com.android.messaging.datamodel.data.ConversationListItemData;
|
||||
import com.android.messaging.util.SimNameUtil;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/** Work / Personal inbox filters: dual-SIM when present, otherwise people vs business. */
|
||||
public final class HomeInboxFilter {
|
||||
private HomeInboxFilter() {
|
||||
}
|
||||
|
||||
public static boolean matchesWork(final Context context, final ConversationListItemData item) {
|
||||
if (item == null) {
|
||||
return false;
|
||||
}
|
||||
if (item.isEnterprise()) {
|
||||
return true;
|
||||
}
|
||||
final List<SubscriptionInfo> infos = SimNameUtil.getActiveSubscriptions();
|
||||
if (infos.size() >= 2) {
|
||||
return isWorkSubscription(context, infos,
|
||||
SimNameUtil.getSubscriptionForSelfId(item.getSelfId()));
|
||||
}
|
||||
return HomeCategoryClassifier.isPromotion(item);
|
||||
}
|
||||
|
||||
public static boolean matchesPersonal(final Context context,
|
||||
final ConversationListItemData item) {
|
||||
if (item == null) {
|
||||
return false;
|
||||
}
|
||||
if (item.isEnterprise()) {
|
||||
return false;
|
||||
}
|
||||
final List<SubscriptionInfo> infos = SimNameUtil.getActiveSubscriptions();
|
||||
if (infos.size() >= 2) {
|
||||
return !isWorkSubscription(context, infos,
|
||||
SimNameUtil.getSubscriptionForSelfId(item.getSelfId()));
|
||||
}
|
||||
return HomeCategoryClassifier.isPersonal(item);
|
||||
}
|
||||
|
||||
public static String workSimName(final Context context) {
|
||||
return SimNameUtil.getDisplayName(context, workSubscription(context));
|
||||
}
|
||||
|
||||
public static String personalSimName(final Context context) {
|
||||
return SimNameUtil.getDisplayName(context, personalSubscription(context));
|
||||
}
|
||||
|
||||
public static SubscriptionInfo workSubscription(final Context context) {
|
||||
final List<SubscriptionInfo> infos = SimNameUtil.getActiveSubscriptions();
|
||||
if (infos.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
for (final SubscriptionInfo info : infos) {
|
||||
if (info != null && isWorkSubscription(context, infos, info)) {
|
||||
return info;
|
||||
}
|
||||
}
|
||||
return infos.get(infos.size() - 1);
|
||||
}
|
||||
|
||||
public static SubscriptionInfo personalSubscription(final Context context) {
|
||||
final List<SubscriptionInfo> infos = SimNameUtil.getActiveSubscriptions();
|
||||
if (infos.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
for (final SubscriptionInfo info : infos) {
|
||||
if (info != null && !isWorkSubscription(context, infos, info)) {
|
||||
return info;
|
||||
}
|
||||
}
|
||||
return infos.get(0);
|
||||
}
|
||||
|
||||
private static boolean isWorkSubscription(final Context context,
|
||||
final List<SubscriptionInfo> infos, final SubscriptionInfo match) {
|
||||
if (match == null || infos == null || infos.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
final boolean namedWork = looksLike(context, match, true);
|
||||
final boolean namedPersonal = looksLike(context, match, false);
|
||||
if (namedWork != namedPersonal) {
|
||||
return namedWork;
|
||||
}
|
||||
int minSlot = Integer.MAX_VALUE;
|
||||
int maxSlot = Integer.MIN_VALUE;
|
||||
for (final SubscriptionInfo info : infos) {
|
||||
if (info == null) {
|
||||
continue;
|
||||
}
|
||||
final int slot = info.getSimSlotIndex();
|
||||
minSlot = Math.min(minSlot, slot);
|
||||
maxSlot = Math.max(maxSlot, slot);
|
||||
}
|
||||
if (minSlot != maxSlot) {
|
||||
return match.getSimSlotIndex() == maxSlot;
|
||||
}
|
||||
return infos.indexOf(match) == infos.size() - 1;
|
||||
}
|
||||
|
||||
private static boolean looksLike(final Context context, final SubscriptionInfo info,
|
||||
final boolean work) {
|
||||
final CharSequence displayName = info.getDisplayName();
|
||||
if (TextUtils.isEmpty(displayName)) {
|
||||
return false;
|
||||
}
|
||||
final String name = displayName.toString().toLowerCase(Locale.ROOT);
|
||||
if (work) {
|
||||
final String label = context.getString(R.string.home_chip_work)
|
||||
.toLowerCase(Locale.ROOT);
|
||||
return containsToken(name, "work")
|
||||
|| containsToken(name, "office")
|
||||
|| name.contains("업무")
|
||||
|| name.contains("회사")
|
||||
|| (!TextUtils.isEmpty(label) && name.contains(label));
|
||||
}
|
||||
final String label = context.getString(R.string.home_chip_personal)
|
||||
.toLowerCase(Locale.ROOT);
|
||||
return containsToken(name, "personal")
|
||||
|| containsToken(name, "private")
|
||||
|| containsToken(name, "home")
|
||||
|| name.contains("개인")
|
||||
|| name.contains("집")
|
||||
|| (!TextUtils.isEmpty(label) && name.contains(label));
|
||||
}
|
||||
|
||||
private static boolean containsToken(final String haystack, final String token) {
|
||||
return haystack.equals(token) || haystack.contains(token);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,281 @@
|
||||
package com.android.messaging.ui.home;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.PopupWindow;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.android.messaging.R;
|
||||
import com.android.messaging.ui.UIIntents;
|
||||
import com.android.messaging.ui.conversationlist.AbstractConversationListActivity;
|
||||
|
||||
/** Edit + filter chrome shared by home sub-lists (Spam / Archive / Trash). */
|
||||
public final class HomeInboxMenu {
|
||||
|
||||
public enum Screen {
|
||||
MESSAGES,
|
||||
SPAM,
|
||||
ARCHIVE,
|
||||
TRASH
|
||||
}
|
||||
|
||||
private HomeInboxMenu() {
|
||||
}
|
||||
|
||||
public static void bind(final Activity activity, final Screen screen) {
|
||||
if (activity == null) {
|
||||
return;
|
||||
}
|
||||
final View actions = activity.findViewById(R.id.home_sub_header_inbox_actions);
|
||||
final View edit = activity.findViewById(R.id.home_sub_header_edit);
|
||||
final View filter = activity.findViewById(R.id.home_sub_header_filter);
|
||||
if (actions == null || filter == null) {
|
||||
return;
|
||||
}
|
||||
actions.setVisibility(View.VISIBLE);
|
||||
filter.setOnClickListener(v -> showFilter(activity, v, screen));
|
||||
bindSelectAction(activity, v -> {
|
||||
if (activity instanceof AbstractConversationListActivity) {
|
||||
((AbstractConversationListActivity) activity).onSubListPrimaryAction();
|
||||
} else if (activity instanceof HomeSpamActivity) {
|
||||
((HomeSpamActivity) activity).onSelectPrimaryAction();
|
||||
}
|
||||
});
|
||||
if (edit == null) {
|
||||
return;
|
||||
}
|
||||
edit.setTag(null);
|
||||
if (edit instanceof TextView) {
|
||||
final TextView text = (TextView) edit;
|
||||
text.setText(R.string.home_select);
|
||||
text.setTextColor(activity.getColor(R.color.home_text_primary));
|
||||
text.setCompoundDrawablesRelative(null, null, null, null);
|
||||
text.setCompoundDrawablePadding(0);
|
||||
}
|
||||
edit.setVisibility(View.VISIBLE);
|
||||
final View.OnClickListener toggleSelect = v -> {
|
||||
if (activity instanceof AbstractConversationListActivity) {
|
||||
final AbstractConversationListActivity list =
|
||||
(AbstractConversationListActivity) activity;
|
||||
if (list.isSelectionMode()) {
|
||||
list.exitMultiSelectState();
|
||||
} else {
|
||||
list.enterHomeEditMode();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (activity instanceof HomeSpamActivity) {
|
||||
((HomeSpamActivity) activity).toggleSelectMode();
|
||||
}
|
||||
};
|
||||
edit.setOnClickListener(toggleSelect);
|
||||
final View done = activity.findViewById(R.id.home_sub_header_done);
|
||||
if (done != null) {
|
||||
done.setOnClickListener(toggleSelect);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setSelecting(final Activity activity, final boolean selecting) {
|
||||
final View actions = activity == null
|
||||
? null : activity.findViewById(R.id.home_sub_header_inbox_actions);
|
||||
final View done = activity == null ? null : activity.findViewById(R.id.home_sub_header_done);
|
||||
if (selecting) {
|
||||
HomeUiMotion.swapOverlayChip(actions, done);
|
||||
} else {
|
||||
HomeUiMotion.swapOverlayChip(done, actions);
|
||||
}
|
||||
final View bar = activity == null ? null : activity.findViewById(R.id.home_sub_select_bar);
|
||||
HomeUiMotion.swapSubListDock(bar, selecting);
|
||||
}
|
||||
|
||||
public static void setSelectAction(final Activity activity, final int iconRes,
|
||||
final int textResId, final boolean enabled, final boolean destructive) {
|
||||
final View tab = activity == null
|
||||
? null : activity.findViewById(R.id.home_sub_select_action);
|
||||
if (tab == null) {
|
||||
return;
|
||||
}
|
||||
tab.setEnabled(enabled);
|
||||
tab.setAlpha(enabled ? 1f : 0.4f);
|
||||
final int color = activity.getColor(destructive
|
||||
? R.color.home_destructive
|
||||
: R.color.home_nav_inactive);
|
||||
final ImageView icon = tab.findViewById(R.id.home_nav_tab_icon);
|
||||
if (icon != null) {
|
||||
if (iconRes != 0) {
|
||||
icon.setImageResource(iconRes);
|
||||
}
|
||||
icon.setImageTintList(android.content.res.ColorStateList.valueOf(color));
|
||||
}
|
||||
final TextView label = tab.findViewById(R.id.home_nav_tab_label);
|
||||
if (label != null) {
|
||||
label.setText(textResId);
|
||||
label.setTextColor(color);
|
||||
label.setTypeface(null, android.graphics.Typeface.NORMAL);
|
||||
}
|
||||
}
|
||||
|
||||
public static void bindSelectAction(final Activity activity,
|
||||
final View.OnClickListener onAction) {
|
||||
final View action = activity == null
|
||||
? null : activity.findViewById(R.id.home_sub_select_action);
|
||||
if (action != null) {
|
||||
action.setOnClickListener(onAction);
|
||||
}
|
||||
}
|
||||
|
||||
private static void showFilter(final Activity activity, final View anchor,
|
||||
final Screen screen) {
|
||||
final PopupWindow popup = showPopover(activity, anchor, R.layout.home_popover_filter);
|
||||
final View content = popup.getContentView();
|
||||
bindFilterSims(content);
|
||||
bindFilterChecks(content, screen);
|
||||
setGone(content, R.id.popover_unread);
|
||||
setGone(content, R.id.popover_work);
|
||||
setGone(content, R.id.popover_personal);
|
||||
setGone(content, R.id.popover_filter_by_label);
|
||||
setGone(content, R.id.popover_filter_by_divider);
|
||||
content.findViewById(R.id.popover_messages).setOnClickListener(v -> {
|
||||
popup.dismiss();
|
||||
if (screen != Screen.MESSAGES) {
|
||||
activity.finish();
|
||||
}
|
||||
});
|
||||
content.findViewById(R.id.popover_spam).setOnClickListener(v -> {
|
||||
popup.dismiss();
|
||||
openScreen(activity, screen, Screen.SPAM,
|
||||
() -> UIIntents.get().launchHomeSpamActivity(activity));
|
||||
});
|
||||
content.findViewById(R.id.popover_archive).setOnClickListener(v -> {
|
||||
popup.dismiss();
|
||||
openScreen(activity, screen, Screen.ARCHIVE,
|
||||
() -> UIIntents.get().launchArchivedConversationsActivity(activity));
|
||||
});
|
||||
content.findViewById(R.id.popover_recently_deleted).setOnClickListener(v -> {
|
||||
popup.dismiss();
|
||||
openScreen(activity, screen, Screen.TRASH,
|
||||
() -> UIIntents.get().launchRecentlyDeletedConversationsActivity(activity));
|
||||
});
|
||||
content.findViewById(R.id.popover_share_app).setOnClickListener(v -> {
|
||||
popup.dismiss();
|
||||
shareThisApp(activity);
|
||||
});
|
||||
content.findViewById(R.id.popover_settings).setOnClickListener(v -> {
|
||||
popup.dismiss();
|
||||
UIIntents.get().launchSettingsActivity(activity);
|
||||
activity.overridePendingTransition(R.anim.home_activity_open,
|
||||
R.anim.home_activity_hold);
|
||||
});
|
||||
}
|
||||
|
||||
private static void showEdit(final Activity activity, final View anchor) {
|
||||
final PopupWindow popup = showPopover(activity, anchor, R.layout.home_popover_edit);
|
||||
final View content = popup.getContentView();
|
||||
final View pinned = content.findViewById(R.id.popover_edit_pinned);
|
||||
if (pinned != null) {
|
||||
pinned.setVisibility(View.GONE);
|
||||
}
|
||||
content.findViewById(R.id.popover_select_messages).setOnClickListener(v -> {
|
||||
popup.dismiss();
|
||||
if (activity instanceof AbstractConversationListActivity) {
|
||||
((AbstractConversationListActivity) activity).enterHomeEditMode();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void openScreen(final Activity activity, final Screen current,
|
||||
final Screen target, final Runnable launch) {
|
||||
if (current == target) {
|
||||
return;
|
||||
}
|
||||
launch.run();
|
||||
activity.finish();
|
||||
activity.overridePendingTransition(R.anim.home_activity_open, R.anim.home_activity_hold);
|
||||
}
|
||||
|
||||
private static void bindFilterSims(final View content) {
|
||||
final Context context = content.getContext();
|
||||
setFilterText(content, R.id.popover_work_badge, HomeInboxFilter.workSimName(context));
|
||||
setFilterText(content, R.id.popover_work_label, HomeInboxFilter.workSimName(context));
|
||||
setFilterText(content, R.id.popover_personal_badge,
|
||||
HomeInboxFilter.personalSimName(context));
|
||||
setFilterText(content, R.id.popover_personal_label,
|
||||
HomeInboxFilter.personalSimName(context));
|
||||
}
|
||||
|
||||
private static void bindFilterChecks(final View content, final Screen screen) {
|
||||
setCheck(content, R.id.popover_messages_check, screen == Screen.MESSAGES);
|
||||
setCheck(content, R.id.popover_spam_check, screen == Screen.SPAM);
|
||||
setCheck(content, R.id.popover_archive_check, screen == Screen.ARCHIVE);
|
||||
setCheck(content, R.id.popover_recently_deleted_check, screen == Screen.TRASH);
|
||||
setCheck(content, R.id.popover_unread_check, false);
|
||||
setCheck(content, R.id.popover_work_check, false);
|
||||
setCheck(content, R.id.popover_personal_check, false);
|
||||
}
|
||||
|
||||
private static void setCheck(final View content, final int id, final boolean on) {
|
||||
final View check = content.findViewById(id);
|
||||
if (check != null) {
|
||||
check.setVisibility(on ? View.VISIBLE : View.INVISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
private static void setFilterText(final View content, final int id, final String text) {
|
||||
final View view = content.findViewById(id);
|
||||
if (view instanceof TextView) {
|
||||
((TextView) view).setText(text);
|
||||
}
|
||||
}
|
||||
|
||||
private static void setGone(final View content, final int id) {
|
||||
final View view = content.findViewById(id);
|
||||
if (view != null) {
|
||||
view.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private static PopupWindow showPopover(final Activity activity, final View anchor,
|
||||
final int layoutResId) {
|
||||
final View content = LayoutInflater.from(activity).inflate(layoutResId, null, false);
|
||||
final int width = activity.getResources().getDimensionPixelSize(R.dimen.home_popover_width);
|
||||
content.measure(
|
||||
View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
|
||||
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
|
||||
final PopupWindow popup = new PopupWindow(content, width,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT, true);
|
||||
popup.setOutsideTouchable(true);
|
||||
popup.setFocusable(true);
|
||||
popup.setElevation(16f);
|
||||
popup.setBackgroundDrawable(new ColorDrawable(android.graphics.Color.TRANSPARENT));
|
||||
final int endInset = activity.getResources().getDimensionPixelSize(
|
||||
R.dimen.looply_screen_padding);
|
||||
final int[] anchorLoc = new int[2];
|
||||
anchor.getLocationOnScreen(anchorLoc);
|
||||
final int windowRight = activity.getResources().getDisplayMetrics().widthPixels;
|
||||
int xOff = windowRight - endInset - width - anchorLoc[0];
|
||||
final int overflow = (anchorLoc[0] + xOff + width) - (windowRight - endInset);
|
||||
if (overflow > 0) {
|
||||
xOff -= overflow;
|
||||
}
|
||||
popup.showAsDropDown(anchor, xOff, 8);
|
||||
HomeUiMotion.popoverEnter(content, true);
|
||||
return popup;
|
||||
}
|
||||
|
||||
private static void shareThisApp(final Activity activity) {
|
||||
final String storeUrl = "https://play.google.com/store/apps/details?id="
|
||||
+ activity.getPackageName();
|
||||
final Intent share = new Intent(Intent.ACTION_SEND);
|
||||
share.setType("text/plain");
|
||||
share.putExtra(Intent.EXTRA_TEXT,
|
||||
activity.getString(R.string.home_share_app_text, storeUrl));
|
||||
activity.startActivity(Intent.createChooser(share,
|
||||
activity.getString(R.string.home_popover_share_app)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
package com.android.messaging.ui.home;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.mms.pdu.PduHeaders;
|
||||
import android.text.TextUtils;
|
||||
import android.text.format.DateUtils;
|
||||
import android.text.format.Formatter;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.android.messaging.R;
|
||||
import com.android.messaging.datamodel.data.ConversationMessageData;
|
||||
import com.android.messaging.datamodel.data.MessagePartData;
|
||||
import com.android.messaging.sms.MmsUtils;
|
||||
import com.android.messaging.ui.BugleActionBarActivity;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/** Home sub-screen for a single message's details. */
|
||||
public class HomeMessageDetailsActivity extends BugleActionBarActivity {
|
||||
|
||||
static final String EXTRA_PREVIEW = "preview";
|
||||
static final String EXTRA_INCOMING = "incoming";
|
||||
static final String EXTRA_SMS = "sms";
|
||||
static final String EXTRA_SENT = "sent";
|
||||
static final String EXTRA_RECEIVED = "received";
|
||||
static final String EXTRA_SEND_COMPLETE = "send_complete";
|
||||
static final String EXTRA_PRIORITY = "priority";
|
||||
static final String EXTRA_SUBJECT = "subject";
|
||||
static final String EXTRA_SIZE = "size";
|
||||
|
||||
public static void start(final Context context, final ConversationMessageData data) {
|
||||
if (context == null || data == null) {
|
||||
return;
|
||||
}
|
||||
final Intent intent = new Intent(context, HomeMessageDetailsActivity.class);
|
||||
intent.putExtra(EXTRA_PREVIEW, previewText(context, data));
|
||||
intent.putExtra(EXTRA_INCOMING, data.getIsIncoming());
|
||||
intent.putExtra(EXTRA_SMS, data.getIsSms());
|
||||
intent.putExtra(EXTRA_SENT, data.getSentTimeStamp());
|
||||
intent.putExtra(EXTRA_RECEIVED, data.getReceivedTimeStamp());
|
||||
intent.putExtra(EXTRA_SEND_COMPLETE, data.getIsSendComplete());
|
||||
intent.putExtra(EXTRA_PRIORITY, data.getSmsPriority());
|
||||
intent.putExtra(EXTRA_SUBJECT, data.getMmsSubject());
|
||||
intent.putExtra(EXTRA_SIZE, data.getSmsMessageSize());
|
||||
context.startActivity(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.home_message_details_activity);
|
||||
if (getSupportActionBar() != null) {
|
||||
getSupportActionBar().hide();
|
||||
}
|
||||
HomeSubHeader.bind(this, R.string.home_message_details_title, v -> finish());
|
||||
bindDetails(getIntent());
|
||||
}
|
||||
|
||||
private void bindDetails(final Intent intent) {
|
||||
if (intent == null) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
final TextView bubble = findViewById(R.id.home_message_details_bubble);
|
||||
final boolean incoming = intent.getBooleanExtra(EXTRA_INCOMING, true);
|
||||
bubble.setText(intent.getStringExtra(EXTRA_PREVIEW));
|
||||
final FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) bubble.getLayoutParams();
|
||||
if (incoming) {
|
||||
lp.gravity = Gravity.START;
|
||||
bubble.setBackgroundResource(R.drawable.home_bg_preview_bubble_in);
|
||||
bubble.setTextColor(getColor(R.color.home_text_primary));
|
||||
} else {
|
||||
lp.gravity = Gravity.END;
|
||||
bubble.setBackgroundResource(R.drawable.home_bg_preview_bubble_out);
|
||||
bubble.setTextColor(getColor(R.color.looply_bubble_outgoing_text));
|
||||
}
|
||||
bubble.setLayoutParams(lp);
|
||||
|
||||
final boolean sms = intent.getBooleanExtra(EXTRA_SMS, true);
|
||||
final TextView type = findViewById(R.id.home_message_details_type);
|
||||
type.setText(sms ? R.string.text_message : R.string.multimedia_message);
|
||||
|
||||
bindStatus(intent);
|
||||
bindPriority(intent.getIntExtra(EXTRA_PRIORITY, PduHeaders.PRIORITY_NORMAL));
|
||||
bindOptional(R.id.home_message_details_subject_section,
|
||||
R.id.home_message_details_subject,
|
||||
MmsUtils.cleanseMmsSubject(getResources(), intent.getStringExtra(EXTRA_SUBJECT)));
|
||||
final int size = intent.getIntExtra(EXTRA_SIZE, 0);
|
||||
if (size > 0) {
|
||||
bindOptional(R.id.home_message_details_size_section,
|
||||
R.id.home_message_details_size,
|
||||
Formatter.formatFileSize(this, size));
|
||||
}
|
||||
}
|
||||
|
||||
private void bindStatus(final Intent intent) {
|
||||
final boolean incoming = intent.getBooleanExtra(EXTRA_INCOMING, true);
|
||||
final boolean sendComplete = intent.getBooleanExtra(EXTRA_SEND_COMPLETE, false);
|
||||
final long sent = intent.getLongExtra(EXTRA_SENT, MmsUtils.INVALID_TIMESTAMP);
|
||||
final long received = intent.getLongExtra(EXTRA_RECEIVED, MmsUtils.INVALID_TIMESTAMP);
|
||||
|
||||
final boolean showReceived = incoming && received != MmsUtils.INVALID_TIMESTAMP;
|
||||
final boolean showSent;
|
||||
final long sentDisplay;
|
||||
if (incoming) {
|
||||
showSent = sent != MmsUtils.INVALID_TIMESTAMP;
|
||||
sentDisplay = sent;
|
||||
} else if (sendComplete && received != MmsUtils.INVALID_TIMESTAMP) {
|
||||
showSent = true;
|
||||
sentDisplay = received;
|
||||
} else {
|
||||
showSent = sent != MmsUtils.INVALID_TIMESTAMP;
|
||||
sentDisplay = sent;
|
||||
}
|
||||
|
||||
setRow(R.id.home_message_details_received_row,
|
||||
R.id.home_message_details_received, showReceived, received);
|
||||
setRow(R.id.home_message_details_sent_row,
|
||||
R.id.home_message_details_sent, showSent, sentDisplay);
|
||||
findViewById(R.id.home_message_details_status_divider)
|
||||
.setVisibility(showReceived && showSent ? View.VISIBLE : View.GONE);
|
||||
findViewById(R.id.home_message_details_status_section)
|
||||
.setVisibility(showReceived || showSent ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
private void bindPriority(final int priority) {
|
||||
final View section = findViewById(R.id.home_message_details_priority_section);
|
||||
final TextView value = findViewById(R.id.home_message_details_priority);
|
||||
final int label;
|
||||
switch (priority) {
|
||||
case PduHeaders.PRIORITY_HIGH:
|
||||
label = R.string.priority_high;
|
||||
break;
|
||||
case PduHeaders.PRIORITY_LOW:
|
||||
label = R.string.priority_low;
|
||||
break;
|
||||
case PduHeaders.PRIORITY_NORMAL:
|
||||
default:
|
||||
label = R.string.priority_normal;
|
||||
break;
|
||||
}
|
||||
value.setText(label);
|
||||
section.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
private void bindOptional(final int sectionId, final int valueId, final CharSequence text) {
|
||||
final View section = findViewById(sectionId);
|
||||
final TextView value = findViewById(valueId);
|
||||
if (TextUtils.isEmpty(text)) {
|
||||
section.setVisibility(View.GONE);
|
||||
return;
|
||||
}
|
||||
value.setText(text);
|
||||
section.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
private void setRow(final int rowId, final int valueId, final boolean show, final long time) {
|
||||
final View row = findViewById(rowId);
|
||||
final TextView value = findViewById(valueId);
|
||||
if (!show || time == MmsUtils.INVALID_TIMESTAMP) {
|
||||
row.setVisibility(View.GONE);
|
||||
return;
|
||||
}
|
||||
value.setText(formatTime(time));
|
||||
row.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
private String formatTime(final long time) {
|
||||
return DateUtils.formatDateTime(this, time,
|
||||
DateUtils.FORMAT_SHOW_DATE
|
||||
| DateUtils.FORMAT_SHOW_YEAR
|
||||
| DateUtils.FORMAT_SHOW_TIME
|
||||
| DateUtils.FORMAT_ABBREV_MONTH);
|
||||
}
|
||||
|
||||
private static String previewText(final Context context, final ConversationMessageData data) {
|
||||
final String text = data.getText();
|
||||
if (!TextUtils.isEmpty(text)) {
|
||||
return text;
|
||||
}
|
||||
final List<MessagePartData> attachments = data.getAttachments();
|
||||
if (attachments != null) {
|
||||
for (final MessagePartData part : attachments) {
|
||||
if (part.isImage()) {
|
||||
return context.getString(R.string.home_message_details_photo);
|
||||
}
|
||||
if (part.isVideo()) {
|
||||
return context.getString(R.string.home_message_details_video);
|
||||
}
|
||||
if (part.isAudio()) {
|
||||
return context.getString(R.string.home_message_details_audio);
|
||||
}
|
||||
if (part.isVCard()) {
|
||||
return context.getString(R.string.home_message_details_contact);
|
||||
}
|
||||
}
|
||||
if (!attachments.isEmpty()) {
|
||||
return context.getString(R.string.home_message_details_attachment);
|
||||
}
|
||||
}
|
||||
return context.getString(R.string.home_message_details_attachment);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish() {
|
||||
super.finish();
|
||||
overridePendingTransition(R.anim.home_activity_hold, R.anim.home_activity_close);
|
||||
}
|
||||
}
|
||||
@@ -97,8 +97,10 @@ public final class HomeModal {
|
||||
private CharSequence mTitle;
|
||||
private CharSequence mMessage;
|
||||
private CharSequence mPositive;
|
||||
private CharSequence mNeutral;
|
||||
private CharSequence mNegative;
|
||||
private Action mOnPositive;
|
||||
private Action mOnNeutral;
|
||||
private Action mOnNegative;
|
||||
private View mCustom;
|
||||
private boolean mCancelable = true;
|
||||
@@ -149,6 +151,18 @@ public final class HomeModal {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder neutral(@StringRes final int labelRes, @Nullable final Action action) {
|
||||
mNeutral = mContext.getString(labelRes);
|
||||
mOnNeutral = action;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder neutral(final CharSequence label, @Nullable final Action action) {
|
||||
mNeutral = label;
|
||||
mOnNeutral = action;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder negative(@StringRes final int labelRes, @Nullable final Action action) {
|
||||
mNegative = mContext.getString(labelRes);
|
||||
mOnNegative = action;
|
||||
@@ -195,9 +209,11 @@ public final class HomeModal {
|
||||
}
|
||||
|
||||
final TextView positive = content.findViewById(R.id.home_modal_positive);
|
||||
final TextView neutral = content.findViewById(R.id.home_modal_neutral);
|
||||
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);
|
||||
bindButton(neutral, options.mNeutral, options.mOnNeutral, dialog, true);
|
||||
bindButton(negative, options.mNegative, options.mOnNegative, dialog, true);
|
||||
|
||||
dialog.show();
|
||||
|
||||
|
||||
@@ -403,7 +403,16 @@ public class HomeProfileActivity extends BugleActionBarActivity
|
||||
}
|
||||
|
||||
private ParticipantData getOtherParticipant() {
|
||||
return mBinding.isBound() ? mBinding.getData().getOtherParticipant() : null;
|
||||
if (mBinding.isBound()) {
|
||||
final ParticipantData bound = mBinding.getData().getOtherParticipant();
|
||||
if (bound != null) {
|
||||
return bound;
|
||||
}
|
||||
}
|
||||
if (mParticipants != null && mParticipants.size() == 1) {
|
||||
return mParticipants.get(0);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String getCallablePhoneNumber() {
|
||||
@@ -460,15 +469,19 @@ public class HomeProfileActivity extends BugleActionBarActivity
|
||||
|
||||
private void showRenameDialog() {
|
||||
final View content = LayoutInflater.from(this)
|
||||
.inflate(R.layout.home_profile_rename_dialog, null);
|
||||
.inflate(R.layout.home_profile_rename_dialog, null, false);
|
||||
final EditText field = content.findViewById(R.id.home_profile_rename_field);
|
||||
field.setText(mNameView.getText());
|
||||
field.setSelection(field.getText().length());
|
||||
new AlertDialog.Builder(this, R.style.Home_Dialog)
|
||||
.setTitle(R.string.home_profile_rename)
|
||||
.setView(content)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setPositiveButton(R.string.home_profile_rename_save, (dialog, which) -> {
|
||||
final CharSequence current = mNameView == null ? "" : mNameView.getText();
|
||||
field.setText(current);
|
||||
if (!TextUtils.isEmpty(current)) {
|
||||
field.setSelection(current.length());
|
||||
}
|
||||
final HomeModal.Handle handle = HomeModal.builder(this)
|
||||
.icon(R.drawable.ic_home_edit)
|
||||
.title(R.string.home_profile_rename)
|
||||
.custom(content)
|
||||
.negative(android.R.string.cancel, null)
|
||||
.positive(R.string.home_profile_rename_save, () -> {
|
||||
final String name = field.getText().toString().trim();
|
||||
// An empty name would hide the conversation everywhere it is listed.
|
||||
if (!TextUtils.isEmpty(name)) {
|
||||
@@ -476,6 +489,15 @@ public class HomeProfileActivity extends BugleActionBarActivity
|
||||
}
|
||||
})
|
||||
.show();
|
||||
handle.dialog.setOnDismissListener(dialog -> {
|
||||
if (!isFinishing() && mConversation != null) {
|
||||
updateViews();
|
||||
}
|
||||
});
|
||||
field.post(() -> {
|
||||
field.requestFocus();
|
||||
ImeUtil.get().showImeKeyboard(this, field);
|
||||
});
|
||||
}
|
||||
|
||||
private void openNotificationSettings() {
|
||||
@@ -522,15 +544,27 @@ public class HomeProfileActivity extends BugleActionBarActivity
|
||||
}
|
||||
|
||||
private void confirmDelete() {
|
||||
new AlertDialog.Builder(this, R.style.Home_Dialog)
|
||||
.setTitle(getResources().getQuantityString(
|
||||
R.plurals.delete_conversations_confirmation_dialog_title, 1))
|
||||
.setNegativeButton(R.string.delete_conversation_decline_button, null)
|
||||
.setPositiveButton(R.string.delete_conversation_confirmation_button,
|
||||
(dialog, which) -> {
|
||||
mConversation.deleteConversation();
|
||||
closeConversation();
|
||||
})
|
||||
if (mConversation == null) {
|
||||
return;
|
||||
}
|
||||
final boolean trashed = mConversation.getIsTrashed();
|
||||
HomeModal.builder(this)
|
||||
.icon(R.drawable.ic_home_menu_trash)
|
||||
.title(trashed
|
||||
? getString(R.string.home_trash_permanent_title)
|
||||
: getResources().getQuantityString(
|
||||
R.plurals.delete_conversations_confirmation_dialog_title, 1))
|
||||
.message(trashed
|
||||
? getString(R.string.home_trash_permanent_message_one)
|
||||
: getString(R.string.home_delete_alert_message_one))
|
||||
.negative(R.string.delete_conversation_decline_button, null)
|
||||
.positive(R.string.home_delete_confirm, () -> {
|
||||
mConversation.deleteConversation();
|
||||
if (!trashed) {
|
||||
HomeToast.show(this, R.string.home_trash_moved_toast_one);
|
||||
}
|
||||
closeConversation();
|
||||
})
|
||||
.show();
|
||||
}
|
||||
|
||||
|
||||
@@ -17,14 +17,14 @@
|
||||
package com.android.messaging.ui.home;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.android.messaging.R;
|
||||
import com.android.messaging.ui.BugleActionBarActivity;
|
||||
|
||||
/** Home-style screen to view and remove spam-registered (blocked) numbers. */
|
||||
public class HomeSpamActivity extends BugleActionBarActivity {
|
||||
public class HomeSpamActivity extends BugleActionBarActivity implements HomeSpamFragment.Host {
|
||||
|
||||
private boolean mSelecting;
|
||||
|
||||
@Override
|
||||
protected void onCreate(final Bundle savedInstanceState) {
|
||||
@@ -34,16 +34,62 @@ public class HomeSpamActivity extends BugleActionBarActivity {
|
||||
getSupportActionBar().hide();
|
||||
}
|
||||
|
||||
final ImageButton back = findViewById(R.id.home_sub_header_back);
|
||||
if (back != null) {
|
||||
back.setOnClickListener(v -> finish());
|
||||
HomeSubHeader.bind(this, R.string.home_spam_title, v -> onBackPressed());
|
||||
HomeInboxMenu.bind(this, HomeInboxMenu.Screen.SPAM);
|
||||
}
|
||||
|
||||
public void toggleSelectMode() {
|
||||
if (!mSelecting) {
|
||||
final HomeSpamFragment fragment = fragment();
|
||||
if (fragment == null || fragment.getVisibleCount() == 0) {
|
||||
HomeToast.show(this, R.string.home_toast_nothing_to_select);
|
||||
return;
|
||||
}
|
||||
}
|
||||
final TextView title = findViewById(R.id.home_sub_header_title);
|
||||
if (title != null) {
|
||||
title.setText(R.string.home_spam_title);
|
||||
setSelecting(!mSelecting);
|
||||
}
|
||||
|
||||
public void onSelectPrimaryAction() {
|
||||
final HomeSpamFragment fragment = fragment();
|
||||
if (fragment != null) {
|
||||
fragment.unblockSelectedOrAll();
|
||||
}
|
||||
}
|
||||
|
||||
void setSelecting(final boolean selecting) {
|
||||
mSelecting = selecting;
|
||||
final HomeSpamFragment fragment = fragment();
|
||||
if (fragment != null) {
|
||||
fragment.setSelecting(selecting);
|
||||
} else {
|
||||
onSpamSelectionChanged(0, 0, selecting);
|
||||
}
|
||||
HomeInboxMenu.setSelecting(this, selecting);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSpamSelectionChanged(final int selectedCount, final int visibleCount,
|
||||
final boolean selecting) {
|
||||
mSelecting = selecting;
|
||||
HomeInboxMenu.setSelectAction(this, R.drawable.ic_home_menu_spam,
|
||||
selectedCount > 0 ? R.string.home_sub_unblock : R.string.home_sub_unblock_all,
|
||||
visibleCount > 0, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (mSelecting) {
|
||||
setSelecting(false);
|
||||
return;
|
||||
}
|
||||
super.onBackPressed();
|
||||
}
|
||||
|
||||
private HomeSpamFragment fragment() {
|
||||
return (HomeSpamFragment) getSupportFragmentManager()
|
||||
.findFragmentById(R.id.home_spam_fragment_host);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish() {
|
||||
super.finish();
|
||||
|
||||
@@ -1,31 +1,20 @@
|
||||
/*
|
||||
* Copyright (C) 2025 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.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ListView;
|
||||
import androidx.cursoradapter.widget.CursorAdapter;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.loader.app.LoaderManager;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.android.messaging.R;
|
||||
import com.android.messaging.datamodel.DataModel;
|
||||
@@ -33,43 +22,177 @@ import com.android.messaging.datamodel.binding.Binding;
|
||||
import com.android.messaging.datamodel.binding.BindingBase;
|
||||
import com.android.messaging.datamodel.data.BlockedParticipantsData;
|
||||
import com.android.messaging.datamodel.data.BlockedParticipantsData.BlockedParticipantsDataListener;
|
||||
import com.android.messaging.ui.BlockedParticipantListItemView;
|
||||
import com.android.messaging.util.Assert;
|
||||
import com.android.messaging.datamodel.data.ParticipantListItemData;
|
||||
import com.android.messaging.ui.ContactIconView;
|
||||
import com.android.messaging.ui.CursorRecyclerAdapter;
|
||||
|
||||
/** Home UI list of spam-registered (blocked) senders with unblock on tap. */
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/** Home UI list of spam-registered (blocked) senders. Tap Unblock to restore the number. */
|
||||
public class HomeSpamFragment extends Fragment implements BlockedParticipantsDataListener {
|
||||
|
||||
private ListView mListView;
|
||||
public interface Host {
|
||||
void onSpamSelectionChanged(int selectedCount, int visibleCount, boolean selecting);
|
||||
}
|
||||
|
||||
private RecyclerView mListView;
|
||||
private HomeEmptyState mEmptyView;
|
||||
private BlockedParticipantListAdapter mAdapter;
|
||||
private SpamAdapter mAdapter;
|
||||
private Host mHost;
|
||||
private boolean mSelecting;
|
||||
private final Set<String> mSelectedDestinations = new HashSet<>();
|
||||
private final Binding<BlockedParticipantsData> mBinding = BindingBase.createBinding(this);
|
||||
|
||||
@Override
|
||||
public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
|
||||
final Bundle savedInstanceState) {
|
||||
final View root = inflater.inflate(R.layout.blocked_participants_fragment, container,
|
||||
false);
|
||||
mListView = root.findViewById(android.R.id.list);
|
||||
mAdapter = new BlockedParticipantListAdapter(requireContext(), null);
|
||||
final View root = inflater.inflate(R.layout.home_spam_fragment, container, false);
|
||||
mListView = root.findViewById(R.id.home_spam_list);
|
||||
mAdapter = new SpamAdapter(requireContext());
|
||||
mListView.setLayoutManager(new LinearLayoutManager(requireContext()));
|
||||
mListView.setAdapter(mAdapter);
|
||||
mBinding.bind(DataModel.get().createBlockedParticipantsData(requireContext(), this));
|
||||
mBinding.getData().init(LoaderManager.getInstance(this), mBinding);
|
||||
|
||||
final View activityRoot = requireActivity().findViewById(R.id.home_spam_empty);
|
||||
if (activityRoot instanceof HomeEmptyState) {
|
||||
mEmptyView = (HomeEmptyState) activityRoot;
|
||||
final View activityEmpty = requireActivity().findViewById(R.id.home_spam_empty);
|
||||
if (activityEmpty instanceof HomeEmptyState) {
|
||||
mEmptyView = (HomeEmptyState) activityEmpty;
|
||||
mEmptyView.setContent(R.drawable.ic_home_menu_spam,
|
||||
R.string.home_empty_spam_title,
|
||||
R.string.home_empty_spam_body);
|
||||
}
|
||||
HomeSubListSearch.bind(requireActivity(), mListView, query -> {
|
||||
if (mAdapter != null) {
|
||||
mAdapter.setSearchQuery(query);
|
||||
pruneSelection();
|
||||
updateEmptyState();
|
||||
notifyHost();
|
||||
}
|
||||
});
|
||||
HomeSubListSearch.bindListFades(mListView,
|
||||
root.findViewById(R.id.home_list_top_fade),
|
||||
root.findViewById(R.id.home_list_bottom_fade));
|
||||
if (getActivity() instanceof Host) {
|
||||
mHost = (Host) getActivity();
|
||||
}
|
||||
notifyHost();
|
||||
return root;
|
||||
}
|
||||
|
||||
void setSelecting(final boolean selecting) {
|
||||
mSelecting = selecting;
|
||||
if (!selecting) {
|
||||
mSelectedDestinations.clear();
|
||||
}
|
||||
notifyHost();
|
||||
final View list = mListView;
|
||||
if (list != null) {
|
||||
list.post(() -> {
|
||||
if (!isAdded() || mListView == null) {
|
||||
return;
|
||||
}
|
||||
applySelectPadding();
|
||||
if (mAdapter != null) {
|
||||
mAdapter.notifyDataSetChanged();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
applySelectPadding();
|
||||
if (mAdapter != null) {
|
||||
mAdapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int getSelectedCount() {
|
||||
return mSelectedDestinations.size();
|
||||
}
|
||||
|
||||
int getVisibleCount() {
|
||||
return mAdapter == null ? 0 : mAdapter.getItemCount();
|
||||
}
|
||||
|
||||
void unblockSelectedOrAll() {
|
||||
final Context context = getContext();
|
||||
if (context == null || mAdapter == null) {
|
||||
return;
|
||||
}
|
||||
final List<ParticipantListItemData> targets = mSelectedDestinations.isEmpty()
|
||||
? mAdapter.snapshotVisible()
|
||||
: mAdapter.snapshotSelected(mSelectedDestinations);
|
||||
if (targets.isEmpty()) {
|
||||
HomeToast.show(context, R.string.home_toast_nothing_to_unblock);
|
||||
return;
|
||||
}
|
||||
for (final ParticipantListItemData data : targets) {
|
||||
data.unblockQuietly(context);
|
||||
}
|
||||
HomeToast.show(context, R.string.home_sub_unblocked_toast);
|
||||
if (getActivity() instanceof HomeSpamActivity) {
|
||||
((HomeSpamActivity) getActivity()).setSelecting(false);
|
||||
} else {
|
||||
setSelecting(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void applySelectPadding() {
|
||||
if (mListView == null) {
|
||||
return;
|
||||
}
|
||||
mListView.setPadding(
|
||||
mListView.getPaddingLeft(),
|
||||
mListView.getPaddingTop(),
|
||||
mListView.getPaddingRight(),
|
||||
getResources().getDimensionPixelSize(mSelecting
|
||||
? R.dimen.home_sub_select_list_padding
|
||||
: R.dimen.looply_space_lg));
|
||||
}
|
||||
|
||||
private void notifyHost() {
|
||||
if (mHost != null) {
|
||||
mHost.onSpamSelectionChanged(mSelectedDestinations.size(),
|
||||
getVisibleCount(), mSelecting);
|
||||
}
|
||||
}
|
||||
|
||||
private void onRowClicked(final ParticipantListItemData data) {
|
||||
if (mSelecting) {
|
||||
toggleSelected(data);
|
||||
} else {
|
||||
confirmUnblock(data);
|
||||
}
|
||||
}
|
||||
|
||||
private void toggleSelected(final ParticipantListItemData data) {
|
||||
if (data == null) {
|
||||
return;
|
||||
}
|
||||
final String destination = data.getNormalizedDestination();
|
||||
if (TextUtils.isEmpty(destination)) {
|
||||
return;
|
||||
}
|
||||
if (!mSelectedDestinations.add(destination)) {
|
||||
mSelectedDestinations.remove(destination);
|
||||
}
|
||||
if (mAdapter != null) {
|
||||
mAdapter.notifyDataSetChanged();
|
||||
}
|
||||
notifyHost();
|
||||
}
|
||||
|
||||
private boolean isSelected(final ParticipantListItemData data) {
|
||||
return data != null && mSelectedDestinations.contains(data.getNormalizedDestination());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
mEmptyView = null;
|
||||
mListView = null;
|
||||
mAdapter = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -80,33 +203,249 @@ public class HomeSpamFragment extends Fragment implements BlockedParticipantsDat
|
||||
|
||||
@Override
|
||||
public void onBlockedParticipantsCursorUpdated(final Cursor cursor) {
|
||||
mAdapter.swapCursor(cursor);
|
||||
final boolean empty = mAdapter.getCount() == 0;
|
||||
if (mAdapter != null) {
|
||||
mAdapter.swapCursor(cursor);
|
||||
pruneSelection();
|
||||
}
|
||||
updateEmptyState();
|
||||
notifyHost();
|
||||
}
|
||||
|
||||
private void pruneSelection() {
|
||||
if (mAdapter == null || mSelectedDestinations.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
final HashSet<String> visible = new HashSet<>();
|
||||
for (final ParticipantListItemData data : mAdapter.snapshotVisible()) {
|
||||
visible.add(data.getNormalizedDestination());
|
||||
}
|
||||
mSelectedDestinations.retainAll(visible);
|
||||
}
|
||||
|
||||
private void updateEmptyState() {
|
||||
final boolean empty = mAdapter == null || mAdapter.getItemCount() == 0;
|
||||
if (mListView != null) {
|
||||
mListView.setVisibility(empty ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
if (mEmptyView != null) {
|
||||
if (empty && mAdapter != null && mAdapter.hasSearchQuery()) {
|
||||
mEmptyView.setContent(R.drawable.ic_home_search_line,
|
||||
getString(R.string.home_empty_sub_search_title),
|
||||
getString(R.string.home_empty_sub_search_body, mAdapter.getSearchQuery()));
|
||||
} else if (empty) {
|
||||
mEmptyView.setContent(R.drawable.ic_home_menu_spam,
|
||||
R.string.home_empty_spam_title,
|
||||
R.string.home_empty_spam_body);
|
||||
}
|
||||
mEmptyView.setVisibility(empty ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private class BlockedParticipantListAdapter extends CursorAdapter {
|
||||
private void confirmUnblock(final ParticipantListItemData data) {
|
||||
final Context context = getContext();
|
||||
if (context == null || data == null) {
|
||||
return;
|
||||
}
|
||||
final String name = data.getDisplayName();
|
||||
HomeModal.builder(context)
|
||||
.icon(R.drawable.ic_home_menu_spam)
|
||||
.title(R.string.home_spam_unblock_title)
|
||||
.message(context.getString(R.string.home_spam_unblock_message, name))
|
||||
.negative(android.R.string.cancel, null)
|
||||
.positive(R.string.home_spam_unblock_action, () -> data.unblock(context))
|
||||
.show();
|
||||
}
|
||||
|
||||
BlockedParticipantListAdapter(final Context context, final Cursor cursor) {
|
||||
super(context, cursor, 0);
|
||||
private final class SpamAdapter extends CursorRecyclerAdapter<SpamHolder> {
|
||||
private String mSearchQuery = "";
|
||||
private int[] mDisplayPositions = new int[0];
|
||||
|
||||
SpamAdapter(final Context context) {
|
||||
super(context, null, 0);
|
||||
setHasStableIds(true);
|
||||
}
|
||||
|
||||
void setSearchQuery(final String query) {
|
||||
final String next = query == null ? "" : query.trim();
|
||||
if (next.equals(mSearchQuery)) {
|
||||
return;
|
||||
}
|
||||
mSearchQuery = next;
|
||||
rebuildFilterIndex();
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
String getSearchQuery() {
|
||||
return mSearchQuery;
|
||||
}
|
||||
|
||||
boolean hasSearchQuery() {
|
||||
return !mSearchQuery.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public View newView(final Context context, final Cursor cursor, final ViewGroup parent) {
|
||||
return LayoutInflater.from(context).inflate(
|
||||
R.layout.blocked_participant_list_item_view, parent, false);
|
||||
public Cursor swapCursor(final Cursor newCursor) {
|
||||
final Cursor old = super.swapCursor(newCursor);
|
||||
rebuildFilterIndex();
|
||||
notifyDataSetChanged();
|
||||
return old;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindView(final View view, final Context context, final Cursor cursor) {
|
||||
Assert.isTrue(view instanceof BlockedParticipantListItemView);
|
||||
final BlockedParticipantListItemView item = (BlockedParticipantListItemView) view;
|
||||
item.bind(mBinding.getData().createParticipantListItemData(cursor));
|
||||
public int getItemCount() {
|
||||
return mDisplayPositions.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(final int position) {
|
||||
return super.getItemId(mapPosition(position));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull final SpamHolder holder, final int position) {
|
||||
super.onBindViewHolder(holder, mapPosition(position));
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpamHolder createViewHolder(final Context context, final ViewGroup parent,
|
||||
final int viewType) {
|
||||
final View item = LayoutInflater.from(context).inflate(
|
||||
R.layout.home_spam_list_item, parent, false);
|
||||
return new SpamHolder(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindViewHolder(final SpamHolder holder, final Context context,
|
||||
final Cursor cursor) {
|
||||
if (!mBinding.isBound()) {
|
||||
return;
|
||||
}
|
||||
holder.bind(mBinding.getData().createParticipantListItemData(cursor));
|
||||
}
|
||||
|
||||
private int mapPosition(final int position) {
|
||||
if (position < 0 || position >= mDisplayPositions.length) {
|
||||
return position;
|
||||
}
|
||||
return mDisplayPositions[position];
|
||||
}
|
||||
|
||||
private void rebuildFilterIndex() {
|
||||
final Cursor cursor = getCursor();
|
||||
if (cursor == null || cursor.getCount() == 0) {
|
||||
mDisplayPositions = new int[0];
|
||||
return;
|
||||
}
|
||||
final int saved = cursor.getPosition();
|
||||
final int[] matches = new int[cursor.getCount()];
|
||||
int count = 0;
|
||||
for (int i = 0; i < cursor.getCount(); i++) {
|
||||
if (!cursor.moveToPosition(i)) {
|
||||
continue;
|
||||
}
|
||||
final ParticipantListItemData data =
|
||||
mBinding.isBound() ? mBinding.getData().createParticipantListItemData(cursor)
|
||||
: null;
|
||||
if (data == null) {
|
||||
continue;
|
||||
}
|
||||
if (HomeSubListSearch.matches(mSearchQuery, data.getDisplayName(),
|
||||
data.getDetails(), data.getNormalizedDestination())) {
|
||||
matches[count++] = i;
|
||||
}
|
||||
}
|
||||
if (saved >= 0 && saved < cursor.getCount()) {
|
||||
cursor.moveToPosition(saved);
|
||||
}
|
||||
mDisplayPositions = new int[count];
|
||||
System.arraycopy(matches, 0, mDisplayPositions, 0, count);
|
||||
}
|
||||
|
||||
List<ParticipantListItemData> snapshotVisible() {
|
||||
return snapshotMatching(null);
|
||||
}
|
||||
|
||||
List<ParticipantListItemData> snapshotSelected(final Set<String> destinations) {
|
||||
return snapshotMatching(destinations);
|
||||
}
|
||||
|
||||
private List<ParticipantListItemData> snapshotMatching(final Set<String> destinations) {
|
||||
final List<ParticipantListItemData> out = new ArrayList<>();
|
||||
final Cursor cursor = getCursor();
|
||||
if (cursor == null || !mBinding.isBound()) {
|
||||
return out;
|
||||
}
|
||||
final int saved = cursor.getPosition();
|
||||
for (int i = 0; i < mDisplayPositions.length; i++) {
|
||||
if (!cursor.moveToPosition(mDisplayPositions[i])) {
|
||||
continue;
|
||||
}
|
||||
final ParticipantListItemData data =
|
||||
mBinding.getData().createParticipantListItemData(cursor);
|
||||
if (data == null) {
|
||||
continue;
|
||||
}
|
||||
if (destinations == null
|
||||
|| destinations.contains(data.getNormalizedDestination())) {
|
||||
out.add(data);
|
||||
}
|
||||
}
|
||||
if (saved >= 0 && saved < cursor.getCount()) {
|
||||
cursor.moveToPosition(saved);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
private final class SpamHolder extends RecyclerView.ViewHolder {
|
||||
private final ContactIconView mAvatar;
|
||||
private final TextView mName;
|
||||
private final TextView mDetails;
|
||||
private final View mUnblock;
|
||||
private final View mIndicator;
|
||||
private final View mRing;
|
||||
private final ImageView mCheck;
|
||||
private ParticipantListItemData mData;
|
||||
|
||||
SpamHolder(final View itemView) {
|
||||
super(itemView);
|
||||
mAvatar = itemView.findViewById(R.id.home_spam_avatar);
|
||||
mName = itemView.findViewById(R.id.home_spam_name);
|
||||
mDetails = itemView.findViewById(R.id.home_spam_details);
|
||||
mUnblock = itemView.findViewById(R.id.home_spam_unblock);
|
||||
mIndicator = itemView.findViewById(R.id.home_spam_selection_indicator);
|
||||
mRing = itemView.findViewById(R.id.home_spam_selection_ring);
|
||||
mCheck = itemView.findViewById(R.id.home_spam_selection_check);
|
||||
itemView.setOnClickListener(v -> onRowClicked(mData));
|
||||
mUnblock.setOnClickListener(v -> confirmUnblock(mData));
|
||||
}
|
||||
|
||||
void bind(final ParticipantListItemData data) {
|
||||
mData = data;
|
||||
mName.setText(data.getDisplayName());
|
||||
final String details = data.getDetails();
|
||||
if (TextUtils.isEmpty(details)
|
||||
|| TextUtils.equals(details, data.getDisplayName())) {
|
||||
mDetails.setText(R.string.home_spam_item_hint);
|
||||
} else {
|
||||
mDetails.setText(details);
|
||||
}
|
||||
mAvatar.setImageResourceUri(data.getAvatarUri(), data.getContactId(),
|
||||
data.getLookupKey(), data.getNormalizedDestination());
|
||||
mAvatar.setClickable(!mSelecting);
|
||||
mUnblock.setVisibility(mSelecting ? View.GONE : View.VISIBLE);
|
||||
if (mIndicator != null) {
|
||||
mIndicator.setVisibility(mSelecting ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
final boolean selected = mSelecting && isSelected(data);
|
||||
if (mRing != null) {
|
||||
mRing.setBackgroundResource(selected
|
||||
? R.drawable.home_bg_selection_ring_checked
|
||||
: R.drawable.home_bg_selection_ring);
|
||||
}
|
||||
if (mCheck != null) {
|
||||
mCheck.setVisibility(selected ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,363 @@
|
||||
package com.android.messaging.ui.home;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.app.Activity;
|
||||
import android.text.Editable;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.android.messaging.R;
|
||||
import com.android.messaging.util.ImeUtil;
|
||||
import com.android.messaging.util.UiUtils;
|
||||
|
||||
/** Binds the shared home-style search field on Spam / Archive / Trash lists. */
|
||||
public final class HomeSubListSearch {
|
||||
|
||||
private static final int ANIM_MS = 220;
|
||||
private static final int SCROLL_THRESHOLD_DP = 10;
|
||||
|
||||
public interface Listener {
|
||||
void onQueryChanged(String query);
|
||||
}
|
||||
|
||||
private static final class Chrome {
|
||||
View collapse;
|
||||
View search;
|
||||
EditText field;
|
||||
int naturalHeight;
|
||||
boolean shown;
|
||||
int accum;
|
||||
ValueAnimator heightAnimator;
|
||||
}
|
||||
|
||||
private HomeSubListSearch() {
|
||||
}
|
||||
|
||||
public static boolean matches(final String query, final String... fields) {
|
||||
if (TextUtils.isEmpty(query)) {
|
||||
return true;
|
||||
}
|
||||
final String needle = query.trim().toLowerCase();
|
||||
if (needle.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
final String digits = digitsOnly(needle);
|
||||
for (final String field : fields) {
|
||||
if (TextUtils.isEmpty(field)) {
|
||||
continue;
|
||||
}
|
||||
if (field.toLowerCase().contains(needle)) {
|
||||
return true;
|
||||
}
|
||||
if (!digits.isEmpty() && digitsOnly(field).contains(digits)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void bind(final Activity activity, final Listener listener) {
|
||||
bind(activity, null, listener);
|
||||
}
|
||||
|
||||
public static void bind(final Activity activity, final RecyclerView list,
|
||||
final Listener listener) {
|
||||
if (activity == null || listener == null) {
|
||||
return;
|
||||
}
|
||||
final EditText field = activity.findViewById(R.id.home_sub_search_field);
|
||||
final ImageButton clear = activity.findViewById(R.id.home_sub_search_clear);
|
||||
if (field == null) {
|
||||
return;
|
||||
}
|
||||
field.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(final CharSequence s, final int start, final int count,
|
||||
final int after) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(final CharSequence s, final int start, final int before,
|
||||
final int count) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(final Editable s) {
|
||||
final String query = s == null ? "" : s.toString();
|
||||
if (clear != null) {
|
||||
clear.setVisibility(TextUtils.isEmpty(query) ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
listener.onQueryChanged(query);
|
||||
}
|
||||
});
|
||||
field.setOnEditorActionListener((v, actionId, event) -> {
|
||||
if (actionId == EditorInfo.IME_ACTION_SEARCH || actionId == EditorInfo.IME_ACTION_DONE) {
|
||||
ImeUtil.get().hideImeKeyboard(activity, field);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
if (clear != null) {
|
||||
clear.setOnClickListener(v -> field.setText(""));
|
||||
}
|
||||
attachScrollChrome(activity, list);
|
||||
}
|
||||
|
||||
public static void bindListFades(final RecyclerView list, final View topFade,
|
||||
final View bottomFade) {
|
||||
if (list == null) {
|
||||
return;
|
||||
}
|
||||
final Runnable update = () -> {
|
||||
setFadeVisible(topFade, !isListFlushAtTop(list));
|
||||
setFadeVisible(bottomFade, !isListFlushAtBottom(list));
|
||||
};
|
||||
list.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||
@Override
|
||||
public void onScrolled(@NonNull final RecyclerView recyclerView, final int dx,
|
||||
final int dy) {
|
||||
update.run();
|
||||
}
|
||||
});
|
||||
list.addOnLayoutChangeListener((v, l, t, r, b, ol, ot, or, ob) -> update.run());
|
||||
list.post(update);
|
||||
}
|
||||
|
||||
private static void setFadeVisible(final View fade, final boolean show) {
|
||||
if (fade == null) {
|
||||
return;
|
||||
}
|
||||
final int visibility = show ? View.VISIBLE : View.GONE;
|
||||
if (fade.getVisibility() != visibility) {
|
||||
fade.setVisibility(visibility);
|
||||
}
|
||||
}
|
||||
|
||||
private static void attachScrollChrome(final Activity activity, final RecyclerView list) {
|
||||
if (activity == null || list == null) {
|
||||
return;
|
||||
}
|
||||
final View collapse = activity.findViewById(R.id.home_sub_search_collapse);
|
||||
final View search = activity.findViewById(R.id.home_sub_search_box);
|
||||
final EditText field = activity.findViewById(R.id.home_sub_search_field);
|
||||
if (collapse == null || search == null) {
|
||||
return;
|
||||
}
|
||||
final Chrome chrome = new Chrome();
|
||||
chrome.collapse = collapse;
|
||||
chrome.search = search;
|
||||
chrome.field = field;
|
||||
search.setVisibility(View.INVISIBLE);
|
||||
search.setAlpha(1f);
|
||||
setChromeInteractive(search, false);
|
||||
applyCollapseHeight(collapse, 0, false);
|
||||
collapse.addOnLayoutChangeListener((v, l, t, r, b, ol, ot, or, ob) -> {
|
||||
if (chrome.shown && v.getHeight() > 0) {
|
||||
chrome.naturalHeight = v.getHeight();
|
||||
}
|
||||
});
|
||||
if (field != null) {
|
||||
field.setOnFocusChangeListener((v, hasFocus) -> {
|
||||
if (hasFocus) {
|
||||
setCollapseShown(chrome, true, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
list.post(() -> applyChromeSlot(chrome, chrome.shown, false));
|
||||
list.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||
@Override
|
||||
public void onScrolled(@NonNull final RecyclerView recyclerView, final int dx,
|
||||
final int dy) {
|
||||
if (dy == 0 || isSearchPinned(chrome)) {
|
||||
return;
|
||||
}
|
||||
if (isScrolledToFirstItem(recyclerView) && dy < 0) {
|
||||
chrome.accum = 0;
|
||||
if (field != null) {
|
||||
ImeUtil.get().hideImeKeyboard(activity, field);
|
||||
}
|
||||
setCollapseShown(chrome, false, true);
|
||||
return;
|
||||
}
|
||||
chrome.accum += dy;
|
||||
final int threshold = dp(recyclerView, SCROLL_THRESHOLD_DP);
|
||||
if (chrome.accum > threshold) {
|
||||
chrome.accum = 0;
|
||||
setCollapseShown(chrome, true, true);
|
||||
} else if (chrome.accum < -threshold) {
|
||||
chrome.accum = 0;
|
||||
if (field != null) {
|
||||
ImeUtil.get().hideImeKeyboard(activity, field);
|
||||
}
|
||||
setCollapseShown(chrome, false, true);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static boolean isSearchPinned(final Chrome chrome) {
|
||||
if (chrome.field == null) {
|
||||
return false;
|
||||
}
|
||||
return chrome.field.hasFocus() || !TextUtils.isEmpty(chrome.field.getText());
|
||||
}
|
||||
|
||||
private static boolean isScrolledToFirstItem(final RecyclerView list) {
|
||||
final RecyclerView.LayoutManager manager = list.getLayoutManager();
|
||||
if (!(manager instanceof LinearLayoutManager)) {
|
||||
return !list.canScrollVertically(-1);
|
||||
}
|
||||
final int first = ((LinearLayoutManager) manager).findFirstCompletelyVisibleItemPosition();
|
||||
return first != RecyclerView.NO_POSITION && first <= 0;
|
||||
}
|
||||
|
||||
private static boolean isListFlushAtTop(final RecyclerView list) {
|
||||
if (list.getAdapter() == null || list.getAdapter().getItemCount() == 0) {
|
||||
return true;
|
||||
}
|
||||
final RecyclerView.LayoutManager manager = list.getLayoutManager();
|
||||
if (!(manager instanceof LinearLayoutManager)) {
|
||||
return !list.canScrollVertically(-1);
|
||||
}
|
||||
final LinearLayoutManager linear = (LinearLayoutManager) manager;
|
||||
if (linear.findFirstVisibleItemPosition() > 0) {
|
||||
return false;
|
||||
}
|
||||
final View first = linear.findViewByPosition(0);
|
||||
if (first == null) {
|
||||
return !list.canScrollVertically(-1);
|
||||
}
|
||||
return first.getTop() >= list.getPaddingTop() - 1;
|
||||
}
|
||||
|
||||
private static boolean isListFlushAtBottom(final RecyclerView list) {
|
||||
if (list.getAdapter() == null || list.getAdapter().getItemCount() == 0) {
|
||||
return true;
|
||||
}
|
||||
return !list.canScrollVertically(1);
|
||||
}
|
||||
|
||||
/** Messages chrome: search slides in a clipped slot; slot is 0 when hidden so the list is flush. */
|
||||
private static void setCollapseShown(final Chrome chrome, final boolean shown,
|
||||
final boolean animate) {
|
||||
if (chrome.shown == shown) {
|
||||
return;
|
||||
}
|
||||
chrome.shown = shown;
|
||||
applyChromeSlot(chrome, shown, animate);
|
||||
}
|
||||
|
||||
private static void applyChromeSlot(final Chrome chrome, final boolean searchShown,
|
||||
final boolean animate) {
|
||||
final int slot = resolveNaturalHeight(chrome);
|
||||
final float searchY = searchShown ? 0f : -slot;
|
||||
final int targetHeight = searchShown ? slot : 0;
|
||||
chrome.search.animate().cancel();
|
||||
if (chrome.heightAnimator != null) {
|
||||
chrome.heightAnimator.cancel();
|
||||
chrome.heightAnimator = null;
|
||||
}
|
||||
chrome.search.setAlpha(1f);
|
||||
setChromeInteractive(chrome.search, searchShown);
|
||||
if (!animate || slot <= 0) {
|
||||
chrome.search.setTranslationY(searchY);
|
||||
chrome.search.setVisibility(searchShown ? View.VISIBLE : View.INVISIBLE);
|
||||
applyCollapseHeight(chrome.collapse, targetHeight, searchShown);
|
||||
return;
|
||||
}
|
||||
chrome.search.setVisibility(View.VISIBLE);
|
||||
chrome.search.animate()
|
||||
.translationY(searchY)
|
||||
.setDuration(ANIM_MS)
|
||||
.setInterpolator(UiUtils.DEFAULT_INTERPOLATOR)
|
||||
.withEndAction(() -> {
|
||||
if (!chrome.shown) {
|
||||
chrome.search.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
});
|
||||
final int from = chrome.collapse.getHeight();
|
||||
final ValueAnimator va = ValueAnimator.ofInt(from, targetHeight);
|
||||
chrome.heightAnimator = va;
|
||||
va.setDuration(ANIM_MS);
|
||||
va.setInterpolator(UiUtils.DEFAULT_INTERPOLATOR);
|
||||
va.addUpdateListener(a -> applyCollapseHeight(chrome.collapse,
|
||||
(int) a.getAnimatedValue(), false));
|
||||
va.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(final Animator animation) {
|
||||
applyCollapseHeight(chrome.collapse, targetHeight, searchShown);
|
||||
chrome.heightAnimator = null;
|
||||
}
|
||||
});
|
||||
va.start();
|
||||
}
|
||||
|
||||
private static void applyCollapseHeight(final View collapse, final int height,
|
||||
final boolean wrap) {
|
||||
final ViewGroup.LayoutParams lp = collapse.getLayoutParams();
|
||||
if (lp == null) {
|
||||
return;
|
||||
}
|
||||
lp.height = wrap && height > 0 ? ViewGroup.LayoutParams.WRAP_CONTENT : height;
|
||||
collapse.setLayoutParams(lp);
|
||||
}
|
||||
|
||||
private static void setChromeInteractive(final View view, final boolean interactive) {
|
||||
if (view == null) {
|
||||
return;
|
||||
}
|
||||
view.setClickable(interactive);
|
||||
view.setImportantForAccessibility(interactive
|
||||
? View.IMPORTANT_FOR_ACCESSIBILITY_YES
|
||||
: View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
|
||||
if (view instanceof ViewGroup) {
|
||||
((ViewGroup) view).setDescendantFocusability(interactive
|
||||
? ViewGroup.FOCUS_AFTER_DESCENDANTS
|
||||
: ViewGroup.FOCUS_BLOCK_DESCENDANTS);
|
||||
}
|
||||
view.setOnTouchListener(interactive ? null : (v, event) -> true);
|
||||
}
|
||||
|
||||
private static int resolveNaturalHeight(final Chrome chrome) {
|
||||
if (chrome.naturalHeight > 0) {
|
||||
return chrome.naturalHeight;
|
||||
}
|
||||
if (chrome.search.getHeight() > 0) {
|
||||
chrome.naturalHeight = chrome.search.getHeight();
|
||||
return chrome.naturalHeight;
|
||||
}
|
||||
final int width = Math.max(chrome.collapse.getWidth(), chrome.search.getWidth());
|
||||
chrome.search.measure(
|
||||
View.MeasureSpec.makeMeasureSpec(Math.max(width, 0),
|
||||
width > 0 ? View.MeasureSpec.EXACTLY : View.MeasureSpec.UNSPECIFIED),
|
||||
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
|
||||
chrome.naturalHeight = chrome.search.getMeasuredHeight();
|
||||
return chrome.naturalHeight;
|
||||
}
|
||||
|
||||
private static int dp(final View view, final int dp) {
|
||||
return (int) (dp * view.getResources().getDisplayMetrics().density + 0.5f);
|
||||
}
|
||||
|
||||
private static String digitsOnly(final String value) {
|
||||
final StringBuilder out = new StringBuilder(value.length());
|
||||
for (int i = 0; i < value.length(); i++) {
|
||||
final char c = value.charAt(i);
|
||||
if (Character.isDigit(c)) {
|
||||
out.append(c);
|
||||
}
|
||||
}
|
||||
return out.toString();
|
||||
}
|
||||
}
|
||||
@@ -282,6 +282,97 @@ public final class HomeUiMotion {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Overlay chip swap used by Select/Edit ↔ round check (list, archive, spam).
|
||||
* Incoming pops in (scale + fade); outgoing shrinks out.
|
||||
*/
|
||||
public static void swapOverlayChip(final View outgoing, final View incoming) {
|
||||
if (incoming == null && outgoing == null) {
|
||||
return;
|
||||
}
|
||||
final boolean alreadyIn = incoming != null
|
||||
&& incoming.getVisibility() == View.VISIBLE
|
||||
&& incoming.getAlpha() > 0.99f
|
||||
&& outgoing != null
|
||||
&& outgoing.getVisibility() != View.VISIBLE;
|
||||
if (alreadyIn) {
|
||||
return;
|
||||
}
|
||||
if (!enabled()) {
|
||||
applyOverlayChipInstant(outgoing, incoming);
|
||||
return;
|
||||
}
|
||||
if (outgoing != null) {
|
||||
outgoing.setTag(R.id.home_sub_header_done, null);
|
||||
outgoing.animate().cancel();
|
||||
if (outgoing.getVisibility() == View.VISIBLE) {
|
||||
beginLayer(outgoing);
|
||||
outgoing.animate()
|
||||
.alpha(0f)
|
||||
.scaleX(0.6f)
|
||||
.scaleY(0.6f)
|
||||
.setDuration(DOCK_SWAP_MS)
|
||||
.setInterpolator(EASE_MATERIAL)
|
||||
.withEndAction(() -> {
|
||||
outgoing.setVisibility(View.INVISIBLE);
|
||||
outgoing.setAlpha(1f);
|
||||
outgoing.setScaleX(1f);
|
||||
outgoing.setScaleY(1f);
|
||||
endLayer(outgoing);
|
||||
})
|
||||
.start();
|
||||
} else if (outgoing.getVisibility() != View.INVISIBLE) {
|
||||
outgoing.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
}
|
||||
if (incoming != null) {
|
||||
incoming.animate().cancel();
|
||||
incoming.setVisibility(View.VISIBLE);
|
||||
incoming.setAlpha(0f);
|
||||
incoming.setScaleX(0.6f);
|
||||
incoming.setScaleY(0.6f);
|
||||
beginLayer(incoming);
|
||||
final Runnable play = () -> incoming.animate()
|
||||
.alpha(1f)
|
||||
.scaleX(1f)
|
||||
.scaleY(1f)
|
||||
.setDuration(DOCK_SWAP_MS)
|
||||
.setInterpolator(EASE_MATERIAL)
|
||||
.withEndAction(() -> endLayer(incoming))
|
||||
.start();
|
||||
if (incoming.getWidth() == 0) {
|
||||
final Object token = new Object();
|
||||
incoming.setTag(R.id.home_sub_header_done, token);
|
||||
incoming.post(() -> {
|
||||
if (incoming.getTag(R.id.home_sub_header_done) != token) {
|
||||
return;
|
||||
}
|
||||
play.run();
|
||||
});
|
||||
} else {
|
||||
incoming.setTag(R.id.home_sub_header_done, null);
|
||||
play.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void applyOverlayChipInstant(final View outgoing, final View incoming) {
|
||||
if (incoming != null) {
|
||||
incoming.animate().cancel();
|
||||
incoming.setVisibility(View.VISIBLE);
|
||||
incoming.setAlpha(1f);
|
||||
incoming.setScaleX(1f);
|
||||
incoming.setScaleY(1f);
|
||||
}
|
||||
if (outgoing != null && outgoing != incoming) {
|
||||
outgoing.animate().cancel();
|
||||
outgoing.setVisibility(View.INVISIBLE);
|
||||
outgoing.setAlpha(1f);
|
||||
outgoing.setScaleX(1f);
|
||||
outgoing.setScaleY(1f);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Swaps two headers that share a vertical container, so they are never both laid out.
|
||||
*/
|
||||
@@ -429,6 +520,71 @@ public final class HomeUiMotion {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Spam / Archive / Trash selection dock. Animates the inner pill after the overlay has
|
||||
* laid out so a GONE→VISIBLE snap does not cut the scale+fade short.
|
||||
*/
|
||||
public static void swapSubListDock(final View bar, final boolean selectionActive) {
|
||||
if (bar == null) {
|
||||
return;
|
||||
}
|
||||
final View pill = bar.findViewById(R.id.home_sub_select_pill);
|
||||
final View target = pill != null ? pill : bar;
|
||||
target.animate().cancel();
|
||||
bar.setTag(R.id.home_sub_select_bar, null);
|
||||
if (!enabled()) {
|
||||
bar.setVisibility(selectionActive ? View.VISIBLE : View.GONE);
|
||||
resetDockTransform(target);
|
||||
return;
|
||||
}
|
||||
if (selectionActive) {
|
||||
final boolean showing = bar.getVisibility() == View.VISIBLE
|
||||
&& target.getAlpha() > 0.99f
|
||||
&& target.getScaleX() > 0.99f;
|
||||
if (showing) {
|
||||
return;
|
||||
}
|
||||
bar.setVisibility(View.VISIBLE);
|
||||
target.setAlpha(0f);
|
||||
target.setScaleX(0.86f);
|
||||
target.setScaleY(0.86f);
|
||||
beginLayer(target);
|
||||
final Object token = new Object();
|
||||
bar.setTag(R.id.home_sub_select_bar, token);
|
||||
bar.post(() -> {
|
||||
if (bar.getTag(R.id.home_sub_select_bar) != token) {
|
||||
return;
|
||||
}
|
||||
target.animate()
|
||||
.alpha(1f)
|
||||
.scaleX(1f)
|
||||
.scaleY(1f)
|
||||
.setDuration(DOCK_SWAP_MS)
|
||||
.setInterpolator(EASE_MATERIAL)
|
||||
.withEndAction(() -> endLayer(target))
|
||||
.start();
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (bar.getVisibility() != View.VISIBLE) {
|
||||
resetDockTransform(target);
|
||||
return;
|
||||
}
|
||||
beginLayer(target);
|
||||
target.animate()
|
||||
.alpha(0f)
|
||||
.scaleX(0.86f)
|
||||
.scaleY(0.86f)
|
||||
.setDuration(DOCK_SWAP_MS)
|
||||
.setInterpolator(EASE_MATERIAL)
|
||||
.withEndAction(() -> {
|
||||
bar.setVisibility(View.GONE);
|
||||
resetDockTransform(target);
|
||||
endLayer(target);
|
||||
})
|
||||
.start();
|
||||
}
|
||||
|
||||
/** Main bottom nav ↔ selection action dock; both sit stacked at the bottom of a frame. */
|
||||
public static void swapBottomDock(final View mainNav, final View selectionBar,
|
||||
final boolean selectionActive, final Runnable onEnd) {
|
||||
@@ -445,6 +601,23 @@ public final class HomeUiMotion {
|
||||
}
|
||||
return;
|
||||
}
|
||||
final boolean barVisible = selectionBar != null
|
||||
&& selectionBar.getVisibility() == View.VISIBLE
|
||||
&& selectionBar.getAlpha() > 0.99f;
|
||||
if (selectionActive && barVisible) {
|
||||
if (onEnd != null) {
|
||||
onEnd.run();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!selectionActive && (selectionBar == null
|
||||
|| selectionBar.getVisibility() != View.VISIBLE)
|
||||
&& (mainNav == null || mainNav.getVisibility() == View.VISIBLE)) {
|
||||
if (onEnd != null) {
|
||||
onEnd.run();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (mainNav != null) {
|
||||
mainNav.animate().cancel();
|
||||
}
|
||||
@@ -456,14 +629,17 @@ public final class HomeUiMotion {
|
||||
|
||||
if (selectionActive) {
|
||||
if (mainNav != null && mainNav.getVisibility() == View.VISIBLE) {
|
||||
beginLayer(mainNav);
|
||||
mainNav.animate()
|
||||
.alpha(0f)
|
||||
.scaleX(0.88f)
|
||||
.scaleY(0.88f)
|
||||
.setDuration(DOCK_SWAP_MS)
|
||||
.setInterpolator(EASE_MATERIAL)
|
||||
.withEndAction(() -> {
|
||||
mainNav.setVisibility(View.GONE);
|
||||
mainNav.setAlpha(1f);
|
||||
mainNav.setTranslationY(0f);
|
||||
resetDockTransform(mainNav);
|
||||
endLayer(mainNav);
|
||||
})
|
||||
.start();
|
||||
} else if (mainNav != null) {
|
||||
@@ -472,26 +648,41 @@ public final class HomeUiMotion {
|
||||
if (selectionBar != null) {
|
||||
selectionBar.setVisibility(View.VISIBLE);
|
||||
selectionBar.setAlpha(0f);
|
||||
selectionBar.setTranslationY(0f);
|
||||
selectionBar.animate()
|
||||
selectionBar.setScaleX(0.86f);
|
||||
selectionBar.setScaleY(0.86f);
|
||||
beginLayer(selectionBar);
|
||||
final Runnable play = () -> selectionBar.animate()
|
||||
.alpha(1f)
|
||||
.scaleX(1f)
|
||||
.scaleY(1f)
|
||||
.setDuration(DOCK_SWAP_MS)
|
||||
.setInterpolator(EASE_MATERIAL)
|
||||
.withEndAction(finish)
|
||||
.withEndAction(() -> {
|
||||
endLayer(selectionBar);
|
||||
finish.run();
|
||||
})
|
||||
.start();
|
||||
if (selectionBar.getWidth() == 0) {
|
||||
selectionBar.post(play);
|
||||
} else {
|
||||
play.run();
|
||||
}
|
||||
} else {
|
||||
finish.run();
|
||||
}
|
||||
} else {
|
||||
if (selectionBar != null && selectionBar.getVisibility() == View.VISIBLE) {
|
||||
beginLayer(selectionBar);
|
||||
selectionBar.animate()
|
||||
.alpha(0f)
|
||||
.scaleX(0.86f)
|
||||
.scaleY(0.86f)
|
||||
.setDuration(DOCK_SWAP_MS)
|
||||
.setInterpolator(EASE_MATERIAL)
|
||||
.withEndAction(() -> {
|
||||
selectionBar.setVisibility(View.GONE);
|
||||
selectionBar.setAlpha(1f);
|
||||
selectionBar.setTranslationY(0f);
|
||||
resetDockTransform(selectionBar);
|
||||
endLayer(selectionBar);
|
||||
})
|
||||
.start();
|
||||
} else if (selectionBar != null) {
|
||||
@@ -500,12 +691,19 @@ public final class HomeUiMotion {
|
||||
if (mainNav != null) {
|
||||
mainNav.setVisibility(View.VISIBLE);
|
||||
mainNav.setAlpha(0f);
|
||||
mainNav.setTranslationY(0f);
|
||||
mainNav.setScaleX(0.88f);
|
||||
mainNav.setScaleY(0.88f);
|
||||
beginLayer(mainNav);
|
||||
mainNav.animate()
|
||||
.alpha(1f)
|
||||
.scaleX(1f)
|
||||
.scaleY(1f)
|
||||
.setDuration(DOCK_SWAP_MS)
|
||||
.setInterpolator(EASE_MATERIAL)
|
||||
.withEndAction(finish)
|
||||
.withEndAction(() -> {
|
||||
endLayer(mainNav);
|
||||
finish.run();
|
||||
})
|
||||
.start();
|
||||
} else {
|
||||
finish.run();
|
||||
@@ -731,16 +929,21 @@ public final class HomeUiMotion {
|
||||
final boolean selectionActive) {
|
||||
if (mainNav != null) {
|
||||
mainNav.setVisibility(selectionActive ? View.GONE : View.VISIBLE);
|
||||
mainNav.setAlpha(1f);
|
||||
mainNav.setTranslationY(0f);
|
||||
resetDockTransform(mainNav);
|
||||
}
|
||||
if (selectionBar != null) {
|
||||
selectionBar.setVisibility(selectionActive ? View.VISIBLE : View.GONE);
|
||||
selectionBar.setAlpha(1f);
|
||||
selectionBar.setTranslationY(0f);
|
||||
resetDockTransform(selectionBar);
|
||||
}
|
||||
}
|
||||
|
||||
private static void resetDockTransform(final View view) {
|
||||
view.setAlpha(1f);
|
||||
view.setTranslationY(0f);
|
||||
view.setScaleX(1f);
|
||||
view.setScaleY(1f);
|
||||
}
|
||||
|
||||
public static void popoverResetInstant(final View content) {
|
||||
if (content == null) {
|
||||
return;
|
||||
|
||||
@@ -8,6 +8,7 @@ import android.text.TextUtils;
|
||||
import com.android.messaging.R;
|
||||
import com.android.messaging.datamodel.data.ParticipantData;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public final class SimNameUtil {
|
||||
@@ -25,28 +26,45 @@ public final class SimNameUtil {
|
||||
}
|
||||
}
|
||||
|
||||
public static String getSimDisplayName(final Context context, final String selfId) {
|
||||
List<SubscriptionInfo> infos;
|
||||
public static List<SubscriptionInfo> getActiveSubscriptions() {
|
||||
try {
|
||||
infos = PhoneUtils.getDefault().getActiveSubscriptionInfoList();
|
||||
final List<SubscriptionInfo> infos =
|
||||
PhoneUtils.getDefault().getActiveSubscriptionInfoList();
|
||||
if (infos == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return infos;
|
||||
} catch (final Exception ignored) {
|
||||
infos = null;
|
||||
return Collections.emptyList();
|
||||
}
|
||||
if (infos == null || infos.isEmpty()) {
|
||||
return context.getString(R.string.sim_slot_identifier, "1");
|
||||
}
|
||||
|
||||
public static SubscriptionInfo getSubscriptionForSelfId(final String selfId) {
|
||||
final List<SubscriptionInfo> infos = getActiveSubscriptions();
|
||||
if (infos.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
SubscriptionInfo match = infos.get(0);
|
||||
if (infos.size() > 1 && !TextUtils.isEmpty(selfId)) {
|
||||
if (!TextUtils.isEmpty(selfId)) {
|
||||
for (final SubscriptionInfo info : infos) {
|
||||
final String participantId =
|
||||
ParticipantData.getCachedParticipantId(info.getSubscriptionId());
|
||||
if (TextUtils.equals(participantId, selfId)) {
|
||||
match = info;
|
||||
break;
|
||||
return info;
|
||||
}
|
||||
}
|
||||
}
|
||||
return formatName(context, match);
|
||||
return infos.get(0);
|
||||
}
|
||||
|
||||
public static String getDisplayName(final Context context, final SubscriptionInfo info) {
|
||||
if (info == null) {
|
||||
return context.getString(R.string.sim_slot_identifier, "1");
|
||||
}
|
||||
return formatName(context, info);
|
||||
}
|
||||
|
||||
public static String getSimDisplayName(final Context context, final String selfId) {
|
||||
return getDisplayName(context, getSubscriptionForSelfId(selfId));
|
||||
}
|
||||
|
||||
private static final int NAME_SOURCE_USER_INPUT = 2;
|
||||
|
||||
@@ -68,7 +68,7 @@ public class WidgetConversationListService extends RemoteViewsService {
|
||||
protected Cursor doQuery() {
|
||||
return mContext.getContentResolver().query(MessagingContentProvider.CONVERSATIONS_URI,
|
||||
ConversationListItemData.PROJECTION,
|
||||
ConversationListData.WHERE_NOT_ARCHIVED,
|
||||
ConversationListData.WHERE_INBOX,
|
||||
null, // selection args
|
||||
ConversationListData.SORT_ORDER);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user