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:
@@ -146,6 +146,14 @@
|
||||
<activity
|
||||
android:name=".ui.conversationlist.ArchivedConversationListActivity"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden"
|
||||
android:label="@string/home_archived_title"
|
||||
android:parentActivityName="com.android.messaging.ui.conversationlist.ConversationListActivity"
|
||||
android:screenOrientation="user"
|
||||
android:theme="@style/Home.SubScreen" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.conversationlist.RecentlyDeletedConversationListActivity"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden"
|
||||
android:label="@string/home_recently_deleted_title"
|
||||
android:parentActivityName="com.android.messaging.ui.conversationlist.ConversationListActivity"
|
||||
android:screenOrientation="user"
|
||||
@@ -332,6 +340,14 @@
|
||||
android:theme="@style/Home.SubScreen"
|
||||
android:windowSoftInputMode="adjustResize|stateVisible" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.home.HomeMessageDetailsActivity"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden"
|
||||
android:label="@string/home_message_details_title"
|
||||
android:parentActivityName="com.android.messaging.ui.conversation.ConversationActivity"
|
||||
android:screenOrientation="user"
|
||||
android:theme="@style/Home.SubScreen" />
|
||||
|
||||
<!-- License -->
|
||||
<activity
|
||||
android:name=".ui.LicenseActivity"
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
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) {
|
||||
|
||||
+150
-42
@@ -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();
|
||||
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
|
||||
archiveLabel.setText(trashed
|
||||
? R.string.home_archive_restore
|
||||
: (archived
|
||||
? R.string.home_profile_option_unarchive
|
||||
: R.string.home_profile_option_archive);
|
||||
: 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(
|
||||
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))
|
||||
.setPositiveButton(R.string.delete_conversation_confirmation_button,
|
||||
(dialog, button) -> deleteConversation())
|
||||
.setNegativeButton(R.string.delete_conversation_decline_button, null)
|
||||
.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*/,
|
||||
|
||||
+286
-44
@@ -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();
|
||||
if (isTrashList()) {
|
||||
for (final SelectedConversation conversation : conversations) {
|
||||
DeleteConversationAction.deleteConversation(
|
||||
conversation.conversationId,
|
||||
conversation.timestamp);
|
||||
}
|
||||
showDeleteCompletedToast(count, reportSpam);
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
HomeToast.show(this, 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 */);
|
||||
: 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);
|
||||
}
|
||||
|
||||
|
||||
+516
-52
@@ -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) {
|
||||
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);
|
||||
HomeUiMotion.swapOverlayChip(mHomePinnedDoneButton, mHomeEditButton);
|
||||
if (mHomeEditButton != null) {
|
||||
mHomeEditButton.setText(R.string.home_edit);
|
||||
mHomeEditButton.setOnClickListener(this::showHomeEditPopover);
|
||||
if (mHomePinnedDoneButton != null) {
|
||||
mHomePinnedDoneButton.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void refreshPinnedStrip() {
|
||||
if (mHomePinnedStrip == null || mArchiveMode || mForwardMessageMode
|
||||
if (mHomePinnedStrip == null || isSubList()
|
||||
|| mHomeTab != HOME_TAB_MESSAGES) {
|
||||
mAdapter.setPinnedHeaderVisible(false);
|
||||
return;
|
||||
|
||||
+54
@@ -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();
|
||||
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,13 +544,25 @@ public class HomeProfileActivity extends BugleActionBarActivity
|
||||
}
|
||||
|
||||
private void confirmDelete() {
|
||||
new AlertDialog.Builder(this, R.style.Home_Dialog)
|
||||
.setTitle(getResources().getQuantityString(
|
||||
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))
|
||||
.setNegativeButton(R.string.delete_conversation_decline_button, null)
|
||||
.setPositiveButton(R.string.delete_conversation_confirmation_button,
|
||||
(dialog, which) -> {
|
||||
.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,15 +34,61 @@ 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);
|
||||
}
|
||||
final TextView title = findViewById(R.id.home_sub_header_title);
|
||||
if (title != null) {
|
||||
title.setText(R.string.home_spam_title);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
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() {
|
||||
|
||||
@@ -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) {
|
||||
if (mAdapter != null) {
|
||||
mAdapter.swapCursor(cursor);
|
||||
final boolean empty = mAdapter.getCount() == 0;
|
||||
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");
|
||||
}
|
||||
SubscriptionInfo match = infos.get(0);
|
||||
if (infos.size() > 1 && !TextUtils.isEmpty(selfId)) {
|
||||
|
||||
public static SubscriptionInfo getSubscriptionForSelfId(final String selfId) {
|
||||
final List<SubscriptionInfo> infos = getActiveSubscriptions();
|
||||
if (infos.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/home_surface" />
|
||||
<corners android:radius="@dimen/looply_radius_lg" />
|
||||
<solid android:color="@color/home_row_bg" />
|
||||
</shape>
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape android:shape="oval">
|
||||
<solid android:color="@color/home_bg" />
|
||||
</shape>
|
||||
</item>
|
||||
<item
|
||||
android:bottom="1.5dp"
|
||||
android:left="1.5dp"
|
||||
android:right="1.5dp"
|
||||
android:top="1.5dp">
|
||||
<shape android:shape="oval">
|
||||
<solid android:color="@color/home_sim_badge_bg" />
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="@color/home_surface_elevated">
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="@color/home_chip_bg" />
|
||||
<corners android:radius="@dimen/looply_radius_pill" />
|
||||
</shape>
|
||||
</item>
|
||||
</ripple>
|
||||
@@ -141,6 +141,8 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<include layout="@layout/home_conversation_trash_bar" />
|
||||
|
||||
<include layout="@layout/home_conversation_message_action_bar" />
|
||||
</FrameLayout>
|
||||
</com.android.messaging.ui.conversation.ConversationChatAreaLayout>
|
||||
|
||||
@@ -400,41 +400,70 @@
|
||||
android:layout_gravity="bottom"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:gravity="center_vertical"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="@dimen/looply_space_md"
|
||||
android:paddingEnd="@dimen/looply_space_md"
|
||||
android:paddingBottom="@dimen/looply_space_md"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/home_read_all_button"
|
||||
<FrameLayout
|
||||
android:id="@+id/home_selection_actions"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/home_selection_dock_height"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:background="@drawable/home_bg_dock_pill"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:elevation="6dp"
|
||||
android:gravity="center"
|
||||
android:minWidth="120dp"
|
||||
android:outlineAmbientShadowColor="@color/home_pill_shadow_ambient"
|
||||
android:outlineSpotShadowColor="@color/home_pill_shadow_spot"
|
||||
android:paddingStart="20dp"
|
||||
android:paddingEnd="20dp"
|
||||
android:paddingBottom="@dimen/home_selection_dock_shadow_inset"
|
||||
android:text="@string/home_read_all"
|
||||
android:textColor="@color/home_text_primary"
|
||||
android:textSize="15sp" />
|
||||
android:paddingStart="@dimen/home_nav_item_padding"
|
||||
android:paddingTop="@dimen/home_nav_item_padding"
|
||||
android:paddingEnd="@dimen/home_nav_item_padding"
|
||||
android:paddingBottom="@dimen/home_nav_dock_padding_bottom">
|
||||
|
||||
<View
|
||||
android:id="@+id/home_selection_indicator"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1" />
|
||||
android:background="@drawable/home_bg_nav_active_primary" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/home_selection_tabs"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:baselineAligned="false"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<include
|
||||
android:id="@+id/home_selection_unread"
|
||||
layout="@layout/home_bottom_nav_tab_item"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical" />
|
||||
|
||||
<include
|
||||
android:id="@+id/home_selection_spam"
|
||||
layout="@layout/home_bottom_nav_tab_item"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical" />
|
||||
|
||||
<include
|
||||
android:id="@+id/home_selection_archive"
|
||||
layout="@layout/home_bottom_nav_tab_item"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical" />
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/home_selection_delete"
|
||||
android:layout_width="@dimen/home_selection_dock_height"
|
||||
android:layout_height="@dimen/home_selection_dock_height"
|
||||
android:layout_width="@dimen/home_bottom_dock_height"
|
||||
android:layout_height="@dimen/home_bottom_dock_height"
|
||||
android:background="@drawable/home_bg_dock_fab"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
@@ -443,7 +472,10 @@
|
||||
android:enabled="false"
|
||||
android:outlineAmbientShadowColor="@color/home_pill_shadow_ambient"
|
||||
android:outlineSpotShadowColor="@color/home_pill_shadow_spot"
|
||||
android:paddingBottom="@dimen/home_dock_fab_icon_lift"
|
||||
android:paddingStart="3dp"
|
||||
android:paddingTop="2dp"
|
||||
android:paddingEnd="3dp"
|
||||
android:paddingBottom="@dimen/home_bottom_dock_shadow_inset"
|
||||
android:scaleType="centerInside"
|
||||
app:srcCompat="@drawable/ic_delete_small_dark"
|
||||
app:tint="@color/home_nav_inactive" />
|
||||
|
||||
@@ -151,7 +151,9 @@
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
android:layout_height="wrap_content"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false">
|
||||
<com.android.messaging.ui.ContactIconView
|
||||
android:id="@+id/conversation_icon"
|
||||
android:layout_width="@dimen/home_list_avatar_size"
|
||||
@@ -179,6 +181,18 @@
|
||||
android:importantForAccessibility="no"
|
||||
android:contentDescription="@null"
|
||||
android:background="@android:color/transparent" />
|
||||
<ImageView
|
||||
android:id="@+id/conversation_mute_badge"
|
||||
android:layout_width="@dimen/home_mute_badge_size"
|
||||
android:layout_height="@dimen/home_mute_badge_size"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:background="@drawable/home_bg_mute_badge"
|
||||
android:contentDescription="@string/home_conversation_muted_toast"
|
||||
android:padding="3.5dp"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/ic_home_bell_off"
|
||||
android:tint="@color/home_sim_badge_text"
|
||||
android:visibility="gone" />
|
||||
</FrameLayout>
|
||||
|
||||
<LinearLayout
|
||||
|
||||
@@ -3,25 +3,39 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/home_bg"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:fitsSystemWindows="true"
|
||||
android:orientation="vertical">
|
||||
|
||||
<include layout="@layout/home_sub_header" />
|
||||
|
||||
<TextView
|
||||
<FrameLayout
|
||||
android:id="@+id/home_sub_search_collapse"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="@dimen/looply_screen_padding"
|
||||
android:paddingEnd="@dimen/looply_screen_padding"
|
||||
android:paddingBottom="@dimen/looply_space_sm"
|
||||
android:text="@string/home_recently_deleted_subtitle"
|
||||
android:textColor="@color/home_text_secondary"
|
||||
android:textSize="14sp" />
|
||||
android:layout_height="0dp"
|
||||
android:background="@color/home_bg"
|
||||
android:clipChildren="true"
|
||||
android:clipToPadding="true">
|
||||
|
||||
<include layout="@layout/home_sub_search_field" />
|
||||
</FrameLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:background="@color/home_bg"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/home_archived_list_host"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:background="@color/home_bg" />
|
||||
android:layout_height="match_parent"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false" />
|
||||
|
||||
<include layout="@layout/home_sub_select_bar" />
|
||||
</FrameLayout>
|
||||
</LinearLayout>
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/home_conversation_trash_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_horizontal"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="@dimen/looply_space_lg"
|
||||
android:paddingTop="@dimen/looply_space_md"
|
||||
android:paddingEnd="@dimen/looply_space_lg"
|
||||
android:paddingBottom="@dimen/looply_space_lg"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:includeFontPadding="false"
|
||||
android:lineSpacingExtra="3dp"
|
||||
android:text="@string/home_trash_compose_message"
|
||||
android:textColor="@color/home_text_secondary"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/home_conversation_trash_restore"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/home_modal_button_height"
|
||||
android:layout_marginTop="@dimen/looply_space_md"
|
||||
android:background="@drawable/home_bg_modal_primary"
|
||||
android:gravity="center"
|
||||
android:includeFontPadding="false"
|
||||
android:text="@string/home_archive_restore"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="15sp"
|
||||
android:textStyle="bold" />
|
||||
</LinearLayout>
|
||||
@@ -1,44 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/home_bg_alert_dialog"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="@dimen/looply_space_lg"
|
||||
android:paddingTop="@dimen/looply_space_xl"
|
||||
android:paddingEnd="@dimen/looply_space_lg"
|
||||
android:paddingBottom="@dimen/looply_space_lg">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/home_delete_alert_message"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:lineSpacingExtra="4dp"
|
||||
android:textColor="@color/home_text_primary"
|
||||
android:textSize="17sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/home_delete_alert_delete"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/looply_button_height"
|
||||
android:layout_marginTop="@dimen/looply_space_xl"
|
||||
android:background="@drawable/home_bg_sheet_action"
|
||||
android:gravity="center"
|
||||
android:text="@string/home_delete_confirm"
|
||||
android:textColor="@color/home_destructive"
|
||||
android:textSize="17sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/home_delete_alert_delete_spam"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/looply_button_height"
|
||||
android:layout_marginTop="@dimen/looply_space_sm"
|
||||
android:background="@drawable/home_bg_sheet_action"
|
||||
android:gravity="center"
|
||||
android:text="@string/home_delete_confirm_spam"
|
||||
android:textColor="@color/home_destructive"
|
||||
android:textSize="17sp"
|
||||
android:textStyle="bold" />
|
||||
</LinearLayout>
|
||||
@@ -5,17 +5,20 @@
|
||||
android:gravity="center_horizontal"
|
||||
android:orientation="vertical">
|
||||
|
||||
<FrameLayout
|
||||
<LinearLayout
|
||||
android:id="@+id/home_lock_prompt"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.android.messaging.ui.home.HomePatternLockView
|
||||
android:id="@+id/home_lock_pattern"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center" />
|
||||
</FrameLayout>
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/looply_space_lg" />
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/home_lock_pattern_recorded"
|
||||
|
||||
@@ -2,20 +2,27 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center_horizontal"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/home_lock_prompt"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<include
|
||||
android:id="@+id/home_lock_pin_dots"
|
||||
layout="@layout/home_lock_pin_dots"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/looply_space_lg" />
|
||||
</LinearLayout>
|
||||
|
||||
<include
|
||||
android:id="@+id/home_lock_pin_keypad"
|
||||
layout="@layout/home_lock_pin_keypad"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="@dimen/looply_space_xl"
|
||||
android:layout_weight="1" />
|
||||
android:layout_height="wrap_content" />
|
||||
</LinearLayout>
|
||||
|
||||
@@ -80,28 +80,6 @@
|
||||
android:text="@string/home_message_menu_star" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/home_message_menu_delete"
|
||||
style="@style/Home.PopoverItem"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="22dp"
|
||||
android:layout_height="22dp"
|
||||
android:contentDescription="@null"
|
||||
android:importantForAccessibility="no"
|
||||
app:srcCompat="@drawable/ic_home_menu_trash"
|
||||
app:tint="@color/home_text_primary" />
|
||||
|
||||
<TextView
|
||||
style="@style/Home.PopoverLabel"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="14dp"
|
||||
android:text="@string/home_message_menu_delete" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/home_message_menu_select_more"
|
||||
style="@style/Home.PopoverItem"
|
||||
|
||||
@@ -0,0 +1,216 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/home_bg"
|
||||
android:fitsSystemWindows="true"
|
||||
android:orientation="vertical">
|
||||
|
||||
<include layout="@layout/home_sub_header" />
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:clipToPadding="false"
|
||||
android:paddingStart="@dimen/looply_space_lg"
|
||||
android:paddingTop="@dimen/looply_space_md"
|
||||
android:paddingEnd="@dimen/looply_space_lg"
|
||||
android:paddingBottom="@dimen/looply_screen_padding"
|
||||
android:scrollbars="none">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/home_bg_details_card"
|
||||
android:padding="@dimen/looply_space_lg">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/home_message_details_bubble"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="start"
|
||||
android:background="@drawable/home_bg_preview_bubble_in"
|
||||
android:includeFontPadding="false"
|
||||
android:lineSpacingMultiplier="1.15"
|
||||
android:maxWidth="@dimen/looply_bubble_max_width"
|
||||
android:paddingStart="@dimen/looply_bubble_padding_h"
|
||||
android:paddingTop="@dimen/looply_bubble_padding_v"
|
||||
android:paddingEnd="@dimen/looply_bubble_padding_h"
|
||||
android:paddingBottom="@dimen/looply_bubble_padding_v"
|
||||
android:textColor="@color/home_text_primary"
|
||||
android:textSize="15sp" />
|
||||
</FrameLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/home_message_details_status_section"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/looply_space_lg"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
style="@style/Home.SettingsSection"
|
||||
android:text="@string/home_message_details_status" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/home_bg_details_card"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/home_message_details_received_row"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:minHeight="52dp"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="@dimen/looply_space_lg"
|
||||
android:paddingEnd="@dimen/looply_space_lg">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:includeFontPadding="false"
|
||||
android:text="@string/home_message_details_received"
|
||||
android:textColor="@color/home_text_primary"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/home_message_details_received"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:includeFontPadding="false"
|
||||
android:textColor="@color/home_text_secondary"
|
||||
android:textSize="14sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:id="@+id/home_message_details_status_divider"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_marginStart="@dimen/looply_space_lg"
|
||||
android:background="@color/home_divider" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/home_message_details_sent_row"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:minHeight="52dp"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="@dimen/looply_space_lg"
|
||||
android:paddingEnd="@dimen/looply_space_lg">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:includeFontPadding="false"
|
||||
android:text="@string/home_message_details_sent"
|
||||
android:textColor="@color/home_text_primary"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/home_message_details_sent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:includeFontPadding="false"
|
||||
android:textColor="@color/home_text_secondary"
|
||||
android:textSize="14sp" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
style="@style/Home.SettingsSection"
|
||||
android:layout_marginTop="@dimen/looply_space_lg"
|
||||
android:text="@string/home_message_details_type" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/home_message_details_type"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:includeFontPadding="false"
|
||||
android:paddingStart="@dimen/looply_space_md"
|
||||
android:paddingEnd="@dimen/looply_space_md"
|
||||
android:textColor="@color/home_text_primary"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/home_message_details_priority_section"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
style="@style/Home.SettingsSection"
|
||||
android:layout_marginTop="@dimen/looply_space_lg"
|
||||
android:text="@string/home_message_details_priority" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/home_message_details_priority"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:includeFontPadding="false"
|
||||
android:paddingStart="@dimen/looply_space_md"
|
||||
android:paddingEnd="@dimen/looply_space_md"
|
||||
android:textColor="@color/home_text_primary"
|
||||
android:textSize="16sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/home_message_details_subject_section"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
style="@style/Home.SettingsSection"
|
||||
android:layout_marginTop="@dimen/looply_space_lg"
|
||||
android:text="@string/home_message_details_subject" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/home_message_details_subject"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:includeFontPadding="false"
|
||||
android:paddingStart="@dimen/looply_space_md"
|
||||
android:paddingEnd="@dimen/looply_space_md"
|
||||
android:textColor="@color/home_text_primary"
|
||||
android:textSize="16sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/home_message_details_size_section"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
style="@style/Home.SettingsSection"
|
||||
android:layout_marginTop="@dimen/looply_space_lg"
|
||||
android:text="@string/home_message_details_size" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/home_message_details_size"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:includeFontPadding="false"
|
||||
android:paddingStart="@dimen/looply_space_md"
|
||||
android:paddingEnd="@dimen/looply_space_md"
|
||||
android:textColor="@color/home_text_primary"
|
||||
android:textSize="16sp" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
</LinearLayout>
|
||||
@@ -95,6 +95,18 @@
|
||||
android:textStyle="bold"
|
||||
android:visibility="gone" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/home_modal_neutral"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/home_modal_button_height"
|
||||
android:layout_marginTop="@dimen/looply_space_sm"
|
||||
android:background="@drawable/home_bg_modal_cancel"
|
||||
android:gravity="center"
|
||||
android:includeFontPadding="false"
|
||||
android:textColor="@color/home_text_primary"
|
||||
android:textSize="15sp"
|
||||
android:visibility="gone" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/home_modal_negative"
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -5,18 +5,20 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipToPadding="false"
|
||||
android:clickable="false"
|
||||
android:fadeScrollbars="true"
|
||||
android:fillViewport="false"
|
||||
android:overScrollMode="ifContentScrolls"
|
||||
android:scrollbars="none">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:id="@+id/popover_preview_card"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/home_bg_conversation_preview"
|
||||
android:elevation="16dp"
|
||||
@@ -34,6 +36,7 @@
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/popover_menu_card"
|
||||
android:layout_width="@dimen/home_popover_width"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
@@ -43,6 +46,29 @@
|
||||
android:paddingTop="6dp"
|
||||
android:paddingBottom="6dp">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/popover_conversation_restore"
|
||||
style="@style/Home.PopoverItem"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="44dp"
|
||||
android:visibility="gone">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="22dp"
|
||||
android:layout_height="22dp"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_home_menu_archive"
|
||||
android:tint="@color/home_text_primary" />
|
||||
|
||||
<TextView
|
||||
style="@style/Home.PopoverLabel"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/home_archive_restore" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/popover_conversation_pin"
|
||||
style="@style/Home.PopoverItem"
|
||||
@@ -136,6 +162,51 @@
|
||||
android:text="@string/home_category_assign" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/popover_conversation_archive"
|
||||
style="@style/Home.PopoverItem"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="44dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="22dp"
|
||||
android:layout_height="22dp"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_home_menu_archive"
|
||||
android:tint="@color/home_text_primary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/popover_conversation_archive_label"
|
||||
style="@style/Home.PopoverLabel"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/home_profile_option_archive" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/popover_conversation_spam"
|
||||
style="@style/Home.PopoverItem"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="44dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="22dp"
|
||||
android:layout_height="22dp"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_home_menu_spam"
|
||||
android:tint="@color/home_text_primary" />
|
||||
|
||||
<TextView
|
||||
style="@style/Home.PopoverLabel"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/home_unknown_sender_report_spam" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/popover_conversation_delete"
|
||||
style="@style/Home.PopoverItem"
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="@dimen/home_popover_width"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/home_bg_popover"
|
||||
android:elevation="12dp"
|
||||
android:minWidth="168dp"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="6dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:paddingBottom="6dp">
|
||||
|
||||
<LinearLayout
|
||||
@@ -24,10 +26,9 @@
|
||||
|
||||
<TextView
|
||||
style="@style/Home.PopoverLabel"
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/home_profile_action_add_people" />
|
||||
</LinearLayout>
|
||||
|
||||
@@ -46,10 +47,9 @@
|
||||
|
||||
<TextView
|
||||
style="@style/Home.PopoverLabel"
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/home_profile_title" />
|
||||
</LinearLayout>
|
||||
|
||||
@@ -68,10 +68,9 @@
|
||||
|
||||
<TextView
|
||||
style="@style/Home.PopoverLabel"
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/home_profile_action_search" />
|
||||
</LinearLayout>
|
||||
|
||||
@@ -91,13 +90,33 @@
|
||||
<TextView
|
||||
android:id="@+id/popover_overflow_archive_label"
|
||||
style="@style/Home.PopoverLabel"
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/home_profile_option_archive" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/popover_overflow_spam"
|
||||
style="@style/Home.PopoverItem"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="44dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="22dp"
|
||||
android:layout_height="22dp"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_home_menu_spam"
|
||||
android:tint="@color/home_text_primary" />
|
||||
|
||||
<TextView
|
||||
style="@style/Home.PopoverLabel"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:text="@string/home_unknown_sender_report_spam" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/popover_overflow_delete"
|
||||
style="@style/Home.PopoverItem"
|
||||
@@ -113,10 +132,9 @@
|
||||
|
||||
<TextView
|
||||
style="@style/Home.PopoverLabel"
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/home_conversation_delete"
|
||||
android:textColor="@color/home_destructive" />
|
||||
</LinearLayout>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
android:layout_marginEnd="8dp"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_home_check"
|
||||
android:visibility="visible" />
|
||||
android:visibility="invisible" />
|
||||
|
||||
<ImageView
|
||||
android:layout_width="22dp"
|
||||
@@ -48,10 +48,14 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="44dp">
|
||||
|
||||
<View
|
||||
<ImageView
|
||||
android:id="@+id/popover_spam_check"
|
||||
android:layout_width="18dp"
|
||||
android:layout_height="18dp"
|
||||
android:layout_marginEnd="8dp" />
|
||||
android:layout_marginEnd="8dp"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_home_check"
|
||||
android:visibility="invisible" />
|
||||
|
||||
<ImageView
|
||||
android:layout_width="22dp"
|
||||
@@ -70,15 +74,19 @@
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/popover_recently_deleted"
|
||||
android:id="@+id/popover_archive"
|
||||
style="@style/Home.PopoverItem"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="44dp">
|
||||
|
||||
<View
|
||||
<ImageView
|
||||
android:id="@+id/popover_archive_check"
|
||||
android:layout_width="18dp"
|
||||
android:layout_height="18dp"
|
||||
android:layout_marginEnd="8dp" />
|
||||
android:layout_marginEnd="8dp"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_home_check"
|
||||
android:visibility="invisible" />
|
||||
|
||||
<ImageView
|
||||
android:layout_width="22dp"
|
||||
@@ -87,6 +95,37 @@
|
||||
android:src="@drawable/ic_home_menu_archive"
|
||||
android:tint="@color/home_text_primary" />
|
||||
|
||||
<TextView
|
||||
style="@style/Home.PopoverLabel"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/home_popover_archive" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/popover_recently_deleted"
|
||||
style="@style/Home.PopoverItem"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="44dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/popover_recently_deleted_check"
|
||||
android:layout_width="18dp"
|
||||
android:layout_height="18dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_home_check"
|
||||
android:visibility="invisible" />
|
||||
|
||||
<ImageView
|
||||
android:layout_width="22dp"
|
||||
android:layout_height="22dp"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_home_menu_trash"
|
||||
android:tint="@color/home_text_primary" />
|
||||
|
||||
<TextView
|
||||
style="@style/Home.PopoverLabel"
|
||||
android:layout_width="0dp"
|
||||
@@ -97,6 +136,7 @@
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:id="@+id/popover_filter_by_divider"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_marginTop="4dp"
|
||||
@@ -104,6 +144,7 @@
|
||||
android:background="@color/home_divider" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/popover_filter_by_label"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="16dp"
|
||||
@@ -120,10 +161,14 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="44dp">
|
||||
|
||||
<View
|
||||
<ImageView
|
||||
android:id="@+id/popover_unread_check"
|
||||
android:layout_width="18dp"
|
||||
android:layout_height="18dp"
|
||||
android:layout_marginEnd="8dp" />
|
||||
android:layout_marginEnd="8dp"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_home_check"
|
||||
android:visibility="invisible" />
|
||||
|
||||
<ImageView
|
||||
android:layout_width="22dp"
|
||||
@@ -149,33 +194,41 @@
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false">
|
||||
|
||||
<View
|
||||
<ImageView
|
||||
android:id="@+id/popover_work_check"
|
||||
android:layout_width="18dp"
|
||||
android:layout_height="18dp"
|
||||
android:layout_marginEnd="8dp" />
|
||||
android:layout_marginEnd="8dp"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_home_check"
|
||||
android:visibility="invisible" />
|
||||
|
||||
<!-- Same 22dp icon column as other rows; badge may extend left into the check spacer. -->
|
||||
<!-- Same icon column as other rows; badge can grow to the SIM name used in the list. -->
|
||||
<FrameLayout
|
||||
android:layout_width="22dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="22dp"
|
||||
android:minWidth="22dp"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/popover_work_badge"
|
||||
style="@style/Home.SimBadge"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical|end"
|
||||
android:text="@string/home_chip_work" />
|
||||
android:ellipsize="end"
|
||||
android:maxWidth="72dp"
|
||||
android:maxLines="1" />
|
||||
</FrameLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/popover_work_label"
|
||||
style="@style/Home.PopoverLabel"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/home_popover_work" />
|
||||
android:layout_weight="1" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
@@ -186,32 +239,40 @@
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false">
|
||||
|
||||
<View
|
||||
<ImageView
|
||||
android:id="@+id/popover_personal_check"
|
||||
android:layout_width="18dp"
|
||||
android:layout_height="18dp"
|
||||
android:layout_marginEnd="8dp" />
|
||||
android:layout_marginEnd="8dp"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_home_check"
|
||||
android:visibility="invisible" />
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="22dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="22dp"
|
||||
android:minWidth="22dp"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/popover_personal_badge"
|
||||
style="@style/Home.SimBadge"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical|end"
|
||||
android:text="@string/home_chip_personal" />
|
||||
android:ellipsize="end"
|
||||
android:maxWidth="72dp"
|
||||
android:maxLines="1" />
|
||||
</FrameLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/popover_personal_label"
|
||||
style="@style/Home.PopoverLabel"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/home_popover_personal" />
|
||||
android:layout_weight="1" />
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
|
||||
@@ -2,30 +2,21 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="@dimen/looply_screen_padding"
|
||||
android:paddingTop="@dimen/looply_space_sm"
|
||||
android:paddingEnd="@dimen/looply_screen_padding">
|
||||
android:orientation="vertical">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/home_profile_rename_field"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/looply_input_height"
|
||||
android:background="@drawable/home_bg_input_field"
|
||||
style="@style/Home.ModalInput"
|
||||
android:hint="@string/home_profile_rename_hint"
|
||||
android:imeOptions="actionDone"
|
||||
android:inputType="textCapSentences"
|
||||
android:maxLines="1"
|
||||
android:paddingStart="@dimen/looply_space_lg"
|
||||
android:paddingEnd="@dimen/looply_space_lg"
|
||||
android:textColor="@color/home_text_primary"
|
||||
android:textColorHint="@color/home_text_tertiary"
|
||||
android:textSize="16sp" />
|
||||
android:maxLines="1" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/looply_space_sm"
|
||||
android:gravity="center"
|
||||
android:text="@string/home_profile_rename_note"
|
||||
android:textColor="@color/home_text_secondary"
|
||||
android:textSize="13sp" />
|
||||
|
||||
@@ -1,28 +1,32 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/home_bg"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:fitsSystemWindows="true"
|
||||
android:orientation="vertical">
|
||||
|
||||
<include layout="@layout/home_sub_header" />
|
||||
|
||||
<TextView
|
||||
<FrameLayout
|
||||
android:id="@+id/home_sub_search_collapse"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="@dimen/looply_screen_padding"
|
||||
android:paddingEnd="@dimen/looply_screen_padding"
|
||||
android:paddingBottom="@dimen/looply_space_sm"
|
||||
android:text="@string/home_spam_subtitle"
|
||||
android:textColor="@color/home_text_secondary"
|
||||
android:textSize="14sp" />
|
||||
android:layout_height="0dp"
|
||||
android:background="@color/home_bg"
|
||||
android:clipChildren="true"
|
||||
android:clipToPadding="true">
|
||||
|
||||
<include layout="@layout/home_sub_search_field" />
|
||||
</FrameLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
android:layout_weight="1"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false">
|
||||
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/home_spam_fragment_host"
|
||||
@@ -36,5 +40,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="gone" />
|
||||
|
||||
<include layout="@layout/home_sub_select_bar" />
|
||||
</FrameLayout>
|
||||
</LinearLayout>
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/home_bg"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/home_spam_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/home_bg"
|
||||
android:clipToPadding="false"
|
||||
android:overScrollMode="ifContentScrolls"
|
||||
android:paddingBottom="@dimen/looply_space_lg"
|
||||
android:scrollbars="none" />
|
||||
|
||||
<com.android.messaging.ui.home.HomePassThroughView
|
||||
android:id="@+id/home_list_top_fade"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/home_list_top_fade_height"
|
||||
android:layout_gravity="top"
|
||||
android:background="@drawable/home_bg_list_top_fade"
|
||||
android:clickable="false"
|
||||
android:focusable="false"
|
||||
android:importantForAccessibility="no"
|
||||
android:visibility="gone" />
|
||||
|
||||
<com.android.messaging.ui.home.HomePassThroughView
|
||||
android:id="@+id/home_list_bottom_fade"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/home_list_bottom_fade_height"
|
||||
android:layout_gravity="bottom"
|
||||
android:layout_marginBottom="@dimen/home_list_bottom_fade_margin"
|
||||
android:background="@drawable/home_bg_list_bottom_fade"
|
||||
android:clickable="false"
|
||||
android:focusable="false"
|
||||
android:importantForAccessibility="no"
|
||||
android:visibility="gone" />
|
||||
</FrameLayout>
|
||||
@@ -0,0 +1,108 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:gravity="center_vertical"
|
||||
android:minHeight="72dp"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="@dimen/looply_space_md"
|
||||
android:paddingTop="@dimen/looply_space_sm"
|
||||
android:paddingEnd="@dimen/looply_space_lg"
|
||||
android:paddingBottom="@dimen/looply_space_md">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/home_spam_selection_indicator"
|
||||
android:layout_width="@dimen/home_selection_indicator_size"
|
||||
android:layout_height="@dimen/home_selection_indicator_size"
|
||||
android:layout_marginEnd="@dimen/looply_space_sm"
|
||||
android:visibility="gone">
|
||||
|
||||
<View
|
||||
android:id="@+id/home_spam_selection_ring"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@drawable/home_bg_selection_ring" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/home_spam_selection_check"
|
||||
android:layout_width="@dimen/home_selection_check_size"
|
||||
android:layout_height="@dimen/home_selection_check_size"
|
||||
android:layout_gravity="center"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_checkmark_light"
|
||||
android:visibility="gone" />
|
||||
</FrameLayout>
|
||||
|
||||
<com.android.messaging.ui.ContactIconView
|
||||
android:id="@+id/home_spam_avatar"
|
||||
android:layout_width="@dimen/home_list_avatar_size"
|
||||
android:layout_height="@dimen/home_list_avatar_size"
|
||||
android:contentDescription="@null"
|
||||
app:contactIconSize="normal" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/looply_space_md"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/home_spam_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:includeFontPadding="false"
|
||||
android:maxLines="1"
|
||||
android:textColor="@color/home_text_primary"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/home_spam_details"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="2dp"
|
||||
android:ellipsize="end"
|
||||
android:includeFontPadding="false"
|
||||
android:maxLines="1"
|
||||
android:textColor="@color/home_text_secondary"
|
||||
android:textSize="14sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/home_spam_unblock"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="32dp"
|
||||
android:layout_marginStart="@dimen/looply_space_sm"
|
||||
android:background="@drawable/home_bg_unblock_pill"
|
||||
android:clickable="true"
|
||||
android:contentDescription="@string/home_spam_unblock_action"
|
||||
android:focusable="true"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="14dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:contentDescription="@null"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/ic_home_menu_spam"
|
||||
android:tint="@color/home_text_primary" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="6dp"
|
||||
android:includeFontPadding="false"
|
||||
android:text="@string/home_spam_unblock_action"
|
||||
android:textColor="@color/home_text_primary"
|
||||
android:textSize="13sp"
|
||||
android:textStyle="bold" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
@@ -1,21 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Shared header for home sub screens: back arrow plus a left aligned title. -->
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/home_bg"
|
||||
android:gravity="center_vertical"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:minHeight="@dimen/home_header_height"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingEnd="12dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<include layout="@layout/home_back_button" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/home_sub_header_title"
|
||||
style="@style/Home.SubHeaderTitle" />
|
||||
style="@style/Home.SubHeaderTitle"
|
||||
android:layout_marginEnd="@dimen/home_header_title_inset" />
|
||||
|
||||
<!-- Shown instead of the back arrow when the screen is presented as a sheet. -->
|
||||
<include
|
||||
@@ -52,3 +60,65 @@
|
||||
android:textSize="16sp"
|
||||
android:visibility="gone" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/home_sub_header_inbox_actions"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end|center_vertical"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/home_sub_header_edit"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="45dp"
|
||||
android:background="@drawable/home_bg_dock_pill"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:elevation="6dp"
|
||||
android:gravity="center"
|
||||
android:minWidth="64dp"
|
||||
android:outlineAmbientShadowColor="@color/home_pill_shadow_ambient"
|
||||
android:outlineSpotShadowColor="@color/home_pill_shadow_spot"
|
||||
android:paddingStart="18dp"
|
||||
android:paddingEnd="18dp"
|
||||
android:paddingBottom="5dp"
|
||||
android:text="@string/home_select"
|
||||
android:textColor="@color/home_text_primary"
|
||||
android:textSize="15sp" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/home_sub_header_filter"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="45dp"
|
||||
android:layout_marginStart="@dimen/looply_space_sm"
|
||||
android:background="@drawable/home_bg_circle"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:contentDescription="@string/home_filter_content_description"
|
||||
android:elevation="6dp"
|
||||
android:outlineAmbientShadowColor="@color/home_pill_shadow_ambient"
|
||||
android:outlineSpotShadowColor="@color/home_pill_shadow_spot"
|
||||
android:padding="9dp"
|
||||
android:paddingBottom="5dp"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/ic_home_menu_lines"
|
||||
android:tint="@color/home_text_primary" />
|
||||
</LinearLayout>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/home_sub_header_done"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_gravity="end|center_vertical"
|
||||
android:background="@android:color/transparent"
|
||||
android:contentDescription="@string/home_selection_done"
|
||||
android:padding="0dp"
|
||||
android:scaleType="fitCenter"
|
||||
android:visibility="invisible"
|
||||
app:srcCompat="@drawable/ic_checkmark_circle_primary" />
|
||||
</FrameLayout>
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/home_sub_search_box"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/home_bg"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:paddingBottom="4dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/home_search_field_height"
|
||||
android:addStatesFromChildren="true"
|
||||
android:background="@drawable/home_bg_search_field_box"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="@dimen/looply_space_md"
|
||||
android:paddingEnd="@dimen/looply_space_sm">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="18dp"
|
||||
android:layout_height="18dp"
|
||||
android:contentDescription="@null"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/ic_home_search_line"
|
||||
android:tint="@color/home_search_hint" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/home_sub_search_field"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="@dimen/looply_space_sm"
|
||||
android:layout_weight="1"
|
||||
android:background="@android:color/transparent"
|
||||
android:gravity="center_vertical|start"
|
||||
android:hint="@string/home_sub_list_search_hint"
|
||||
android:imeOptions="actionSearch"
|
||||
android:includeFontPadding="false"
|
||||
android:inputType="text"
|
||||
android:maxLines="1"
|
||||
android:textColor="@color/home_text_primary"
|
||||
android:textColorHint="@color/home_search_hint"
|
||||
android:textCursorDrawable="@drawable/home_cursor"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/home_sub_search_clear"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:background="@android:color/transparent"
|
||||
android:contentDescription="@string/home_search_clear"
|
||||
android:focusable="false"
|
||||
android:padding="8dp"
|
||||
android:scaleType="centerInside"
|
||||
android:visibility="gone"
|
||||
app:srcCompat="@drawable/ic_home_close"
|
||||
app:tint="@color/home_text_secondary" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/home_sub_select_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:paddingStart="@dimen/looply_space_md"
|
||||
android:paddingEnd="@dimen/looply_space_md"
|
||||
android:paddingBottom="@dimen/looply_space_md"
|
||||
android:visibility="gone">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/home_sub_select_pill"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:background="@drawable/home_bg_dock_pill"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:elevation="6dp"
|
||||
android:minWidth="@dimen/home_sub_select_pill_min_width"
|
||||
android:outlineAmbientShadowColor="@color/home_pill_shadow_ambient"
|
||||
android:outlineSpotShadowColor="@color/home_pill_shadow_spot"
|
||||
android:paddingStart="@dimen/home_sub_select_pill_padding_h"
|
||||
android:paddingTop="@dimen/home_nav_item_padding"
|
||||
android:paddingEnd="@dimen/home_sub_select_pill_padding_h"
|
||||
android:paddingBottom="@dimen/home_nav_dock_padding_bottom">
|
||||
|
||||
<include
|
||||
android:id="@+id/home_sub_select_action"
|
||||
layout="@layout/home_bottom_nav_tab_item"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center" />
|
||||
</FrameLayout>
|
||||
</FrameLayout>
|
||||
@@ -14,6 +14,19 @@
|
||||
<string name="home_title">Message Q</string>
|
||||
<string name="home_title_avatar_content_description">프로필</string>
|
||||
<string name="home_edit">편집</string>
|
||||
<string name="home_select">선택</string>
|
||||
<string name="home_sub_unblock_all">모두 스팸 해제</string>
|
||||
<string name="home_sub_unblock">스팸 해제</string>
|
||||
<string name="home_sub_unarchive_all">모두 보관 해제</string>
|
||||
<string name="home_sub_unarchive">보관 해제</string>
|
||||
<string name="home_empty_trash_action">모두 비우기</string>
|
||||
<string name="home_toast_nothing_to_edit">편집할 대화가 없습니다</string>
|
||||
<string name="home_toast_nothing_to_select">선택할 항목이 없습니다</string>
|
||||
<string name="home_toast_nothing_to_empty">휴지통이 비어 있습니다</string>
|
||||
<string name="home_toast_nothing_to_unblock">차단 해제할 스팸이 없습니다</string>
|
||||
<string name="home_toast_nothing_to_unarchive">보관 해제할 대화가 없습니다</string>
|
||||
<string name="home_sub_unblocked_toast">스팸을 해제했습니다</string>
|
||||
<string name="home_sub_unarchived_toast_many">대화 %1$d개의 보관을 해제했습니다</string>
|
||||
<string name="home_search_hint">검색</string>
|
||||
<string name="home_chip_personal">개인</string>
|
||||
<string name="home_chip_work">업무</string>
|
||||
@@ -142,10 +155,18 @@
|
||||
<string name="home_automation_empty_body">메시지를 자동으로 처리할 규칙을 만드세요.</string>
|
||||
<string name="home_empty_messages_title">대화가 없습니다</string>
|
||||
<string name="home_empty_messages_body">새 채팅을 시작하면 여기에 표시됩니다.</string>
|
||||
<string name="home_empty_unread_title">안 읽은 대화가 없습니다</string>
|
||||
<string name="home_empty_unread_body">안 읽은 대화가 여기에 표시됩니다.</string>
|
||||
<string name="home_empty_work_title">업무 대화가 없습니다</string>
|
||||
<string name="home_empty_work_body">업무 SIM이나 업무 관련 메시지가 여기에 표시됩니다.</string>
|
||||
<string name="home_empty_personal_title">개인 대화가 없습니다</string>
|
||||
<string name="home_empty_personal_body">개인 SIM이나 지인과의 대화가 여기에 표시됩니다.</string>
|
||||
<string name="home_empty_messages_sync_title">대화를 불러오는 중</string>
|
||||
<string name="home_empty_messages_sync_body">잠시 후 메시지가 여기에 나타납니다.</string>
|
||||
<string name="home_empty_archive_title">보관된 대화가 없습니다</string>
|
||||
<string name="home_empty_archive_body">보관한 채팅은 복원하거나 삭제할 때까지 여기에 있습니다.</string>
|
||||
<string name="home_empty_trash_title">휴지통이 비어 있습니다</string>
|
||||
<string name="home_empty_trash_body">삭제한 채팅은 30일 동안 여기에 있다가 영구 삭제됩니다.</string>
|
||||
<string name="home_empty_spam_title">스팸 번호가 없습니다</string>
|
||||
<string name="home_empty_spam_body">차단된 발신자가 여기에 표시됩니다. 항목을 눌러 해제하세요.</string>
|
||||
<string name="home_empty_contacts_title">표시할 연락처가 없습니다</string>
|
||||
@@ -172,11 +193,20 @@
|
||||
<string name="home_mark_unread">안 읽음</string>
|
||||
<string name="home_selection_done">완료</string>
|
||||
<string name="home_selection_delete">선택한 메시지 삭제</string>
|
||||
<string name="home_selection_spam">스팸신고</string>
|
||||
<string name="home_selection_archive">보관함</string>
|
||||
<string name="home_selection_archived_toast_many">대화 %1$d개를 보관했습니다</string>
|
||||
<string name="home_report_spam_title">스팸으로 신고할까요?</string>
|
||||
<string name="home_report_spam_message_one">이 발신자를 차단하고 대화를 스팸으로 보냅니다.</string>
|
||||
<string name="home_report_spam_message_many">발신자 %1$d명을 차단하고 대화를 스팸으로 보냅니다.</string>
|
||||
<string name="home_report_spam_groups_only">그룹 대화는 스팸으로 신고할 수 없습니다</string>
|
||||
<string name="home_report_spam_toast_one">스팸으로 신고했습니다</string>
|
||||
<string name="home_report_spam_toast_many">대화 %1$d개를 스팸으로 신고했습니다</string>
|
||||
<string name="home_message_selection_title">1개 선택됨</string>
|
||||
<string name="home_message_action_copy">복사</string>
|
||||
<string name="home_message_action_forward">전달</string>
|
||||
<string name="home_message_action_share">공유</string>
|
||||
<string name="home_message_action_details">상세</string>
|
||||
<string name="home_message_action_details">세부정보</string>
|
||||
<string name="home_message_action_save">저장</string>
|
||||
<string name="home_message_action_send">다시 보내기</string>
|
||||
<string name="home_message_action_download">다운로드</string>
|
||||
@@ -191,18 +221,43 @@
|
||||
<string name="home_message_menu_unstar">별표 해제</string>
|
||||
<string name="home_message_menu_delete">삭제</string>
|
||||
<string name="home_message_menu_select_more">더 선택</string>
|
||||
<string name="home_message_menu_details">상세</string>
|
||||
<string name="home_delete_alert_message_one">이 대화가 삭제됩니다.\n\n스팸으로 신고하면 발신자도 차단 목록에 추가됩니다.</string>
|
||||
<string name="home_delete_alert_message_many">선택한 대화 %1$d개가 삭제됩니다.\n\n스팸으로 신고하면 1:1 대화 발신자도 차단 목록에 추가됩니다.</string>
|
||||
<string name="home_message_menu_details">세부정보</string>
|
||||
<string name="home_message_details_title">세부정보</string>
|
||||
<string name="home_message_details_status">상태</string>
|
||||
<string name="home_message_details_received">수신됨</string>
|
||||
<string name="home_message_details_sent">전송됨</string>
|
||||
<string name="home_message_details_type">유형</string>
|
||||
<string name="home_message_details_priority">우선순위</string>
|
||||
<string name="home_message_details_subject">제목</string>
|
||||
<string name="home_message_details_size">크기</string>
|
||||
<string name="home_message_details_photo">사진</string>
|
||||
<string name="home_message_details_video">동영상</string>
|
||||
<string name="home_message_details_audio">음성</string>
|
||||
<string name="home_message_details_contact">연락처</string>
|
||||
<string name="home_message_details_attachment">첨부파일</string>
|
||||
<string name="home_delete_alert_message_one">이 대화는 휴지통으로 옮겨지고 30일 후 영구 삭제됩니다.</string>
|
||||
<string name="home_delete_alert_message_many">선택한 대화 %1$d개가 휴지통으로 옮겨지고 30일 후 영구 삭제됩니다.</string>
|
||||
<string name="home_delete_confirm">삭제</string>
|
||||
<string name="home_delete_confirm_spam">삭제하고 스팸 신고</string>
|
||||
<string name="home_delete_toast_one">대화를 삭제했습니다</string>
|
||||
<string name="home_delete_toast_many">대화 %1$d개를 삭제했습니다</string>
|
||||
<string name="home_delete_spam_toast_one">삭제하고 스팸으로 신고했습니다</string>
|
||||
<string name="home_delete_spam_toast_many">대화 %1$d개를 삭제하고 스팸으로 신고했습니다</string>
|
||||
<string name="home_trash_moved_toast_one">휴지통으로 옮겼습니다</string>
|
||||
<string name="home_trash_moved_toast_many">대화 %1$d개를 휴지통으로 옮겼습니다</string>
|
||||
<string name="home_trash_restored_toast">휴지통에서 복원했습니다</string>
|
||||
<string name="home_trash_compose_message">이 대화는 휴지통에 있습니다. 메시지를 보내려면 복원하세요.</string>
|
||||
<string name="home_trash_permanent_title">지금 삭제할까요?</string>
|
||||
<string name="home_trash_permanent_message_one">이 대화가 바로 삭제됩니다. 되돌릴 수 없습니다.</string>
|
||||
<string name="home_trash_permanent_message_many">대화 %1$d개가 바로 삭제됩니다. 되돌릴 수 없습니다.</string>
|
||||
<string name="home_empty_all_title">모두 비울까요?</string>
|
||||
<string name="home_empty_all_message">휴지통의 모든 대화가 바로 삭제됩니다. 되돌릴 수 없습니다.</string>
|
||||
<string name="home_empty_all_confirm">휴지통 메시지 삭제</string>
|
||||
<string name="home_spam_title">스팸</string>
|
||||
<string name="home_spam_subtitle">목록의 번호에서 온 메시지는 차단됩니다. 항목을 눌러 제거하세요.</string>
|
||||
<string name="home_spam_empty">스팸 번호가 없습니다.</string>
|
||||
<string name="home_spam_item_hint">차단된 발신자</string>
|
||||
<string name="home_spam_unblock_title">이 번호를 차단 해제할까요?</string>
|
||||
<string name="home_spam_unblock_message">%1$s에서 온 메시지와 알림을 다시 받게 됩니다.</string>
|
||||
<string name="home_spam_unblock_action">차단 해제</string>
|
||||
<string name="home_archive_restore">복원</string>
|
||||
<string name="home_sub_header_back">뒤로</string>
|
||||
<string name="home_sub_header_close">닫기</string>
|
||||
<string name="home_add_content_description">추가</string>
|
||||
@@ -211,10 +266,13 @@
|
||||
|
||||
<string name="home_popover_messages">메시지</string>
|
||||
<string name="home_popover_spam">스팸</string>
|
||||
<string name="home_popover_recently_deleted">보관함</string>
|
||||
<string name="home_recently_deleted_title">보관함</string>
|
||||
<string name="home_recently_deleted_subtitle">보관한 대화는 복원하거나 삭제할 때까지 여기에 있습니다.</string>
|
||||
<string name="home_recently_deleted_empty">보관된 대화가 없습니다</string>
|
||||
<string name="home_popover_archive">보관함</string>
|
||||
<string name="home_popover_recently_deleted">휴지통</string>
|
||||
<string name="home_archived_title">보관함</string>
|
||||
<string name="home_archived_subtitle">보관한 대화는 복원하거나 삭제할 때까지 여기에 있습니다.</string>
|
||||
<string name="home_recently_deleted_title">휴지통</string>
|
||||
<string name="home_recently_deleted_subtitle">삭제한 대화는 30일 동안 여기에 있다가 영구 삭제됩니다.</string>
|
||||
<string name="home_recently_deleted_empty">휴지통이 비어 있습니다</string>
|
||||
<string name="home_popover_filter_by">필터</string>
|
||||
<string name="home_popover_unread">안 읽음</string>
|
||||
<string name="home_popover_work">업무</string>
|
||||
@@ -235,6 +293,9 @@
|
||||
|
||||
<string name="home_search_title">검색</string>
|
||||
<string name="home_search_field_hint">메시지 또는 연락처 검색</string>
|
||||
<string name="home_sub_list_search_hint">검색</string>
|
||||
<string name="home_empty_sub_search_title">결과가 없습니다</string>
|
||||
<string name="home_empty_sub_search_body">“%1$s”와 일치하는 항목이 없습니다.</string>
|
||||
<string name="home_search_clear">지우기</string>
|
||||
<string name="home_search_reset">초기화</string>
|
||||
<string name="home_search_voice">음성 검색</string>
|
||||
@@ -373,6 +434,7 @@
|
||||
<string name="home_conversation_marked_unread_toast">안 읽음으로 표시했습니다</string>
|
||||
<string name="home_conversation_marked_read_toast">읽음으로 표시했습니다</string>
|
||||
<string name="home_swipe_mute">알림 숨기기</string>
|
||||
<string name="home_swipe_unmute">알림 켜기</string>
|
||||
<string name="home_swipe_delete">삭제</string>
|
||||
<string name="home_swipe_archive">보관</string>
|
||||
<string name="home_requires_default_sms_app">기본 SMS 앱을 Messages로 설정하세요</string>
|
||||
|
||||
@@ -99,6 +99,7 @@
|
||||
<dimen name="home_pinned_remove_offset">-4dp</dimen>
|
||||
<dimen name="looply_badge_size">20dp</dimen>
|
||||
<dimen name="home_unread_dot_size">8dp</dimen>
|
||||
<dimen name="home_mute_badge_size">18dp</dimen>
|
||||
<!-- dot width + trailing margin (looply_space_xs) -->
|
||||
<dimen name="home_unread_dot_slot_width">12dp</dimen>
|
||||
<dimen name="home_selection_indicator_size">22dp</dimen>
|
||||
@@ -122,6 +123,9 @@
|
||||
<dimen name="home_compose_group_button_height">52dp</dimen>
|
||||
<dimen name="home_selection_dock_height">55dp</dimen>
|
||||
<dimen name="home_selection_dock_shadow_inset">5dp</dimen>
|
||||
<dimen name="home_sub_select_list_padding">104dp</dimen>
|
||||
<dimen name="home_sub_select_pill_min_width">148dp</dimen>
|
||||
<dimen name="home_sub_select_pill_padding_h">20dp</dimen>
|
||||
<!-- Square inset for dock FAB face: (size - shadow_depth) / 2 on square buttons -->
|
||||
<dimen name="home_dock_fab_shadow_depth">6dp</dimen>
|
||||
<dimen name="home_dock_fab_circle_inset">3dp</dimen>
|
||||
|
||||
@@ -15,6 +15,19 @@
|
||||
<string name="home_title" translatable="false">Message Q</string>
|
||||
<string name="home_title_avatar_content_description" translatable="false">Profile</string>
|
||||
<string name="home_edit" translatable="false">Edit</string>
|
||||
<string name="home_select" translatable="false">Select</string>
|
||||
<string name="home_sub_unblock_all" translatable="false">Unblock all</string>
|
||||
<string name="home_sub_unblock" translatable="false">Unblock</string>
|
||||
<string name="home_sub_unarchive_all" translatable="false">Unarchive all</string>
|
||||
<string name="home_sub_unarchive" translatable="false">Unarchive</string>
|
||||
<string name="home_empty_trash_action" translatable="false">Empty All</string>
|
||||
<string name="home_toast_nothing_to_edit" translatable="false">Nothing to edit</string>
|
||||
<string name="home_toast_nothing_to_select" translatable="false">Nothing to select</string>
|
||||
<string name="home_toast_nothing_to_empty" translatable="false">Trash is empty</string>
|
||||
<string name="home_toast_nothing_to_unblock" translatable="false">No spam to unblock</string>
|
||||
<string name="home_toast_nothing_to_unarchive" translatable="false">No conversations to unarchive</string>
|
||||
<string name="home_sub_unblocked_toast" translatable="false">Unblocked</string>
|
||||
<string name="home_sub_unarchived_toast_many" translatable="false">%1$d conversations unarchived</string>
|
||||
<string name="home_search_hint" translatable="false">Search</string>
|
||||
<string name="home_chip_personal" translatable="false">Personal</string>
|
||||
<string name="home_chip_work" translatable="false">Work</string>
|
||||
@@ -143,10 +156,18 @@
|
||||
<string name="home_automation_empty_body" translatable="false">Create rules to handle messages automatically.</string>
|
||||
<string name="home_empty_messages_title" translatable="false">No conversations yet</string>
|
||||
<string name="home_empty_messages_body" translatable="false">Start a new chat and it will show up here.</string>
|
||||
<string name="home_empty_unread_title" translatable="false">You\'re all caught up</string>
|
||||
<string name="home_empty_unread_body" translatable="false">Unread conversations will show up here.</string>
|
||||
<string name="home_empty_work_title" translatable="false">No work conversations</string>
|
||||
<string name="home_empty_work_body" translatable="false">Chats on your work SIM, and business messages, appear here.</string>
|
||||
<string name="home_empty_personal_title" translatable="false">No personal conversations</string>
|
||||
<string name="home_empty_personal_body" translatable="false">Chats on your personal SIM, and messages from people you know, appear here.</string>
|
||||
<string name="home_empty_messages_sync_title" translatable="false">Loading conversations</string>
|
||||
<string name="home_empty_messages_sync_body" translatable="false">Your messages will appear here in a moment.</string>
|
||||
<string name="home_empty_archive_title" translatable="false">No archived conversations</string>
|
||||
<string name="home_empty_archive_body" translatable="false">Archived chats stay here until you restore or delete them.</string>
|
||||
<string name="home_empty_trash_title" translatable="false">Trash is empty</string>
|
||||
<string name="home_empty_trash_body" translatable="false">Deleted chats stay here for 30 days, then they are permanently deleted.</string>
|
||||
<string name="home_empty_spam_title" translatable="false">No spam numbers yet</string>
|
||||
<string name="home_empty_spam_body" translatable="false">Blocked senders will appear here. Tap an entry to unblock.</string>
|
||||
<string name="home_empty_contacts_title" translatable="false">No contacts to show</string>
|
||||
@@ -173,6 +194,15 @@
|
||||
<string name="home_mark_unread" translatable="false">Unread</string>
|
||||
<string name="home_selection_done" translatable="false">Done</string>
|
||||
<string name="home_selection_delete" translatable="false">Delete selected messages</string>
|
||||
<string name="home_selection_spam" translatable="false">Spam</string>
|
||||
<string name="home_selection_archive" translatable="false">Archive</string>
|
||||
<string name="home_selection_archived_toast_many" translatable="false">%1$d conversations archived</string>
|
||||
<string name="home_report_spam_title" translatable="false">Report as spam?</string>
|
||||
<string name="home_report_spam_message_one" translatable="false">This sender will be blocked and the conversation moved to Spam.</string>
|
||||
<string name="home_report_spam_message_many" translatable="false">%1$d senders will be blocked and their conversations moved to Spam.</string>
|
||||
<string name="home_report_spam_groups_only" translatable="false">Group conversations can\'t be reported as spam</string>
|
||||
<string name="home_report_spam_toast_one" translatable="false">Reported as spam</string>
|
||||
<string name="home_report_spam_toast_many" translatable="false">%1$d conversations reported as spam</string>
|
||||
<string name="home_message_selection_title" translatable="false">1 selected</string>
|
||||
<string name="home_message_action_copy" translatable="false">Copy</string>
|
||||
<string name="home_message_action_forward" translatable="false">Forward</string>
|
||||
@@ -193,17 +223,42 @@
|
||||
<string name="home_message_menu_delete" translatable="false">Delete</string>
|
||||
<string name="home_message_menu_select_more" translatable="false">Select more</string>
|
||||
<string name="home_message_menu_details" translatable="false">Details</string>
|
||||
<string name="home_delete_alert_message_one" translatable="false">This conversation will be deleted.\n\nReporting it as spam also adds the sender to your blocked list.</string>
|
||||
<string name="home_delete_alert_message_many" translatable="false">The %1$d selected conversations will be deleted.\n\nReporting them as spam also adds the senders of one-to-one conversations to your blocked list.</string>
|
||||
<string name="home_message_details_title" translatable="false">Details</string>
|
||||
<string name="home_message_details_status" translatable="false">Status</string>
|
||||
<string name="home_message_details_received" translatable="false">Received</string>
|
||||
<string name="home_message_details_sent" translatable="false">Sent</string>
|
||||
<string name="home_message_details_type" translatable="false">Type</string>
|
||||
<string name="home_message_details_priority" translatable="false">Priority</string>
|
||||
<string name="home_message_details_subject" translatable="false">Subject</string>
|
||||
<string name="home_message_details_size" translatable="false">Size</string>
|
||||
<string name="home_message_details_photo" translatable="false">Photo</string>
|
||||
<string name="home_message_details_video" translatable="false">Video</string>
|
||||
<string name="home_message_details_audio" translatable="false">Audio</string>
|
||||
<string name="home_message_details_contact" translatable="false">Contact</string>
|
||||
<string name="home_message_details_attachment" translatable="false">Attachment</string>
|
||||
<string name="home_delete_alert_message_one" translatable="false">This conversation will move to Trash and be permanently deleted after 30 days.</string>
|
||||
<string name="home_delete_alert_message_many" translatable="false">The %1$d selected conversations will move to Trash and be permanently deleted after 30 days.</string>
|
||||
<string name="home_delete_confirm" translatable="false">Delete</string>
|
||||
<string name="home_delete_confirm_spam" translatable="false">Delete and report spam</string>
|
||||
<string name="home_delete_toast_one" translatable="false">Conversation deleted</string>
|
||||
<string name="home_delete_toast_many" translatable="false">%1$d conversations deleted</string>
|
||||
<string name="home_delete_spam_toast_one" translatable="false">Deleted and reported as spam</string>
|
||||
<string name="home_delete_spam_toast_many" translatable="false">%1$d conversations deleted and reported as spam</string>
|
||||
<string name="home_trash_moved_toast_one" translatable="false">Moved to Trash</string>
|
||||
<string name="home_trash_moved_toast_many" translatable="false">%1$d conversations moved to Trash</string>
|
||||
<string name="home_trash_restored_toast" translatable="false">Restored from Trash</string>
|
||||
<string name="home_trash_compose_message" translatable="false">This conversation is in Trash. Restore it to send messages.</string>
|
||||
<string name="home_trash_permanent_title" translatable="false">Delete permanently?</string>
|
||||
<string name="home_trash_permanent_message_one" translatable="false">This conversation will be deleted now. This can\'t be undone.</string>
|
||||
<string name="home_trash_permanent_message_many" translatable="false">%1$d conversations will be deleted now. This can\'t be undone.</string>
|
||||
<string name="home_empty_all_title" translatable="false">Empty All?</string>
|
||||
<string name="home_empty_all_message" translatable="false">All conversations in trash will be deleted now. This can\'t be undone.</string>
|
||||
<string name="home_empty_all_confirm" translatable="false">Delete trash messages</string>
|
||||
<string name="home_spam_title" translatable="false">Spam</string>
|
||||
<string name="home_spam_subtitle" translatable="false">Messages from listed numbers are blocked. Tap an entry to remove it.</string>
|
||||
<string name="home_spam_empty" translatable="false">No spam numbers yet.</string>
|
||||
<string name="home_spam_item_hint" translatable="false">Blocked sender</string>
|
||||
<string name="home_spam_unblock_title" translatable="false">Unblock this number?</string>
|
||||
<string name="home_spam_unblock_message" translatable="false">You\'ll receive messages and notifications from %1$s again.</string>
|
||||
<string name="home_spam_unblock_action" translatable="false">Unblock</string>
|
||||
<string name="home_archive_restore" translatable="false">Restore</string>
|
||||
<string name="home_sub_header_back" translatable="false">Back</string>
|
||||
<string name="home_sub_header_close" translatable="false">Close</string>
|
||||
<string name="home_add_content_description" translatable="false">Add</string>
|
||||
@@ -213,10 +268,13 @@
|
||||
<!-- Popover menus -->
|
||||
<string name="home_popover_messages" translatable="false">Messages</string>
|
||||
<string name="home_popover_spam" translatable="false">Spam</string>
|
||||
<string name="home_popover_recently_deleted" translatable="false">Archive</string>
|
||||
<string name="home_recently_deleted_title" translatable="false">Archive</string>
|
||||
<string name="home_recently_deleted_subtitle" translatable="false">Archived conversations stay here until you restore or delete them.</string>
|
||||
<string name="home_recently_deleted_empty" translatable="false">No archived conversations</string>
|
||||
<string name="home_popover_archive" translatable="false">Archive</string>
|
||||
<string name="home_popover_recently_deleted" translatable="false">Trash</string>
|
||||
<string name="home_archived_title" translatable="false">Archive</string>
|
||||
<string name="home_archived_subtitle" translatable="false">Archived conversations stay here until you restore or delete them.</string>
|
||||
<string name="home_recently_deleted_title" translatable="false">Trash</string>
|
||||
<string name="home_recently_deleted_subtitle" translatable="false">Deleted conversations stay here for 30 days, then they are permanently deleted.</string>
|
||||
<string name="home_recently_deleted_empty" translatable="false">Trash is empty</string>
|
||||
<string name="home_popover_filter_by" translatable="false">Filter By</string>
|
||||
<string name="home_popover_unread" translatable="false">Unread</string>
|
||||
<string name="home_popover_work" translatable="false">Work</string>
|
||||
@@ -238,6 +296,9 @@
|
||||
<!-- Message search screen -->
|
||||
<string name="home_search_title" translatable="false">Search</string>
|
||||
<string name="home_search_field_hint" translatable="false">Search messages or contacts</string>
|
||||
<string name="home_sub_list_search_hint" translatable="false">Search</string>
|
||||
<string name="home_empty_sub_search_title" translatable="false">No results</string>
|
||||
<string name="home_empty_sub_search_body" translatable="false">Nothing matches “%1$s”.</string>
|
||||
<string name="home_search_clear" translatable="false">Clear</string>
|
||||
<string name="home_search_reset" translatable="false">Reset</string>
|
||||
<string name="home_search_voice" translatable="false">Voice search</string>
|
||||
@@ -380,6 +441,7 @@
|
||||
<string name="home_conversation_marked_unread_toast" translatable="false">Marked as unread</string>
|
||||
<string name="home_conversation_marked_read_toast" translatable="false">Marked as read</string>
|
||||
<string name="home_swipe_mute" translatable="false">Hide alerts</string>
|
||||
<string name="home_swipe_unmute" translatable="false">Show alerts</string>
|
||||
<string name="home_swipe_delete" translatable="false">Delete</string>
|
||||
<string name="home_swipe_archive" translatable="false">Archive</string>
|
||||
<string name="home_requires_default_sms_app" translatable="false">Set Messages as the default SMS app</string>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
-->
|
||||
<resources>
|
||||
<!-- DB version -->
|
||||
<string name="database_version" translatable="false">2</string>
|
||||
<string name="database_version" translatable="false">3</string>
|
||||
|
||||
<!-- Version for shared preferences. This is used for handling prefs migration when old pref
|
||||
keys are moved or renamed. You don't need to bump up the version number if you are just
|
||||
|
||||
Reference in New Issue
Block a user