Messaging: Looply empty states, popovers, and conversation polish
Keep English-only UI, align home menus to the screen guideline, and match message-action icon weight across conversation and list popovers. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+21
-2
@@ -2,17 +2,24 @@ plugins {
|
||||
id 'com.android.application'
|
||||
}
|
||||
|
||||
def keystoreProperties = new Properties()
|
||||
def keystorePropertiesFile = rootProject.file('keystore.properties')
|
||||
if (keystorePropertiesFile.exists()) {
|
||||
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
|
||||
}
|
||||
|
||||
android {
|
||||
namespace 'com.android.messaging'
|
||||
compileSdk 35
|
||||
|
||||
defaultConfig {
|
||||
applicationId 'com.android.messaging'
|
||||
applicationId 'com.bnkode.notie'
|
||||
minSdk 33
|
||||
targetSdk 34
|
||||
versionCode 10001040
|
||||
versionName '1.0.001'
|
||||
vectorDrawables.useSupportLibrary true
|
||||
resourceConfigurations += ['en']
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
@@ -20,9 +27,21 @@ android {
|
||||
targetCompatibility JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
release {
|
||||
if (keystorePropertiesFile.exists()) {
|
||||
storeFile rootProject.file(keystoreProperties['storeFile'])
|
||||
storePassword keystoreProperties['storePassword']
|
||||
keyAlias keystoreProperties['keyAlias']
|
||||
keyPassword keystoreProperties['keyPassword']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled true
|
||||
signingConfig signingConfigs.release
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'),
|
||||
rootProject.file('proguard-release.flags'),
|
||||
rootProject.file('proguard.flags')
|
||||
@@ -85,5 +104,5 @@ tasks.register('launchMessaging', Exec) {
|
||||
description = "Launch Messaging on ${messagingAvdSerial}"
|
||||
dependsOn 'installMessaging'
|
||||
commandLine adbExecutable.absolutePath, '-s', messagingAvdSerial, 'shell', 'am', 'start',
|
||||
'-n', 'com.android.messaging/.ui.conversationlist.ConversationListActivity'
|
||||
'-n', 'com.bnkode.notie/com.android.messaging.ui.conversationlist.ConversationListActivity'
|
||||
}
|
||||
|
||||
@@ -134,10 +134,10 @@
|
||||
<activity
|
||||
android:name=".ui.conversationlist.ArchivedConversationListActivity"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden"
|
||||
android:label="@string/archived_activity_title"
|
||||
android:label="@string/home_recently_deleted_title"
|
||||
android:parentActivityName="com.android.messaging.ui.conversationlist.ConversationListActivity"
|
||||
android:screenOrientation="user"
|
||||
android:theme="@style/BugleTheme.ArchivedConversationListActivity" />
|
||||
android:theme="@style/Home.SubScreen" />
|
||||
|
||||
<!-- Displays the contents of a single conversation -->
|
||||
<activity
|
||||
@@ -165,6 +165,22 @@
|
||||
android:screenOrientation="user"
|
||||
android:theme="@style/Home.SubScreen" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.home.HomeAutomationActivity"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden"
|
||||
android:label="@string/home_automation_title"
|
||||
android:parentActivityName="com.android.messaging.ui.conversationlist.ConversationListActivity"
|
||||
android:screenOrientation="user"
|
||||
android:theme="@style/Home.SubScreen" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.home.HomeAutomationCreateActivity"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden"
|
||||
android:label="@string/home_automation_create_title"
|
||||
android:parentActivityName="com.android.messaging.ui.conversationlist.ConversationListActivity"
|
||||
android:theme="@style/Home.BottomSheet"
|
||||
android:windowSoftInputMode="adjustResize|stateHidden" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.home.HomeSearchActivity"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden"
|
||||
@@ -206,7 +222,7 @@
|
||||
android:label="@string/settings_activity_title"
|
||||
android:parentActivityName="com.android.messaging.ui.conversationlist.ConversationListActivity"
|
||||
android:screenOrientation="user"
|
||||
android:theme="@style/BugleTheme.SettingsActivity" />
|
||||
android:theme="@style/Home.SubScreen" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.appsettings.PerSubscriptionSettingsActivity"
|
||||
@@ -214,7 +230,7 @@
|
||||
android:label="@string/advanced_settings_activity_title"
|
||||
android:parentActivityName="com.android.messaging.ui.appsettings.SettingsActivity"
|
||||
android:screenOrientation="user"
|
||||
android:theme="@style/BugleTheme.SettingsActivity" />
|
||||
android:theme="@style/Home.SubScreen" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.appsettings.ApplicationSettingsActivity"
|
||||
@@ -223,7 +239,7 @@
|
||||
android:label="@string/general_settings_activity_title"
|
||||
android:parentActivityName="com.android.messaging.ui.appsettings.SettingsActivity"
|
||||
android:screenOrientation="user"
|
||||
android:theme="@style/BugleTheme.SettingsActivity">
|
||||
android:theme="@style/Home.SubScreen">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
@@ -265,7 +281,8 @@
|
||||
android:label="@string/home_profile_title"
|
||||
android:parentActivityName="com.android.messaging.ui.conversation.ConversationActivity"
|
||||
android:screenOrientation="user"
|
||||
android:theme="@style/Home.SubScreen" />
|
||||
android:theme="@style/Home.SubScreen"
|
||||
android:windowSoftInputMode="stateAlwaysHidden|adjustResize" />
|
||||
|
||||
<!-- Conversation search -->
|
||||
<activity
|
||||
@@ -281,9 +298,8 @@
|
||||
<activity
|
||||
android:name=".ui.LicenseActivity"
|
||||
android:exported="true"
|
||||
android:label="@string/menu_license"
|
||||
android:theme="@android:style/Theme.Holo.Light.Dialog">
|
||||
</activity>
|
||||
android:label="@string/home_settings_licenses"
|
||||
android:theme="@style/Home.SubScreen" />
|
||||
|
||||
<!-- Message Forwarding -->
|
||||
<activity
|
||||
@@ -327,20 +343,20 @@
|
||||
|
||||
<provider
|
||||
android:name=".datamodel.MessagingContentProvider"
|
||||
android:authorities="com.android.messaging.datamodel.MessagingContentProvider"
|
||||
android:authorities="com.bnkode.notie.datamodel.MessagingContentProvider"
|
||||
android:exported="false"
|
||||
android:label="@string/app_name">
|
||||
</provider>
|
||||
|
||||
<provider
|
||||
android:name=".datamodel.MmsFileProvider"
|
||||
android:authorities="com.android.messaging.datamodel.MmsFileProvider"
|
||||
android:authorities="com.bnkode.notie.datamodel.MmsFileProvider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true" />
|
||||
|
||||
<provider
|
||||
android:name=".datamodel.MediaScratchFileProvider"
|
||||
android:authorities="com.android.messaging.datamodel.MediaScratchFileProvider"
|
||||
android:authorities="com.bnkode.notie.datamodel.MediaScratchFileProvider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true" />
|
||||
|
||||
@@ -396,18 +412,18 @@
|
||||
android:name=".receiver.SendStatusReceiver"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="com.android.messaging.receiver.SendStatusReceiver.MESSAGE_SENT" />
|
||||
<action android:name="com.bnkode.notie.receiver.SendStatusReceiver.MESSAGE_SENT" />
|
||||
<data android:scheme="content" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="com.android.messaging.receiver.SendStatusReceiver.MESSAGE_DELIVERED" />
|
||||
<action android:name="com.bnkode.notie.receiver.SendStatusReceiver.MESSAGE_DELIVERED" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="com.android.messaging.receiver.SendStatusReceiver.MMS_SENT" />
|
||||
<action android:name="com.bnkode.notie.receiver.SendStatusReceiver.MMS_SENT" />
|
||||
<data android:scheme="content" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="com.android.messaging.receiver.SendStatusReceiver.MMS_DOWNLOADED" />
|
||||
<action android:name="com.bnkode.notie.receiver.SendStatusReceiver.MMS_DOWNLOADED" />
|
||||
<data android:scheme="content" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
@@ -457,7 +473,7 @@
|
||||
android:name=".datamodel.action.ActionServiceImpl$PendingActionReceiver"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="com.android.messaging.datamodel.PENDING_ACTION" />
|
||||
<action android:name="com.bnkode.notie.datamodel.PENDING_ACTION" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ import com.android.messaging.sms.BugleApnSettingsLoader;
|
||||
import com.android.messaging.sms.BugleUserAgentInfoLoader;
|
||||
import com.android.messaging.sms.MmsConfig;
|
||||
import com.android.messaging.ui.ConversationDrawables;
|
||||
import com.android.messaging.ui.home.HomeToast;
|
||||
import com.android.messaging.util.BuglePrefsKeys;
|
||||
import com.android.messaging.util.LogUtil;
|
||||
import com.android.messaging.util.PhoneUtils;
|
||||
@@ -57,6 +58,7 @@ public class BugleApplication extends Application implements UncaughtExceptionHa
|
||||
super.onCreate();
|
||||
|
||||
FactoryImpl.register(getApplicationContext(), this);
|
||||
HomeToast.install(this);
|
||||
|
||||
sSystemUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
|
||||
Thread.setDefaultUncaughtExceptionHandler(this);
|
||||
|
||||
@@ -66,6 +66,7 @@ import com.android.messaging.datamodel.media.UriImageRequestDescriptor;
|
||||
import com.android.messaging.sms.MmsSmsUtils;
|
||||
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;
|
||||
@@ -354,6 +355,9 @@ public class BugleNotifications {
|
||||
final Context context = Factory.get().getApplicationContext();
|
||||
// TODO: Need to fix this for multi conversation notifications to rate limit dings.
|
||||
final String conversationId = state.mConversationIds.first();
|
||||
if (MutedConversationStore.isMuted(context, conversationId)) {
|
||||
return;
|
||||
}
|
||||
String id = NotificationsUtil.DEFAULT_CHANNEL_ID;
|
||||
if (NotificationsUtil.getNotificationChannel(context, conversationId) != null) {
|
||||
id = conversationId;
|
||||
|
||||
@@ -103,7 +103,7 @@ public abstract class FileProvider extends ContentProvider {
|
||||
final String fileId = uri.getPath();
|
||||
if (isValidFileId(fileId)) {
|
||||
final File file = getFile(fileId, getExtensionFromUri(uri));
|
||||
return file.delete() ? 1 : 0;
|
||||
return file != null && file.delete() ? 1 : 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -114,6 +114,9 @@ public abstract class FileProvider extends ContentProvider {
|
||||
final String fileId = uri.getPath();
|
||||
if (isValidFileId(fileId)) {
|
||||
final File file = getFile(fileId, getExtensionFromUri(uri));
|
||||
if (file == null) {
|
||||
throw new FileNotFoundException(String.valueOf(uri));
|
||||
}
|
||||
final int mode =
|
||||
(TextUtils.equals(fileMode, "r") ? ParcelFileDescriptor.MODE_READ_ONLY :
|
||||
ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_TRUNCATE);
|
||||
|
||||
@@ -46,7 +46,7 @@ public class MediaScratchFileProvider extends FileProvider {
|
||||
private static final SimpleArrayMap<Uri, String> sUriToDisplayNameMap = new SimpleArrayMap<>();
|
||||
|
||||
public static final String AUTHORITY =
|
||||
"com.android.messaging.datamodel.MediaScratchFileProvider";
|
||||
"com.bnkode.notie.datamodel.MediaScratchFileProvider";
|
||||
private static final String MEDIA_SCRATCH_SPACE_DIR = "mediascratchspace";
|
||||
|
||||
public static boolean isMediaScratchSpaceUri(final Uri uri) {
|
||||
@@ -68,8 +68,8 @@ public class MediaScratchFileProvider extends FileProvider {
|
||||
public static Uri buildMediaScratchSpaceUri(final String extension) {
|
||||
final Uri uri = FileProvider.buildFileUri(AUTHORITY, extension);
|
||||
final File file = getFileWithExtension(uri.getPath(), extension);
|
||||
if (!ensureFileExists(file)) {
|
||||
LogUtil.e(TAG, "Failed to create temp file " + file.getAbsolutePath());
|
||||
if (file == null || !ensureFileExists(file)) {
|
||||
LogUtil.e(TAG, "Failed to create temp file for " + uri);
|
||||
}
|
||||
return uri;
|
||||
}
|
||||
@@ -90,8 +90,12 @@ public class MediaScratchFileProvider extends FileProvider {
|
||||
|
||||
private static File getFileWithExtension(final String path, final String extension) {
|
||||
final Context context = Factory.get().getApplicationContext();
|
||||
String name = path;
|
||||
if (name != null && name.startsWith("/")) {
|
||||
name = name.substring(1);
|
||||
}
|
||||
final File filePath = new File(getDirectory(context),
|
||||
TextUtils.isEmpty(extension) ? path : path + "." + extension);
|
||||
TextUtils.isEmpty(extension) ? name : name + "." + extension);
|
||||
|
||||
try {
|
||||
if (!filePath.getCanonicalPath()
|
||||
|
||||
@@ -53,7 +53,7 @@ public class MessagingContentProvider extends ContentProvider {
|
||||
private static final String TAG = LogUtil.BUGLE_TAG;
|
||||
|
||||
public static final String AUTHORITY =
|
||||
"com.android.messaging.datamodel.MessagingContentProvider";
|
||||
"com.bnkode.notie.datamodel.MessagingContentProvider";
|
||||
private static final String CONTENT_AUTHORITY = "content://" + AUTHORITY + '/';
|
||||
|
||||
// Conversations query
|
||||
|
||||
@@ -32,7 +32,7 @@ import java.io.IOException;
|
||||
public class MmsFileProvider extends FileProvider {
|
||||
private static final String TAG = LogUtil.BUGLE_TAG;
|
||||
|
||||
static final String AUTHORITY = "com.android.messaging.datamodel.MmsFileProvider";
|
||||
static final String AUTHORITY = "com.bnkode.notie.datamodel.MmsFileProvider";
|
||||
private static final String RAW_MMS_DIR = "rawmms";
|
||||
|
||||
/**
|
||||
|
||||
@@ -137,7 +137,7 @@ public class ActionServiceImpl extends JobIntentService {
|
||||
* Broadcast receiver for alarms scheduled through ActionService.
|
||||
*/
|
||||
public static class PendingActionReceiver extends BroadcastReceiver {
|
||||
static final String ACTION = "com.android.messaging.datamodel.PENDING_ACTION";
|
||||
static final String ACTION = "com.bnkode.notie.datamodel.PENDING_ACTION";
|
||||
|
||||
/**
|
||||
* Allocate an intent with a specific opcode and alarm action.
|
||||
|
||||
@@ -22,6 +22,7 @@ import android.widget.Toast;
|
||||
|
||||
import com.android.messaging.Factory;
|
||||
import com.android.messaging.R;
|
||||
import com.android.messaging.ui.home.HomeToast;
|
||||
import com.android.messaging.datamodel.DataModel;
|
||||
import com.android.messaging.datamodel.data.MessageData;
|
||||
import com.android.messaging.datamodel.data.ParticipantData;
|
||||
@@ -115,13 +116,13 @@ public class BugleActionToasts {
|
||||
}
|
||||
|
||||
private static void showToast(final int messageResId) {
|
||||
ThreadUtil.getMainThreadHandler().post(() -> Toast.makeText(getApplicationContext(),
|
||||
getApplicationContext().getString(messageResId), Toast.LENGTH_LONG).show());
|
||||
ThreadUtil.getMainThreadHandler().post(() -> HomeToast.show(getApplicationContext(),
|
||||
messageResId, Toast.LENGTH_LONG));
|
||||
}
|
||||
|
||||
private static void showToast(final String message) {
|
||||
ThreadUtil.getMainThreadHandler().post(() ->
|
||||
Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show());
|
||||
HomeToast.show(getApplicationContext(), message, Toast.LENGTH_LONG));
|
||||
}
|
||||
|
||||
private static Context getApplicationContext() {
|
||||
@@ -143,12 +144,11 @@ public class BugleActionToasts {
|
||||
final boolean block,
|
||||
final String destination) {
|
||||
if (success) {
|
||||
Toast.makeText(mContext,
|
||||
HomeToast.show(mContext,
|
||||
block
|
||||
? R.string.update_destination_blocked
|
||||
: R.string.update_destination_unblocked,
|
||||
Toast.LENGTH_LONG
|
||||
).show();
|
||||
Toast.LENGTH_LONG);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+110
-13
@@ -367,27 +367,25 @@ public class ConversationMessageData {
|
||||
* @return the text that is a concatenation of all text parts
|
||||
*/
|
||||
public String getText() {
|
||||
// This is optimized for single text part case, which is the majority
|
||||
return stripDisplayText(getRawText());
|
||||
}
|
||||
|
||||
// For single text part, we just return the part without creating the StringBuilder
|
||||
public String getRawText() {
|
||||
// Optimized for the common single text part case.
|
||||
String firstTextPart = null;
|
||||
boolean foundText = false;
|
||||
// For multiple text parts, we need the StringBuilder and the separator for concatenation
|
||||
StringBuilder sb = null;
|
||||
String separator = null;
|
||||
for (final MessagePartData part : mParts) {
|
||||
if (part.isText()) {
|
||||
if (!foundText) {
|
||||
// First text part
|
||||
firstTextPart = part.getText();
|
||||
foundText = true;
|
||||
} else {
|
||||
// Second and beyond
|
||||
if (sb == null) {
|
||||
// Need the StringBuilder and the separator starting from 2nd text part
|
||||
sb = new StringBuilder();
|
||||
if (!TextUtils.isEmpty(firstTextPart)) {
|
||||
sb.append(firstTextPart);
|
||||
sb.append(firstTextPart);
|
||||
}
|
||||
separator = BugleGservicesKeys.MMS_TEXT_CONCAT_SEPARATOR_DEFAULT;
|
||||
}
|
||||
@@ -401,13 +399,112 @@ public class ConversationMessageData {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sb == null) {
|
||||
// Only one text part
|
||||
return firstTextPart;
|
||||
} else {
|
||||
// More than one
|
||||
return sb.toString();
|
||||
return sb == null ? firstTextPart : sb.toString();
|
||||
}
|
||||
|
||||
private static String stripDisplayText(final String text) {
|
||||
return sanitizeDisplayText(text);
|
||||
}
|
||||
|
||||
public static String sanitizeDisplayText(final String text) {
|
||||
if (text == null) {
|
||||
return null;
|
||||
}
|
||||
final StringBuilder out = new StringBuilder(text.length());
|
||||
boolean lineStart = true;
|
||||
for (int i = 0; i < text.length(); ) {
|
||||
final int cp = text.codePointAt(i);
|
||||
if (lineStart && isDisplayPadding(cp)) {
|
||||
i += Character.charCount(cp);
|
||||
continue;
|
||||
}
|
||||
lineStart = cp == '\n' || cp == '\r';
|
||||
out.appendCodePoint(cp);
|
||||
i += Character.charCount(cp);
|
||||
}
|
||||
int end = out.length();
|
||||
while (end > 0 && isDisplayPadding(out.charAt(end - 1))) {
|
||||
end--;
|
||||
}
|
||||
return end <= 0 ? null : out.substring(0, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep original spaces but flatten body line breaks so list / peek preview
|
||||
* wraps by width only.
|
||||
*/
|
||||
public static String flattenPreviewText(final String text) {
|
||||
if (text == null) {
|
||||
return null;
|
||||
}
|
||||
final StringBuilder out = new StringBuilder(text.length());
|
||||
boolean pendingBreak = false;
|
||||
for (int i = 0; i < text.length(); ) {
|
||||
final int cp = text.codePointAt(i);
|
||||
if (cp == '\n' || cp == '\r') {
|
||||
pendingBreak = true;
|
||||
} else {
|
||||
if (pendingBreak && out.length() > 0) {
|
||||
out.append(' ');
|
||||
}
|
||||
pendingBreak = false;
|
||||
out.appendCodePoint(cp);
|
||||
}
|
||||
i += Character.charCount(cp);
|
||||
}
|
||||
return out.length() == 0 ? null : out.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop spaces next to CJK so wrapped lines do not start with a leftover space.
|
||||
*/
|
||||
public static String collapseCjkWrapSpaces(final String text) {
|
||||
if (text == null || text.isEmpty()) {
|
||||
return text;
|
||||
}
|
||||
final StringBuilder out = new StringBuilder(text.length());
|
||||
for (int i = 0; i < text.length(); ) {
|
||||
final int cp = text.codePointAt(i);
|
||||
final int n = Character.charCount(cp);
|
||||
if (cp == ' ' || cp == 0x00A0 || cp == 0x3000) {
|
||||
final int prev = out.length() > 0 ? out.codePointBefore(out.length()) : 0;
|
||||
final int next = i + n < text.length() ? text.codePointAt(i + n) : 0;
|
||||
if (isCjk(prev) || isCjk(next)) {
|
||||
i += n;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
out.appendCodePoint(cp);
|
||||
i += n;
|
||||
}
|
||||
return out.toString();
|
||||
}
|
||||
|
||||
private static boolean isCjk(final int cp) {
|
||||
return Character.UnicodeBlock.of(cp) == Character.UnicodeBlock.HANGUL_SYLLABLES
|
||||
|| Character.UnicodeBlock.of(cp) == Character.UnicodeBlock.HANGUL_JAMO
|
||||
|| Character.UnicodeBlock.of(cp) == Character.UnicodeBlock.HANGUL_COMPATIBILITY_JAMO
|
||||
|| Character.UnicodeBlock.of(cp) == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS
|
||||
|| Character.UnicodeBlock.of(cp) == Character.UnicodeBlock.CJK_SYMBOLS_AND_PUNCTUATION;
|
||||
}
|
||||
|
||||
private static boolean isDisplayPadding(final int cp) {
|
||||
final int type = Character.getType(cp);
|
||||
return Character.isWhitespace(cp)
|
||||
|| Character.isSpaceChar(cp)
|
||||
|| Character.isISOControl(cp)
|
||||
|| type == Character.FORMAT
|
||||
|| type == Character.CONTROL
|
||||
|| cp == 0x00A0
|
||||
|| cp == 0x1680
|
||||
|| cp == 0x180E
|
||||
|| cp == 0x200B
|
||||
|| cp == 0x200C
|
||||
|| cp == 0x200D
|
||||
|| cp == 0x2060
|
||||
|| cp == 0x3000
|
||||
|| cp == 0x3164
|
||||
|| cp == 0xFEFF;
|
||||
}
|
||||
|
||||
public boolean hasAttachments() {
|
||||
|
||||
@@ -282,6 +282,12 @@ public class ParticipantData implements Parcelable {
|
||||
return pd;
|
||||
}
|
||||
|
||||
public static String getCachedParticipantId(final int subId) {
|
||||
synchronized (sSubIdtoParticipantIdCache) {
|
||||
return sSubIdtoParticipantIdCache.get(subId);
|
||||
}
|
||||
}
|
||||
|
||||
public static String getParticipantId(final DatabaseWrapper db, final int subId) {
|
||||
String id;
|
||||
synchronized (sSubIdtoParticipantIdCache) {
|
||||
|
||||
@@ -127,6 +127,14 @@ public class AvatarRequest extends UriImageRequest<AvatarRequestDescriptor> {
|
||||
mContext.getResources(), R.drawable.ic_person_light, mContext.getTheme());
|
||||
float dstWidth = Math.min(defaultPerson.getIntrinsicWidth() * SCALING_FACTOR, width);
|
||||
float dstHeight = Math.min(defaultPerson.getIntrinsicHeight() * SCALING_FACTOR, height);
|
||||
final float dest = Math.min(width, height);
|
||||
// List-size tiles keep the 24dp * 1.33 silhouette. Profile-popup tiles
|
||||
// (240dp) would otherwise leave a tiny icon in the center of the card.
|
||||
if (dest > dstWidth * 2f) {
|
||||
final float large = dest * 0.56f;
|
||||
dstWidth = large;
|
||||
dstHeight = large;
|
||||
}
|
||||
|
||||
canvas.translate((width - dstWidth) / 2, (height - dstHeight) / 2);
|
||||
defaultPerson.setBounds(0, 0, (int)dstWidth, (int)dstHeight);
|
||||
|
||||
@@ -38,13 +38,13 @@ import com.android.messaging.util.LogUtil;
|
||||
*/
|
||||
public class SendStatusReceiver extends BroadcastReceiver {
|
||||
public static final String MESSAGE_SENT_ACTION =
|
||||
"com.android.messaging.receiver.SendStatusReceiver.MESSAGE_SENT";
|
||||
"com.bnkode.notie.receiver.SendStatusReceiver.MESSAGE_SENT";
|
||||
public static final String MESSAGE_DELIVERED_ACTION =
|
||||
"com.android.messaging.receiver.SendStatusReceiver.MESSAGE_DELIVERED";
|
||||
"com.bnkode.notie.receiver.SendStatusReceiver.MESSAGE_DELIVERED";
|
||||
public static final String MMS_SENT_ACTION =
|
||||
"com.android.messaging.receiver.SendStatusReceiver.MMS_SENT";
|
||||
"com.bnkode.notie.receiver.SendStatusReceiver.MMS_SENT";
|
||||
public static final String MMS_DOWNLOADED_ACTION =
|
||||
"com.android.messaging.receiver.SendStatusReceiver.MMS_DOWNLOADED";
|
||||
"com.bnkode.notie.receiver.SendStatusReceiver.MMS_DOWNLOADED";
|
||||
|
||||
// Defined by platform, but no constant provided. See docs for SmsManager.sendTextMessage.
|
||||
public static final String EXTRA_ERROR_CODE = "errorCode";
|
||||
|
||||
@@ -70,9 +70,6 @@ public class BugleActionBarActivity extends AppCompatActivity implements ImeUtil
|
||||
|
||||
mLastScreenHeight = getResources().getDisplayMetrics().heightPixels;
|
||||
LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onCreate");
|
||||
|
||||
getWindow().setStatusBarColor(getResources().getColor(R.color.action_bar_background_color,
|
||||
getTheme()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -42,6 +42,7 @@ public class ContactIconView extends AsyncImageView {
|
||||
private static final int LARGE_ICON_SIZE_ID = 1;
|
||||
private static final int SMALL_ICON_SIZE_ID = 2;
|
||||
private static final int PROFILE_POPUP_ICON_SIZE_ID = 3;
|
||||
private static final int HERO_ICON_SIZE_ID = 4;
|
||||
|
||||
protected final int mIconSize;
|
||||
private final boolean mCropToCircle;
|
||||
@@ -79,6 +80,10 @@ public class ContactIconView extends AsyncImageView {
|
||||
R.dimen.home_list_profile_avatar_size);
|
||||
cropToCircle = false;
|
||||
break;
|
||||
case HERO_ICON_SIZE_ID:
|
||||
mIconSize = (int) resources.getDimension(
|
||||
R.dimen.home_profile_avatar_size);
|
||||
break;
|
||||
default:
|
||||
// For the compiler, something has to be set even with the assert.
|
||||
mIconSize = 0;
|
||||
|
||||
@@ -17,20 +17,31 @@
|
||||
|
||||
package com.android.messaging.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.webkit.WebView;
|
||||
|
||||
import com.android.messaging.R;
|
||||
import com.android.messaging.ui.home.HomeSubHeader;
|
||||
|
||||
public class LicenseActivity extends Activity {
|
||||
private final String LICENSE_URL = "file:///android_asset/licenses.html";
|
||||
public class LicenseActivity extends BugleActionBarActivity {
|
||||
private static final String LICENSE_URL = "file:///android_asset/licenses.html";
|
||||
|
||||
@Override
|
||||
public void onCreate(final Bundle bundle) {
|
||||
super.onCreate(bundle);
|
||||
setContentView(R.layout.license_activity);
|
||||
protected void onCreate(final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.home_license_activity);
|
||||
if (getSupportActionBar() != null) {
|
||||
getSupportActionBar().hide();
|
||||
}
|
||||
HomeSubHeader.bind(this, R.string.home_settings_licenses, v -> finish());
|
||||
final WebView webView = findViewById(R.id.content);
|
||||
webView.setBackgroundColor(getColor(R.color.home_bg));
|
||||
webView.loadUrl(LICENSE_URL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish() {
|
||||
super.finish();
|
||||
overridePendingTransition(R.anim.home_activity_hold, R.anim.home_activity_close);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ import androidx.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup.MarginLayoutParams;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
@@ -272,18 +271,12 @@ public class SnackBar {
|
||||
private void setUpButton() {
|
||||
if (mAction == null || mAction.getActionRunnable() == null) {
|
||||
mActionTextView.setVisibility(View.GONE);
|
||||
// In the XML layout we add left/right padding to the button to add space between
|
||||
// the message text and the button and on the right side we add padding to put space
|
||||
// between the button and the edge of the snack bar. This is so the button can use the
|
||||
// padding area as part of it's click target. Since we have no button, we need to put
|
||||
// some margin on the right side. While the left margin is already set on the wrapper,
|
||||
// we're setting it again to not have to make a special case for RTL.
|
||||
final MarginLayoutParams lp = (MarginLayoutParams) mMessageWrapper.getLayoutParams();
|
||||
final int leftRightMargin = mContext.getResources().getDimensionPixelSize(
|
||||
R.dimen.snack_bar_left_right_margin);
|
||||
lp.leftMargin = leftRightMargin;
|
||||
lp.rightMargin = leftRightMargin;
|
||||
mMessageWrapper.setLayoutParams(lp);
|
||||
final int endPad = mSnackBarView.getPaddingStart();
|
||||
mSnackBarView.setPaddingRelative(
|
||||
mSnackBarView.getPaddingStart(),
|
||||
mSnackBarView.getPaddingTop(),
|
||||
endPad,
|
||||
mSnackBarView.getPaddingBottom());
|
||||
} else {
|
||||
mActionTextView.setVisibility(View.VISIBLE);
|
||||
mActionTextView.setText(mAction.getActionLabel());
|
||||
|
||||
@@ -69,7 +69,7 @@ public abstract class UIIntents {
|
||||
public static final String UI_INTENT_EXTRA_WITH_CUSTOM_TRANSITION = "with_custom_transition";
|
||||
|
||||
public static final String ACTION_RESET_NOTIFICATIONS =
|
||||
"com.android.messaging.reset_notifications";
|
||||
"com.bnkode.notie.reset_notifications";
|
||||
|
||||
// Sending VCard uri to VCard detail activity
|
||||
public static final String UI_INTENT_EXTRA_VCARD_URI = "vcard_uri";
|
||||
@@ -103,7 +103,7 @@ public abstract class UIIntents {
|
||||
public static final String UI_INTENT_EXTRA_ATTACHMENT_TYPE = "attachment_type";
|
||||
|
||||
public static final String ACTION_WIDGET_CONVERSATION =
|
||||
"com.android.messaging.widget_conversation:";
|
||||
"com.bnkode.notie.widget_conversation:";
|
||||
|
||||
public static final String UI_INTENT_EXTRA_REQUIRES_MMS = "requires_mms";
|
||||
|
||||
@@ -210,6 +210,16 @@ public abstract class UIIntents {
|
||||
*/
|
||||
public abstract void launchHomeComposeActivity(final Context context);
|
||||
|
||||
/**
|
||||
* Launch home UI for message automations.
|
||||
*/
|
||||
public abstract void launchHomeAutomationActivity(final Context context);
|
||||
|
||||
/**
|
||||
* Launch home UI to create an automation task.
|
||||
*/
|
||||
public abstract void launchHomeAutomationCreateActivity(final Context context);
|
||||
|
||||
/**
|
||||
* Launch an activity to show a class zero message
|
||||
*/
|
||||
|
||||
@@ -234,6 +234,20 @@ public class UIIntentsImpl extends UIIntents {
|
||||
context.startActivity(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void launchHomeAutomationActivity(final Context context) {
|
||||
final Intent intent = new Intent(context,
|
||||
com.android.messaging.ui.home.HomeAutomationActivity.class);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void launchHomeAutomationCreateActivity(final Context context) {
|
||||
final Intent intent = new Intent(context,
|
||||
com.android.messaging.ui.home.HomeAutomationCreateActivity.class);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void launchHomeProfileActivity(final Activity activity, final String conversationId) {
|
||||
final Intent intent = new Intent(activity,
|
||||
|
||||
+109
-115
@@ -24,148 +24,142 @@ import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
import android.text.TextUtils;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.app.NavUtils;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceFragmentCompat;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
|
||||
import com.android.messaging.R;
|
||||
import com.android.messaging.ui.BugleActionBarActivity;
|
||||
import com.android.messaging.ui.LicenseActivity;
|
||||
import com.android.messaging.ui.UIIntents;
|
||||
import com.android.messaging.ui.home.HomeSettingsRow;
|
||||
import com.android.messaging.ui.home.HomeSubHeader;
|
||||
import com.android.messaging.util.BuglePrefs;
|
||||
import com.android.messaging.util.ChangeDefaultSmsAppHelper;
|
||||
import com.android.messaging.util.PhoneUtils;
|
||||
|
||||
public class ApplicationSettingsActivity extends BugleActionBarActivity {
|
||||
|
||||
private View mSmsRow;
|
||||
private ChangeDefaultSmsAppHelper mChangeDefaultSmsAppHelper;
|
||||
private final ActivityResultLauncher<Intent> mLauncher = registerForActivityResult(
|
||||
new ActivityResultContracts.StartActivityForResult(), result -> {
|
||||
if (result.getResultCode() == Activity.RESULT_OK) {
|
||||
if (mChangeDefaultSmsAppHelper == null) {
|
||||
mChangeDefaultSmsAppHelper = new ChangeDefaultSmsAppHelper();
|
||||
}
|
||||
mChangeDefaultSmsAppHelper.handleChangeDefaultSmsResult(
|
||||
REQUEST_SET_DEFAULT_SMS_APP, result.getResultCode(), null);
|
||||
}
|
||||
});
|
||||
|
||||
@Override
|
||||
protected void onCreate(final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.home_settings_activity);
|
||||
if (getSupportActionBar() != null) {
|
||||
getSupportActionBar().hide();
|
||||
}
|
||||
HomeSubHeader.bind(this, R.string.home_settings_title, v -> finish());
|
||||
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
mSmsRow = findViewById(R.id.home_settings_sms);
|
||||
bindStaticRows();
|
||||
updateSmsRow();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
updateSmsRow();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish() {
|
||||
super.finish();
|
||||
overridePendingTransition(R.anim.home_activity_hold, R.anim.home_activity_close);
|
||||
}
|
||||
|
||||
private void bindStaticRows() {
|
||||
final BuglePrefs prefs = BuglePrefs.getApplicationPrefs();
|
||||
final boolean topLevel = getIntent().getBooleanExtra(
|
||||
UIIntents.UI_INTENT_EXTRA_TOP_LEVEL_SETTINGS, false);
|
||||
|
||||
HomeSettingsRow.bind(findViewById(R.id.home_settings_notifications),
|
||||
R.drawable.ic_home_bell,
|
||||
getString(R.string.home_settings_notifications),
|
||||
getString(R.string.home_settings_notifications_subtitle),
|
||||
true, false, false,
|
||||
v -> {
|
||||
final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
|
||||
intent.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName());
|
||||
startActivity(intent);
|
||||
}, null);
|
||||
|
||||
final String sendKey = getString(R.string.send_sound_pref_key);
|
||||
final boolean sendSound = prefs.getBoolean(sendKey,
|
||||
getResources().getBoolean(R.bool.send_sound_pref_default));
|
||||
HomeSettingsRow.bind(findViewById(R.id.home_settings_send_sound),
|
||||
R.drawable.ic_home_sound,
|
||||
getString(R.string.home_settings_send_sound),
|
||||
null, false, true, sendSound,
|
||||
null, (button, checked) -> prefs.putBoolean(sendKey, checked));
|
||||
|
||||
final String swipeKey = getString(R.string.swipe_right_deletes_conversation_key);
|
||||
final boolean swipeDelete = prefs.getBoolean(swipeKey, false);
|
||||
HomeSettingsRow.bind(findViewById(R.id.home_settings_swipe_delete),
|
||||
R.drawable.ic_home_menu_trash,
|
||||
getString(R.string.home_settings_swipe_delete),
|
||||
getString(R.string.home_settings_swipe_delete_subtitle),
|
||||
false, true, swipeDelete,
|
||||
null, (button, checked) -> prefs.putBoolean(swipeKey, checked));
|
||||
|
||||
final View advancedCard = findViewById(R.id.home_settings_advanced_card);
|
||||
if (topLevel) {
|
||||
getSupportActionBar().setTitle(getString(R.string.settings_activity_title));
|
||||
advancedCard.setVisibility(View.VISIBLE);
|
||||
HomeSettingsRow.bind(findViewById(R.id.home_settings_advanced),
|
||||
R.drawable.ic_home_nav_settings,
|
||||
getString(R.string.home_settings_advanced),
|
||||
null, true, false, false,
|
||||
v -> {
|
||||
startActivity(UIIntents.get().getAdvancedSettingsIntent(this));
|
||||
overridePendingTransition(R.anim.home_activity_open,
|
||||
R.anim.home_activity_hold);
|
||||
},
|
||||
null);
|
||||
} else {
|
||||
advancedCard.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
|
||||
ft.replace(android.R.id.content, new ApplicationSettingsFragment());
|
||||
ft.commit();
|
||||
HomeSettingsRow.bind(findViewById(R.id.home_settings_licenses),
|
||||
R.drawable.ic_info_light,
|
||||
getString(R.string.home_settings_licenses),
|
||||
null, true, false, false,
|
||||
v -> {
|
||||
startActivity(new Intent(this, LicenseActivity.class));
|
||||
overridePendingTransition(R.anim.home_activity_open, R.anim.home_activity_hold);
|
||||
},
|
||||
null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
if (super.onCreateOptionsMenu(menu)) {
|
||||
return true;
|
||||
private void updateSmsRow() {
|
||||
if (mSmsRow == null) {
|
||||
return;
|
||||
}
|
||||
getMenuInflater().inflate(R.menu.settings_menu, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(@NonNull final MenuItem item) {
|
||||
int itemId = item.getItemId();
|
||||
if (itemId == android.R.id.home) {
|
||||
NavUtils.navigateUpFromSameTask(this);
|
||||
return true;
|
||||
} else if (itemId == R.id.action_license) {
|
||||
final Intent intent = new Intent(this, LicenseActivity.class);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
String summary = getString(R.string.default_sms_app,
|
||||
PhoneUtils.getDefault().getDefaultSmsAppLabel());
|
||||
if (TextUtils.isEmpty(PhoneUtils.getDefault().getDefaultSmsAppLabel())) {
|
||||
summary = getString(R.string.app_name);
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
public static class ApplicationSettingsFragment extends PreferenceFragmentCompat {
|
||||
|
||||
private String mNotificationsPreferenceKey;
|
||||
private String mSmsEnabledPrefKey;
|
||||
private Preference mSmsEnabledPreference;
|
||||
private ChangeDefaultSmsAppHelper mChangeDefaultSmsAppHelper;
|
||||
private final ActivityResultLauncher<Intent> mLauncher = registerForActivityResult(
|
||||
new ActivityResultContracts.StartActivityForResult(), result -> {
|
||||
if (result.getResultCode() == Activity.RESULT_OK) {
|
||||
if (mChangeDefaultSmsAppHelper == null) {
|
||||
mChangeDefaultSmsAppHelper = new ChangeDefaultSmsAppHelper();
|
||||
}
|
||||
mChangeDefaultSmsAppHelper.handleChangeDefaultSmsResult(
|
||||
REQUEST_SET_DEFAULT_SMS_APP, result.getResultCode(), null);
|
||||
HomeSettingsRow.bind(mSmsRow,
|
||||
R.drawable.ic_home_nav_messages,
|
||||
getString(R.string.home_settings_default_sms),
|
||||
summary, true, false, false,
|
||||
v -> {
|
||||
if (!PhoneUtils.getDefault().isDefaultSmsApp()) {
|
||||
final Intent intent = UIIntents.get().getChangeDefaultSmsAppIntent(this);
|
||||
mLauncher.launch(intent);
|
||||
}
|
||||
});
|
||||
|
||||
public ApplicationSettingsFragment() {
|
||||
// Required empty constructor
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(@Nullable Bundle savedInstanceState,
|
||||
@Nullable String rootKey) {
|
||||
getPreferenceManager().setSharedPreferencesName(BuglePrefs.SHARED_PREFERENCES_NAME);
|
||||
addPreferencesFromResource(R.xml.preferences_application);
|
||||
|
||||
mNotificationsPreferenceKey =
|
||||
getString(R.string.notifications_pref_key);
|
||||
mSmsEnabledPrefKey = getString(R.string.sms_enabled_pref_key);
|
||||
mSmsEnabledPreference = findPreference(mSmsEnabledPrefKey);
|
||||
|
||||
final PreferenceScreen advancedScreen = findPreference(
|
||||
getString(R.string.advanced_pref_key));
|
||||
final boolean topLevel = getActivity().getIntent().getBooleanExtra(
|
||||
UIIntents.UI_INTENT_EXTRA_TOP_LEVEL_SETTINGS, false);
|
||||
if (topLevel) {
|
||||
advancedScreen.setIntent(UIIntents.get()
|
||||
.getAdvancedSettingsIntent(getPreferenceScreen().getContext()));
|
||||
} else {
|
||||
// Hide the Advanced settings screen if this is not top-level; these are shown at
|
||||
// the parent SettingsActivity.
|
||||
getPreferenceScreen().removePreference(advancedScreen);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceTreeClick(@NonNull Preference preference) {
|
||||
if (preference.getKey().equals(mNotificationsPreferenceKey)) {
|
||||
Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
|
||||
intent.putExtra(Settings.EXTRA_APP_PACKAGE, getContext().getPackageName());
|
||||
startActivity(intent);
|
||||
}
|
||||
return super.onPreferenceTreeClick(preference);
|
||||
}
|
||||
|
||||
private void updateSmsEnabledPreferences() {
|
||||
String defaultSmsAppLabel = getString(R.string.default_sms_app,
|
||||
PhoneUtils.getDefault().getDefaultSmsAppLabel());
|
||||
if (!PhoneUtils.getDefault().isDefaultSmsApp()) {
|
||||
mSmsEnabledPreference.setOnPreferenceClickListener(preference -> {
|
||||
final Intent intent =
|
||||
UIIntents.get().getChangeDefaultSmsAppIntent(getActivity());
|
||||
mLauncher.launch(intent);
|
||||
return true;
|
||||
});
|
||||
} else {
|
||||
mSmsEnabledPreference.setOnPreferenceClickListener(null);
|
||||
// Fallback since we don't always get our own name
|
||||
if (TextUtils.isEmpty(defaultSmsAppLabel)) {
|
||||
defaultSmsAppLabel = getString(R.string.app_name);
|
||||
}
|
||||
}
|
||||
mSmsEnabledPreference.setSummary(defaultSmsAppLabel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
updateSmsEnabledPreferences();
|
||||
}
|
||||
}, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,12 +16,13 @@
|
||||
*/
|
||||
package com.android.messaging.ui.appsettings;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.RadioButton;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import com.android.messaging.R;
|
||||
import com.android.messaging.util.Assert;
|
||||
import com.android.messaging.util.BuglePrefs;
|
||||
@@ -48,9 +49,9 @@ public class GroupMmsSettingDialog {
|
||||
|
||||
private void show() {
|
||||
Assert.isNull(mDialog);
|
||||
mDialog = new AlertDialog.Builder(mContext, R.style.AlertDialogTheme)
|
||||
mDialog = new AlertDialog.Builder(mContext, R.style.Home_Dialog)
|
||||
.setView(createView())
|
||||
.setTitle(R.string.group_mms_pref_title)
|
||||
.setTitle(R.string.home_settings_group_mms)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show();
|
||||
}
|
||||
|
||||
+179
-135
@@ -17,174 +17,218 @@
|
||||
|
||||
package com.android.messaging.ui.appsettings;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.MenuItem;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.app.NavUtils;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceCategory;
|
||||
import androidx.preference.PreferenceFragmentCompat;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import com.android.messaging.Factory;
|
||||
import com.android.messaging.R;
|
||||
import com.android.messaging.datamodel.ParticipantRefresh;
|
||||
import com.android.messaging.datamodel.data.ParticipantData;
|
||||
import com.android.messaging.sms.MmsConfig;
|
||||
import com.android.messaging.ui.BugleActionBarActivity;
|
||||
import com.android.messaging.ui.UIIntents;
|
||||
import com.android.messaging.ui.home.HomeSettingsRow;
|
||||
import com.android.messaging.ui.home.HomeSubHeader;
|
||||
import com.android.messaging.util.BuglePrefs;
|
||||
import com.android.messaging.util.PhoneUtils;
|
||||
|
||||
public class PerSubscriptionSettingsActivity extends BugleActionBarActivity {
|
||||
private int mSubId;
|
||||
private BuglePrefs mPrefs;
|
||||
private String mDefaultPhoneNumber;
|
||||
private View mGroupRow;
|
||||
private View mPhoneRow;
|
||||
private View mRoamingRow;
|
||||
|
||||
@Override
|
||||
protected void onCreate(final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
final String title = getIntent().getStringExtra(
|
||||
UIIntents.UI_INTENT_EXTRA_PER_SUBSCRIPTION_SETTING_TITLE);
|
||||
if (!TextUtils.isEmpty(title)) {
|
||||
getSupportActionBar().setTitle(title);
|
||||
} else {
|
||||
// This will fall back to the default title, i.e. "Messaging settings," so No-op.
|
||||
setContentView(R.layout.home_settings_advanced_activity);
|
||||
if (getSupportActionBar() != null) {
|
||||
getSupportActionBar().hide();
|
||||
}
|
||||
|
||||
final FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
|
||||
final PerSubscriptionSettingsFragment fragment = new PerSubscriptionSettingsFragment();
|
||||
ft.replace(android.R.id.content, fragment);
|
||||
ft.commit();
|
||||
mSubId = getIntent().getIntExtra(UIIntents.UI_INTENT_EXTRA_SUB_ID,
|
||||
ParticipantData.DEFAULT_SELF_SUB_ID);
|
||||
mPrefs = BuglePrefs.getSubscriptionPrefs(mSubId);
|
||||
mDefaultPhoneNumber = PhoneUtils.get(mSubId).getCanonicalForSelf(false /* allowOverride */);
|
||||
|
||||
final String title = getIntent().getStringExtra(
|
||||
UIIntents.UI_INTENT_EXTRA_PER_SUBSCRIPTION_SETTING_TITLE);
|
||||
HomeSubHeader.bind(this,
|
||||
TextUtils.isEmpty(title) ? getString(R.string.home_settings_advanced) : title,
|
||||
v -> finish());
|
||||
|
||||
mGroupRow = findViewById(R.id.home_settings_group_mms);
|
||||
mPhoneRow = findViewById(R.id.home_settings_phone_number);
|
||||
mRoamingRow = findViewById(R.id.home_settings_roaming_retrieve);
|
||||
bindRows();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(@NonNull final MenuItem item) {
|
||||
if (item.getItemId() == android.R.id.home) {
|
||||
NavUtils.navigateUpFromSameTask(this);
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
updateGroupRow();
|
||||
updatePhoneRow();
|
||||
}
|
||||
|
||||
public static class PerSubscriptionSettingsFragment extends PreferenceFragmentCompat
|
||||
implements OnSharedPreferenceChangeListener {
|
||||
private PhoneNumberPreference mPhoneNumberPreference;
|
||||
private Preference mGroupMmsPreference;
|
||||
private String mGroupMmsPrefKey;
|
||||
private String mPhoneNumberKey;
|
||||
private int mSubId;
|
||||
@Override
|
||||
public void finish() {
|
||||
super.finish();
|
||||
overridePendingTransition(R.anim.home_activity_hold, R.anim.home_activity_close);
|
||||
}
|
||||
|
||||
public PerSubscriptionSettingsFragment() {
|
||||
// Required empty constructor
|
||||
private void bindRows() {
|
||||
final boolean defaultSms = PhoneUtils.getDefault().isDefaultSmsApp();
|
||||
final boolean groupEnabled = MmsConfig.get(mSubId).getGroupMmsEnabled();
|
||||
final boolean deliveryEnabled = MmsConfig.get(mSubId).getSMSDeliveryReportsEnabled();
|
||||
|
||||
if (groupEnabled) {
|
||||
mGroupRow.setVisibility(View.VISIBLE);
|
||||
updateGroupRow();
|
||||
HomeSettingsRow.setEnabled(mGroupRow, defaultSms);
|
||||
} else {
|
||||
mGroupRow.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) {
|
||||
// Get sub id from launch intent
|
||||
final Intent intent = requireActivity().getIntent();
|
||||
mSubId = (intent != null) ? intent.getIntExtra(UIIntents.UI_INTENT_EXTRA_SUB_ID,
|
||||
ParticipantData.DEFAULT_SELF_SUB_ID) : ParticipantData.DEFAULT_SELF_SUB_ID;
|
||||
updatePhoneRow();
|
||||
|
||||
final BuglePrefs subPrefs = Factory.get().getSubscriptionPrefs(mSubId);
|
||||
getPreferenceManager().setSharedPreferencesName(subPrefs.getSharedPreferencesName());
|
||||
addPreferencesFromResource(R.xml.preferences_per_subscription);
|
||||
|
||||
mPhoneNumberKey = getString(R.string.mms_phone_number_pref_key);
|
||||
mPhoneNumberPreference = findPreference(mPhoneNumberKey);
|
||||
final PreferenceCategory advancedCategory =
|
||||
findPreference(getString(R.string.advanced_category_pref_key));
|
||||
final PreferenceCategory mmsCategory =
|
||||
findPreference(getString(R.string.mms_messaging_category_pref_key));
|
||||
|
||||
mPhoneNumberPreference.setDefaultPhoneNumber(
|
||||
PhoneUtils.get(mSubId).getCanonicalForSelf(false/*allowOverride*/), mSubId);
|
||||
|
||||
mGroupMmsPrefKey = getString(R.string.group_mms_pref_key);
|
||||
mGroupMmsPreference = findPreference(mGroupMmsPrefKey);
|
||||
if (!MmsConfig.get(mSubId).getGroupMmsEnabled()) {
|
||||
// Always show group messaging setting even if the SIM has no number
|
||||
// If broadcast sms is selected, the SIM number is not needed
|
||||
// If group mms is selected, the phone number dialog will popup when message
|
||||
// is being sent, making sure we will have a self number for group mms.
|
||||
mmsCategory.removePreference(mGroupMmsPreference);
|
||||
} else {
|
||||
mGroupMmsPreference.setOnPreferenceClickListener(pref -> {
|
||||
GroupMmsSettingDialog.showDialog(getActivity(), mSubId);
|
||||
return true;
|
||||
final String autoKey = getString(R.string.auto_retrieve_mms_pref_key);
|
||||
final boolean autoOn = mPrefs.getBoolean(autoKey,
|
||||
getResources().getBoolean(R.bool.auto_retrieve_mms_pref_default));
|
||||
HomeSettingsRow.bind(findViewById(R.id.home_settings_auto_retrieve),
|
||||
R.drawable.ic_home_file_line,
|
||||
getString(R.string.home_settings_auto_retrieve),
|
||||
getString(R.string.home_settings_auto_retrieve_subtitle),
|
||||
false, true, autoOn, null,
|
||||
(button, checked) -> {
|
||||
mPrefs.putBoolean(autoKey, checked);
|
||||
updateRoamingEnabled(checked && defaultSms);
|
||||
});
|
||||
updateGroupMmsPrefSummary();
|
||||
}
|
||||
HomeSettingsRow.setEnabled(findViewById(R.id.home_settings_auto_retrieve), defaultSms);
|
||||
|
||||
if (!MmsConfig.get(mSubId).getSMSDeliveryReportsEnabled()) {
|
||||
final Preference deliveryReportsPref = findPreference(
|
||||
getString(R.string.delivery_reports_pref_key));
|
||||
advancedCategory.removePreference(deliveryReportsPref);
|
||||
}
|
||||
final String roamingKey = getString(R.string.auto_retrieve_mms_when_roaming_pref_key);
|
||||
final boolean roamingOn = mPrefs.getBoolean(roamingKey,
|
||||
getResources().getBoolean(R.bool.auto_retrieve_mms_when_roaming_pref_default));
|
||||
HomeSettingsRow.bind(mRoamingRow,
|
||||
R.drawable.ic_home_place_line,
|
||||
getString(R.string.home_settings_roaming_retrieve),
|
||||
getString(R.string.home_settings_roaming_retrieve_subtitle),
|
||||
false, true, roamingOn, null,
|
||||
(button, checked) -> mPrefs.putBoolean(roamingKey, checked));
|
||||
updateRoamingEnabled(autoOn && defaultSms);
|
||||
|
||||
// We want to disable preferences if we are not the default app, but we do all of the
|
||||
// above first so that the user sees the correct information on the screen
|
||||
if (!PhoneUtils.getDefault().isDefaultSmsApp()) {
|
||||
mGroupMmsPreference.setEnabled(false);
|
||||
final Preference autoRetrieveMmsPreference =
|
||||
findPreference(getString(R.string.auto_retrieve_mms_pref_key));
|
||||
autoRetrieveMmsPreference.setEnabled(false);
|
||||
final Preference deliveryReportsPreference =
|
||||
findPreference(getString(R.string.delivery_reports_pref_key));
|
||||
if (deliveryReportsPreference != null) {
|
||||
deliveryReportsPreference.setEnabled(false);
|
||||
}
|
||||
}
|
||||
if (deliveryEnabled) {
|
||||
final String deliveryKey = getString(R.string.delivery_reports_pref_key);
|
||||
final boolean deliveryOn = mPrefs.getBoolean(deliveryKey,
|
||||
getResources().getBoolean(R.bool.delivery_reports_pref_default));
|
||||
HomeSettingsRow.bind(findViewById(R.id.home_settings_delivery_reports),
|
||||
R.drawable.ic_home_checklist,
|
||||
getString(R.string.home_settings_delivery_reports),
|
||||
getString(R.string.home_settings_delivery_reports_subtitle),
|
||||
false, true, deliveryOn, null,
|
||||
(button, checked) -> mPrefs.putBoolean(deliveryKey, checked));
|
||||
HomeSettingsRow.setEnabled(findViewById(R.id.home_settings_delivery_reports),
|
||||
defaultSms);
|
||||
} else {
|
||||
findViewById(R.id.home_settings_delivery_label).setVisibility(View.GONE);
|
||||
findViewById(R.id.home_settings_delivery_card).setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
if (advancedCategory.getPreferenceCount() == 0) {
|
||||
getPreferenceScreen().removePreference(advancedCategory);
|
||||
private void updateGroupRow() {
|
||||
if (mGroupRow == null || mGroupRow.getVisibility() != View.VISIBLE) {
|
||||
return;
|
||||
}
|
||||
final boolean groupOn = mPrefs.getBoolean(getString(R.string.group_mms_pref_key),
|
||||
getResources().getBoolean(R.bool.group_mms_pref_default));
|
||||
HomeSettingsRow.bind(mGroupRow,
|
||||
R.drawable.ic_home_nav_messages,
|
||||
getString(R.string.home_settings_group_mms),
|
||||
getString(groupOn
|
||||
? R.string.home_settings_group_mms_on
|
||||
: R.string.home_settings_group_mms_off),
|
||||
true, false, false,
|
||||
v -> GroupMmsSettingDialog.showDialog(this, mSubId),
|
||||
null);
|
||||
HomeSettingsRow.setEnabled(mGroupRow, PhoneUtils.getDefault().isDefaultSmsApp());
|
||||
}
|
||||
|
||||
private void updatePhoneRow() {
|
||||
if (mPhoneRow == null) {
|
||||
return;
|
||||
}
|
||||
HomeSettingsRow.bind(mPhoneRow,
|
||||
R.drawable.ic_home_menu_person,
|
||||
getString(R.string.home_settings_phone_number),
|
||||
formatPhoneSummary(),
|
||||
true, false, false,
|
||||
v -> showPhoneNumberDialog(),
|
||||
null);
|
||||
}
|
||||
|
||||
private void updateRoamingEnabled(final boolean enabled) {
|
||||
if (mRoamingRow != null) {
|
||||
HomeSettingsRow.setEnabled(mRoamingRow, enabled);
|
||||
}
|
||||
}
|
||||
|
||||
private String formatPhoneSummary() {
|
||||
String value = mPrefs.getString(getString(R.string.mms_phone_number_pref_key), "");
|
||||
if (TextUtils.isEmpty(value)) {
|
||||
value = mDefaultPhoneNumber;
|
||||
}
|
||||
if (TextUtils.isEmpty(value)) {
|
||||
return getString(R.string.home_settings_phone_unknown);
|
||||
}
|
||||
return PhoneUtils.get(mSubId).formatForDisplay(value);
|
||||
}
|
||||
|
||||
private void showPhoneNumberDialog() {
|
||||
final View content = LayoutInflater.from(this)
|
||||
.inflate(R.layout.home_settings_phone_dialog, null);
|
||||
final EditText field = content.findViewById(R.id.home_settings_phone_field);
|
||||
String value = mPrefs.getString(getString(R.string.mms_phone_number_pref_key), "");
|
||||
if (TextUtils.isEmpty(value)) {
|
||||
value = mDefaultPhoneNumber;
|
||||
}
|
||||
if (value == null) {
|
||||
value = "";
|
||||
}
|
||||
field.setText(value);
|
||||
if (!TextUtils.isEmpty(value)) {
|
||||
field.setSelection(value.length());
|
||||
}
|
||||
new AlertDialog.Builder(this, R.style.Home_Dialog)
|
||||
.setTitle(R.string.home_settings_phone_number)
|
||||
.setView(content)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
||||
savePhoneNumber(field.getText().toString().trim());
|
||||
updatePhoneRow();
|
||||
})
|
||||
.show();
|
||||
}
|
||||
|
||||
private void savePhoneNumber(final String text) {
|
||||
final String key = getString(R.string.mms_phone_number_pref_key);
|
||||
if (TextUtils.isEmpty(text)) {
|
||||
mPrefs.remove(key);
|
||||
} else {
|
||||
final PhoneUtils phoneUtils = PhoneUtils.get(mSubId);
|
||||
final String canonical = phoneUtils.getCanonicalBySystemLocale(text);
|
||||
if (!TextUtils.isEmpty(mDefaultPhoneNumber)
|
||||
&& TextUtils.equals(canonical,
|
||||
phoneUtils.getCanonicalBySystemLocale(mDefaultPhoneNumber))) {
|
||||
mPrefs.remove(key);
|
||||
} else {
|
||||
mPrefs.putString(key, text);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateGroupMmsPrefSummary() {
|
||||
final boolean groupMmsEnabled = getPreferenceScreen().getSharedPreferences().getBoolean(
|
||||
mGroupMmsPrefKey, getResources().getBoolean(R.bool.group_mms_pref_default));
|
||||
mGroupMmsPreference.setSummary(groupMmsEnabled ?
|
||||
R.string.enable_group_mms : R.string.disable_group_mms);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
getPreferenceScreen().getSharedPreferences()
|
||||
.registerOnSharedPreferenceChangeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences,
|
||||
final String key) {
|
||||
if (key.equals(mGroupMmsPrefKey)) {
|
||||
updateGroupMmsPrefSummary();
|
||||
} else if (key.equals(mPhoneNumberKey)) {
|
||||
// Save the changed phone number in preferences specific to the sub id
|
||||
final String newPhoneNumber = mPhoneNumberPreference.getText();
|
||||
final BuglePrefs subPrefs = BuglePrefs.getSubscriptionPrefs(mSubId);
|
||||
if (TextUtils.isEmpty(newPhoneNumber)) {
|
||||
subPrefs.remove(mPhoneNumberKey);
|
||||
} else {
|
||||
subPrefs.putString(getString(R.string.mms_phone_number_pref_key),
|
||||
newPhoneNumber);
|
||||
}
|
||||
// Update the self participants so the new phone number will be reflected
|
||||
// everywhere in the UI.
|
||||
ParticipantRefresh.refreshSelfParticipants();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
getPreferenceScreen().getSharedPreferences()
|
||||
.unregisterOnSharedPreferenceChangeListener(this);
|
||||
}
|
||||
ParticipantRefresh.refreshSelfParticipants();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,20 +17,11 @@
|
||||
|
||||
package com.android.messaging.ui.appsettings;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.app.NavUtils;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.loader.app.LoaderManager;
|
||||
|
||||
import com.android.messaging.R;
|
||||
@@ -42,137 +33,98 @@ import com.android.messaging.datamodel.data.SettingsData.SettingsDataListener;
|
||||
import com.android.messaging.datamodel.data.SettingsData.SettingsItem;
|
||||
import com.android.messaging.ui.BugleActionBarActivity;
|
||||
import com.android.messaging.ui.UIIntents;
|
||||
import com.android.messaging.ui.home.HomeSettingsRow;
|
||||
import com.android.messaging.ui.home.HomeSubHeader;
|
||||
import com.android.messaging.util.Assert;
|
||||
import com.android.messaging.util.PhoneUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Shows the "primary" settings activity that contains two parts, one for application-wide settings
|
||||
* (dubbed "General settings"), and one or more for per-subscription settings (dubbed "Messaging
|
||||
* settings" for single-SIM, and the actual SIM name for multi-SIM). Clicking on either item
|
||||
* (e.g. "General settings") will open the detail settings activity (ApplicationSettingsActivity
|
||||
* in this case).
|
||||
* Multi-SIM settings hub. Single-SIM devices skip this and open ApplicationSettingsActivity.
|
||||
*/
|
||||
public class SettingsActivity extends BugleActionBarActivity {
|
||||
public class SettingsActivity extends BugleActionBarActivity implements SettingsDataListener {
|
||||
private final Binding<SettingsData> mBinding = BindingBase.createBinding(this);
|
||||
private ViewGroup mList;
|
||||
|
||||
@Override
|
||||
protected void onCreate(final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
// Directly open the detailed settings page as the top-level settings activity if this is
|
||||
// not a multi-SIM device.
|
||||
if (PhoneUtils.getDefault().getActiveSubscriptionCount() <= 1) {
|
||||
UIIntents.get().launchApplicationSettingsActivity(this, true /* topLevel */);
|
||||
finish();
|
||||
} else {
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(android.R.id.content, new SettingsFragment())
|
||||
.commit();
|
||||
overridePendingTransition(0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
setContentView(R.layout.home_settings_hub_activity);
|
||||
if (getSupportActionBar() != null) {
|
||||
getSupportActionBar().hide();
|
||||
}
|
||||
HomeSubHeader.bind(this, R.string.home_settings_title, v -> finish());
|
||||
mList = findViewById(R.id.home_settings_hub_list);
|
||||
|
||||
mBinding.bind(DataModel.get().createSettingsData(this, this));
|
||||
mBinding.getData().init(LoaderManager.getInstance(this), mBinding);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (mBinding.isBound()) {
|
||||
mBinding.unbind();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(@NonNull final MenuItem item) {
|
||||
if (item.getItemId() == android.R.id.home) {
|
||||
NavUtils.navigateUpFromSameTask(this);
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
public void finish() {
|
||||
super.finish();
|
||||
overridePendingTransition(R.anim.home_activity_hold, R.anim.home_activity_close);
|
||||
}
|
||||
|
||||
public static class SettingsFragment extends Fragment implements SettingsDataListener {
|
||||
private ListView mListView;
|
||||
private SettingsListAdapter mAdapter;
|
||||
private final Binding<SettingsData> mBinding = BindingBase.createBinding(this);
|
||||
|
||||
@Override
|
||||
public void onCreate(final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
mBinding.bind(DataModel.get().createSettingsData(getActivity(), this));
|
||||
mBinding.getData().init(LoaderManager.getInstance(this), mBinding);
|
||||
@Override
|
||||
public void onSelfParticipantDataLoaded(final SettingsData data) {
|
||||
mBinding.ensureBound(data);
|
||||
if (mList == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
|
||||
final Bundle savedInstanceState) {
|
||||
final View view = inflater.inflate(R.layout.settings_fragment, container, false);
|
||||
mListView = view.findViewById(android.R.id.list);
|
||||
mAdapter = new SettingsListAdapter(getActivity());
|
||||
mListView.setAdapter(mAdapter);
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
mBinding.unbind();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSelfParticipantDataLoaded(SettingsData data) {
|
||||
mBinding.ensureBound(data);
|
||||
mAdapter.setSettingsItems(data.getSettingsItems());
|
||||
}
|
||||
|
||||
/**
|
||||
* An adapter that displays a list of SettingsItem.
|
||||
*/
|
||||
private class SettingsListAdapter extends ArrayAdapter<SettingsItem> {
|
||||
public SettingsListAdapter(final Context context) {
|
||||
super(context, R.layout.settings_item_view, new ArrayList<>());
|
||||
mList.removeAllViews();
|
||||
final LayoutInflater inflater = LayoutInflater.from(this);
|
||||
boolean first = true;
|
||||
for (final SettingsItem item : data.getSettingsItems()) {
|
||||
final View row = inflater.inflate(R.layout.home_settings_row, mList, false);
|
||||
if (!first) {
|
||||
final ViewGroup.MarginLayoutParams lp =
|
||||
(ViewGroup.MarginLayoutParams) row.getLayoutParams();
|
||||
lp.topMargin = getResources().getDimensionPixelSize(R.dimen.looply_space_xs);
|
||||
row.setLayoutParams(lp);
|
||||
}
|
||||
first = false;
|
||||
final int icon = item.getType() == SettingsItem.TYPE_GENERAL_SETTINGS
|
||||
? R.drawable.ic_home_nav_settings
|
||||
: R.drawable.ic_home_nav_messages;
|
||||
final CharSequence title = item.getType() == SettingsItem.TYPE_GENERAL_SETTINGS
|
||||
? getString(R.string.home_settings_general)
|
||||
: item.getDisplayName();
|
||||
HomeSettingsRow.bind(row, icon, title, item.getDisplayDetail(),
|
||||
true, false, false,
|
||||
v -> openItem(item), null);
|
||||
mList.addView(row);
|
||||
}
|
||||
}
|
||||
|
||||
public void setSettingsItems(final List<SettingsItem> newList) {
|
||||
clear();
|
||||
addAll(newList);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public View getView(final int position, final View convertView,
|
||||
@NonNull final ViewGroup parent) {
|
||||
View itemView;
|
||||
if (convertView != null) {
|
||||
itemView = convertView;
|
||||
} else {
|
||||
final LayoutInflater inflater = (LayoutInflater) getContext()
|
||||
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
itemView = inflater.inflate(
|
||||
R.layout.settings_item_view, parent, false);
|
||||
}
|
||||
final SettingsItem item = getItem(position);
|
||||
final TextView titleTextView = itemView.findViewById(R.id.title);
|
||||
final TextView subtitleTextView = itemView.findViewById(R.id.subtitle);
|
||||
final String summaryText = item.getDisplayDetail();
|
||||
titleTextView.setText(item.getDisplayName());
|
||||
if (!TextUtils.isEmpty(summaryText)) {
|
||||
subtitleTextView.setText(summaryText);
|
||||
subtitleTextView.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
subtitleTextView.setVisibility(View.GONE);
|
||||
}
|
||||
itemView.setOnClickListener(view -> {
|
||||
switch (item.getType()) {
|
||||
case SettingsItem.TYPE_GENERAL_SETTINGS:
|
||||
UIIntents.get().launchApplicationSettingsActivity(getActivity(),
|
||||
false /* topLevel */);
|
||||
break;
|
||||
|
||||
case SettingsItem.TYPE_PER_SUBSCRIPTION_SETTINGS:
|
||||
UIIntents.get().launchPerSubscriptionSettingsActivity(getActivity(),
|
||||
item.getSubId(), item.getActivityTitle());
|
||||
break;
|
||||
|
||||
default:
|
||||
Assert.fail("unrecognized setting type!");
|
||||
break;
|
||||
}
|
||||
});
|
||||
return itemView;
|
||||
}
|
||||
private void openItem(final SettingsItem item) {
|
||||
switch (item.getType()) {
|
||||
case SettingsItem.TYPE_GENERAL_SETTINGS:
|
||||
UIIntents.get().launchApplicationSettingsActivity(this, false /* topLevel */);
|
||||
overridePendingTransition(R.anim.home_activity_open, R.anim.home_activity_hold);
|
||||
break;
|
||||
case SettingsItem.TYPE_PER_SUBSCRIPTION_SETTINGS:
|
||||
UIIntents.get().launchPerSubscriptionSettingsActivity(this,
|
||||
item.getSubId(), item.getActivityTitle());
|
||||
overridePendingTransition(R.anim.home_activity_open, R.anim.home_activity_hold);
|
||||
break;
|
||||
default:
|
||||
Assert.fail("unrecognized setting type!");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -557,7 +557,7 @@ public class ContactPickerFragment extends Fragment implements ContactPickerData
|
||||
// etc. will take the spot of the action bar.
|
||||
actionBar.hide();
|
||||
UiUtils.setStatusBarColor(getActivity(),
|
||||
getResources().getColor(R.color.looply_bg, getActivity().getTheme()));
|
||||
getResources().getColor(R.color.home_bg, getActivity().getTheme()));
|
||||
}
|
||||
|
||||
private GetOrCreateConversationActionMonitor mMonitor;
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
*/
|
||||
package com.android.messaging.ui.conversation;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Rect;
|
||||
@@ -34,18 +35,21 @@ import android.text.format.Formatter;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.ContextThemeWrapper;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowManager;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.core.view.WindowInsetsCompat;
|
||||
import androidx.core.view.WindowInsetsControllerCompat;
|
||||
|
||||
import com.android.messaging.Factory;
|
||||
import com.android.messaging.R;
|
||||
@@ -68,8 +72,12 @@ import com.android.messaging.ui.AttachmentPreview;
|
||||
import com.android.messaging.ui.BugleActionBarActivity;
|
||||
import com.android.messaging.ui.PlainTextEditText;
|
||||
import com.android.messaging.ui.conversation.ConversationInputManager.ConversationInputSink;
|
||||
import com.android.messaging.ui.home.HomeComposeAttachPanel;
|
||||
import com.android.messaging.ui.home.HomeToast;
|
||||
import com.android.messaging.ui.home.HomeUiMotion;
|
||||
import com.android.messaging.util.AccessibilityUtil;
|
||||
import com.android.messaging.util.ImageUtils;
|
||||
import com.android.messaging.util.ImeUtil;
|
||||
import com.android.messaging.util.Assert;
|
||||
import com.android.messaging.util.AvatarUriUtil;
|
||||
import com.android.messaging.util.BuglePrefs;
|
||||
@@ -107,6 +115,7 @@ public class ComposeMessageView extends LinearLayout
|
||||
void notifyOfAttachmentLoadFailed();
|
||||
void showAttachmentChooser();
|
||||
void launchSystemGalleryPicker();
|
||||
void requestMediaPermission(Runnable onGranted);
|
||||
boolean shouldShowSubjectEditor();
|
||||
boolean shouldHideAttachmentsWhenSimSelectorShown();
|
||||
Uri getSelfSendButtonIconUri();
|
||||
@@ -137,6 +146,7 @@ public class ComposeMessageView extends LinearLayout
|
||||
private View mComposeBottomStack;
|
||||
private View mComposeBar;
|
||||
private boolean mAttachmentSheetOpen;
|
||||
private HomeComposeAttachPanel.Host mAttachHost;
|
||||
private int mComposeBarPaddingBottom;
|
||||
private ImageButton mVoiceButton;
|
||||
private View mSendColumn;
|
||||
@@ -212,13 +222,20 @@ public class ComposeMessageView extends LinearLayout
|
||||
mComposeEditText.addTextChangedListener(this);
|
||||
mComposeEditText.setOnFocusChangeListener((v, hasFocus) -> {
|
||||
if (v == mComposeEditText && hasFocus) {
|
||||
mHost.onComposeEditTextFocused();
|
||||
if (mHost != null) {
|
||||
mHost.onComposeEditTextFocused();
|
||||
}
|
||||
showComposeKeyboard();
|
||||
}
|
||||
});
|
||||
mComposeEditText.setOnClickListener(arg0 -> {
|
||||
if (mHost.shouldHideAttachmentsWhenSimSelectorShown()) {
|
||||
hideSimSelector();
|
||||
mComposeEditText.setOnTouchListener((v, event) -> {
|
||||
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
|
||||
if (mHost != null && mHost.shouldHideAttachmentsWhenSimSelectorShown()) {
|
||||
hideSimSelector();
|
||||
}
|
||||
showComposeKeyboard();
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
// onFinishInflate() is called before self is loaded from db. We set the default text
|
||||
@@ -301,30 +318,35 @@ public class ComposeMessageView extends LinearLayout
|
||||
if (mComposeBar != null) {
|
||||
mComposeBarPaddingBottom = mComposeBar.getPaddingBottom();
|
||||
}
|
||||
setupComposeAttachmentSheetTiles();
|
||||
updateComposeBottomStackBackground();
|
||||
mAttachHost = createComposeAttachHost();
|
||||
mAttachMediaButton.setOnClickListener(clickView -> toggleComposeAttachmentSheet());
|
||||
|
||||
final ImageButton emojiButton = findViewById(R.id.home_compose_emoji_button);
|
||||
if (emojiButton != null) {
|
||||
emojiButton.setOnClickListener(v -> mComposeEditText.requestFocus());
|
||||
emojiButton.setOnClickListener(v -> showComposeKeyboard());
|
||||
}
|
||||
final ImageButton galleryButton = findViewById(R.id.home_compose_gallery_button);
|
||||
if (galleryButton != null) {
|
||||
galleryButton.setOnClickListener(v -> {
|
||||
closeComposeAttachmentSheet(true /* animate */, () -> {
|
||||
if (mHost != null) {
|
||||
mHost.launchSystemGalleryPicker();
|
||||
}
|
||||
});
|
||||
if (!mAttachmentSheetOpen) {
|
||||
toggleComposeAttachmentSheet();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
mVoiceButton = findViewById(R.id.home_conversation_voice_button);
|
||||
mSendColumn = findViewById(R.id.home_conversation_send_column);
|
||||
if (mVoiceButton != null) {
|
||||
mVoiceButton.setOnClickListener(v -> Toast.makeText(mOriginalContext,
|
||||
R.string.home_conversation_voice_coming_soon, Toast.LENGTH_SHORT).show());
|
||||
mVoiceButton.setOnClickListener(v -> {
|
||||
final String messageText = mComposeEditText.getText().toString();
|
||||
final boolean hasDraft = TextUtils.getTrimmedLength(messageText) > 0
|
||||
|| (mBinding.isBound() && mBinding.getData().hasAttachments());
|
||||
if (hasDraft) {
|
||||
sendMessageInternal(true /* checkMessageSize */);
|
||||
} else {
|
||||
HomeToast.show(mOriginalContext, R.string.home_conversation_voice_coming_soon);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
mAttachmentPreview = findViewById(R.id.attachment_draft_view);
|
||||
@@ -576,11 +598,39 @@ public class ComposeMessageView extends LinearLayout
|
||||
|
||||
@Override
|
||||
public void resumeComposeMessage() {
|
||||
mComposeEditText.requestFocus();
|
||||
mInputManager.showHideImeKeyboard(true, true);
|
||||
showComposeKeyboard();
|
||||
announceAttachmentState();
|
||||
}
|
||||
|
||||
private void showComposeKeyboard() {
|
||||
if (mComposeEditText == null) {
|
||||
return;
|
||||
}
|
||||
mComposeEditText.setEnabled(true);
|
||||
mComposeEditText.setFocusable(true);
|
||||
mComposeEditText.setFocusableInTouchMode(true);
|
||||
mComposeEditText.setShowSoftInputOnFocus(true);
|
||||
mComposeEditText.requestFocus();
|
||||
final Activity activity = resolveActivity();
|
||||
if (activity != null && activity.getWindow() != null) {
|
||||
activity.getWindow().setSoftInputMode(
|
||||
WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
|
||||
| WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
|
||||
new WindowInsetsControllerCompat(activity.getWindow(), mComposeEditText)
|
||||
.show(WindowInsetsCompat.Type.ime());
|
||||
final InputMethodManager imm = activity.getSystemService(InputMethodManager.class);
|
||||
if (imm != null) {
|
||||
imm.restartInput(mComposeEditText);
|
||||
imm.showSoftInput(mComposeEditText, InputMethodManager.SHOW_FORCED);
|
||||
}
|
||||
}
|
||||
final Context imeContext = activity != null ? activity : mOriginalContext;
|
||||
ImeUtil.get().showImeKeyboard(imeContext, mComposeEditText);
|
||||
if (mInputManager != null) {
|
||||
mInputManager.showHideImeKeyboard(true, false);
|
||||
}
|
||||
}
|
||||
|
||||
public void clearAttachments() {
|
||||
mBinding.getData().clearAttachments(mHost.getAttachmentsClearedFlags());
|
||||
mHost.onAttachmentsCleared();
|
||||
@@ -757,6 +807,15 @@ public class ComposeMessageView extends LinearLayout
|
||||
}
|
||||
} else {
|
||||
mSelfSendIcon.setImageResourceUri(null);
|
||||
if (hasWorkingDraft && isDataLoadedForMessageSend()) {
|
||||
UiUtils.revealOrHideViewWithAnimation(mSendButton, VISIBLE, null);
|
||||
UiUtils.revealOrHideViewWithAnimation(mSelfSendIcon, GONE, null);
|
||||
mMmsIndicator.setVisibility(draftMessageData.getIsMms() ? VISIBLE : INVISIBLE);
|
||||
sendWidgetMode = SEND_WIDGET_MODE_SEND_BUTTON;
|
||||
} else {
|
||||
UiUtils.revealOrHideViewWithAnimation(mSendButton, GONE, null);
|
||||
mMmsIndicator.setVisibility(INVISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
if (mSendWidgetMode != sendWidgetMode || sendWidgetMode == SEND_WIDGET_MODE_SIM_SELECTOR) {
|
||||
@@ -988,9 +1047,8 @@ public class ComposeMessageView extends LinearLayout
|
||||
}
|
||||
|
||||
public boolean onBackPressed() {
|
||||
if (mAttachmentSheetOpen) {
|
||||
closeComposeAttachmentSheet(true /* animate */, null);
|
||||
return true;
|
||||
if (HomeComposeAttachPanel.isShowing(resolveActivity())) {
|
||||
return HomeComposeAttachPanel.onBackPressed(resolveActivity());
|
||||
}
|
||||
return mInputManager.onBackPressed();
|
||||
}
|
||||
@@ -1043,135 +1101,166 @@ public class ComposeMessageView extends LinearLayout
|
||||
}
|
||||
}
|
||||
|
||||
private void setupComposeAttachmentSheetTiles() {
|
||||
if (mAttachmentSheetContainer == null) {
|
||||
return;
|
||||
}
|
||||
bindComposeAttachmentTile(R.id.home_mediapicker_tile_gallery,
|
||||
R.drawable.ic_home_image_line, R.string.home_mediapicker_gallery,
|
||||
ConversationInputManager.MEDIA_PICKER_TARGET_GALLERY);
|
||||
bindComposeAttachmentTile(R.id.home_mediapicker_tile_camera,
|
||||
R.drawable.ic_home_camera_line, R.string.home_mediapicker_camera,
|
||||
ConversationInputManager.MEDIA_PICKER_TARGET_CAMERA);
|
||||
bindComposeAttachmentTile(R.id.home_mediapicker_tile_contact,
|
||||
R.drawable.ic_home_menu_person, R.string.home_mediapicker_contact,
|
||||
ConversationInputManager.MEDIA_PICKER_TARGET_CONTACT);
|
||||
bindComposeAttachmentTile(R.id.home_mediapicker_tile_file,
|
||||
R.drawable.ic_attachment_dark, R.string.home_mediapicker_file,
|
||||
ConversationInputManager.MEDIA_PICKER_TARGET_GALLERY);
|
||||
bindComposeAttachmentTile(R.id.home_mediapicker_tile_gif,
|
||||
R.drawable.ic_home_star_line, R.string.home_mediapicker_gif, -1);
|
||||
bindComposeAttachmentTile(R.id.home_mediapicker_tile_sticker,
|
||||
R.drawable.ic_home_emoji, R.string.home_mediapicker_sticker, -1);
|
||||
bindComposeAttachmentTile(R.id.home_mediapicker_tile_schedule,
|
||||
R.drawable.ic_home_bell, R.string.home_mediapicker_schedule, -1);
|
||||
bindComposeAttachmentTile(R.id.home_mediapicker_tile_location_once,
|
||||
R.drawable.ic_home_place_line, R.string.home_mediapicker_location_once, -1);
|
||||
bindComposeAttachmentTile(R.id.home_mediapicker_tile_location_live,
|
||||
R.drawable.ic_home_location_live, R.string.home_mediapicker_location_live, -1);
|
||||
private HomeComposeAttachPanel.Host createComposeAttachHost() {
|
||||
return new HomeComposeAttachPanel.Host() {
|
||||
@Override
|
||||
public void onMediaPicked(@NonNull final List<Uri> uris,
|
||||
@Nullable final String caption) {
|
||||
if (!TextUtils.isEmpty(caption)) {
|
||||
mComposeEditText.setText(caption);
|
||||
mComposeEditText.setSelection(caption.length());
|
||||
if (mBinding.isBound()) {
|
||||
mBinding.getData().setMessageText(caption);
|
||||
}
|
||||
}
|
||||
attachPickedUris(uris);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTakePhoto() {
|
||||
openMediaFromAttachmentSheet(ConversationInputManager.MEDIA_PICKER_TARGET_PHOTO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRecordVideo() {
|
||||
openMediaFromAttachmentSheet(ConversationInputManager.MEDIA_PICKER_TARGET_VIDEO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpenContact() {
|
||||
openMediaFromAttachmentSheet(ConversationInputManager.MEDIA_PICKER_TARGET_CONTACT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestMediaPermission() {
|
||||
if (mHost != null) {
|
||||
mHost.requestMediaPermission(() ->
|
||||
HomeComposeAttachPanel.reloadIfShowing(resolveActivity()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComingSoon() {
|
||||
HomeToast.show(mOriginalContext, R.string.home_popover_coming_soon);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDismiss() {
|
||||
mAttachmentSheetOpen = false;
|
||||
updateAttachButtonContentDescription();
|
||||
updateComposeBottomStackBackground();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachSheetExtentChanged(final int overlapPx) {
|
||||
applyAttachSheetChatPush(overlapPx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestComposeKeyboard() {
|
||||
showComposeKeyboard();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void bindComposeAttachmentTile(final int tileRootId, final int iconRes,
|
||||
final int labelRes, final int mediaPickerTarget) {
|
||||
final View tile = findViewById(tileRootId);
|
||||
if (tile == null) {
|
||||
private void attachPickedUris(final List<Uri> uris) {
|
||||
if (uris == null || uris.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
final ImageView icon = tile.findViewById(R.id.home_mediapicker_tile_icon);
|
||||
final TextView label = tile.findViewById(R.id.home_mediapicker_tile_label);
|
||||
if (icon != null) {
|
||||
icon.setImageResource(iconRes);
|
||||
}
|
||||
if (label != null) {
|
||||
label.setText(labelRes);
|
||||
}
|
||||
tile.setOnClickListener(v -> {
|
||||
if (mediaPickerTarget >= 0) {
|
||||
openMediaFromAttachmentSheet(mediaPickerTarget);
|
||||
} else {
|
||||
Toast.makeText(mOriginalContext, R.string.home_popover_coming_soon,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
final ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||
final Handler handler = new Handler(Looper.getMainLooper());
|
||||
executor.execute(() -> {
|
||||
final List<PendingAttachmentData> pending = new ArrayList<>();
|
||||
for (final Uri uri : uris) {
|
||||
final String contentType = ImageUtils.getContentType(
|
||||
Factory.get().getApplicationContext().getContentResolver(), uri);
|
||||
if (contentType != null) {
|
||||
pending.add(PendingAttachmentData.createPendingAttachmentData(contentType, uri));
|
||||
}
|
||||
}
|
||||
handler.post(() -> {
|
||||
for (final PendingAttachmentData item : pending) {
|
||||
onPendingAttachmentAdded(item);
|
||||
}
|
||||
executor.shutdown();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void applyAttachSheetChatPush(final int overlapPx) {
|
||||
final Activity activity = resolveActivity();
|
||||
if (activity == null) {
|
||||
return;
|
||||
}
|
||||
final View chatArea = activity.findViewById(R.id.conversation_chat_area);
|
||||
if (chatArea == null) {
|
||||
return;
|
||||
}
|
||||
final int inset = Math.max(0, overlapPx);
|
||||
if (chatArea.getPaddingBottom() != inset) {
|
||||
chatArea.setPaddingRelative(
|
||||
chatArea.getPaddingStart(),
|
||||
chatArea.getPaddingTop(),
|
||||
chatArea.getPaddingEnd(),
|
||||
inset);
|
||||
}
|
||||
}
|
||||
|
||||
private void toggleComposeAttachmentSheet() {
|
||||
if (mAttachmentSheetOpen) {
|
||||
closeComposeAttachmentSheet(true /* animate */, null);
|
||||
} else {
|
||||
if (mInputManager != null) {
|
||||
mInputManager.showHideImeKeyboard(false, true);
|
||||
mInputManager.showHideMediaPicker(false, false);
|
||||
}
|
||||
mAttachmentSheetOpen = true;
|
||||
updateAttachButtonContentDescription();
|
||||
updateComposeBottomStackBackground();
|
||||
HomeUiMotion.composeAttachmentSheetOpen(mAttachmentSheetContainer, mAttachMediaButton);
|
||||
return;
|
||||
}
|
||||
final Activity activity = resolveActivity();
|
||||
if (activity == null || mAttachHost == null) {
|
||||
return;
|
||||
}
|
||||
if (mInputManager != null) {
|
||||
mInputManager.showHideImeKeyboard(false, true);
|
||||
mInputManager.showHideMediaPicker(false, false);
|
||||
}
|
||||
mAttachmentSheetOpen = true;
|
||||
updateAttachButtonContentDescription();
|
||||
updateComposeBottomStackBackground();
|
||||
HomeComposeAttachPanel.show(activity, mAttachHost);
|
||||
}
|
||||
|
||||
private void closeComposeAttachmentSheet(final boolean animate, final Runnable endAction) {
|
||||
if (!mAttachmentSheetOpen || mAttachmentSheetContainer == null) {
|
||||
if (endAction != null) {
|
||||
endAction.run();
|
||||
}
|
||||
return;
|
||||
final boolean dismissed = HomeComposeAttachPanel.dismissIfShowing(resolveActivity());
|
||||
if (!dismissed) {
|
||||
applyAttachSheetChatPush(0);
|
||||
}
|
||||
mAttachmentSheetOpen = false;
|
||||
updateAttachButtonContentDescription();
|
||||
updateComposeBottomStackBackground();
|
||||
if (animate) {
|
||||
HomeUiMotion.composeAttachmentSheetClose(mAttachmentSheetContainer, mAttachMediaButton,
|
||||
endAction);
|
||||
} else {
|
||||
mAttachmentSheetContainer.setVisibility(View.GONE);
|
||||
final ViewGroup.LayoutParams params = mAttachmentSheetContainer.getLayoutParams();
|
||||
params.height = 0;
|
||||
mAttachmentSheetContainer.setLayoutParams(params);
|
||||
mAttachmentSheetContainer.setTranslationY(0f);
|
||||
mAttachmentSheetContainer.setAlpha(1f);
|
||||
if (mAttachMediaButton != null) {
|
||||
mAttachMediaButton.setImageResource(R.drawable.ic_home_compose_plus);
|
||||
mAttachMediaButton.setRotation(0f);
|
||||
}
|
||||
if (endAction != null) {
|
||||
endAction.run();
|
||||
}
|
||||
if (endAction != null) {
|
||||
endAction.run();
|
||||
}
|
||||
if (!dismissed && endAction == null) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private Activity resolveActivity() {
|
||||
if (mOriginalContext instanceof Activity) {
|
||||
return (Activity) mOriginalContext;
|
||||
}
|
||||
final Context context = getContext();
|
||||
if (context instanceof Activity) {
|
||||
return (Activity) context;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void updateComposeBottomStackBackground() {
|
||||
if (mComposeBottomStack == null) {
|
||||
return;
|
||||
}
|
||||
if (mAttachmentSheetOpen) {
|
||||
mComposeBottomStack.setBackgroundColor(
|
||||
getResources().getColor(R.color.home_bg, mComposeBottomStack.getContext().getTheme()));
|
||||
mComposeBottomStack.setClipToOutline(false);
|
||||
if (mComposeBar != null) {
|
||||
mComposeBar.setBackgroundColor(
|
||||
getResources().getColor(R.color.home_bg, mComposeBar.getContext().getTheme()));
|
||||
final int gap = getResources().getDimensionPixelSize(
|
||||
R.dimen.home_compose_attach_sheet_gap);
|
||||
mComposeBar.setPadding(mComposeBar.getPaddingLeft(), mComposeBar.getPaddingTop(),
|
||||
mComposeBar.getPaddingRight(), gap);
|
||||
}
|
||||
if (mMessageBodySize != null && mMessageBodySize.getVisibility() != View.VISIBLE) {
|
||||
mMessageBodySize.setVisibility(View.GONE);
|
||||
}
|
||||
} else {
|
||||
mComposeBottomStack.setClipToOutline(false);
|
||||
mComposeBottomStack.setBackgroundColor(
|
||||
getResources().getColor(R.color.home_bg, mComposeBottomStack.getContext().getTheme()));
|
||||
if (mComposeBar != null) {
|
||||
mComposeBar.setBackgroundColor(
|
||||
getResources().getColor(R.color.home_bg, mComposeBar.getContext().getTheme()));
|
||||
mComposeBar.setPadding(mComposeBar.getPaddingLeft(), mComposeBar.getPaddingTop(),
|
||||
mComposeBar.getPaddingRight(), mComposeBarPaddingBottom);
|
||||
}
|
||||
if (mMessageBodySize != null && mMessageBodySize.getVisibility() != View.VISIBLE) {
|
||||
mMessageBodySize.setVisibility(View.GONE);
|
||||
}
|
||||
mComposeBottomStack.setClipToOutline(false);
|
||||
mComposeBottomStack.setBackgroundColor(android.graphics.Color.TRANSPARENT);
|
||||
if (mComposeBar != null) {
|
||||
mComposeBar.setVisibility(View.VISIBLE);
|
||||
mComposeBar.setBackgroundColor(android.graphics.Color.TRANSPARENT);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1181,7 +1270,9 @@ public class ComposeMessageView extends LinearLayout
|
||||
mHost.launchSystemGalleryPicker();
|
||||
return;
|
||||
}
|
||||
if (mediaPickerTarget == ConversationInputManager.MEDIA_PICKER_TARGET_CONTACT) {
|
||||
if (mediaPickerTarget == ConversationInputManager.MEDIA_PICKER_TARGET_CONTACT
|
||||
|| mediaPickerTarget == ConversationInputManager.MEDIA_PICKER_TARGET_PHOTO
|
||||
|| mediaPickerTarget == ConversationInputManager.MEDIA_PICKER_TARGET_VIDEO) {
|
||||
if (mInputManager != null) {
|
||||
mInputManager.showHideMediaPicker(true /* show */, false /* animate */,
|
||||
mediaPickerTarget);
|
||||
|
||||
@@ -150,6 +150,17 @@ public class ConversationActivity extends BugleActionBarActivity
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
protected void onActivityResult(final int requestCode, final int resultCode,
|
||||
final Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
final ConversationFragment conversationFragment = getConversationFragment();
|
||||
if (conversationFragment != null) {
|
||||
conversationFragment.onDefaultSmsAppResult(requestCode, resultCode);
|
||||
}
|
||||
}
|
||||
|
||||
public void suppressNextResumeTransition() {
|
||||
mSuppressNextResumeTransition = true;
|
||||
}
|
||||
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (C) 2026 The LineageOS Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.messaging.ui.conversation;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Conversation chrome container. Compose keeps a higher elevation than the
|
||||
* decorative fades so taps reach the EditText through the normal dispatch path.
|
||||
*/
|
||||
public class ConversationChatAreaLayout extends FrameLayout {
|
||||
public ConversationChatAreaLayout(@NonNull final Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public ConversationChatAreaLayout(@NonNull final Context context,
|
||||
@Nullable final AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
}
|
||||
+672
-47
@@ -28,9 +28,16 @@ import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.res.Configuration;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RenderEffect;
|
||||
import android.graphics.Shader;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
@@ -43,17 +50,22 @@ import android.telephony.PhoneNumberUtils;
|
||||
import android.text.TextUtils;
|
||||
import android.view.ActionMode;
|
||||
import android.view.Display;
|
||||
import android.view.Gravity;
|
||||
import android.view.HapticFeedbackConstants;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.PixelCopy;
|
||||
import android.view.View;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.PopupMenu;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.PopupWindow;
|
||||
import android.widget.Toast;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewParent;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
@@ -99,8 +111,11 @@ import com.android.messaging.ui.ConversationDrawables;
|
||||
import com.android.messaging.ui.SnackBar;
|
||||
import com.android.messaging.ui.UIIntents;
|
||||
import com.android.messaging.ui.animation.PopupTransitionAnimation;
|
||||
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.HomeUiMotion;
|
||||
import com.android.messaging.ui.home.StarredMessagesStore;
|
||||
import com.android.messaging.ui.home.UnknownSenderPromptStore;
|
||||
import com.android.messaging.ui.attachmentchooser.AttachmentChooserActivity;
|
||||
import com.android.messaging.ui.contact.AddContactsConfirmationDialog;
|
||||
@@ -112,7 +127,6 @@ import com.android.messaging.ui.mediapicker.MediaPicker;
|
||||
import com.android.messaging.util.AccessibilityUtil;
|
||||
import com.android.messaging.util.Assert;
|
||||
import com.android.messaging.util.AvatarUriUtil;
|
||||
import com.android.messaging.util.Dates;
|
||||
import com.android.messaging.util.ChangeDefaultSmsAppHelper;
|
||||
import com.android.messaging.util.ContactUtil;
|
||||
import com.android.messaging.util.ImeUtil;
|
||||
@@ -160,14 +174,14 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
|
||||
private ConversationMessageAdapter mAdapter;
|
||||
private ConversationFastScroller mFastScroller;
|
||||
|
||||
private View mConversationComposeDivider;
|
||||
private View mEmptyContainer;
|
||||
private TextView mEmptyTime;
|
||||
private TextView mEmptyStatus;
|
||||
private HomeEmptyState mEmptyContainer;
|
||||
private View mConversationHeader;
|
||||
private TextView mConversationHeaderTitle;
|
||||
private TextView mConversationHeaderSubtitle;
|
||||
private ContactIconView mConversationHeaderAvatar;
|
||||
private ImageButton mConversationHeaderOverflow;
|
||||
private PopupWindow mConversationOverflowPopover;
|
||||
private ImageButton mConversationHeaderCall;
|
||||
private boolean mOptionsMenuVisible = true;
|
||||
private View mUnknownSenderBanner;
|
||||
private TextView mUnknownSenderTitle;
|
||||
@@ -175,6 +189,7 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
|
||||
private View mMessageSelectionHeader;
|
||||
private TextView mMessageSelectionTitle;
|
||||
private View mComposeContainer;
|
||||
private View mComposeStage;
|
||||
private View mMessageActionBar;
|
||||
private View mMessageActionCopy;
|
||||
private View mMessageActionForward;
|
||||
@@ -184,6 +199,9 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
|
||||
private View mMessageActionSend;
|
||||
private View mMessageActionDownload;
|
||||
private View mMessageActionDelete;
|
||||
private PopupWindow mMessageActionPopup;
|
||||
private ViewGroup mMessageBlurOverlay;
|
||||
private boolean mClearSelectionOnPopupDismiss = true;
|
||||
private ChangeDefaultSmsAppHelper mChangeDefaultSmsAppHelper;
|
||||
|
||||
private String mConversationId;
|
||||
@@ -255,7 +273,9 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
|
||||
}
|
||||
final int totalItemCount = mRecyclerView.getAdapter().getItemCount();
|
||||
final boolean isAtBottom = (lastVisibleItem + 1 == totalItemCount);
|
||||
return isAtBottom && lastView.getBottom() <= mRecyclerView.getHeight();
|
||||
return isAtBottom
|
||||
&& lastView.getBottom() <= mRecyclerView.getHeight()
|
||||
- mRecyclerView.getPaddingBottom();
|
||||
}
|
||||
|
||||
private void scrollToBottom(final boolean smoothScroll) {
|
||||
@@ -271,7 +291,6 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
|
||||
// hide the media picker & co.
|
||||
private int mCumulativeScrollDelta;
|
||||
private boolean mScrollToDismissHandled;
|
||||
private boolean mWasScrolledToBottom = true;
|
||||
private int mScrollState = RecyclerView.SCROLL_STATE_IDLE;
|
||||
|
||||
@Override
|
||||
@@ -288,6 +307,9 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
|
||||
|
||||
@Override
|
||||
public void onScrolled(@NonNull final RecyclerView view, final int dx, final int dy) {
|
||||
if (dy != 0) {
|
||||
dismissMessageActionPopup();
|
||||
}
|
||||
if (mScrollState == RecyclerView.SCROLL_STATE_DRAGGING &&
|
||||
!mScrollToDismissHandled) {
|
||||
mCumulativeScrollDelta += dy;
|
||||
@@ -297,10 +319,6 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
|
||||
mScrollToDismissHandled = true;
|
||||
}
|
||||
}
|
||||
if (mWasScrolledToBottom != isScrolledToBottom()) {
|
||||
mConversationComposeDivider.animate().alpha(isScrolledToBottom() ? 0 : 1);
|
||||
mWasScrolledToBottom = isScrolledToBottom();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -424,11 +442,10 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
|
||||
final View view = inflater.inflate(R.layout.conversation_fragment, container, false);
|
||||
mRecyclerView = view.findViewById(android.R.id.list);
|
||||
mEmptyContainer = view.findViewById(R.id.home_conversation_empty);
|
||||
mEmptyTime = view.findViewById(R.id.home_conversation_empty_time);
|
||||
mEmptyStatus = view.findViewById(R.id.home_conversation_empty_status);
|
||||
setupConversationHeader(view);
|
||||
setupUnknownSenderBanner(view);
|
||||
setupMessageSelectionChrome(view);
|
||||
bindConversationEdgeFade(view);
|
||||
final LinearLayoutManager manager = new LinearLayoutManager(getActivity());
|
||||
manager.setStackFromEnd(true);
|
||||
manager.setReverseLayout(false);
|
||||
@@ -517,7 +534,6 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
|
||||
Parcelable.class);
|
||||
}
|
||||
|
||||
mConversationComposeDivider = view.findViewById(R.id.conversation_compose_divider);
|
||||
mScrollToDismissThreshold = ViewConfiguration.get(getActivity()).getScaledTouchSlop();
|
||||
mRecyclerView.addOnScrollListener(mListScrollListener);
|
||||
mFastScroller = ConversationFastScroller.addTo(mRecyclerView,
|
||||
@@ -591,24 +607,311 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
|
||||
|
||||
private void selectMessage(final ConversationMessageView messageView,
|
||||
final MessagePartData attachment) {
|
||||
selectMessage(messageView, attachment, true /* showPopup */);
|
||||
}
|
||||
|
||||
private void selectMessage(final ConversationMessageView messageView,
|
||||
final MessagePartData attachment, final boolean showPopup) {
|
||||
mSelectedMessage = messageView;
|
||||
if (mSelectedMessage == null) {
|
||||
mAdapter.setSelectedMessage(null);
|
||||
mSelectedAttachment = null;
|
||||
dismissMessageActionPopup();
|
||||
updateMessageSelectionChrome(false);
|
||||
updateUnknownSenderBanner();
|
||||
return;
|
||||
}
|
||||
mSelectedAttachment = attachment;
|
||||
mAdapter.setSelectedMessage(messageView.getData().getMessageId());
|
||||
updateMessageSelectionChrome(true);
|
||||
updateUnknownSenderBanner();
|
||||
if (showPopup) {
|
||||
updateMessageSelectionChrome(false);
|
||||
showMessageActionPopup(messageView);
|
||||
} else {
|
||||
dismissMessageActionPopup();
|
||||
updateMessageSelectionChrome(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void dismissMessageSelection() {
|
||||
selectMessage(null);
|
||||
}
|
||||
|
||||
private void showMessageActionPopup(final ConversationMessageView messageView) {
|
||||
if (getActivity() == null || messageView == null) {
|
||||
return;
|
||||
}
|
||||
dismissMessageActionPopup();
|
||||
final ConversationMessageData data = messageView.getData();
|
||||
final View content = LayoutInflater.from(requireContext())
|
||||
.inflate(R.layout.home_message_action_popup, null, false);
|
||||
final View forward = content.findViewById(R.id.home_message_menu_forward);
|
||||
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);
|
||||
|
||||
forward.setVisibility(data.getCanForwardMessage() ? View.VISIBLE : View.GONE);
|
||||
copy.setVisibility(data.getCanCopyMessageToClipboard() ? View.VISIBLE : View.GONE);
|
||||
final boolean starred = StarredMessagesStore.isStarred(requireContext(), data.getMessageId());
|
||||
if (starLabel != null) {
|
||||
starLabel.setText(starred
|
||||
? R.string.home_message_menu_unstar
|
||||
: R.string.home_message_menu_star);
|
||||
}
|
||||
|
||||
forward.setOnClickListener(v -> {
|
||||
dismissMessageActionPopup();
|
||||
performSelectedMessageAction(R.id.home_message_action_forward);
|
||||
});
|
||||
copy.setOnClickListener(v -> {
|
||||
dismissMessageActionPopup();
|
||||
performSelectedMessageAction(R.id.home_message_action_copy);
|
||||
});
|
||||
star.setOnClickListener(v -> {
|
||||
StarredMessagesStore.toggle(requireContext(), data.getMessageId());
|
||||
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 -> {
|
||||
dismissMessageActionPopup();
|
||||
performSelectedMessageAction(R.id.home_message_action_details);
|
||||
});
|
||||
|
||||
content.setLayoutParams(new ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
content.measure(
|
||||
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
|
||||
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
|
||||
final int width = Math.round(content.getMeasuredWidth() * 1.1f);
|
||||
content.measure(
|
||||
View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
|
||||
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
|
||||
final int height = content.getMeasuredHeight();
|
||||
final PopupWindow popup = new PopupWindow(content, width, height, true);
|
||||
popup.setOutsideTouchable(true);
|
||||
popup.setFocusable(true);
|
||||
popup.setElevation(16f);
|
||||
popup.setBackgroundDrawable(new ColorDrawable(android.graphics.Color.TRANSPARENT));
|
||||
popup.setOnDismissListener(() -> {
|
||||
if (mMessageActionPopup == popup) {
|
||||
mMessageActionPopup = null;
|
||||
}
|
||||
if (mClearSelectionOnPopupDismiss && mSelectedMessage == messageView
|
||||
&& (mMessageSelectionHeader == null
|
||||
|| mMessageSelectionHeader.getVisibility() != View.VISIBLE)) {
|
||||
dismissMessageSelection();
|
||||
}
|
||||
mClearSelectionOnPopupDismiss = true;
|
||||
});
|
||||
|
||||
final View bubble = messageView.findViewById(R.id.message_content);
|
||||
final View anchor = bubble != null ? bubble : messageView;
|
||||
final int[] loc = new int[2];
|
||||
anchor.getLocationOnScreen(loc);
|
||||
final int screenW = getResources().getDisplayMetrics().widthPixels;
|
||||
final int screenH = getResources().getDisplayMetrics().heightPixels;
|
||||
final int margin = getResources().getDimensionPixelSize(R.dimen.looply_space_md);
|
||||
int headerBottom = margin;
|
||||
if (mConversationHeader != null && mConversationHeader.getVisibility() == View.VISIBLE) {
|
||||
final int[] headerLoc = new int[2];
|
||||
mConversationHeader.getLocationOnScreen(headerLoc);
|
||||
headerBottom = headerLoc[1] + mConversationHeader.getHeight() + margin;
|
||||
}
|
||||
int dockTop = screenH - margin;
|
||||
if (mComposeStage != null && mComposeStage.getVisibility() == View.VISIBLE) {
|
||||
final int[] dockLoc = new int[2];
|
||||
mComposeStage.getLocationOnScreen(dockLoc);
|
||||
if (mComposeStage.getHeight() > 0) {
|
||||
dockTop = dockLoc[1] - margin;
|
||||
}
|
||||
}
|
||||
int x = data.getIsIncoming()
|
||||
? loc[0]
|
||||
: loc[0] + anchor.getWidth() - width;
|
||||
x = Math.max(margin, Math.min(x, screenW - width - margin));
|
||||
final int gap = margin;
|
||||
final int bubbleTop = loc[1];
|
||||
final int bubbleBottom = loc[1] + anchor.getHeight();
|
||||
final int spaceBelow = dockTop - bubbleBottom;
|
||||
final int spaceAbove = bubbleTop - headerBottom;
|
||||
boolean placeBelow = spaceBelow >= height + gap;
|
||||
if (!placeBelow && spaceAbove < height + gap && mRecyclerView != null) {
|
||||
if (spaceBelow >= spaceAbove) {
|
||||
mRecyclerView.scrollBy(0, Math.max(0, height + gap - spaceBelow));
|
||||
placeBelow = true;
|
||||
} else {
|
||||
mRecyclerView.scrollBy(0, -Math.max(0, height + gap - spaceAbove));
|
||||
}
|
||||
anchor.getLocationOnScreen(loc);
|
||||
} else if (!placeBelow && spaceAbove < height + gap) {
|
||||
placeBelow = spaceBelow >= spaceAbove;
|
||||
}
|
||||
int y = placeBelow
|
||||
? loc[1] + anchor.getHeight() + gap
|
||||
: loc[1] - height - gap;
|
||||
final int popupX = x;
|
||||
final int popupY = y;
|
||||
showMessageBlurOverlay(anchor, () -> {
|
||||
if (mSelectedMessage != messageView || getActivity() == null) {
|
||||
return;
|
||||
}
|
||||
mMessageActionPopup = popup;
|
||||
popup.showAtLocation(messageView, Gravity.TOP | Gravity.START, popupX, popupY);
|
||||
HomeUiMotion.popoverEnter(content, !data.getIsIncoming());
|
||||
});
|
||||
}
|
||||
|
||||
private void showMessageBlurOverlay(final View keepView, final Runnable onReady) {
|
||||
final Activity activity = getActivity();
|
||||
if (activity == null || keepView == null) {
|
||||
if (onReady != null) {
|
||||
onReady.run();
|
||||
}
|
||||
return;
|
||||
}
|
||||
dismissMessageBlurOverlay();
|
||||
final ViewGroup decor = (ViewGroup) activity.getWindow().getDecorView();
|
||||
final Bitmap bubble = snapshotView(keepView);
|
||||
final int[] keepLoc = new int[2];
|
||||
keepView.getLocationInWindow(keepLoc);
|
||||
final int copyW = Math.max(1, decor.getWidth());
|
||||
final int copyH = Math.max(1, decor.getHeight());
|
||||
final Bitmap screen = Bitmap.createBitmap(copyW, copyH, Bitmap.Config.ARGB_8888);
|
||||
try {
|
||||
PixelCopy.request(activity.getWindow(), screen, result -> {
|
||||
if (getActivity() == null) {
|
||||
screen.recycle();
|
||||
if (bubble != null) {
|
||||
bubble.recycle();
|
||||
}
|
||||
return;
|
||||
}
|
||||
attachMessageBlurOverlay(activity, decor, bubble, keepLoc,
|
||||
result == PixelCopy.SUCCESS ? screen : null);
|
||||
if (result != PixelCopy.SUCCESS) {
|
||||
screen.recycle();
|
||||
}
|
||||
if (onReady != null) {
|
||||
onReady.run();
|
||||
}
|
||||
}, new Handler(Looper.getMainLooper()));
|
||||
} catch (final IllegalArgumentException ex) {
|
||||
screen.recycle();
|
||||
attachMessageBlurOverlay(activity, decor, bubble, keepLoc, null);
|
||||
if (onReady != null) {
|
||||
onReady.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void attachMessageBlurOverlay(final Activity activity, final ViewGroup decor,
|
||||
final Bitmap bubble, final int[] keepLoc, final Bitmap screen) {
|
||||
dismissMessageBlurOverlay();
|
||||
final FrameLayout overlay = new FrameLayout(activity);
|
||||
overlay.setLayoutParams(new FrameLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
overlay.setClickable(true);
|
||||
overlay.setOnClickListener(v -> dismissMessageActionPopup());
|
||||
|
||||
final ImageView blur = new ImageView(activity);
|
||||
blur.setLayoutParams(new FrameLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
blur.setScaleType(ImageView.ScaleType.FIT_XY);
|
||||
if (screen != null) {
|
||||
blur.setImageBitmap(screen);
|
||||
blur.setRenderEffect(RenderEffect.createBlurEffect(
|
||||
28f, 28f, Shader.TileMode.CLAMP));
|
||||
blur.setColorFilter(0x40000000, PorterDuff.Mode.SRC_ATOP);
|
||||
} else {
|
||||
blur.setBackgroundColor(0x73000000);
|
||||
}
|
||||
|
||||
final View dim = new View(activity);
|
||||
dim.setLayoutParams(new FrameLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
dim.setBackgroundColor(0x33000000);
|
||||
dim.setClickable(false);
|
||||
|
||||
overlay.addView(blur);
|
||||
overlay.addView(dim);
|
||||
if (bubble != null) {
|
||||
final ImageView keep = new ImageView(activity);
|
||||
keep.setClickable(true);
|
||||
keep.setElevation(8f);
|
||||
keep.setImageBitmap(bubble);
|
||||
final FrameLayout.LayoutParams keepLp = new FrameLayout.LayoutParams(
|
||||
bubble.getWidth(), bubble.getHeight());
|
||||
keepLp.leftMargin = keepLoc[0];
|
||||
keepLp.topMargin = keepLoc[1];
|
||||
keep.setLayoutParams(keepLp);
|
||||
overlay.addView(keep);
|
||||
}
|
||||
decor.addView(overlay);
|
||||
mMessageBlurOverlay = overlay;
|
||||
}
|
||||
|
||||
private void dismissMessageActionPopup() {
|
||||
dismissMessageBlurOverlay();
|
||||
if (mMessageActionPopup != null) {
|
||||
mMessageActionPopup.dismiss();
|
||||
mMessageActionPopup = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void dismissMessageBlurOverlay() {
|
||||
if (mMessageBlurOverlay == null) {
|
||||
return;
|
||||
}
|
||||
final ViewGroup overlay = mMessageBlurOverlay;
|
||||
mMessageBlurOverlay = null;
|
||||
recycleOverlayBitmaps(overlay);
|
||||
final ViewParent parent = overlay.getParent();
|
||||
if (parent instanceof ViewGroup) {
|
||||
((ViewGroup) parent).removeView(overlay);
|
||||
}
|
||||
}
|
||||
|
||||
private static void recycleOverlayBitmaps(final ViewGroup overlay) {
|
||||
for (int i = 0; i < overlay.getChildCount(); i++) {
|
||||
final View child = overlay.getChildAt(i);
|
||||
if (!(child instanceof ImageView)) {
|
||||
continue;
|
||||
}
|
||||
final Drawable drawable = ((ImageView) child).getDrawable();
|
||||
((ImageView) child).setImageDrawable(null);
|
||||
((ImageView) child).setRenderEffect(null);
|
||||
if (drawable instanceof BitmapDrawable) {
|
||||
final Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
|
||||
if (bitmap != null && !bitmap.isRecycled()) {
|
||||
bitmap.recycle();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Bitmap snapshotView(final View view) {
|
||||
if (view.getWidth() <= 0 || view.getHeight() <= 0) {
|
||||
return null;
|
||||
}
|
||||
final Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(),
|
||||
Bitmap.Config.ARGB_8888);
|
||||
final Canvas canvas = new Canvas(bitmap);
|
||||
view.draw(canvas);
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(@NonNull final Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
@@ -643,8 +946,31 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
|
||||
|
||||
setConversationFocus();
|
||||
|
||||
// On resume, invalidate all message views to show the updated timestamp.
|
||||
mAdapter.notifyDataSetChanged();
|
||||
if (mSelectedMessage == null) {
|
||||
if (mConversationHeader != null) {
|
||||
mConversationHeader.setVisibility(View.VISIBLE);
|
||||
}
|
||||
if (mComposeContainer != null) {
|
||||
mComposeContainer.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
if (mHost != null) {
|
||||
mHost.invalidateActionBar();
|
||||
}
|
||||
|
||||
// Camera/gallery return can resume while RecyclerView is still laying out.
|
||||
if (mRecyclerView != null) {
|
||||
mRecyclerView.post(() -> {
|
||||
if (!isAdded() || mAdapter == null) {
|
||||
return;
|
||||
}
|
||||
if (mRecyclerView.isComputingLayout()) {
|
||||
mRecyclerView.post(mAdapter::notifyDataSetChanged);
|
||||
return;
|
||||
}
|
||||
mAdapter.notifyDataSetChanged();
|
||||
});
|
||||
}
|
||||
|
||||
LocalBroadcastManager.getInstance(getActivity()).registerReceiver(
|
||||
mConversationSelfIdChangeReceiver,
|
||||
@@ -684,10 +1010,8 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
|
||||
menu.findItem(R.id.action_archive).setVisible(!isArchived);
|
||||
menu.findItem(R.id.action_unarchive).setVisible(isArchived);
|
||||
|
||||
// Conditionally enable the phone call button.
|
||||
final boolean supportCallAction = (PhoneUtils.getDefault().isVoiceCapable() &&
|
||||
data.getParticipantPhoneNumber() != null);
|
||||
menu.findItem(R.id.action_call).setVisible(supportCallAction);
|
||||
// Call lives on the header pill; keep it out of the overflow.
|
||||
menu.findItem(R.id.action_call).setVisible(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -759,6 +1083,7 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
|
||||
// Ensure that the action bar is updated with the current data.
|
||||
invalidateOptionsMenu();
|
||||
final Cursor oldCursor = mAdapter.swapCursor(cursor);
|
||||
updateHomeConversationHeader();
|
||||
|
||||
if (cursor != null && oldCursor == null) {
|
||||
if (mListState != null) {
|
||||
@@ -884,6 +1209,8 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
dismissMessageActionPopup();
|
||||
dismissConversationOverflowPopover();
|
||||
if (mComposeMessageView != null && !mSuppressWriteDraft) {
|
||||
mComposeMessageView.writeDraftMessage();
|
||||
}
|
||||
@@ -891,8 +1218,12 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
|
||||
mBinding.getData().unsetFocus();
|
||||
mListState = mRecyclerView.getLayoutManager().onSaveInstanceState();
|
||||
|
||||
LocalBroadcastManager.getInstance(getActivity())
|
||||
.unregisterReceiver(mConversationSelfIdChangeReceiver);
|
||||
try {
|
||||
LocalBroadcastManager.getInstance(getActivity())
|
||||
.unregisterReceiver(mConversationSelfIdChangeReceiver);
|
||||
} catch (final IllegalArgumentException ignored) {
|
||||
// onResume may have aborted before the receiver was registered (camera return).
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -915,6 +1246,14 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
|
||||
MediaPicker.FRAGMENT_TAG);
|
||||
}
|
||||
|
||||
/** Forwards default-SMS role result from {@link ConversationActivity#onActivityResult}. */
|
||||
public void onDefaultSmsAppResult(final int requestCode, final int resultCode) {
|
||||
if (mChangeDefaultSmsAppHelper == null) {
|
||||
mChangeDefaultSmsAppHelper = new ChangeDefaultSmsAppHelper();
|
||||
}
|
||||
mChangeDefaultSmsAppHelper.handleChangeDefaultSmsResult(requestCode, resultCode, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(final MessageData message) {
|
||||
if (isReadyForAction()) {
|
||||
@@ -1020,6 +1359,7 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
|
||||
public void retrySend(final String messageId) {
|
||||
if (isReadyForAction()) {
|
||||
if (ensureKnownRecipients()) {
|
||||
showSendingStatus(messageId);
|
||||
mBinding.getData().resendMessage(mBinding, messageId);
|
||||
}
|
||||
} else {
|
||||
@@ -1027,6 +1367,22 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
|
||||
}
|
||||
}
|
||||
|
||||
private void showSendingStatus(final String messageId) {
|
||||
if (mRecyclerView == null || TextUtils.isEmpty(messageId)) {
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < mRecyclerView.getChildCount(); i++) {
|
||||
final View child = mRecyclerView.getChildAt(i);
|
||||
if (child instanceof ConversationMessageView) {
|
||||
final ConversationMessageView messageView = (ConversationMessageView) child;
|
||||
if (TextUtils.equals(messageView.getData().getMessageId(), messageId)) {
|
||||
messageView.showSendingStatus();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void deleteMessage(final String messageId) {
|
||||
if (isReadyForDeleteAction()) {
|
||||
final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
|
||||
@@ -1106,6 +1462,9 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
|
||||
}
|
||||
|
||||
public boolean onBackPressed() {
|
||||
if (HomeComposeAttachPanel.onBackPressed(getActivity())) {
|
||||
return true;
|
||||
}
|
||||
if (HomeGallerySheet.dismissIfShowing(getActivity())) {
|
||||
return true;
|
||||
}
|
||||
@@ -1394,6 +1753,15 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestMediaPermission(final Runnable onGranted) {
|
||||
if (mGalleryPicker != null) {
|
||||
mGalleryPicker.requestPermission(onGranted);
|
||||
} else if (onGranted != null) {
|
||||
onGranted.run();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showAttachmentChooser() {
|
||||
showAttachmentChooser(mConversationId, getActivity());
|
||||
@@ -1407,10 +1775,11 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
|
||||
}
|
||||
|
||||
private void updateActionAndStatusBarColor(final ActionBar actionBar) {
|
||||
final int barColor = getResources().getColor(R.color.home_conversation_chrome_bg,
|
||||
final int barColor = getResources().getColor(R.color.home_conversation_canvas,
|
||||
getActivity().getTheme());
|
||||
actionBar.setBackgroundDrawable(new ColorDrawable(barColor));
|
||||
UiUtils.setStatusBarColor(getActivity(), barColor);
|
||||
getActivity().getWindow().setNavigationBarColor(barColor);
|
||||
}
|
||||
|
||||
private void updateHomeEmptyState() {
|
||||
@@ -1422,14 +1791,11 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
|
||||
if (!empty) {
|
||||
return;
|
||||
}
|
||||
if (mEmptyTime != null) {
|
||||
mEmptyTime.setText(Dates.getMessageTimeString(System.currentTimeMillis()));
|
||||
}
|
||||
if (mEmptyStatus != null) {
|
||||
final String name = getConversationName();
|
||||
mEmptyStatus.setText(getString(R.string.home_conversation_empty_status,
|
||||
TextUtils.isEmpty(name) ? getString(R.string.app_name) : name));
|
||||
}
|
||||
final String name = getConversationName();
|
||||
mEmptyContainer.setContent(R.drawable.ic_home_menu_messages,
|
||||
getString(R.string.home_empty_conversation_title),
|
||||
getString(R.string.home_empty_conversation_body,
|
||||
TextUtils.isEmpty(name) ? getString(R.string.app_name) : name));
|
||||
}
|
||||
|
||||
private void setupUnknownSenderBanner(final View root) {
|
||||
@@ -1552,6 +1918,7 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
|
||||
});
|
||||
}
|
||||
mConversationHeaderTitle = root.findViewById(R.id.conversation_header_title);
|
||||
mConversationHeaderSubtitle = root.findViewById(R.id.conversation_header_subtitle);
|
||||
mConversationHeaderAvatar = root.findViewById(R.id.conversation_header_avatar);
|
||||
if (mConversationHeaderAvatar != null) {
|
||||
mConversationHeaderAvatar.setOnClickListener(v -> openConversationProfileDetails());
|
||||
@@ -1559,24 +1926,136 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
|
||||
if (mConversationHeaderTitle != null) {
|
||||
mConversationHeaderTitle.setOnClickListener(v -> openConversationProfileDetails());
|
||||
}
|
||||
if (mConversationHeaderSubtitle != null) {
|
||||
mConversationHeaderSubtitle.setOnClickListener(v -> openConversationProfileDetails());
|
||||
}
|
||||
final View identity = root.findViewById(R.id.conversation_header_identity);
|
||||
if (identity != null) {
|
||||
identity.setOnClickListener(v -> openConversationProfileDetails());
|
||||
}
|
||||
mConversationHeaderOverflow = root.findViewById(R.id.conversation_header_overflow);
|
||||
if (mConversationHeaderOverflow != null) {
|
||||
mConversationHeaderOverflow.setOnClickListener(this::showConversationOverflowMenu);
|
||||
}
|
||||
mConversationHeaderCall = root.findViewById(R.id.conversation_header_call);
|
||||
if (mConversationHeaderCall != null) {
|
||||
mConversationHeaderCall.setOnClickListener(this::placeConversationCall);
|
||||
}
|
||||
}
|
||||
|
||||
private void bindConversationEdgeFade(final View root) {
|
||||
if (mRecyclerView == null) {
|
||||
return;
|
||||
}
|
||||
mRecyclerView.setClipToPadding(false);
|
||||
final View fadeTop = root.findViewById(R.id.conversation_fade_top);
|
||||
final View fadeBottom = root.findViewById(R.id.conversation_fade_bottom);
|
||||
final int fadeHeight = getResources().getDimensionPixelSize(
|
||||
R.dimen.home_conversation_edge_fade);
|
||||
final View.OnLayoutChangeListener listener = (v, l, t, r, b, ol, ot, or, ob) -> {
|
||||
if ((b - t) != (ob - ot)) {
|
||||
applyConversationEdgeFade(fadeTop, fadeBottom, fadeHeight);
|
||||
}
|
||||
};
|
||||
if (mConversationHeader != null) {
|
||||
mConversationHeader.addOnLayoutChangeListener(listener);
|
||||
}
|
||||
if (mMessageSelectionHeader != null) {
|
||||
mMessageSelectionHeader.addOnLayoutChangeListener(listener);
|
||||
}
|
||||
if (mComposeStage != null) {
|
||||
mComposeStage.addOnLayoutChangeListener(listener);
|
||||
}
|
||||
mRecyclerView.addOnLayoutChangeListener(listener);
|
||||
root.post(() -> applyConversationEdgeFade(fadeTop, fadeBottom, fadeHeight));
|
||||
}
|
||||
|
||||
private void applyConversationEdgeFade(final View fadeTop, final View fadeBottom,
|
||||
final int fadeHeight) {
|
||||
if (mRecyclerView == null) {
|
||||
return;
|
||||
}
|
||||
View header = null;
|
||||
if (mMessageSelectionHeader != null
|
||||
&& mMessageSelectionHeader.getVisibility() == View.VISIBLE) {
|
||||
header = mMessageSelectionHeader;
|
||||
} else if (mConversationHeader != null) {
|
||||
header = mConversationHeader;
|
||||
}
|
||||
final int headerH = header == null ? 0
|
||||
: Math.max(header.getHeight(), header.getMeasuredHeight());
|
||||
final int composeH = mComposeStage == null ? fadeHeight
|
||||
: Math.max(mComposeStage.getHeight(), mComposeStage.getMeasuredHeight());
|
||||
final int topPad = Math.max(headerH, fadeHeight);
|
||||
final int bottomPad = Math.max(composeH, fadeHeight);
|
||||
// Fade from the top of the compose dock to the bottom of the chat area.
|
||||
final int bottomFadeH = Math.max(composeH, fadeHeight);
|
||||
if (mRecyclerView.getPaddingTop() != topPad
|
||||
|| mRecyclerView.getPaddingBottom() != bottomPad) {
|
||||
mRecyclerView.setPaddingRelative(
|
||||
mRecyclerView.getPaddingStart(),
|
||||
topPad,
|
||||
mRecyclerView.getPaddingEnd(),
|
||||
bottomPad);
|
||||
}
|
||||
if (fadeTop != null && headerH > 0) {
|
||||
final int topFadeH = headerH + (fadeHeight / 2);
|
||||
final ViewGroup.LayoutParams lp = fadeTop.getLayoutParams();
|
||||
if (lp != null && lp.height != topFadeH) {
|
||||
lp.height = topFadeH;
|
||||
fadeTop.setLayoutParams(lp);
|
||||
}
|
||||
}
|
||||
if (fadeBottom != null) {
|
||||
final ViewGroup.LayoutParams lp = fadeBottom.getLayoutParams();
|
||||
if (lp instanceof FrameLayout.LayoutParams) {
|
||||
final FrameLayout.LayoutParams flp = (FrameLayout.LayoutParams) lp;
|
||||
if (flp.height != bottomFadeH || flp.bottomMargin != 0
|
||||
|| flp.gravity != Gravity.BOTTOM) {
|
||||
flp.height = bottomFadeH;
|
||||
flp.gravity = Gravity.BOTTOM;
|
||||
flp.bottomMargin = 0;
|
||||
fadeBottom.setLayoutParams(flp);
|
||||
}
|
||||
} else if (lp != null && lp.height != bottomFadeH) {
|
||||
lp.height = bottomFadeH;
|
||||
fadeBottom.setLayoutParams(lp);
|
||||
}
|
||||
}
|
||||
if (mUnknownSenderBanner != null
|
||||
&& mUnknownSenderBanner.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) {
|
||||
final ViewGroup.MarginLayoutParams mlp =
|
||||
(ViewGroup.MarginLayoutParams) mUnknownSenderBanner.getLayoutParams();
|
||||
final int top = headerH + getResources().getDimensionPixelSize(
|
||||
R.dimen.looply_space_md);
|
||||
if (mlp.topMargin != top) {
|
||||
mlp.topMargin = top;
|
||||
mUnknownSenderBanner.setLayoutParams(mlp);
|
||||
}
|
||||
}
|
||||
if (fadeTop != null) {
|
||||
fadeTop.setClickable(false);
|
||||
fadeTop.setFocusable(false);
|
||||
}
|
||||
if (fadeBottom != null) {
|
||||
fadeBottom.setClickable(false);
|
||||
fadeBottom.setFocusable(false);
|
||||
}
|
||||
if (mComposeStage != null) {
|
||||
mComposeStage.bringToFront();
|
||||
}
|
||||
if (mMessageSelectionHeader != null
|
||||
&& mMessageSelectionHeader.getVisibility() == View.VISIBLE) {
|
||||
mMessageSelectionHeader.bringToFront();
|
||||
} else if (mConversationHeader != null) {
|
||||
mConversationHeader.bringToFront();
|
||||
}
|
||||
}
|
||||
|
||||
private void openConversationProfileDetails() {
|
||||
if (getActivity() == null || TextUtils.isEmpty(mConversationId)) {
|
||||
return;
|
||||
}
|
||||
if (mBinding.isBound()) {
|
||||
final ParticipantData other = mBinding.getData().getOtherParticipant();
|
||||
if (other != null && ContactUtil.openContactDetail(getActivity(),
|
||||
other.getContactId(), other.getLookupKey(),
|
||||
other.getNormalizedDestination())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
UIIntents.get().launchHomeProfileActivity(getActivity(), mConversationId);
|
||||
}
|
||||
|
||||
@@ -1584,6 +2063,7 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
|
||||
mMessageSelectionHeader = root.findViewById(R.id.home_message_selection_header);
|
||||
mMessageSelectionTitle = root.findViewById(R.id.home_message_selection_title);
|
||||
mComposeContainer = root.findViewById(R.id.message_compose_view_container);
|
||||
mComposeStage = root.findViewById(R.id.conversation_compose_stage);
|
||||
mMessageActionBar = root.findViewById(R.id.home_message_action_bar);
|
||||
mMessageActionCopy = root.findViewById(R.id.home_message_action_copy);
|
||||
mMessageActionForward = root.findViewById(R.id.home_message_action_forward);
|
||||
@@ -1644,6 +2124,12 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
|
||||
HomeUiMotion.replaceHeaderInstant(mMessageSelectionHeader, mConversationHeader);
|
||||
mHost.invalidateActionBar();
|
||||
}
|
||||
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)));
|
||||
}
|
||||
HomeUiMotion.swapBottomDock(mComposeContainer, mMessageActionBar, active, null);
|
||||
}
|
||||
|
||||
@@ -1762,14 +2248,127 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
|
||||
}
|
||||
|
||||
private void showConversationOverflowMenu(final View anchor) {
|
||||
if (!mOptionsMenuVisible || getActivity() == null) {
|
||||
if (!mOptionsMenuVisible || getActivity() == null || !mBinding.isBound()) {
|
||||
return;
|
||||
}
|
||||
final PopupMenu popup = new PopupMenu(requireContext(), anchor);
|
||||
popup.setOnMenuItemClickListener(this::onOptionsItemSelected);
|
||||
onCreateOptionsMenu(popup.getMenu(), requireActivity().getMenuInflater());
|
||||
onPrepareOptionsMenu(popup.getMenu());
|
||||
popup.show();
|
||||
dismissConversationOverflowPopover();
|
||||
final View content = LayoutInflater.from(requireContext())
|
||||
.inflate(R.layout.home_popover_conversation_overflow, null, false);
|
||||
final ConversationData data = mBinding.getData();
|
||||
final ParticipantData participant = data.getOtherParticipant();
|
||||
final boolean showAddPeople = participant != null
|
||||
&& !ContactUtil.isParticipantInAddressBook(getActivity(),
|
||||
participant.getContactId(), participant.getLookupKey(),
|
||||
participant.getNormalizedDestination());
|
||||
final View addPeople = content.findViewById(R.id.popover_overflow_add_people);
|
||||
addPeople.setVisibility(showAddPeople ? View.VISIBLE : View.GONE);
|
||||
addPeople.setOnClickListener(v -> {
|
||||
dismissConversationOverflowPopover();
|
||||
performOverflowAddContact();
|
||||
});
|
||||
content.findViewById(R.id.popover_overflow_details).setOnClickListener(v -> {
|
||||
dismissConversationOverflowPopover();
|
||||
UIIntents.get().launchHomeProfileActivity(getActivity(), mConversationId);
|
||||
});
|
||||
content.findViewById(R.id.popover_overflow_search).setOnClickListener(v -> {
|
||||
dismissConversationOverflowPopover();
|
||||
UIIntents.get().launchHomeConversationSearchActivity(getActivity(), mConversationId);
|
||||
});
|
||||
|
||||
final boolean archived = data.getIsArchived();
|
||||
final TextView archiveLabel = content.findViewById(R.id.popover_overflow_archive_label);
|
||||
archiveLabel.setText(archived
|
||||
? R.string.home_profile_option_unarchive
|
||||
: R.string.home_profile_option_archive);
|
||||
content.findViewById(R.id.popover_overflow_archive).setOnClickListener(v -> {
|
||||
dismissConversationOverflowPopover();
|
||||
if (archived) {
|
||||
mBinding.getData().unarchiveConversation(mBinding);
|
||||
} else {
|
||||
mBinding.getData().archiveConversation(mBinding);
|
||||
closeConversation(mConversationId);
|
||||
}
|
||||
});
|
||||
content.findViewById(R.id.popover_overflow_delete).setOnClickListener(v -> {
|
||||
dismissConversationOverflowPopover();
|
||||
confirmDeleteConversation();
|
||||
});
|
||||
|
||||
content.measure(
|
||||
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
|
||||
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
|
||||
final PopupWindow popup = new PopupWindow(content,
|
||||
content.getMeasuredWidth(),
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
true);
|
||||
popup.setOutsideTouchable(true);
|
||||
popup.setFocusable(true);
|
||||
popup.setElevation(16f);
|
||||
popup.setBackgroundDrawable(new ColorDrawable(android.graphics.Color.TRANSPARENT));
|
||||
popup.setOnDismissListener(() -> {
|
||||
if (mConversationOverflowPopover == popup) {
|
||||
mConversationOverflowPopover = null;
|
||||
}
|
||||
});
|
||||
final int endInset = getResources().getDimensionPixelSize(R.dimen.looply_screen_padding);
|
||||
final int[] anchorLoc = new int[2];
|
||||
anchor.getLocationOnScreen(anchorLoc);
|
||||
final View root = getView();
|
||||
final int windowRight;
|
||||
if (root != null) {
|
||||
final int[] rootLoc = new int[2];
|
||||
root.getLocationOnScreen(rootLoc);
|
||||
windowRight = rootLoc[0] + root.getWidth();
|
||||
} else {
|
||||
windowRight = getResources().getDisplayMetrics().widthPixels;
|
||||
}
|
||||
final int desiredLeft = windowRight - endInset - content.getMeasuredWidth();
|
||||
final int xOff = desiredLeft - anchorLoc[0];
|
||||
popup.showAsDropDown(anchor, xOff, 8);
|
||||
HomeUiMotion.popoverEnter(content, true);
|
||||
mConversationOverflowPopover = popup;
|
||||
}
|
||||
|
||||
private void dismissConversationOverflowPopover() {
|
||||
if (mConversationOverflowPopover == null) {
|
||||
return;
|
||||
}
|
||||
final PopupWindow popup = mConversationOverflowPopover;
|
||||
mConversationOverflowPopover = null;
|
||||
if (popup.isShowing()) {
|
||||
popup.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
private void performOverflowAddContact() {
|
||||
if (!mBinding.isBound()) {
|
||||
return;
|
||||
}
|
||||
final ParticipantData participant = mBinding.getData().getOtherParticipant();
|
||||
if (participant == null || getActivity() == null) {
|
||||
return;
|
||||
}
|
||||
final String destination = participant.getNormalizedDestination();
|
||||
final Uri avatarUri = AvatarUriUtil.createAvatarUri(participant);
|
||||
new AddContactsConfirmationDialog(getActivity(), avatarUri, destination).show();
|
||||
}
|
||||
|
||||
private void confirmDeleteConversation() {
|
||||
if (getActivity() == null) {
|
||||
return;
|
||||
}
|
||||
if (isReadyForDeleteAction()) {
|
||||
new AlertDialog.Builder(getActivity(), R.style.Home_Dialog)
|
||||
.setTitle(getResources().getQuantityString(
|
||||
R.plurals.delete_conversations_confirmation_dialog_title, 1))
|
||||
.setPositiveButton(R.string.delete_conversation_confirmation_button,
|
||||
(dialog, button) -> deleteConversation())
|
||||
.setNegativeButton(R.string.delete_conversation_decline_button, null)
|
||||
.show();
|
||||
} else {
|
||||
warnOfMissingActionConditions(false /*sending*/,
|
||||
null /*commandToRunAfterActionConditionResolved*/);
|
||||
}
|
||||
}
|
||||
|
||||
private void bindConversationHeaderAvatar(final ContactIconView avatar) {
|
||||
@@ -1831,10 +2430,36 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
|
||||
}
|
||||
}
|
||||
|
||||
if (mConversationHeaderCall != null) {
|
||||
final boolean showCall = mBinding.isBound()
|
||||
&& PhoneUtils.getDefault().isVoiceCapable()
|
||||
&& mBinding.getData().getParticipantPhoneNumber() != null;
|
||||
mConversationHeaderCall.setVisibility(showCall ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
bindConversationHeaderSubtitle();
|
||||
|
||||
final boolean hideForIme = mHost.isImeOpen() && UiUtils.isLandscapeMode();
|
||||
mConversationHeader.setVisibility(hideForIme ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
|
||||
private void bindConversationHeaderSubtitle() {
|
||||
if (mConversationHeaderSubtitle == null) {
|
||||
return;
|
||||
}
|
||||
if (!mBinding.isBound()) {
|
||||
mConversationHeaderSubtitle.setVisibility(View.GONE);
|
||||
return;
|
||||
}
|
||||
final ConversationData data = mBinding.getData();
|
||||
if (data.getOtherParticipant() == null && data.getParticipantsLoaded()) {
|
||||
mConversationHeaderSubtitle.setText(R.string.home_conversation_header_group);
|
||||
mConversationHeaderSubtitle.setVisibility(View.VISIBLE);
|
||||
return;
|
||||
}
|
||||
mConversationHeaderSubtitle.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
public void updateActionBar(final ActionBar actionBar) {
|
||||
if (mComposeMessageView != null && mComposeMessageView.updateActionBar(actionBar)) {
|
||||
if (mConversationHeader != null) {
|
||||
|
||||
+22
-1
@@ -56,6 +56,8 @@ public class ConversationInputManager implements ConversationInput.ConversationI
|
||||
public static final int MEDIA_PICKER_TARGET_GALLERY = 2;
|
||||
public static final int MEDIA_PICKER_TARGET_AUDIO = 3;
|
||||
public static final int MEDIA_PICKER_TARGET_CONTACT = 4;
|
||||
public static final int MEDIA_PICKER_TARGET_PHOTO = 5;
|
||||
public static final int MEDIA_PICKER_TARGET_VIDEO = 6;
|
||||
|
||||
/**
|
||||
* The host component where all input components are contained. This is typically the
|
||||
@@ -222,6 +224,16 @@ public class ConversationInputManager implements ConversationInput.ConversationI
|
||||
}
|
||||
|
||||
public void showHideImeKeyboard(final boolean show, final boolean animate) {
|
||||
if (show) {
|
||||
final EditText editText = mSink.getComposeEditText();
|
||||
if (editText != null) {
|
||||
ImeUtil.get().showImeKeyboard(mContext, editText);
|
||||
if (mConversationDataModel.isBound() && !mImeInput.mShowing) {
|
||||
showHideInternal(mImeInput, true, animate);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
showHideInternal(mImeInput, show, animate);
|
||||
}
|
||||
|
||||
@@ -262,6 +274,9 @@ public class ConversationInputManager implements ConversationInput.ConversationI
|
||||
}
|
||||
|
||||
if (target.mShowing == show) {
|
||||
if (show && target instanceof ConversationImeKeyboard) {
|
||||
ImeUtil.get().showImeKeyboard(mContext, mSink.getComposeEditText());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
beginUpdate();
|
||||
@@ -432,6 +447,12 @@ public class ConversationInputManager implements ConversationInput.ConversationI
|
||||
mMediaPicker.openContact(animate);
|
||||
// System picker only — do not keep the in-app panel marked open.
|
||||
return false;
|
||||
case MEDIA_PICKER_TARGET_PHOTO:
|
||||
mMediaPicker.openTakePicture();
|
||||
return false;
|
||||
case MEDIA_PICKER_TARGET_VIDEO:
|
||||
mMediaPicker.openTakeVideo();
|
||||
return false;
|
||||
default:
|
||||
mMediaPicker.open(MediaPicker.MEDIA_TYPE_DEFAULT, animate);
|
||||
break;
|
||||
@@ -479,7 +500,7 @@ public class ConversationInputManager implements ConversationInput.ConversationI
|
||||
mFragmentManager.beginTransaction().replace(
|
||||
R.id.mediapicker_container,
|
||||
mediaPicker,
|
||||
MediaPicker.FRAGMENT_TAG).commit();
|
||||
MediaPicker.FRAGMENT_TAG).commitNowAllowingStateLoss();
|
||||
}
|
||||
return mediaPicker;
|
||||
}
|
||||
|
||||
+38
-7
@@ -114,6 +114,7 @@ public class ConversationMessageView extends FrameLayout implements View.OnClick
|
||||
|
||||
private boolean mOneOnOne;
|
||||
private ConversationMessageViewHost mHost;
|
||||
private String mOptimisticSendingMessageId;
|
||||
|
||||
public ConversationMessageView(final Context context, final AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
@@ -244,6 +245,11 @@ public class ConversationMessageView extends FrameLayout implements View.OnClick
|
||||
|
||||
// Update our UI model
|
||||
mData.bind(cursor);
|
||||
if (mOptimisticSendingMessageId != null
|
||||
&& (!TextUtils.equals(mOptimisticSendingMessageId, mData.getMessageId())
|
||||
|| !isOutgoingFailedStatus(mData.getStatus()))) {
|
||||
mOptimisticSendingMessageId = null;
|
||||
}
|
||||
setSelected(TextUtils.equals(mData.getMessageId(), selectedMessageId));
|
||||
|
||||
// Update text and image content for the view.
|
||||
@@ -272,6 +278,26 @@ public class ConversationMessageView extends FrameLayout implements View.OnClick
|
||||
return mData;
|
||||
}
|
||||
|
||||
public void showSendingStatus() {
|
||||
if (mData == null || !isOutgoingFailedStatus(mData.getStatus())) {
|
||||
return;
|
||||
}
|
||||
mOptimisticSendingMessageId = mData.getMessageId();
|
||||
updateViewContent();
|
||||
updateViewAppearance();
|
||||
}
|
||||
|
||||
private boolean isOptimisticSending() {
|
||||
return mOptimisticSendingMessageId != null
|
||||
&& mData != null
|
||||
&& TextUtils.equals(mOptimisticSendingMessageId, mData.getMessageId());
|
||||
}
|
||||
|
||||
private static boolean isOutgoingFailedStatus(final int status) {
|
||||
return status == MessageData.BUGLE_STATUS_OUTGOING_FAILED
|
||||
|| status == MessageData.BUGLE_STATUS_OUTGOING_FAILED_EMERGENCY_NUMBER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether we should show simplified visual style for the message view (i.e. hide the
|
||||
* avatar and bubble arrow, reduce padding).
|
||||
@@ -340,12 +366,9 @@ public class ConversationMessageView extends FrameLayout implements View.OnClick
|
||||
|
||||
case MessageData.BUGLE_STATUS_OUTGOING_YET_TO_SEND:
|
||||
case MessageData.BUGLE_STATUS_OUTGOING_SENDING:
|
||||
statusResId = R.string.message_status_sending;
|
||||
break;
|
||||
|
||||
case MessageData.BUGLE_STATUS_OUTGOING_RESENDING:
|
||||
case MessageData.BUGLE_STATUS_OUTGOING_AWAITING_RETRY:
|
||||
statusResId = R.string.message_status_send_retrying;
|
||||
statusResId = R.string.message_status_sending;
|
||||
break;
|
||||
|
||||
case MessageData.BUGLE_STATUS_OUTGOING_FAILED_EMERGENCY_NUMBER:
|
||||
@@ -354,6 +377,10 @@ public class ConversationMessageView extends FrameLayout implements View.OnClick
|
||||
|
||||
case MessageData.BUGLE_STATUS_OUTGOING_FAILED:
|
||||
// don't show the error state unless we're the default sms app
|
||||
if (isOptimisticSending()) {
|
||||
statusResId = R.string.message_status_sending;
|
||||
break;
|
||||
}
|
||||
if (PhoneUtils.getDefault().isDefaultSmsApp()) {
|
||||
if (isSelected()) {
|
||||
statusResId = R.string.message_status_resend;
|
||||
@@ -697,7 +724,7 @@ public class ConversationMessageView extends FrameLayout implements View.OnClick
|
||||
textBottomPadding = textBottomPaddingDefault;
|
||||
textLeftPadding = messageTextLeftRightPadding;
|
||||
textRightPadding = messageTextLeftRightPadding;
|
||||
mMessageTextView.setTextIsSelectable(isSelected());
|
||||
mMessageTextView.setTextIsSelectable(false);
|
||||
} else {
|
||||
// Attachment(s) only
|
||||
contentLeftPadding = incoming ? arrowWidth : 0;
|
||||
@@ -723,7 +750,7 @@ public class ConversationMessageView extends FrameLayout implements View.OnClick
|
||||
textTopMargin = 0;
|
||||
textTopPadding = textTopPaddingDefault;
|
||||
textBottomPadding = textBottomPaddingDefault;
|
||||
mMessageTextView.setTextIsSelectable(isSelected());
|
||||
mMessageTextView.setTextIsSelectable(false);
|
||||
textLeftPadding = messageTextLeftRightPadding;
|
||||
textRightPadding = messageTextLeftRightPadding;
|
||||
}
|
||||
@@ -908,7 +935,11 @@ public class ConversationMessageView extends FrameLayout implements View.OnClick
|
||||
|
||||
case MessageData.BUGLE_STATUS_OUTGOING_FAILED:
|
||||
case MessageData.BUGLE_STATUS_OUTGOING_FAILED_EMERGENCY_NUMBER:
|
||||
timestampColorResId = R.color.message_failed_timestamp_text;
|
||||
if (isOptimisticSending()) {
|
||||
timestampColorResId = R.color.timestamp_text_outgoing;
|
||||
} else {
|
||||
timestampColorResId = R.color.message_failed_timestamp_text;
|
||||
}
|
||||
subjectLabelColorResId = R.color.timestamp_text_outgoing;
|
||||
break;
|
||||
|
||||
|
||||
+13
-5
@@ -26,8 +26,6 @@ import android.content.res.Resources;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -47,6 +45,7 @@ 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.HomeToast;
|
||||
import com.android.messaging.ui.conversationlist.ConversationListFragment.ConversationListFragmentHost;
|
||||
import com.android.messaging.ui.conversationlist.MultiSelectActionModeCallback.SelectedConversation;
|
||||
import com.android.messaging.util.ChangeDefaultSmsAppHelper;
|
||||
@@ -146,6 +145,14 @@ public abstract class AbstractConversationListActivity extends BugleActionBarAct
|
||||
}
|
||||
}
|
||||
|
||||
public void confirmDeleteConversation(final ConversationListItemData data) {
|
||||
if (data == null) {
|
||||
return;
|
||||
}
|
||||
showHomeDeleteAlert(java.util.Collections.singletonList(
|
||||
new MultiSelectActionModeCallback.SelectedConversation(data)));
|
||||
}
|
||||
|
||||
public void markSelectedConversationsUnread() {
|
||||
if (!isInConversationListSelectMode()) {
|
||||
return;
|
||||
@@ -156,6 +163,7 @@ public abstract class AbstractConversationListActivity extends BugleActionBarAct
|
||||
: callback.getSelectedConversations()) {
|
||||
MarkAsUnreadAction.markAsUnread(conversation.conversationId);
|
||||
}
|
||||
exitMultiSelectState();
|
||||
}
|
||||
|
||||
protected boolean isInConversationListSelectMode() {
|
||||
@@ -195,13 +203,13 @@ public abstract class AbstractConversationListActivity extends BugleActionBarAct
|
||||
final Activity activity = this;
|
||||
UiUtils.showSnackBarWithCustomAction(this,
|
||||
getWindow().getDecorView().getRootView(),
|
||||
getString(R.string.requires_default_sms_app),
|
||||
getString(R.string.home_requires_default_sms_app),
|
||||
SnackBar.Action.createCustomAction(() -> {
|
||||
final Intent intent =
|
||||
UIIntents.get().getChangeDefaultSmsAppIntent(activity);
|
||||
mLauncher.launch(intent);
|
||||
},
|
||||
getString(R.string.requires_default_sms_change_button)),
|
||||
getString(R.string.home_requires_default_sms_change)),
|
||||
null /* interactions */,
|
||||
null /* placement */);
|
||||
return false;
|
||||
@@ -238,7 +246,7 @@ public abstract class AbstractConversationListActivity extends BugleActionBarAct
|
||||
? getString(R.string.home_delete_toast_one)
|
||||
: getString(R.string.home_delete_toast_many, count);
|
||||
}
|
||||
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
|
||||
HomeToast.show(this, message);
|
||||
}
|
||||
|
||||
private void performDeleteAndRegisterSpam(
|
||||
|
||||
+35
-17
@@ -16,37 +16,49 @@
|
||||
*/
|
||||
package com.android.messaging.ui.conversationlist;
|
||||
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.os.Bundle;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
|
||||
import com.android.messaging.R;
|
||||
import com.android.messaging.ui.home.HomeSubHeader;
|
||||
|
||||
public class ArchivedConversationListActivity 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());
|
||||
|
||||
mConversationListFragment =
|
||||
ConversationListFragment.createArchivedConversationListFragment();
|
||||
getSupportFragmentManager()
|
||||
.beginTransaction()
|
||||
.add(android.R.id.content, mConversationListFragment)
|
||||
.commit();
|
||||
invalidateActionBar();
|
||||
if (savedInstanceState == null) {
|
||||
mConversationListFragment =
|
||||
ConversationListFragment.createArchivedConversationListFragment();
|
||||
getSupportFragmentManager()
|
||||
.beginTransaction()
|
||||
.replace(R.id.home_archived_list_host, mConversationListFragment)
|
||||
.commit();
|
||||
} else {
|
||||
mConversationListFragment = (ConversationListFragment) getSupportFragmentManager()
|
||||
.findFragmentById(R.id.home_archived_list_host);
|
||||
}
|
||||
}
|
||||
|
||||
protected void updateActionBar(ActionBar actionBar) {
|
||||
actionBar.setTitle(getString(R.string.archived_activity_title));
|
||||
actionBar.setDisplayShowTitleEnabled(true);
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
actionBar.setBackgroundDrawable(new ColorDrawable(
|
||||
getResources().getColor(
|
||||
R.color.archived_conversation_action_bar_background_color_dark)));
|
||||
actionBar.show();
|
||||
@Override
|
||||
protected boolean useHomeSelectionChrome() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateActionBar(final ActionBar actionBar) {
|
||||
if (actionBar == null) {
|
||||
return;
|
||||
}
|
||||
actionBar.hide();
|
||||
super.updateActionBar(actionBar);
|
||||
}
|
||||
|
||||
@@ -60,7 +72,7 @@ public class ArchivedConversationListActivity extends AbstractConversationListAc
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem menuItem) {
|
||||
public boolean onOptionsItemSelected(final MenuItem menuItem) {
|
||||
if (menuItem.getItemId() == android.R.id.home) {
|
||||
onActionBarHome();
|
||||
return true;
|
||||
@@ -77,4 +89,10 @@ public class ArchivedConversationListActivity extends AbstractConversationListAc
|
||||
public boolean isSwipeAnimatable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish() {
|
||||
super.finish();
|
||||
overridePendingTransition(R.anim.home_activity_hold, R.anim.home_activity_close);
|
||||
}
|
||||
}
|
||||
|
||||
+4
-3
@@ -21,8 +21,6 @@ import android.os.Bundle;
|
||||
import android.view.ActionMode;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.widget.PopupMenu;
|
||||
@@ -80,6 +78,9 @@ public class ConversationListActivity extends AbstractConversationListActivity {
|
||||
public void onBackPressed() {
|
||||
if (isInConversationListSelectMode()) {
|
||||
exitMultiSelectState();
|
||||
} else if (mConversationListFragment != null
|
||||
&& mConversationListFragment.onHomeBackPressed()) {
|
||||
return;
|
||||
} else {
|
||||
super.onBackPressed();
|
||||
}
|
||||
@@ -146,7 +147,7 @@ public class ConversationListActivity extends AbstractConversationListActivity {
|
||||
}
|
||||
|
||||
public void showAutomateComingSoon() {
|
||||
Toast.makeText(this, R.string.home_automate_coming_soon, Toast.LENGTH_SHORT).show();
|
||||
UIIntents.get().launchHomeAutomationActivity(this);
|
||||
}
|
||||
|
||||
public void enterHomeEditMode() {
|
||||
|
||||
+677
-45
@@ -20,18 +20,24 @@ import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RenderEffect;
|
||||
import android.graphics.Shader;
|
||||
import android.telephony.PhoneNumberUtils;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.text.Layout;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.Gravity;
|
||||
import android.view.HapticFeedbackConstants;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewGroup.MarginLayoutParams;
|
||||
@@ -41,10 +47,9 @@ import android.view.accessibility.AccessibilityManager;
|
||||
import android.widget.AbsListView;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.PopupWindow;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.view.ViewGroupCompat;
|
||||
@@ -56,26 +61,33 @@ import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.android.messaging.R;
|
||||
import com.android.messaging.datamodel.DataModel;
|
||||
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.binding.Binding;
|
||||
import com.android.messaging.datamodel.binding.BindingBase;
|
||||
import com.android.messaging.datamodel.data.ConversationListData;
|
||||
import com.android.messaging.datamodel.data.ConversationListData.ConversationListDataListener;
|
||||
import com.android.messaging.datamodel.data.ConversationListItemData;
|
||||
import com.android.messaging.ui.ContactIconView;
|
||||
import com.android.messaging.ui.ListEmptyView;
|
||||
import com.android.messaging.ui.home.HomeEmptyState;
|
||||
import com.android.messaging.ui.SnackBarInteraction;
|
||||
import com.android.messaging.ui.UIIntents;
|
||||
import com.android.messaging.ui.contact.AddContactsConfirmationDialog;
|
||||
import com.android.messaging.util.AvatarUriUtil;
|
||||
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.MutedConversationStore;
|
||||
import com.android.messaging.ui.home.HomeToast;
|
||||
import com.android.messaging.ui.home.HomeUiMotion;
|
||||
import com.android.messaging.ui.home.PinnedConversationStore;
|
||||
import com.android.messaging.util.AccessibilityUtil;
|
||||
import com.android.messaging.util.ImeUtil;
|
||||
import com.android.messaging.util.LogUtil;
|
||||
import com.android.messaging.util.ThreadUtil;
|
||||
import com.android.messaging.util.UiUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -118,16 +130,30 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
private TextView mHomeReadAllButton;
|
||||
private int mHomeSelectionCount;
|
||||
private View mHomeMessagesTop;
|
||||
private TextView mHomeTitle;
|
||||
private View mHomeAutomationPane;
|
||||
private View mHomeAutomationCreateButton;
|
||||
private boolean mHomeTabReady;
|
||||
private View mHomePinnedStrip;
|
||||
private TextView mHomeEditButton;
|
||||
private ImageButton mHomePinnedDoneButton;
|
||||
private boolean mHomePinnedEditMode;
|
||||
private View mHomeNavMessages;
|
||||
private View mHomeNavAutomate;
|
||||
private View mHomeNavTabGap;
|
||||
private View mHomeNavTabs;
|
||||
private View mHomeNavIndicator;
|
||||
private ImageView mHomeNavMessagesIcon;
|
||||
private TextView mHomeNavMessagesLabel;
|
||||
private ImageView mHomeNavAutomateIcon;
|
||||
private TextView mHomeNavAutomateLabel;
|
||||
private int mHomeNavMessagesNaturalWidth;
|
||||
private int mHomeNavAutomateNaturalWidth;
|
||||
private int mHomeTab = HOME_TAB_MESSAGES;
|
||||
private View mHomeListProfilePopup;
|
||||
private View mHomeListProfileScrim;
|
||||
private ImageView mHomeListProfileBlur;
|
||||
private Bitmap mHomeListProfileBlurBitmap;
|
||||
private View mHomeListProfileContent;
|
||||
private ContactIconView mHomeListProfileAvatar;
|
||||
private View mHomeListProfileActionCall;
|
||||
@@ -138,7 +164,7 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
private RecyclerView mRecyclerView;
|
||||
private View mHomeComposeContainer;
|
||||
private ImageButton mStartNewConversationButton;
|
||||
private ListEmptyView mEmptyListMessageView;
|
||||
private HomeEmptyState mEmptyListMessageView;
|
||||
private ConversationListAdapter mAdapter;
|
||||
|
||||
// Saved Instance State Data - only for temporal data which is nice to maintain but not
|
||||
@@ -150,6 +176,7 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
private static final String SAVED_INSTANCE_STATE_HOME_TAB = "homeTab";
|
||||
private static final String SAVED_INSTANCE_STATE_PINNED_EDIT = "homePinnedEdit";
|
||||
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;
|
||||
private static final int SEARCH_BAR_ANIM_MS = 280;
|
||||
private static final int SEARCH_BAR_SLIDE_FALLBACK_DP = 72;
|
||||
@@ -264,7 +291,6 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
container, false);
|
||||
mRecyclerView = rootView.findViewById(android.R.id.list);
|
||||
mEmptyListMessageView = rootView.findViewById(R.id.no_conversations_view);
|
||||
mEmptyListMessageView.setImageHint(R.drawable.ic_oobe_conv_list);
|
||||
// The default behavior for default layout param generation by LinearLayoutManager is to
|
||||
// provide width and height of WRAP_CONTENT, but this is not desirable for
|
||||
// ConversationListFragment; the view in each row should be a width of MATCH_PARENT so that
|
||||
@@ -333,26 +359,51 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
|
||||
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) {
|
||||
rootView.setFitsSystemWindows(false);
|
||||
if (mHomeComposeContainer != null) {
|
||||
mHomeComposeContainer.setVisibility(View.GONE);
|
||||
}
|
||||
hideHomeChrome(rootView);
|
||||
if (mRecyclerView != null) {
|
||||
mRecyclerView.setPadding(
|
||||
mRecyclerView.getPaddingLeft(),
|
||||
mRecyclerView.getPaddingTop(),
|
||||
mRecyclerView.getPaddingRight(),
|
||||
getResources().getDimensionPixelSize(R.dimen.looply_space_lg));
|
||||
}
|
||||
} else {
|
||||
if (mStartNewConversationButton != null) {
|
||||
mStartNewConversationButton.setOnClickListener(v -> launchHomeSubScreen(
|
||||
() -> UIIntents.get().launchHomeComposeActivity(requireActivity()),
|
||||
R.anim.home_sheet_open));
|
||||
}
|
||||
if (mHomeAutomationCreateButton != null) {
|
||||
mHomeAutomationCreateButton.setOnClickListener(v -> launchHomeSubScreen(
|
||||
() -> UIIntents.get().launchHomeAutomationCreateActivity(
|
||||
requireActivity()),
|
||||
R.anim.home_sheet_open));
|
||||
}
|
||||
setupHomeChrome(rootView);
|
||||
mHomeMessagesTop = rootView.findViewById(R.id.home_messages_top);
|
||||
mHomeTitle = rootView.findViewById(R.id.home_title);
|
||||
mHomeAutomationPane = rootView.findViewById(R.id.home_automation_pane);
|
||||
mHomeNavMessages = rootView.findViewById(R.id.home_nav_messages);
|
||||
bindHomeNavTab(mHomeNavMessages, R.drawable.ic_home_nav_messages,
|
||||
R.string.home_nav_messages);
|
||||
bindHomeNavTab(rootView.findViewById(R.id.home_nav_automate),
|
||||
R.drawable.ic_home_nav_automate, R.string.home_nav_automate);
|
||||
mHomeNavAutomate = rootView.findViewById(R.id.home_nav_automate);
|
||||
bindHomeNavTab(mHomeNavAutomate, R.drawable.ic_home_nav_automate,
|
||||
R.string.home_nav_automate);
|
||||
mHomeNavTabs = rootView.findViewById(R.id.home_nav_tabs);
|
||||
mHomeNavIndicator = rootView.findViewById(R.id.home_nav_indicator);
|
||||
bindHomeNavMetrics(rootView.findViewById(R.id.home_nav_tab_gap));
|
||||
mHomeNavMessagesIcon = mHomeNavMessages.findViewById(R.id.home_nav_tab_icon);
|
||||
mHomeNavMessagesLabel = mHomeNavMessages.findViewById(R.id.home_nav_tab_label);
|
||||
if (mHomeNavAutomate != null) {
|
||||
mHomeNavAutomateIcon = mHomeNavAutomate.findViewById(R.id.home_nav_tab_icon);
|
||||
mHomeNavAutomateLabel = mHomeNavAutomate.findViewById(R.id.home_nav_tab_label);
|
||||
}
|
||||
if (savedInstanceState != null) {
|
||||
mHomeTab = savedInstanceState.getInt(SAVED_INSTANCE_STATE_HOME_TAB,
|
||||
HOME_TAB_MESSAGES);
|
||||
@@ -373,6 +424,96 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
return rootView;
|
||||
}
|
||||
|
||||
private void bindHomeNavMetrics(final View gap) {
|
||||
mHomeNavTabGap = gap;
|
||||
if (mHomeNavMessages == null) {
|
||||
return;
|
||||
}
|
||||
mHomeNavMessages.addOnLayoutChangeListener((v, l, t, r, b, ol, ot, or, ob) ->
|
||||
applyHomeNavMetrics());
|
||||
mHomeNavMessages.post(this::applyHomeNavMetrics);
|
||||
}
|
||||
|
||||
private void applyHomeNavMetrics() {
|
||||
if (mHomeNavMessagesNaturalWidth <= 0 && mHomeNavMessages != null) {
|
||||
mHomeNavMessagesNaturalWidth = measureNavTabNaturalWidth(mHomeNavMessages);
|
||||
}
|
||||
if (mHomeNavAutomateNaturalWidth <= 0 && mHomeNavAutomate != null) {
|
||||
mHomeNavAutomateNaturalWidth = measureNavTabNaturalWidth(mHomeNavAutomate);
|
||||
}
|
||||
final int gapBase = mHomeNavMessagesNaturalWidth > 0
|
||||
? mHomeNavMessagesNaturalWidth : mHomeNavAutomateNaturalWidth;
|
||||
if (gapBase > 0 && mHomeNavTabGap != null) {
|
||||
final int gapWidth = Math.max(Math.round(gapBase * 0.6f), 1);
|
||||
final ViewGroup.LayoutParams gapParams = mHomeNavTabGap.getLayoutParams();
|
||||
if (gapParams != null && gapParams.width != gapWidth) {
|
||||
gapParams.width = gapWidth;
|
||||
mHomeNavTabGap.setLayoutParams(gapParams);
|
||||
}
|
||||
}
|
||||
applyHomeNavTabActiveSize(mHomeNavMessages, mHomeNavMessagesNaturalWidth);
|
||||
applyHomeNavTabActiveSize(mHomeNavAutomate, mHomeNavAutomateNaturalWidth);
|
||||
}
|
||||
|
||||
private void applyHomeNavTabActiveSize(final View tab, final int natural) {
|
||||
if (tab == null || natural <= 0) {
|
||||
return;
|
||||
}
|
||||
final int basePad = tab.getResources()
|
||||
.getDimensionPixelSize(R.dimen.home_nav_item_padding);
|
||||
final int activeWidth = Math.max(Math.round(natural * 1.65f), natural + 1);
|
||||
final ViewGroup.LayoutParams params = tab.getLayoutParams();
|
||||
if (params != null && params.width != activeWidth) {
|
||||
params.width = activeWidth;
|
||||
tab.setLayoutParams(params);
|
||||
}
|
||||
tab.setMinimumWidth(activeWidth);
|
||||
final int extraPad = Math.max((activeWidth - natural) / 2, 0);
|
||||
tab.setPadding(basePad + extraPad, basePad, basePad + extraPad, basePad);
|
||||
}
|
||||
|
||||
private void slideHomeNavIndicator(final boolean animate) {
|
||||
final View tab = mHomeTab == HOME_TAB_AUTOMATE ? mHomeNavAutomate : mHomeNavMessages;
|
||||
if (tab == null || mHomeNavIndicator == null || mHomeNavTabs == null
|
||||
|| tab.getWidth() <= 0) {
|
||||
return;
|
||||
}
|
||||
final float x = mHomeNavTabs.getLeft() + tab.getLeft();
|
||||
final float y = mHomeNavTabs.getTop() + tab.getTop();
|
||||
HomeUiMotion.slideNavIndicator(mHomeNavIndicator, x, y, tab.getWidth(), tab.getHeight(),
|
||||
animate);
|
||||
}
|
||||
|
||||
private int measureNavTabNaturalWidth(final View tab) {
|
||||
final int basePad = tab.getResources()
|
||||
.getDimensionPixelSize(R.dimen.home_nav_item_padding);
|
||||
tab.setPadding(basePad, basePad, basePad, basePad);
|
||||
tab.setMinimumWidth(0);
|
||||
final ViewGroup.LayoutParams params = tab.getLayoutParams();
|
||||
final int oldWidth = params != null ? params.width : ViewGroup.LayoutParams.WRAP_CONTENT;
|
||||
if (params != null) {
|
||||
params.width = ViewGroup.LayoutParams.WRAP_CONTENT;
|
||||
}
|
||||
final TextView label = tab.findViewById(R.id.home_nav_tab_label);
|
||||
final int restoreStyle = label != null && label.getTypeface() != null
|
||||
&& label.getTypeface().isBold()
|
||||
? android.graphics.Typeface.BOLD : android.graphics.Typeface.NORMAL;
|
||||
if (label != null) {
|
||||
// Bold is wider than regular; measure the widest state so the last glyph is not clipped.
|
||||
label.setTypeface(null, android.graphics.Typeface.BOLD);
|
||||
}
|
||||
tab.measure(
|
||||
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
|
||||
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
|
||||
if (label != null) {
|
||||
label.setTypeface(null, restoreStyle);
|
||||
}
|
||||
if (params != null) {
|
||||
params.width = oldWidth;
|
||||
}
|
||||
return tab.getMeasuredWidth();
|
||||
}
|
||||
|
||||
private void bindHomeNavTab(final View tabRoot, final int iconRes, final int labelRes) {
|
||||
if (tabRoot == null) {
|
||||
return;
|
||||
@@ -579,9 +720,7 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
navMessages.setOnClickListener(v -> selectHomeTab(HOME_TAB_MESSAGES));
|
||||
}
|
||||
if (navAutomate != null) {
|
||||
navAutomate.setOnClickListener(v ->
|
||||
Toast.makeText(requireContext(), R.string.home_automate_coming_soon,
|
||||
Toast.LENGTH_SHORT).show());
|
||||
navAutomate.setOnClickListener(v -> selectHomeTab(HOME_TAB_AUTOMATE));
|
||||
}
|
||||
|
||||
final View selectionDone = rootView.findViewById(R.id.home_selection_done);
|
||||
@@ -612,6 +751,7 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
return;
|
||||
}
|
||||
mHomeListProfileScrim = mHomeListProfilePopup.findViewById(R.id.home_list_profile_scrim);
|
||||
mHomeListProfileBlur = mHomeListProfilePopup.findViewById(R.id.home_list_profile_blur);
|
||||
mHomeListProfileContent = mHomeListProfilePopup.findViewById(R.id.home_list_profile_content);
|
||||
mHomeListProfileAvatar = mHomeListProfilePopup.findViewById(R.id.home_list_profile_avatar);
|
||||
mHomeListProfileActionCall =
|
||||
@@ -693,6 +833,7 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
}
|
||||
mHomeListProfileContent.setAlpha(0f);
|
||||
mHomeListProfilePopup.setVisibility(View.INVISIBLE);
|
||||
bindListProfileBlur();
|
||||
positionListProfilePopup(anchorView, () -> {
|
||||
mHomeListProfilePopup.setVisibility(View.VISIBLE);
|
||||
HomeUiMotion.listProfilePopupEnter(mHomeListProfileScrim, mHomeListProfileContent,
|
||||
@@ -824,12 +965,14 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
private void dismissHomeListProfilePopup(final boolean animate) {
|
||||
if (mHomeListProfilePopup == null
|
||||
|| mHomeListProfilePopup.getVisibility() != View.VISIBLE) {
|
||||
recycleListProfileBlur();
|
||||
mHomeListProfileData = null;
|
||||
return;
|
||||
}
|
||||
final Runnable finish = () -> {
|
||||
mHomeListProfilePopup.setVisibility(View.GONE);
|
||||
resetListProfilePopupPosition();
|
||||
recycleListProfileBlur();
|
||||
mHomeListProfileData = null;
|
||||
};
|
||||
if (animate) {
|
||||
@@ -865,12 +1008,15 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
if (data == null || getActivity() == null) {
|
||||
return;
|
||||
}
|
||||
if (!data.getIsGroup() && ContactUtil.openContactDetail(requireContext(),
|
||||
data.getParticipantContactId(), data.getParticipantLookupKey(),
|
||||
data.getOtherParticipantNormalizedDestination())) {
|
||||
if (data.getIsGroup()) {
|
||||
UIIntents.get().launchHomeProfileActivity(getActivity(), data.getConversationId());
|
||||
return;
|
||||
}
|
||||
UIIntents.get().launchHomeProfileActivity(getActivity(), data.getConversationId());
|
||||
if (!ContactUtil.openContactDetail(getActivity(),
|
||||
data.getParticipantContactId(), data.getParticipantLookupKey(),
|
||||
data.getOtherParticipantNormalizedDestination())) {
|
||||
UIIntents.get().launchHomeProfileActivity(getActivity(), data.getConversationId());
|
||||
}
|
||||
}
|
||||
|
||||
private void addListProfileContact() {
|
||||
@@ -932,10 +1078,37 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
if (isSelectionMode() && tab != HOME_TAB_MESSAGES) {
|
||||
return;
|
||||
}
|
||||
if (mHomeTabReady && tab == mHomeTab) {
|
||||
return;
|
||||
}
|
||||
final boolean animate = mHomeTabReady;
|
||||
mHomeTab = tab;
|
||||
dismissHomePopoverImmediate();
|
||||
dismissHomeListProfilePopup(false);
|
||||
final boolean showMessages = tab == HOME_TAB_MESSAGES;
|
||||
if (mHomeMessagesTop != null) {
|
||||
mHomeMessagesTop.setVisibility(showMessages ? View.VISIBLE : View.GONE);
|
||||
mHomeMessagesTop.setVisibility(View.VISIBLE);
|
||||
}
|
||||
final CharSequence title = getString(showMessages
|
||||
? R.string.home_title : R.string.home_automation_title);
|
||||
if (mHomeTitle != null) {
|
||||
if (animate) {
|
||||
HomeUiMotion.fadeSwapText(mHomeTitle, title);
|
||||
} else {
|
||||
mHomeTitle.setText(title);
|
||||
}
|
||||
}
|
||||
final View incoming = showMessages ? mRecyclerView : mHomeAutomationPane;
|
||||
final View outgoing = showMessages ? mHomeAutomationPane : mRecyclerView;
|
||||
if (animate) {
|
||||
HomeUiMotion.crossfadeHomePages(outgoing, incoming, !showMessages);
|
||||
} else {
|
||||
if (mRecyclerView != null) {
|
||||
mRecyclerView.setVisibility(showMessages ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
if (mHomeAutomationPane != null) {
|
||||
mHomeAutomationPane.setVisibility(showMessages ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
}
|
||||
if (!showMessages) {
|
||||
mSearchBarShown = false;
|
||||
@@ -953,10 +1126,19 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
applySearchBarVisibility();
|
||||
updateEmptyListUi(mAdapter.getConversationItemCount() == 0);
|
||||
}
|
||||
updateHomeComposeButtonVisibility();
|
||||
updateHomeNavHighlight();
|
||||
updateHomeComposeButtonVisibility(animate);
|
||||
updateHomeNavHighlight(animate);
|
||||
updateHomeMainNavVisibility();
|
||||
refreshPinnedStrip();
|
||||
mHomeTabReady = true;
|
||||
}
|
||||
|
||||
public boolean onHomeBackPressed() {
|
||||
if (mHomeTab == HOME_TAB_AUTOMATE) {
|
||||
selectHomeTab(HOME_TAB_MESSAGES);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Main tabs show nav; selection mode and other activities hide it ({@link #onPause}). */
|
||||
@@ -978,23 +1160,46 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
} else {
|
||||
mHomeBottomNav.setVisibility(isResumed() ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
updateHomeComposeButtonVisibility();
|
||||
updateHomeComposeButtonVisibility(false);
|
||||
}
|
||||
|
||||
private void updateHomeComposeButtonVisibility() {
|
||||
updateHomeComposeButtonVisibility(false);
|
||||
}
|
||||
|
||||
private void updateHomeComposeButtonVisibility(final boolean animate) {
|
||||
if (mArchiveMode || mForwardMessageMode) {
|
||||
return;
|
||||
}
|
||||
final boolean showCompose = isResumed() && mHomeTab == HOME_TAB_MESSAGES
|
||||
&& !isSelectionMode();
|
||||
final boolean showDock = isResumed() && !isSelectionMode();
|
||||
if (mHomeComposeContainer != null) {
|
||||
mHomeComposeContainer.setVisibility(showCompose ? View.VISIBLE : View.GONE);
|
||||
mHomeComposeContainer.setVisibility(showDock ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
if (mStartNewConversationButton != null && showCompose) {
|
||||
mStartNewConversationButton.animate().cancel();
|
||||
mStartNewConversationButton.setTranslationX(0f);
|
||||
mStartNewConversationButton.setVisibility(View.VISIBLE);
|
||||
mStartNewConversationButton.setEnabled(true);
|
||||
final boolean showCompose = showDock && mHomeTab == HOME_TAB_MESSAGES;
|
||||
final boolean showCreate = showDock && mHomeTab == HOME_TAB_AUTOMATE;
|
||||
final View incoming = showCreate ? mHomeAutomationCreateButton
|
||||
: (showCompose ? mStartNewConversationButton : null);
|
||||
final View outgoing = showCreate ? mStartNewConversationButton
|
||||
: (showCompose ? mHomeAutomationCreateButton : null);
|
||||
if (incoming != null) {
|
||||
incoming.setEnabled(true);
|
||||
incoming.setTranslationX(0f);
|
||||
}
|
||||
if (animate && incoming != null) {
|
||||
HomeUiMotion.fadeSwapViews(outgoing, incoming);
|
||||
} else {
|
||||
if (mStartNewConversationButton != null) {
|
||||
mStartNewConversationButton.animate().cancel();
|
||||
mStartNewConversationButton.setAlpha(1f);
|
||||
mStartNewConversationButton.setVisibility(
|
||||
showCompose ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
if (mHomeAutomationCreateButton != null) {
|
||||
mHomeAutomationCreateButton.animate().cancel();
|
||||
mHomeAutomationCreateButton.setAlpha(1f);
|
||||
mHomeAutomationCreateButton.setVisibility(
|
||||
showCreate ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1017,8 +1222,18 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
}
|
||||
|
||||
private void updateHomeNavHighlight() {
|
||||
updateHomeNavHighlight(false);
|
||||
}
|
||||
|
||||
private void updateHomeNavHighlight(final boolean animate) {
|
||||
applyHomeNavTabStyle(mHomeNavMessages, mHomeNavMessagesIcon, mHomeNavMessagesLabel,
|
||||
mHomeTab == HOME_TAB_MESSAGES);
|
||||
applyHomeNavTabStyle(mHomeNavAutomate, mHomeNavAutomateIcon, mHomeNavAutomateLabel,
|
||||
mHomeTab == HOME_TAB_AUTOMATE);
|
||||
applyHomeNavMetrics();
|
||||
if (mHomeNavIndicator != null) {
|
||||
mHomeNavIndicator.post(() -> slideHomeNavIndicator(animate));
|
||||
}
|
||||
}
|
||||
|
||||
private void applyHomeNavTabStyle(final View tab, final ImageView icon, final TextView label,
|
||||
@@ -1026,11 +1241,7 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
if (tab == null) {
|
||||
return;
|
||||
}
|
||||
if (active) {
|
||||
tab.setBackgroundResource(R.drawable.home_bg_nav_active_primary);
|
||||
} else {
|
||||
tab.setBackgroundResource(android.R.color.transparent);
|
||||
}
|
||||
tab.setBackgroundResource(android.R.color.transparent);
|
||||
final int color = ContextCompat.getColor(requireContext(),
|
||||
active ? R.color.home_nav_active_content : R.color.home_nav_inactive);
|
||||
if (icon != null) {
|
||||
@@ -1077,6 +1288,8 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
}
|
||||
|
||||
private PopupWindow mHomePopover;
|
||||
private View mHomePeekCard;
|
||||
private Bitmap mHomePeekBlur;
|
||||
|
||||
private void dismissHomePopover() {
|
||||
dismissHomePopoverAndThen(null);
|
||||
@@ -1086,6 +1299,7 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
private void dismissHomePopoverImmediate() {
|
||||
if (mHomePopover == null || !mHomePopover.isShowing()) {
|
||||
mHomePopover = null;
|
||||
recyclePeekBlur();
|
||||
return;
|
||||
}
|
||||
final View content = mHomePopover.getContentView();
|
||||
@@ -1093,6 +1307,10 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
content.animate().cancel();
|
||||
HomeUiMotion.popoverResetInstant(content);
|
||||
}
|
||||
if (mHomePeekCard != null) {
|
||||
mHomePeekCard.animate().cancel();
|
||||
HomeUiMotion.popoverResetInstant(mHomePeekCard);
|
||||
}
|
||||
final PopupWindow popup = mHomePopover;
|
||||
mHomePopover = null;
|
||||
popup.dismiss();
|
||||
@@ -1101,15 +1319,18 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
private void dismissHomePopoverAndThen(final Runnable afterDismiss) {
|
||||
if (mHomePopover == null || !mHomePopover.isShowing()) {
|
||||
mHomePopover = null;
|
||||
recyclePeekBlur();
|
||||
if (afterDismiss != null) {
|
||||
afterDismiss.run();
|
||||
}
|
||||
return;
|
||||
}
|
||||
final PopupWindow popup = mHomePopover;
|
||||
final View content = popup.getContentView();
|
||||
final View host = popup.getContentView();
|
||||
final View card = mHomePeekCard != null ? mHomePeekCard : host;
|
||||
mHomePopover = null;
|
||||
HomeUiMotion.popoverExit(content, () -> {
|
||||
fadePeekBackdrop(host, 0f, 160);
|
||||
HomeUiMotion.popoverExit(card, () -> {
|
||||
if (popup.isShowing()) {
|
||||
popup.dismiss();
|
||||
}
|
||||
@@ -1119,6 +1340,382 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
});
|
||||
}
|
||||
|
||||
private void fadePeekBackdrop(final View host, final float alpha, final int duration) {
|
||||
if (host == null) {
|
||||
return;
|
||||
}
|
||||
final View blur = host.findViewById(R.id.home_peek_blur);
|
||||
final View dim = host.findViewById(R.id.home_peek_dim);
|
||||
if (blur != null) {
|
||||
blur.animate().cancel();
|
||||
blur.animate().alpha(alpha).setDuration(duration).start();
|
||||
}
|
||||
if (dim != null) {
|
||||
dim.animate().cancel();
|
||||
dim.animate().alpha(alpha).setDuration(duration).start();
|
||||
}
|
||||
}
|
||||
|
||||
private void recyclePeekBlur() {
|
||||
mHomePeekCard = null;
|
||||
if (mHomePeekBlur != null && !mHomePeekBlur.isRecycled()) {
|
||||
mHomePeekBlur.recycle();
|
||||
}
|
||||
mHomePeekBlur = null;
|
||||
}
|
||||
|
||||
private void bindListProfileBlur() {
|
||||
recycleListProfileBlur();
|
||||
if (mHomeListProfileBlur == null) {
|
||||
return;
|
||||
}
|
||||
mHomeListProfileBlurBitmap = captureDecorSnapshot();
|
||||
if (mHomeListProfileBlurBitmap == null) {
|
||||
return;
|
||||
}
|
||||
mHomeListProfileBlur.setImageBitmap(mHomeListProfileBlurBitmap);
|
||||
mHomeListProfileBlur.setRenderEffect(RenderEffect.createBlurEffect(
|
||||
36f, 36f, Shader.TileMode.CLAMP));
|
||||
}
|
||||
|
||||
private void recycleListProfileBlur() {
|
||||
if (mHomeListProfileBlur != null) {
|
||||
mHomeListProfileBlur.setRenderEffect(null);
|
||||
mHomeListProfileBlur.setImageDrawable(null);
|
||||
}
|
||||
if (mHomeListProfileBlurBitmap != null && !mHomeListProfileBlurBitmap.isRecycled()) {
|
||||
mHomeListProfileBlurBitmap.recycle();
|
||||
}
|
||||
mHomeListProfileBlurBitmap = null;
|
||||
}
|
||||
|
||||
private void showConversationActionPopover(final ConversationListItemData data,
|
||||
final ConversationListItemView conversationView) {
|
||||
conversationView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
|
||||
dismissHomePopover();
|
||||
final View content = LayoutInflater.from(requireContext())
|
||||
.inflate(R.layout.home_popover_conversation, null, false);
|
||||
bindConversationPreviewPlaceholder(content, data);
|
||||
final boolean pinned = PinnedConversationStore.isPinned(requireContext(),
|
||||
data.getConversationId());
|
||||
final boolean muted = MutedConversationStore.isMuted(requireContext(),
|
||||
data.getConversationId());
|
||||
final boolean read = data.getIsRead();
|
||||
|
||||
final TextView pinLabel = content.findViewById(R.id.popover_conversation_pin_label);
|
||||
if (pinLabel != null) {
|
||||
pinLabel.setText(pinned
|
||||
? R.string.home_conversation_unpin
|
||||
: R.string.home_conversation_pin);
|
||||
}
|
||||
final TextView unreadLabel = content.findViewById(R.id.popover_conversation_unread_label);
|
||||
if (unreadLabel != null) {
|
||||
unreadLabel.setText(read
|
||||
? R.string.home_conversation_mark_unread
|
||||
: R.string.home_conversation_mark_read);
|
||||
}
|
||||
final TextView muteLabel = content.findViewById(R.id.popover_conversation_mute_label);
|
||||
if (muteLabel != null) {
|
||||
muteLabel.setText(muted
|
||||
? R.string.home_conversation_show_alerts
|
||||
: R.string.home_conversation_hide_alerts);
|
||||
}
|
||||
final ImageView muteIcon = content.findViewById(R.id.popover_conversation_mute_icon);
|
||||
if (muteIcon != null) {
|
||||
muteIcon.setImageResource(muted
|
||||
? R.drawable.ic_home_bell
|
||||
: R.drawable.ic_home_bell_off);
|
||||
}
|
||||
|
||||
content.findViewById(R.id.popover_conversation_pin).setOnClickListener(v -> {
|
||||
dismissHomePopover();
|
||||
onConversationPinButtonClicked(data.getConversationId());
|
||||
if (PinnedConversationStore.isPinned(requireContext(), data.getConversationId())
|
||||
!= pinned) {
|
||||
HomeToast.show(requireContext(), pinned
|
||||
? R.string.home_conversation_unpinned_toast
|
||||
: R.string.home_conversation_pinned_toast);
|
||||
}
|
||||
});
|
||||
content.findViewById(R.id.popover_conversation_unread).setOnClickListener(v -> {
|
||||
dismissHomePopover();
|
||||
if (read) {
|
||||
MarkAsUnreadAction.markAsUnread(data.getConversationId());
|
||||
HomeToast.show(requireContext(), R.string.home_conversation_marked_unread_toast);
|
||||
} else {
|
||||
MarkAsReadAction.markAsRead(data.getConversationId());
|
||||
HomeToast.show(requireContext(), R.string.home_conversation_marked_read_toast);
|
||||
}
|
||||
});
|
||||
content.findViewById(R.id.popover_conversation_mute).setOnClickListener(v -> {
|
||||
dismissHomePopover();
|
||||
toggleConversationMute(data);
|
||||
});
|
||||
content.findViewById(R.id.popover_conversation_delete).setOnClickListener(v -> {
|
||||
dismissHomePopover();
|
||||
confirmDeleteConversation(data);
|
||||
});
|
||||
final PopupWindow popup = showHomePeekPopover(conversationView, content);
|
||||
loadConversationPreview(data.getConversationId(), conversationView, content, popup);
|
||||
}
|
||||
|
||||
private void toggleConversationMute(final ConversationListItemData data) {
|
||||
final boolean muted = MutedConversationStore.toggle(requireContext(),
|
||||
data.getConversationId());
|
||||
HomeToast.show(requireContext(), muted
|
||||
? R.string.home_conversation_muted_toast
|
||||
: R.string.home_conversation_unmuted_toast);
|
||||
}
|
||||
|
||||
private void confirmDeleteConversation(final ConversationListItemData data) {
|
||||
if (getActivity() instanceof AbstractConversationListActivity) {
|
||||
((AbstractConversationListActivity) getActivity()).confirmDeleteConversation(data);
|
||||
}
|
||||
}
|
||||
|
||||
private static final int PREVIEW_MESSAGE_LIMIT = 3;
|
||||
|
||||
private static final class PreviewMessage {
|
||||
final String text;
|
||||
final boolean incoming;
|
||||
final long timestamp;
|
||||
|
||||
PreviewMessage(final String text, final boolean incoming, final long timestamp) {
|
||||
this.text = text;
|
||||
this.incoming = incoming;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
private void bindConversationPreviewPlaceholder(final View content,
|
||||
final ConversationListItemData data) {
|
||||
final String snippet = formatPreviewText(data.getSnippetText());
|
||||
if (TextUtils.isEmpty(snippet)) {
|
||||
return;
|
||||
}
|
||||
final List<PreviewMessage> placeholder = new ArrayList<>(1);
|
||||
placeholder.add(new PreviewMessage(snippet, true, 0L));
|
||||
bindConversationPreview(content, placeholder);
|
||||
}
|
||||
|
||||
private void loadConversationPreview(final String conversationId, final View anchor,
|
||||
final View content, final PopupWindow popup) {
|
||||
final Context context = getContext();
|
||||
if (context == null) {
|
||||
return;
|
||||
}
|
||||
final Context appContext = context.getApplicationContext();
|
||||
final String attachmentLabel = context.getString(
|
||||
R.string.home_conversation_preview_attachment);
|
||||
new Thread(() -> {
|
||||
final List<PreviewMessage> messages = queryPreviewMessages(
|
||||
appContext, conversationId, attachmentLabel);
|
||||
ThreadUtil.getMainThreadHandler().post(() -> {
|
||||
if (!isAdded() || mHomePopover != popup || !popup.isShowing()) {
|
||||
return;
|
||||
}
|
||||
if (!messages.isEmpty()) {
|
||||
bindConversationPreview(content, messages);
|
||||
positionPeekCard(popup.getContentView(), anchor, content);
|
||||
}
|
||||
});
|
||||
}, "home-conv-preview").start();
|
||||
}
|
||||
|
||||
private List<PreviewMessage> queryPreviewMessages(final Context context,
|
||||
final String conversationId, final String attachmentLabel) {
|
||||
final List<PreviewMessage> out = new ArrayList<>();
|
||||
final Uri uri = MessagingContentProvider.buildConversationMessagesUri(conversationId);
|
||||
try (Cursor cursor = context.getContentResolver().query(
|
||||
uri, ConversationMessageData.getProjection(), null, null, null)) {
|
||||
if (cursor == null || !cursor.moveToLast()) {
|
||||
return out;
|
||||
}
|
||||
final ConversationMessageData message = new ConversationMessageData();
|
||||
int remaining = PREVIEW_MESSAGE_LIMIT;
|
||||
do {
|
||||
message.bind(cursor);
|
||||
String text = formatPreviewText(message.getRawText());
|
||||
if (TextUtils.isEmpty(text)) {
|
||||
text = attachmentLabel;
|
||||
}
|
||||
out.add(0, new PreviewMessage(text, message.getIsIncoming(),
|
||||
message.getReceivedTimeStamp()));
|
||||
remaining--;
|
||||
} while (remaining > 0 && cursor.moveToPrevious());
|
||||
} catch (final Exception ignored) {
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private void bindConversationPreview(final View content,
|
||||
final List<PreviewMessage> messages) {
|
||||
final ViewGroup host = content.findViewById(R.id.popover_preview_messages);
|
||||
if (host == null) {
|
||||
return;
|
||||
}
|
||||
host.removeAllViews();
|
||||
final LayoutInflater inflater = LayoutInflater.from(content.getContext());
|
||||
boolean timeShown = false;
|
||||
for (final PreviewMessage message : messages) {
|
||||
final View row = inflater.inflate(R.layout.home_popover_preview_bubble, host, false);
|
||||
final TextView time = row.findViewById(R.id.home_preview_bubble_time);
|
||||
final TextView bubble = row.findViewById(R.id.home_preview_bubble_text);
|
||||
if (!timeShown && message.timestamp > 0L) {
|
||||
time.setVisibility(View.VISIBLE);
|
||||
time.setText(Dates.getMessageTimeString(message.timestamp));
|
||||
timeShown = true;
|
||||
}
|
||||
final float density = content.getResources().getDisplayMetrics().density;
|
||||
final int screenW = content.getResources().getDisplayMetrics().widthPixels;
|
||||
final int cardW = Math.max(0, screenW - Math.round(32f * density));
|
||||
bubble.setMaxWidth(Math.max(0, cardW - Math.round(40f * density)));
|
||||
bubble.setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE);
|
||||
bubble.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE);
|
||||
bindPreviewBubbleText(bubble, message.text);
|
||||
if (message.incoming) {
|
||||
bubble.setBackgroundResource(R.drawable.home_bg_preview_bubble_in);
|
||||
bubble.setTextColor(content.getContext().getColor(R.color.home_text_primary));
|
||||
((LinearLayout.LayoutParams) bubble.getLayoutParams()).gravity = Gravity.START;
|
||||
} else {
|
||||
bubble.setBackgroundResource(R.drawable.home_bg_preview_bubble_out);
|
||||
bubble.setTextColor(content.getContext().getColor(R.color.home_text_primary));
|
||||
((LinearLayout.LayoutParams) bubble.getLayoutParams()).gravity = Gravity.END;
|
||||
}
|
||||
host.addView(row);
|
||||
}
|
||||
}
|
||||
|
||||
private static String formatPreviewText(final String text) {
|
||||
return ConversationMessageData.flattenPreviewText(text);
|
||||
}
|
||||
|
||||
private static void bindPreviewBubbleText(final TextView bubble, final String raw) {
|
||||
final String text = formatPreviewText(raw);
|
||||
bubble.setText(text == null ? "" : text);
|
||||
}
|
||||
|
||||
private PopupWindow showHomePeekPopover(final View anchor, final View content) {
|
||||
mHomePeekCard = content;
|
||||
final ViewGroup host = (ViewGroup) LayoutInflater.from(requireContext())
|
||||
.inflate(R.layout.home_popover_peek_host, null, false);
|
||||
final ImageView blur = host.findViewById(R.id.home_peek_blur);
|
||||
final View dim = host.findViewById(R.id.home_peek_dim);
|
||||
mHomePeekBlur = captureDecorSnapshot();
|
||||
if (mHomePeekBlur != null && blur != null) {
|
||||
blur.setImageBitmap(mHomePeekBlur);
|
||||
blur.setRenderEffect(RenderEffect.createBlurEffect(
|
||||
36f, 36f, Shader.TileMode.CLAMP));
|
||||
blur.setAlpha(0f);
|
||||
}
|
||||
if (dim != null) {
|
||||
dim.setAlpha(0f);
|
||||
dim.setOnClickListener(v -> dismissHomePopover());
|
||||
}
|
||||
host.addView(content, new FrameLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
final PopupWindow popup = new PopupWindow(host,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
true);
|
||||
popup.setOutsideTouchable(true);
|
||||
popup.setFocusable(true);
|
||||
popup.setClippingEnabled(false);
|
||||
popup.setBackgroundDrawable(new android.graphics.drawable.ColorDrawable(
|
||||
android.graphics.Color.TRANSPARENT));
|
||||
popup.setOnDismissListener(() -> {
|
||||
if (mHomePopover == popup) {
|
||||
mHomePopover = null;
|
||||
}
|
||||
if (blur != null) {
|
||||
blur.setRenderEffect(null);
|
||||
blur.setImageDrawable(null);
|
||||
}
|
||||
recyclePeekBlur();
|
||||
});
|
||||
popup.showAtLocation(anchor, Gravity.NO_GRAVITY, 0, 0);
|
||||
host.post(() -> {
|
||||
if (mHomePopover != popup || !popup.isShowing()) {
|
||||
return;
|
||||
}
|
||||
positionPeekCard(host, anchor, content);
|
||||
HomeUiMotion.popoverEnter(content, false);
|
||||
fadePeekBackdrop(host, 1f, 180);
|
||||
});
|
||||
mHomePopover = popup;
|
||||
return popup;
|
||||
}
|
||||
|
||||
private Bitmap captureDecorSnapshot() {
|
||||
final Activity activity = getActivity();
|
||||
if (activity == null) {
|
||||
return null;
|
||||
}
|
||||
final View root = activity.getWindow().getDecorView();
|
||||
if (root.getWidth() <= 0 || root.getHeight() <= 0) {
|
||||
return null;
|
||||
}
|
||||
final Bitmap bitmap = Bitmap.createBitmap(
|
||||
root.getWidth(), root.getHeight(), Bitmap.Config.ARGB_8888);
|
||||
final Canvas canvas = new Canvas(bitmap);
|
||||
root.draw(canvas);
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
private void positionPeekCard(final View host, final View anchor, final View content) {
|
||||
if (host == null || anchor == null || content == null) {
|
||||
return;
|
||||
}
|
||||
final int[] hostLoc = new int[2];
|
||||
host.getLocationOnScreen(hostLoc);
|
||||
final int[] anchorLoc = new int[2];
|
||||
anchor.getLocationOnScreen(anchorLoc);
|
||||
final int hostWidth = host.getWidth() > 0 ? host.getWidth()
|
||||
: getResources().getDisplayMetrics().widthPixels;
|
||||
final int hostHeight = host.getHeight() > 0 ? host.getHeight()
|
||||
: getResources().getDisplayMetrics().heightPixels;
|
||||
final float density = getResources().getDisplayMetrics().density;
|
||||
final int margin = Math.round(16f * density);
|
||||
final int width = Math.max(0, hostWidth - margin * 2);
|
||||
final ViewGroup.LayoutParams lp = content.getLayoutParams();
|
||||
if (lp != null) {
|
||||
lp.width = width;
|
||||
content.setLayoutParams(lp);
|
||||
}
|
||||
final int maxHeight = Math.max(0, hostHeight - margin * 2);
|
||||
content.measure(
|
||||
View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
|
||||
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
|
||||
final int naturalHeight = content.getMeasuredHeight();
|
||||
final boolean needsScroll = naturalHeight > maxHeight;
|
||||
final int height = needsScroll ? maxHeight : naturalHeight;
|
||||
if (lp != null) {
|
||||
lp.height = needsScroll ? maxHeight : ViewGroup.LayoutParams.WRAP_CONTENT;
|
||||
content.setLayoutParams(lp);
|
||||
}
|
||||
content.measure(
|
||||
View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
|
||||
View.MeasureSpec.makeMeasureSpec(height,
|
||||
needsScroll ? View.MeasureSpec.EXACTLY : View.MeasureSpec.UNSPECIFIED));
|
||||
final int x = Math.max(0, (hostWidth - width) / 2);
|
||||
final int yBelow = anchorLoc[1] + anchor.getHeight() + 8 - hostLoc[1];
|
||||
final int yAbove = anchorLoc[1] - height - 8 - hostLoc[1];
|
||||
final boolean fitsBelow = yBelow + height <= hostHeight - margin;
|
||||
final boolean fitsAbove = yAbove >= margin;
|
||||
final int y;
|
||||
if (needsScroll) {
|
||||
y = margin;
|
||||
} else if (fitsBelow) {
|
||||
y = yBelow;
|
||||
} else if (fitsAbove) {
|
||||
y = yAbove;
|
||||
} else {
|
||||
y = Math.max(margin, (hostHeight - height) / 2);
|
||||
}
|
||||
content.setX(x);
|
||||
content.setY(y);
|
||||
}
|
||||
|
||||
private PopupWindow showHomePopover(final View anchor, final int layoutResId,
|
||||
final boolean alignEnd) {
|
||||
dismissHomePopover();
|
||||
@@ -1141,9 +1738,25 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
mHomePopover = null;
|
||||
}
|
||||
});
|
||||
final int xOff = alignEnd
|
||||
? Math.max(0, anchor.getWidth() - content.getMeasuredWidth())
|
||||
: 0;
|
||||
final int xOff;
|
||||
if (alignEnd) {
|
||||
final int endInset = getResources().getDimensionPixelSize(
|
||||
R.dimen.looply_screen_padding);
|
||||
final int[] anchorLoc = new int[2];
|
||||
anchor.getLocationOnScreen(anchorLoc);
|
||||
final View root = getView();
|
||||
final int windowRight;
|
||||
if (root != null) {
|
||||
final int[] rootLoc = new int[2];
|
||||
root.getLocationOnScreen(rootLoc);
|
||||
windowRight = rootLoc[0] + root.getWidth();
|
||||
} else {
|
||||
windowRight = getResources().getDisplayMetrics().widthPixels;
|
||||
}
|
||||
xOff = windowRight - endInset - content.getMeasuredWidth() - anchorLoc[0];
|
||||
} else {
|
||||
xOff = 0;
|
||||
}
|
||||
popup.showAsDropDown(anchor, xOff, 8);
|
||||
HomeUiMotion.popoverEnter(content, alignEnd);
|
||||
mHomePopover = popup;
|
||||
@@ -1172,8 +1785,7 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
});
|
||||
content.findViewById(R.id.popover_work).setOnClickListener(v -> {
|
||||
dismissHomePopover();
|
||||
Toast.makeText(requireContext(), R.string.home_popover_coming_soon,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
HomeToast.show(requireContext(), R.string.home_popover_coming_soon);
|
||||
});
|
||||
content.findViewById(R.id.popover_personal).setOnClickListener(v -> {
|
||||
selectHomeChip(ConversationListAdapter.FILTER_ALL);
|
||||
@@ -1316,11 +1928,25 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
@Override
|
||||
public void onConversationClicked(final ConversationListItemData conversationListItemData,
|
||||
final boolean isLongClick, final ConversationListItemView conversationView) {
|
||||
if (isLongClick && !mHost.isSelectionMode()) {
|
||||
showConversationActionPopover(conversationListItemData, conversationView);
|
||||
return;
|
||||
}
|
||||
final ConversationListData listData = mListBinding.getData();
|
||||
mHost.onConversationClick(listData, conversationListItemData, isLongClick,
|
||||
conversationView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConversationSwipeMuteClicked(final ConversationListItemData data) {
|
||||
toggleConversationMute(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConversationSwipeDeleteClicked(final ConversationListItemData data) {
|
||||
confirmDeleteConversation(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConversationAvatarClicked(final ConversationListItemData data,
|
||||
final View avatarView) {
|
||||
@@ -1371,19 +1997,25 @@ public class ConversationListFragment extends Fragment implements ConversationLi
|
||||
|
||||
// Show and hide empty list UI as needed with appropriate text based on view specifics
|
||||
private void updateEmptyListUi(final boolean isEmpty) {
|
||||
if (mEmptyListMessageView == null) {
|
||||
return;
|
||||
}
|
||||
if (isEmpty) {
|
||||
int emptyListText;
|
||||
if (!mListBinding.getData().getHasFirstSyncCompleted()) {
|
||||
emptyListText = R.string.conversation_list_first_sync_text;
|
||||
mEmptyListMessageView.setContent(R.drawable.ic_home_nav_messages,
|
||||
R.string.home_empty_messages_sync_title,
|
||||
R.string.home_empty_messages_sync_body);
|
||||
} else if (mArchiveMode) {
|
||||
emptyListText = R.string.archived_conversation_list_empty_text;
|
||||
mEmptyListMessageView.setContent(R.drawable.ic_home_menu_archive,
|
||||
R.string.home_empty_archive_title,
|
||||
R.string.home_empty_archive_body);
|
||||
} else {
|
||||
emptyListText = R.string.conversation_list_empty_text;
|
||||
mEmptyListMessageView.setContent(R.drawable.ic_home_nav_messages,
|
||||
R.string.home_empty_messages_title,
|
||||
R.string.home_empty_messages_body);
|
||||
}
|
||||
mEmptyListMessageView.setTextHint(emptyListText);
|
||||
mEmptyListMessageView.setReserveDock(!mArchiveMode && !mForwardMessageMode);
|
||||
mEmptyListMessageView.setVisibility(View.VISIBLE);
|
||||
mEmptyListMessageView.setIsImageVisible(true);
|
||||
mEmptyListMessageView.setIsVerticallyCentered(true);
|
||||
} else {
|
||||
mEmptyListMessageView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
+221
-43
@@ -22,12 +22,17 @@ import android.database.Cursor;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.Typeface;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.support.v7.mms.pdu.ContentType;
|
||||
import android.text.SpannableString;
|
||||
import android.text.Spanned;
|
||||
import android.text.StaticLayout;
|
||||
import android.text.TextPaint;
|
||||
import android.text.TextUtils;
|
||||
import android.text.style.LeadingMarginSpan;
|
||||
import android.telephony.PhoneNumberUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.TypedValue;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.View.OnLayoutChangeListener;
|
||||
@@ -39,7 +44,6 @@ import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.core.content.res.ResourcesCompat;
|
||||
import androidx.core.text.BidiFormatter;
|
||||
import androidx.core.text.TextDirectionHeuristicsCompat;
|
||||
|
||||
@@ -48,6 +52,7 @@ import com.android.messaging.R;
|
||||
import com.android.messaging.datamodel.MessagingContentProvider;
|
||||
import com.android.messaging.datamodel.action.UpdateConversationArchiveStatusAction;
|
||||
import com.android.messaging.datamodel.data.ConversationListItemData;
|
||||
import com.android.messaging.datamodel.data.ConversationMessageData;
|
||||
import com.android.messaging.datamodel.data.MessageData;
|
||||
import com.android.messaging.datamodel.media.UriImageRequestDescriptor;
|
||||
import com.android.messaging.sms.MmsUtils;
|
||||
@@ -59,6 +64,7 @@ import com.android.messaging.ui.SnackBarInteraction;
|
||||
import com.android.messaging.util.Assert;
|
||||
import com.android.messaging.util.ImageUtils;
|
||||
import com.android.messaging.util.PhoneUtils;
|
||||
import com.android.messaging.util.SimNameUtil;
|
||||
import com.android.messaging.util.Typefaces;
|
||||
import com.android.messaging.util.UiUtils;
|
||||
import com.android.messaging.util.UriUtil;
|
||||
@@ -72,8 +78,9 @@ import java.util.List;
|
||||
*/
|
||||
public class ConversationListItemView extends FrameLayout implements OnClickListener,
|
||||
OnLongClickListener, OnLayoutChangeListener {
|
||||
static final int UNREAD_SNIPPET_LINE_COUNT = 2;
|
||||
static final int NO_UNREAD_SNIPPET_LINE_COUNT = 2;
|
||||
static final int UNREAD_SNIPPET_LINE_COUNT = 3;
|
||||
static final int NO_UNREAD_SNIPPET_LINE_COUNT = 3;
|
||||
private static final int SNIPPET_ELLIPSIS_RESERVE_CHARS = 5;
|
||||
private static final int LIST_DIVIDER_INSET_DEFAULT_DP = 74;
|
||||
private static final int LIST_DIVIDER_INSET_SELECTION_DP = 104;
|
||||
private int mListItemReadColor;
|
||||
@@ -98,6 +105,8 @@ public class ConversationListItemView extends FrameLayout implements OnClickList
|
||||
boolean shouldShowConversationPinButton();
|
||||
boolean isConversationPinned(final String conversationId);
|
||||
void onConversationPinButtonClicked(final String conversationId);
|
||||
void onConversationSwipeMuteClicked(ConversationListItemData data);
|
||||
void onConversationSwipeDeleteClicked(ConversationListItemData data);
|
||||
}
|
||||
|
||||
private final OnClickListener fullScreenPreviewClickListener = new OnClickListener() {
|
||||
@@ -145,7 +154,9 @@ public class ConversationListItemView extends FrameLayout implements OnClickList
|
||||
private ImageView mContactCheckmarkView;
|
||||
private ImageView mFailedStatusIconView;
|
||||
private ImageView mCrossSwipeArchiveLeftImageView;
|
||||
private ImageView mCrossSwipeArchiveRightImageView;
|
||||
private View mCrossSwipeRightActions;
|
||||
private ImageView mCrossSwipeMuteView;
|
||||
private ImageView mCrossSwipeDeleteView;
|
||||
private AsyncImageView mImagePreviewView;
|
||||
private AudioAttachmentView mAudioAttachmentView;
|
||||
private ImageView mChevronView;
|
||||
@@ -184,7 +195,25 @@ public class ConversationListItemView extends FrameLayout implements OnClickList
|
||||
mContactCheckmarkView = findViewById(R.id.conversation_checkmark);
|
||||
mFailedStatusIconView = findViewById(R.id.conversation_failed_status_icon);
|
||||
mCrossSwipeArchiveLeftImageView = findViewById(R.id.crossSwipeArchiveIconLeft);
|
||||
mCrossSwipeArchiveRightImageView = findViewById(R.id.crossSwipeArchiveIconRight);
|
||||
mCrossSwipeRightActions = findViewById(R.id.cross_swipe_right_actions);
|
||||
mCrossSwipeMuteView = findViewById(R.id.cross_swipe_mute);
|
||||
mCrossSwipeDeleteView = findViewById(R.id.cross_swipe_delete);
|
||||
if (mCrossSwipeMuteView != null) {
|
||||
mCrossSwipeMuteView.setOnClickListener(v -> {
|
||||
if (mHostInterface != null) {
|
||||
mHostInterface.onConversationSwipeMuteClicked(mData);
|
||||
}
|
||||
setSwipeTranslationX(0);
|
||||
});
|
||||
}
|
||||
if (mCrossSwipeDeleteView != null) {
|
||||
mCrossSwipeDeleteView.setOnClickListener(v -> {
|
||||
if (mHostInterface != null) {
|
||||
mHostInterface.onConversationSwipeDeleteClicked(mData);
|
||||
}
|
||||
setSwipeTranslationX(0);
|
||||
});
|
||||
}
|
||||
mImagePreviewView = findViewById(R.id.conversation_image_preview);
|
||||
mAudioAttachmentView = findViewById(R.id.audio_attachment_view);
|
||||
mChevronView = findViewById(R.id.conversation_chevron);
|
||||
@@ -219,12 +248,20 @@ public class ConversationListItemView extends FrameLayout implements OnClickList
|
||||
|
||||
private void setWorkProfileIcon() {
|
||||
mWorkProfileIconView.setVisibility(mData.isEnterprise() ? View.VISIBLE : View.GONE);
|
||||
if (mCategoryChipView != null) {
|
||||
mCategoryChipView.setVisibility(View.VISIBLE);
|
||||
mCategoryChipView.setText(mData.isEnterprise()
|
||||
? R.string.home_chip_work
|
||||
: R.string.home_chip_personal);
|
||||
setSimBadge();
|
||||
}
|
||||
|
||||
private void setSimBadge() {
|
||||
if (mCategoryChipView == null) {
|
||||
return;
|
||||
}
|
||||
if (!SimNameUtil.shouldShowSimName(getContext())) {
|
||||
mCategoryChipView.setText(null);
|
||||
mCategoryChipView.setVisibility(GONE);
|
||||
return;
|
||||
}
|
||||
mCategoryChipView.setText(SimNameUtil.getSimDisplayName(getContext(), mData.getSelfId()));
|
||||
mCategoryChipView.setVisibility(VISIBLE);
|
||||
}
|
||||
|
||||
private void setConversationName() {
|
||||
@@ -244,6 +281,8 @@ public class ConversationListItemView extends FrameLayout implements OnClickList
|
||||
}
|
||||
|
||||
final String conversationName = mData.getName();
|
||||
mConversationNameView.setTextSize(TypedValue.COMPLEX_UNIT_SP,
|
||||
looksLikePhoneNumberName(conversationName) ? 18 : 17);
|
||||
|
||||
// For group conversations, ellipsize the group members that do not fit
|
||||
final CharSequence ellipsizedName = UiUtils.commaEllipsize(
|
||||
@@ -261,6 +300,23 @@ public class ConversationListItemView extends FrameLayout implements OnClickList
|
||||
mConversationNameView.setText(bidiFormattedName);
|
||||
}
|
||||
|
||||
private static boolean looksLikePhoneNumberName(final String name) {
|
||||
if (TextUtils.isEmpty(name)) {
|
||||
return false;
|
||||
}
|
||||
final String digits = PhoneNumberUtils.normalizeNumber(name);
|
||||
if (digits.length() < 6) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < digits.length(); i++) {
|
||||
final char c = digits.charAt(i);
|
||||
if (c != '+' && !Character.isDigit(c)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static String getPlusOneString() {
|
||||
if (sPlusOneString == null) {
|
||||
sPlusOneString = Factory.get().getApplicationContext().getResources()
|
||||
@@ -278,20 +334,89 @@ public class ConversationListItemView extends FrameLayout implements OnClickList
|
||||
}
|
||||
|
||||
private void setSubject() {
|
||||
final String subjectText = mData.getShowDraft() ?
|
||||
mData.getDraftSubject() :
|
||||
MmsUtils.cleanseMmsSubject(getContext().getResources(), mData.getSubject());
|
||||
if (!TextUtils.isEmpty(subjectText)) {
|
||||
final String subjectPrepend = getResources().getString(R.string.subject_label);
|
||||
mSubjectTextView.setText(TextUtils.concat(subjectPrepend, subjectText));
|
||||
mSubjectTextView.setVisibility(VISIBLE);
|
||||
} else {
|
||||
if (mSubjectTextView != null) {
|
||||
mSubjectTextView.setVisibility(GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void setSnippet() {
|
||||
mSnippetTextView.setText(getSnippetText());
|
||||
final String snippetText = ellipsizeSnippetForMaxLines(getSnippetText());
|
||||
final int firstLineMargin = getSnippetLeadingMargin();
|
||||
final CharSequence toSet;
|
||||
if (firstLineMargin > 0 && !TextUtils.isEmpty(snippetText)) {
|
||||
final SpannableString spanned = new SpannableString(snippetText);
|
||||
spanned.setSpan(new LeadingMarginSpan.Standard(firstLineMargin, 0),
|
||||
0, spanned.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
toSet = spanned;
|
||||
} else {
|
||||
toSet = snippetText;
|
||||
}
|
||||
if (TextUtils.equals(toSet, mSnippetTextView.getText())) {
|
||||
return;
|
||||
}
|
||||
mSnippetTextView.setText(toSet);
|
||||
}
|
||||
|
||||
private String ellipsizeSnippetForMaxLines(final String source) {
|
||||
if (TextUtils.isEmpty(source)) {
|
||||
return source;
|
||||
}
|
||||
final int maxLines = mSnippetTextView.getMaxLines();
|
||||
final int width = mSnippetTextView.getWidth()
|
||||
- mSnippetTextView.getPaddingLeft() - mSnippetTextView.getPaddingRight();
|
||||
if (maxLines <= 0 || width <= 0) {
|
||||
return source;
|
||||
}
|
||||
final int firstLineMargin = getSnippetLeadingMargin();
|
||||
final CharSequence measured;
|
||||
if (firstLineMargin > 0) {
|
||||
final SpannableString spanned = new SpannableString(source);
|
||||
spanned.setSpan(new LeadingMarginSpan.Standard(firstLineMargin, 0),
|
||||
0, spanned.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
measured = spanned;
|
||||
} else {
|
||||
measured = source;
|
||||
}
|
||||
final StaticLayout layout = StaticLayout.Builder
|
||||
.obtain(measured, 0, measured.length(), mSnippetTextView.getPaint(), width)
|
||||
.setMaxLines(maxLines + 1)
|
||||
.setEllipsize(null)
|
||||
.setIncludePad(false)
|
||||
.setLineSpacing(mSnippetTextView.getLineSpacingExtra(),
|
||||
mSnippetTextView.getLineSpacingMultiplier())
|
||||
.build();
|
||||
if (layout.getLineCount() <= maxLines) {
|
||||
return source;
|
||||
}
|
||||
final int last = maxLines - 1;
|
||||
final int lineStart = layout.getLineStart(last);
|
||||
int cut = layout.getLineEnd(last);
|
||||
while (cut > lineStart && Character.isWhitespace(source.charAt(cut - 1))) {
|
||||
cut--;
|
||||
}
|
||||
int reserved = 0;
|
||||
while (cut > lineStart && reserved < SNIPPET_ELLIPSIS_RESERVE_CHARS) {
|
||||
cut -= Character.charCount(Character.codePointBefore(source, cut));
|
||||
reserved++;
|
||||
}
|
||||
while (cut > lineStart && Character.isWhitespace(source.charAt(cut - 1))) {
|
||||
cut--;
|
||||
}
|
||||
if (cut <= 0) {
|
||||
return "...";
|
||||
}
|
||||
return source.substring(0, cut) + "...";
|
||||
}
|
||||
|
||||
private int getSnippetLeadingMargin() {
|
||||
if (mCategoryChipView == null || mCategoryChipView.getVisibility() != VISIBLE) {
|
||||
return 0;
|
||||
}
|
||||
mCategoryChipView.measure(
|
||||
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
|
||||
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
|
||||
return mCategoryChipView.getMeasuredWidth()
|
||||
+ Math.round(6f * getResources().getDisplayMetrics().density);
|
||||
}
|
||||
|
||||
private void updateSelectionIndicator(final boolean selectionMode, final boolean isSelected,
|
||||
@@ -562,16 +687,17 @@ public class ConversationListItemView extends FrameLayout implements OnClickList
|
||||
color = getResources().getColor(R.color.home_text_snippet, getContext().getTheme());
|
||||
|
||||
mSnippetTextView.setMaxLines(maxLines);
|
||||
mSnippetTextView.setEllipsize(TextUtils.TruncateAt.END);
|
||||
mSnippetTextView.setTextColor(color);
|
||||
mSnippetTextView.setTypeface(typeface, typefaceStyle);
|
||||
mSubjectTextView.setTextColor(color);
|
||||
mSubjectTextView.setTypeface(typeface, typefaceStyle);
|
||||
|
||||
mSnippetTextView.setVisibility(VISIBLE);
|
||||
setWorkProfileIcon();
|
||||
setSnippet();
|
||||
setSubject();
|
||||
setConversationName();
|
||||
setWorkProfileIcon();
|
||||
setContentDescription(buildContentDescription(resources, mData,
|
||||
mConversationNameView.getPaint()));
|
||||
|
||||
@@ -599,11 +725,13 @@ public class ConversationListItemView extends FrameLayout implements OnClickList
|
||||
mTimestampTextView.setText(resources.getString(
|
||||
R.string.conversation_list_item_view_draft_message));
|
||||
} else {
|
||||
final int timestampColor = (mData.getIsRead() || mData.getShowDraft())
|
||||
? resources.getColor(R.color.home_text_secondary, getContext().getTheme())
|
||||
: color;
|
||||
mTimestampTextView.setTextColor(timestampColor);
|
||||
mTimestampTextView.setTypeface(mListItemReadTypeface, typefaceStyle);
|
||||
final boolean unread = !mData.getIsRead();
|
||||
mTimestampTextView.setTextColor(unread
|
||||
? color
|
||||
: resources.getColor(R.color.home_text_secondary, getContext().getTheme()));
|
||||
mTimestampTextView.setTypeface(
|
||||
unread ? mListItemUnreadTypeface : mListItemReadTypeface,
|
||||
unread ? Typeface.BOLD : typefaceStyle);
|
||||
final String formattedTimestamp = mData.getFormattedTimestamp();
|
||||
if (mData.getIsSendRequested()) {
|
||||
mTimestampTextView.setText(R.string.message_status_sending);
|
||||
@@ -688,15 +816,11 @@ public class ConversationListItemView extends FrameLayout implements OnClickList
|
||||
mAudioAttachmentView.setOnLongClickListener(this);
|
||||
mAudioAttachmentView.setVisibility(audioPreviewVisiblity);
|
||||
|
||||
Drawable archiveDrawable = ResourcesCompat.getDrawable(
|
||||
getResources(), R.drawable.ic_archive_small_dark, getContext().getTheme());
|
||||
if (PrefsUtils.isSwipeRightToDeleteEnabled()) {
|
||||
mCrossSwipeArchiveLeftImageView.setImageDrawable(ResourcesCompat.getDrawable(
|
||||
getResources(), R.drawable.ic_delete_small_dark, getContext().getTheme()));
|
||||
mCrossSwipeArchiveRightImageView.setImageDrawable(archiveDrawable);
|
||||
} else {
|
||||
mCrossSwipeArchiveLeftImageView.setImageDrawable(archiveDrawable);
|
||||
mCrossSwipeArchiveRightImageView.setImageDrawable(archiveDrawable);
|
||||
if (mCrossSwipeArchiveLeftImageView != null) {
|
||||
mCrossSwipeArchiveLeftImageView.setImageResource(
|
||||
PrefsUtils.isSwipeRightToDeleteEnabled()
|
||||
? R.drawable.ic_delete_small_dark
|
||||
: R.drawable.ic_archive_small_dark);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -731,24 +855,72 @@ public class ConversationListItemView extends FrameLayout implements OnClickList
|
||||
return mSwipeableContainer.getTranslationX();
|
||||
}
|
||||
|
||||
public float getSwipeActionsRevealWidth() {
|
||||
if (mCrossSwipeRightActions == null) {
|
||||
return 0f;
|
||||
}
|
||||
if (mCrossSwipeRightActions.getWidth() <= 0) {
|
||||
mCrossSwipeRightActions.measure(
|
||||
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
|
||||
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
|
||||
return mCrossSwipeRightActions.getMeasuredWidth()
|
||||
+ getResources().getDimensionPixelSize(R.dimen.looply_space_lg);
|
||||
}
|
||||
return mCrossSwipeRightActions.getWidth()
|
||||
+ getResources().getDimensionPixelSize(R.dimen.looply_space_lg);
|
||||
}
|
||||
|
||||
public boolean isSwipeActionsOpen() {
|
||||
final float reveal = getSwipeActionsRevealWidth();
|
||||
return reveal > 0 && getSwipeTranslationX() <= -reveal * 0.85f;
|
||||
}
|
||||
|
||||
public boolean isTouchOnSwipeActions(final float parentX, final float parentY) {
|
||||
if (!isSwipeActionsOpen() || mCrossSwipeRightActions == null
|
||||
|| mCrossSwipeRightActions.getVisibility() != VISIBLE) {
|
||||
return false;
|
||||
}
|
||||
final int[] parentLoc = new int[2];
|
||||
getLocationOnScreen(parentLoc);
|
||||
final int[] actionLoc = new int[2];
|
||||
mCrossSwipeRightActions.getLocationOnScreen(actionLoc);
|
||||
final float screenX = parentLoc[0] + parentX;
|
||||
final float screenY = parentLoc[1] + parentY;
|
||||
return screenX >= actionLoc[0]
|
||||
&& screenX <= actionLoc[0] + mCrossSwipeRightActions.getWidth()
|
||||
&& screenY >= actionLoc[1]
|
||||
&& screenY <= actionLoc[1] + mCrossSwipeRightActions.getHeight();
|
||||
}
|
||||
|
||||
public void setSwipeTranslationX(final float translationX) {
|
||||
mSwipeableContainer.setTranslationX(translationX);
|
||||
if (translationX == 0) {
|
||||
mCrossSwipeBackground.setVisibility(View.GONE);
|
||||
mCrossSwipeArchiveLeftImageView.setVisibility(GONE);
|
||||
mCrossSwipeArchiveRightImageView.setVisibility(GONE);
|
||||
|
||||
if (mCrossSwipeArchiveLeftImageView != null) {
|
||||
mCrossSwipeArchiveLeftImageView.setVisibility(GONE);
|
||||
}
|
||||
if (mCrossSwipeRightActions != null) {
|
||||
mCrossSwipeRightActions.setVisibility(GONE);
|
||||
}
|
||||
mSwipeableContainer.setBackgroundColor(Color.TRANSPARENT);
|
||||
} else {
|
||||
mCrossSwipeBackground.setVisibility(View.VISIBLE);
|
||||
if (translationX > 0) {
|
||||
mCrossSwipeArchiveLeftImageView.setVisibility(VISIBLE);
|
||||
mCrossSwipeArchiveRightImageView.setVisibility(GONE);
|
||||
if (mCrossSwipeArchiveLeftImageView != null) {
|
||||
mCrossSwipeArchiveLeftImageView.setVisibility(VISIBLE);
|
||||
}
|
||||
if (mCrossSwipeRightActions != null) {
|
||||
mCrossSwipeRightActions.setVisibility(GONE);
|
||||
}
|
||||
} else {
|
||||
mCrossSwipeArchiveLeftImageView.setVisibility(GONE);
|
||||
mCrossSwipeArchiveRightImageView.setVisibility(VISIBLE);
|
||||
if (mCrossSwipeArchiveLeftImageView != null) {
|
||||
mCrossSwipeArchiveLeftImageView.setVisibility(GONE);
|
||||
}
|
||||
if (mCrossSwipeRightActions != null) {
|
||||
mCrossSwipeRightActions.setVisibility(VISIBLE);
|
||||
}
|
||||
}
|
||||
mSwipeableContainer.setBackgroundResource(R.color.window_background);
|
||||
mSwipeableContainer.setBackgroundResource(R.color.home_bg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -835,6 +1007,11 @@ public class ConversationListItemView extends FrameLayout implements OnClickList
|
||||
Assert.isTrue(v == mSwipeableContainer || v == mImagePreviewView);
|
||||
Assert.notNull(mData.getName());
|
||||
|
||||
if (!isLongClick && isSwipeActionsOpen()) {
|
||||
setSwipeTranslationX(0);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (mHostInterface != null) {
|
||||
mHostInterface.onConversationClicked(mData, isLongClick, this);
|
||||
return true;
|
||||
@@ -855,6 +1032,7 @@ public class ConversationListItemView extends FrameLayout implements OnClickList
|
||||
mData.getDraftSnippetText() : mData.getSnippetText();
|
||||
final String previewContentType = mData.getShowDraft() ?
|
||||
mData.getDraftPreviewContentType() : mData.getPreviewContentType();
|
||||
snippetText = ConversationMessageData.flattenPreviewText(snippetText);
|
||||
if (TextUtils.isEmpty(snippetText)) {
|
||||
Resources resources = getResources();
|
||||
// Use the attachment type as a snippet so the preview doesn't look odd
|
||||
|
||||
+66
-4
@@ -63,8 +63,10 @@ public class ConversationListSwipeHelper implements OnItemTouchListener {
|
||||
private VelocityTracker mVelocityTracker;
|
||||
private float mInitialX;
|
||||
private float mInitialY;
|
||||
private float mStartTranslationX;
|
||||
private boolean mIsSwiping;
|
||||
private ConversationListItemView mListItemView;
|
||||
private ConversationListItemView mOpenActionsItem;
|
||||
|
||||
public ConversationListSwipeHelper(final RecyclerView recyclerView) {
|
||||
mRecyclerView = recyclerView;
|
||||
@@ -106,9 +108,13 @@ public class ConversationListSwipeHelper implements OnItemTouchListener {
|
||||
final View viewAtPoint = mRecyclerView.findChildViewUnder(mInitialX, mInitialY);
|
||||
if (viewAtPoint instanceof ConversationListItemView) {
|
||||
final ConversationListItemView child = (ConversationListItemView) viewAtPoint;
|
||||
if (child.isSwipeAnimatable()) {
|
||||
if (child.isTouchOnSwipeActions(mInitialX - child.getLeft(),
|
||||
mInitialY - child.getTop())) {
|
||||
mListItemView = null;
|
||||
} else if (child.isSwipeAnimatable()) {
|
||||
// Begin detecting swipe on the target for the rest of the gesture.
|
||||
mListItemView = child;
|
||||
mStartTranslationX = child.getSwipeTranslationX();
|
||||
if (mListItemView.isAnimating()) {
|
||||
mListItemView = null;
|
||||
}
|
||||
@@ -189,7 +195,8 @@ public class ConversationListSwipeHelper implements OnItemTouchListener {
|
||||
case MotionEvent.ACTION_OUTSIDE:
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
if (hasValidGestureSwipeTarget()) {
|
||||
mListItemView.setSwipeTranslationX(event.getX() - mInitialX);
|
||||
mListItemView.setSwipeTranslationX(clampTranslation(
|
||||
mListItemView, mStartTranslationX + event.getX() - mInitialX));
|
||||
}
|
||||
break;
|
||||
case MotionEvent.ACTION_UP:
|
||||
@@ -211,17 +218,27 @@ public class ConversationListSwipeHelper implements OnItemTouchListener {
|
||||
|
||||
final boolean fastEnough = isTargetSwipedFastEnough();
|
||||
final boolean farEnough = isTargetSwipedFarEnough();
|
||||
|
||||
final boolean shouldDismiss = (fastEnough || farEnough);
|
||||
final boolean shouldRevealActions = swipeDirection == SWIPE_DIRECTION_LEFT
|
||||
&& isTargetSwipedFarEnoughForActions();
|
||||
final boolean shouldDismiss = swipeDirection == SWIPE_DIRECTION_RIGHT
|
||||
&& (fastEnough || farEnough);
|
||||
|
||||
if (shouldDismiss) {
|
||||
closeOpenActions(mListItemView);
|
||||
if (fastEnough) {
|
||||
animateDismiss(mListItemView, velocityX);
|
||||
} else {
|
||||
animateDismiss(mListItemView, swipeDirection);
|
||||
}
|
||||
} else if (shouldRevealActions) {
|
||||
closeOpenActions(mListItemView);
|
||||
animateRevealActions(mListItemView, velocityX);
|
||||
mOpenActionsItem = mListItemView;
|
||||
} else {
|
||||
animateRestore(mListItemView, velocityX);
|
||||
if (mOpenActionsItem == mListItemView) {
|
||||
mOpenActionsItem = null;
|
||||
}
|
||||
}
|
||||
|
||||
onSwipeGestureEnd(mListItemView,
|
||||
@@ -269,6 +286,51 @@ public class ConversationListSwipeHelper implements OnItemTouchListener {
|
||||
mRecyclerView.getParent().requestDisallowInterceptTouchEvent(true);
|
||||
setHardwareAnimatingLayerType(itemView, ANIMATING);
|
||||
itemView.setAnimating(true);
|
||||
closeOpenActions(itemView);
|
||||
}
|
||||
|
||||
private void closeOpenActions(final ConversationListItemView keepOpen) {
|
||||
if (mOpenActionsItem != null && mOpenActionsItem != keepOpen) {
|
||||
animateRestore(mOpenActionsItem, 0f);
|
||||
mOpenActionsItem = null;
|
||||
}
|
||||
}
|
||||
|
||||
private float clampTranslation(final ConversationListItemView itemView,
|
||||
final float translationX) {
|
||||
final float reveal = itemView.getSwipeActionsRevealWidth();
|
||||
if (translationX < 0) {
|
||||
return Math.max(translationX, -reveal * 1.2f);
|
||||
}
|
||||
return translationX;
|
||||
}
|
||||
|
||||
private boolean isTargetSwipedFarEnoughForActions() {
|
||||
final float translationX = mListItemView.getSwipeTranslationX();
|
||||
final float reveal = mListItemView.getSwipeActionsRevealWidth();
|
||||
return translationX < 0 && reveal > 0 && Math.abs(translationX) > reveal * 0.45f;
|
||||
}
|
||||
|
||||
private void animateRevealActions(final ConversationListItemView itemView,
|
||||
final float velocityX) {
|
||||
onSwipeAnimationStart(itemView);
|
||||
final float animateTo = -itemView.getSwipeActionsRevealWidth();
|
||||
final long duration;
|
||||
if (velocityX != 0) {
|
||||
duration = calculateTranslationDuration(
|
||||
animateTo - itemView.getSwipeTranslationX(), velocityX);
|
||||
} else {
|
||||
duration = mDefaultRestoreAnimationDuration;
|
||||
}
|
||||
final ObjectAnimator animator = getSwipeTranslationXAnimator(
|
||||
itemView, animateTo, duration, UiUtils.DEFAULT_INTERPOLATOR);
|
||||
animator.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(final Animator animation) {
|
||||
onSwipeAnimationEnd(itemView);
|
||||
}
|
||||
});
|
||||
animator.start();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright (C) 2026 The LineageOS Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.messaging.ui.home;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Full-screen attach overlay. Sheet/dock keep their own touches. A tap above the
|
||||
* sheet dismisses it; a tap on the conversation compose field also asks for the IME.
|
||||
*/
|
||||
public class HomeAttachOverlayLayout extends FrameLayout {
|
||||
public interface OutsideTapListener {
|
||||
void onOutsideTap(boolean onComposeField);
|
||||
}
|
||||
|
||||
@Nullable private View mSheet;
|
||||
@Nullable private View mDock;
|
||||
@Nullable private View mComposeField;
|
||||
@Nullable private OutsideTapListener mOutsideTapListener;
|
||||
private boolean mDownOutside;
|
||||
|
||||
public HomeAttachOverlayLayout(@NonNull final Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public HomeAttachOverlayLayout(@NonNull final Context context,
|
||||
@Nullable final AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public void bind(@Nullable final View sheet, @Nullable final View dock,
|
||||
@Nullable final View composeField, @Nullable final OutsideTapListener listener) {
|
||||
mSheet = sheet;
|
||||
mDock = dock;
|
||||
mComposeField = composeField;
|
||||
mOutsideTapListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dispatchTouchEvent(final MotionEvent ev) {
|
||||
final int action = ev.getActionMasked();
|
||||
if (action == MotionEvent.ACTION_DOWN) {
|
||||
mDownOutside = !isOnSheetOrDock(ev);
|
||||
}
|
||||
if (mDownOutside) {
|
||||
if (action == MotionEvent.ACTION_UP && mOutsideTapListener != null) {
|
||||
mOutsideTapListener.onOutsideTap(isOnComposeField(ev));
|
||||
}
|
||||
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
|
||||
mDownOutside = false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return super.dispatchTouchEvent(ev);
|
||||
}
|
||||
|
||||
private boolean isOnSheetOrDock(final MotionEvent ev) {
|
||||
final float x = ev.getX();
|
||||
final float y = ev.getY();
|
||||
if (containsLocal(mDock, x, y)) {
|
||||
return true;
|
||||
}
|
||||
return mSheet != null && mSheet.getVisibility() == VISIBLE && y >= mSheet.getY();
|
||||
}
|
||||
|
||||
private boolean isOnComposeField(final MotionEvent ev) {
|
||||
if (mComposeField == null || mComposeField.getVisibility() != VISIBLE) {
|
||||
return false;
|
||||
}
|
||||
final int[] loc = new int[2];
|
||||
mComposeField.getLocationInWindow(loc);
|
||||
final float x = ev.getRawX();
|
||||
final float y = ev.getRawY();
|
||||
return x >= loc[0] && x < loc[0] + mComposeField.getWidth()
|
||||
&& y >= loc[1] && y < loc[1] + mComposeField.getHeight();
|
||||
}
|
||||
|
||||
private static boolean containsLocal(@Nullable final View view, final float x, final float y) {
|
||||
if (view == null || view.getVisibility() != VISIBLE) {
|
||||
return false;
|
||||
}
|
||||
return x >= view.getX() && x < view.getX() + view.getWidth()
|
||||
&& y >= view.getY() && y < view.getY() + view.getHeight();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (C) 2026 The LineageOS Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.messaging.ui.home;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.android.messaging.R;
|
||||
import com.android.messaging.ui.BugleActionBarActivity;
|
||||
|
||||
/** Home Automation screen launched from the bottom nav. */
|
||||
public class HomeAutomationActivity extends BugleActionBarActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.home_automation_activity);
|
||||
if (getSupportActionBar() != null) {
|
||||
getSupportActionBar().hide();
|
||||
}
|
||||
HomeSubHeader.bind(this, R.string.home_automation_title, v -> finish());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish() {
|
||||
super.finish();
|
||||
overridePendingTransition(R.anim.home_activity_hold, R.anim.home_activity_close);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (C) 2026 The LineageOS Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.messaging.ui.home;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.ImageButton;
|
||||
|
||||
import com.android.messaging.R;
|
||||
import com.android.messaging.ui.BugleActionBarActivity;
|
||||
|
||||
/** Sheet to create a new automation task. */
|
||||
public class HomeAutomationCreateActivity extends BugleActionBarActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.home_automation_create_activity);
|
||||
if (getSupportActionBar() != null) {
|
||||
getSupportActionBar().hide();
|
||||
}
|
||||
|
||||
final ImageButton back = findViewById(R.id.home_sub_header_back);
|
||||
if (back != null) {
|
||||
back.setVisibility(View.GONE);
|
||||
}
|
||||
final ImageButton close = findViewById(R.id.home_sub_header_close);
|
||||
if (close != null) {
|
||||
close.setVisibility(View.VISIBLE);
|
||||
close.setOnClickListener(v -> finish());
|
||||
}
|
||||
HomeSubHeader.bind(this, R.string.home_automation_create_title, v -> finish());
|
||||
|
||||
final View scrim = findViewById(R.id.home_automation_create_scrim);
|
||||
if (scrim != null) {
|
||||
scrim.setOnClickListener(v -> finish());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish() {
|
||||
super.finish();
|
||||
overridePendingTransition(R.anim.home_activity_hold, R.anim.home_sheet_close);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,6 @@ import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -56,7 +55,7 @@ public class HomeContactsFragment extends Fragment implements ContactPickerDataL
|
||||
private final LinkedHashMap<String, RecipientEntry> mSelected = new LinkedHashMap<>();
|
||||
private HomeContactListAdapter mAdapter;
|
||||
private GetOrCreateConversationActionMonitor mMonitor;
|
||||
private TextView mEmptyView;
|
||||
private HomeEmptyState mEmptyView;
|
||||
private ContactListItemView mSendToView;
|
||||
private Cursor mAllContactsCursor;
|
||||
private String mFilterText;
|
||||
@@ -271,10 +270,19 @@ public class HomeContactsFragment extends Fragment implements ContactPickerDataL
|
||||
mEmptyView.setVisibility(View.GONE);
|
||||
return;
|
||||
}
|
||||
mEmptyView.setText(filtering ? R.string.home_compose_no_results
|
||||
: ContactUtil.hasReadContactsPermission()
|
||||
? R.string.home_compose_empty
|
||||
: R.string.home_compose_permission_required);
|
||||
if (filtering) {
|
||||
mEmptyView.setContent(R.drawable.ic_home_nav_contacts,
|
||||
R.string.home_empty_contacts_no_results_title,
|
||||
R.string.home_empty_contacts_no_results_body);
|
||||
} else if (ContactUtil.hasReadContactsPermission()) {
|
||||
mEmptyView.setContent(R.drawable.ic_home_nav_contacts,
|
||||
R.string.home_empty_contacts_title,
|
||||
R.string.home_empty_contacts_body);
|
||||
} else {
|
||||
mEmptyView.setContent(R.drawable.ic_home_nav_contacts,
|
||||
R.string.home_empty_contacts_permission_title,
|
||||
R.string.home_empty_contacts_permission_body);
|
||||
}
|
||||
mEmptyView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
+21
-23
@@ -30,7 +30,6 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
@@ -61,8 +60,7 @@ public class HomeConversationSearchActivity extends BugleActionBarActivity
|
||||
private EditText mSearchField;
|
||||
private ImageButton mClearButton;
|
||||
private ListView mListView;
|
||||
private View mIdleContainer;
|
||||
private TextView mEmptyView;
|
||||
private HomeEmptyState mEmptyView;
|
||||
private SearchResultAdapter mAdapter;
|
||||
|
||||
@Override
|
||||
@@ -80,7 +78,6 @@ public class HomeConversationSearchActivity extends BugleActionBarActivity
|
||||
}
|
||||
|
||||
mListView = findViewById(R.id.home_conversation_search_list);
|
||||
mIdleContainer = findViewById(R.id.home_conversation_search_idle);
|
||||
mEmptyView = findViewById(R.id.home_conversation_search_empty);
|
||||
mAdapter = new SearchResultAdapter(this);
|
||||
mListView.setAdapter(mAdapter);
|
||||
@@ -136,32 +133,25 @@ public class HomeConversationSearchActivity extends BugleActionBarActivity
|
||||
}
|
||||
|
||||
private void updateEmptyView() {
|
||||
if (mEmptyView == null || mListView == null) {
|
||||
return;
|
||||
}
|
||||
final String searchText = mBinding.isBound() ? mBinding.getData().getSearchText() : null;
|
||||
final ImageView idleIcon = mIdleContainer == null ? null
|
||||
: mIdleContainer.findViewById(R.id.home_conversation_search_idle_icon);
|
||||
if (TextUtils.isEmpty(searchText)) {
|
||||
mEmptyView.setText(R.string.home_conversation_search_prompt);
|
||||
if (idleIcon != null) {
|
||||
idleIcon.setVisibility(View.VISIBLE);
|
||||
}
|
||||
if (mIdleContainer != null) {
|
||||
mIdleContainer.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
mEmptyView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
mEmptyView.setContent(R.drawable.ic_home_search_line,
|
||||
R.string.home_empty_conversation_search_idle_title,
|
||||
R.string.home_empty_conversation_search_idle_body);
|
||||
mEmptyView.setVisibility(View.VISIBLE);
|
||||
mListView.setVisibility(View.GONE);
|
||||
return;
|
||||
}
|
||||
final boolean empty = mAdapter.getCount() == 0;
|
||||
mEmptyView.setText(getString(R.string.home_conversation_search_empty, searchText));
|
||||
if (idleIcon != null) {
|
||||
idleIcon.setVisibility(View.GONE);
|
||||
}
|
||||
if (mIdleContainer != null) {
|
||||
mIdleContainer.setVisibility(empty ? View.VISIBLE : View.GONE);
|
||||
} else {
|
||||
mEmptyView.setVisibility(empty ? View.VISIBLE : View.GONE);
|
||||
if (empty) {
|
||||
mEmptyView.setContent(R.drawable.ic_home_search_line,
|
||||
getString(R.string.home_empty_conversation_search_title),
|
||||
getString(R.string.home_empty_conversation_search_body, searchText));
|
||||
}
|
||||
mEmptyView.setVisibility(empty ? View.VISIBLE : View.GONE);
|
||||
mListView.setVisibility(empty ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
|
||||
@@ -170,6 +160,14 @@ public class HomeConversationSearchActivity extends BugleActionBarActivity
|
||||
UIIntents.get().launchConversationActivity(this, mConversationId, null, null, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
if (mSearchField != null) {
|
||||
ImeUtil.get().hideImeKeyboard(this, mSearchField);
|
||||
}
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish() {
|
||||
if (mSearchField != null) {
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* 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.util.AttributeSet;
|
||||
import android.view.Gravity;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.StringRes;
|
||||
|
||||
import com.android.messaging.R;
|
||||
|
||||
/** Centered icon + title + body used when a home list has no records. */
|
||||
public class HomeEmptyState extends LinearLayout {
|
||||
|
||||
private ImageView mIcon;
|
||||
private TextView mTitle;
|
||||
private TextView mBody;
|
||||
|
||||
public HomeEmptyState(final Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public HomeEmptyState(final Context context, final AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public HomeEmptyState(final Context context, final AttributeSet attrs,
|
||||
final int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
setOrientation(VERTICAL);
|
||||
setGravity(Gravity.CENTER);
|
||||
inflate(context, R.layout.home_empty_state_content, this);
|
||||
mIcon = findViewById(R.id.home_empty_icon);
|
||||
mTitle = findViewById(R.id.home_empty_title);
|
||||
mBody = findViewById(R.id.home_empty_body);
|
||||
}
|
||||
|
||||
public void setContent(@DrawableRes final int iconRes, @StringRes final int titleRes,
|
||||
@StringRes final int bodyRes) {
|
||||
setContent(iconRes, getResources().getText(titleRes), getResources().getText(bodyRes));
|
||||
}
|
||||
|
||||
public void setContent(@DrawableRes final int iconRes, final CharSequence title,
|
||||
final CharSequence body) {
|
||||
if (mIcon != null) {
|
||||
mIcon.setImageResource(iconRes);
|
||||
}
|
||||
if (mTitle != null) {
|
||||
mTitle.setText(title);
|
||||
}
|
||||
if (mBody != null) {
|
||||
mBody.setText(body);
|
||||
}
|
||||
}
|
||||
|
||||
public void setReserveDock(final boolean reserve) {
|
||||
final int bottom = reserve
|
||||
? getResources().getDimensionPixelSize(R.dimen.home_empty_dock_padding)
|
||||
: 0;
|
||||
setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), bottom);
|
||||
}
|
||||
}
|
||||
@@ -22,16 +22,16 @@ import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.provider.MediaStore;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.VelocityTracker;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -63,6 +63,7 @@ public final class HomeGallerySheet {
|
||||
}
|
||||
|
||||
private static final int SPAN_COUNT = 3;
|
||||
private static final int PEEK_ROWS = 3;
|
||||
private static final String[] PROJECTION = new String[] {
|
||||
MediaStore.Files.FileColumns._ID,
|
||||
MediaStore.Files.FileColumns.MEDIA_TYPE,
|
||||
@@ -140,7 +141,7 @@ public final class HomeGallerySheet {
|
||||
final View header = content.findViewById(R.id.home_gallery_header);
|
||||
final RecyclerView grid = content.findViewById(R.id.home_gallery_grid);
|
||||
final View empty = content.findViewById(R.id.home_gallery_empty);
|
||||
final TextView emptyMessage = content.findViewById(R.id.home_gallery_empty_message);
|
||||
final HomeEmptyState emptyState = content.findViewById(R.id.home_gallery_empty_state);
|
||||
final View allow = content.findViewById(R.id.home_gallery_allow);
|
||||
|
||||
final int screenHeight = activity.getResources().getDisplayMetrics().heightPixels;
|
||||
@@ -164,7 +165,8 @@ public final class HomeGallerySheet {
|
||||
content.setTag(dismissAnimated);
|
||||
|
||||
scrim.setOnClickListener(v -> dismissAnimated.run());
|
||||
bindDragToDismiss(header, scrim, sheet, dismissAnimated);
|
||||
final float[] peekTranslation = new float[] { 0f };
|
||||
bindHandleDrag(header, scrim, sheet, peekTranslation, dismissAnimated);
|
||||
|
||||
final Adapter adapter = new Adapter(uri -> {
|
||||
dismissAnimated.run();
|
||||
@@ -190,23 +192,22 @@ public final class HomeGallerySheet {
|
||||
overlayLp.topMargin = sheetTop;
|
||||
content.setLayoutParams(overlayLp);
|
||||
|
||||
final int hostHeight = host.getHeight() > 0 ? host.getHeight() : screenHeight;
|
||||
final float sheetFraction = activity.getResources().getFraction(
|
||||
R.fraction.home_gallery_sheet_height, 1, 1);
|
||||
final FrameLayout.LayoutParams sheetLp = new FrameLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
Math.max(1, Math.round(hostHeight * sheetFraction)),
|
||||
Gravity.BOTTOM);
|
||||
sheet.setLayoutParams(sheetLp);
|
||||
|
||||
adapter.setItems(items);
|
||||
if (items.isEmpty()) {
|
||||
empty.setVisibility(View.VISIBLE);
|
||||
if (!OsUtil.hasMediaPermissions()) {
|
||||
emptyMessage.setText(R.string.home_gallery_permission);
|
||||
if (emptyState != null) {
|
||||
emptyState.setContent(R.drawable.ic_home_image_line,
|
||||
R.string.home_empty_gallery_permission_title,
|
||||
R.string.home_empty_gallery_permission_body);
|
||||
}
|
||||
allow.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
emptyMessage.setText(R.string.home_gallery_empty);
|
||||
if (emptyState != null) {
|
||||
emptyState.setContent(R.drawable.ic_home_image_line,
|
||||
R.string.home_empty_gallery_title,
|
||||
R.string.home_empty_gallery_body);
|
||||
}
|
||||
allow.setVisibility(View.GONE);
|
||||
}
|
||||
} else {
|
||||
@@ -222,50 +223,65 @@ public final class HomeGallerySheet {
|
||||
if (content.getViewTreeObserver().isAlive()) {
|
||||
content.getViewTreeObserver().removeOnPreDrawListener(this);
|
||||
}
|
||||
peekTranslation[0] = resolvePeekTranslation(content, header, sheet);
|
||||
final int height = sheet.getHeight() > 0 ? sheet.getHeight() : screenHeight;
|
||||
sheet.setTranslationY(height);
|
||||
if (scrim != null) {
|
||||
scrim.setAlpha(0f);
|
||||
}
|
||||
HomeUiMotion.gallerySheetOpen(scrim, sheet);
|
||||
HomeUiMotion.gallerySheetOpen(scrim, sheet, peekTranslation[0]);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void bindDragToDismiss(final View header, final View scrim, final View sheet,
|
||||
final Runnable dismissAnimated) {
|
||||
private static void bindHandleDrag(final View header, final View scrim, final View sheet,
|
||||
final float[] peekTranslation, final Runnable dismissAnimated) {
|
||||
header.setOnTouchListener(new View.OnTouchListener() {
|
||||
private float mDownY;
|
||||
private float mDownTranslation;
|
||||
private boolean mDragging;
|
||||
private VelocityTracker mTracker;
|
||||
|
||||
@Override
|
||||
public boolean onTouch(final View v, final MotionEvent event) {
|
||||
if (mTracker == null) {
|
||||
mTracker = VelocityTracker.obtain();
|
||||
}
|
||||
mTracker.addMovement(event);
|
||||
switch (event.getActionMasked()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
mDownY = event.getRawY();
|
||||
mDownTranslation = sheet.getTranslationY();
|
||||
mDragging = false;
|
||||
sheet.animate().cancel();
|
||||
return true;
|
||||
case MotionEvent.ACTION_MOVE: {
|
||||
final float dy = Math.max(0f, event.getRawY() - mDownY);
|
||||
if (dy > 0f) {
|
||||
final float dy = event.getRawY() - mDownY;
|
||||
if (Math.abs(dy) > ViewConfiguration.get(v.getContext())
|
||||
.getScaledTouchSlop()) {
|
||||
mDragging = true;
|
||||
}
|
||||
sheet.setTranslationY(dy);
|
||||
final int height = Math.max(sheet.getHeight(), 1);
|
||||
scrim.setAlpha(1f - Math.min(1f, dy / height));
|
||||
final float translation = clamp(mDownTranslation + dy, 0f, height);
|
||||
sheet.setTranslationY(translation);
|
||||
updateScrimForDrag(scrim, translation, peekTranslation[0], height);
|
||||
return true;
|
||||
}
|
||||
case MotionEvent.ACTION_UP:
|
||||
case MotionEvent.ACTION_CANCEL: {
|
||||
final float dy = Math.max(0f, event.getRawY() - mDownY);
|
||||
final int height = Math.max(sheet.getHeight(), 1);
|
||||
if (mDragging && dy > height * 0.18f) {
|
||||
dismissAnimated.run();
|
||||
} else {
|
||||
sheet.animate().translationY(0f).setDuration(180).start();
|
||||
scrim.animate().alpha(1f).setDuration(180).start();
|
||||
mTracker.computeCurrentVelocity(1000);
|
||||
final float velocityY = mTracker.getYVelocity();
|
||||
mTracker.recycle();
|
||||
mTracker = null;
|
||||
if (!mDragging && event.getActionMasked() == MotionEvent.ACTION_CANCEL) {
|
||||
HomeUiMotion.gallerySheetSnap(sheet, peekTranslation[0]);
|
||||
updateScrimForDrag(scrim, peekTranslation[0], peekTranslation[0],
|
||||
Math.max(sheet.getHeight(), 1));
|
||||
return true;
|
||||
}
|
||||
settleHandleDrag(sheet, scrim, peekTranslation[0], velocityY,
|
||||
dismissAnimated);
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
@@ -275,6 +291,59 @@ public final class HomeGallerySheet {
|
||||
});
|
||||
}
|
||||
|
||||
private static void settleHandleDrag(final View sheet, final View scrim,
|
||||
final float peekY, final float velocityY, final Runnable dismissAnimated) {
|
||||
final int height = Math.max(sheet.getHeight(), 1);
|
||||
final float translation = sheet.getTranslationY();
|
||||
final float dismissDistance = Math.max(height - peekY, 1f);
|
||||
if (translation > peekY && (velocityY > 1400f
|
||||
|| translation > peekY + dismissDistance * 0.22f)) {
|
||||
dismissAnimated.run();
|
||||
return;
|
||||
}
|
||||
final float target;
|
||||
if (velocityY < -1400f || translation < peekY * 0.45f) {
|
||||
target = 0f;
|
||||
} else {
|
||||
target = peekY;
|
||||
}
|
||||
HomeUiMotion.gallerySheetSnap(sheet, target);
|
||||
if (scrim != null) {
|
||||
scrim.animate().alpha(1f).setDuration(220).start();
|
||||
}
|
||||
}
|
||||
|
||||
private static void updateScrimForDrag(final View scrim, final float translation,
|
||||
final float peekY, final int height) {
|
||||
if (scrim == null) {
|
||||
return;
|
||||
}
|
||||
if (translation <= peekY) {
|
||||
scrim.setAlpha(1f);
|
||||
return;
|
||||
}
|
||||
final float span = Math.max(height - peekY, 1f);
|
||||
scrim.setAlpha(1f - Math.min(1f, (translation - peekY) / span));
|
||||
}
|
||||
|
||||
private static float clamp(final float value, final float min, final float max) {
|
||||
return Math.max(min, Math.min(max, value));
|
||||
}
|
||||
|
||||
private static float resolvePeekTranslation(final View overlay, final View header,
|
||||
final View sheet) {
|
||||
final int sheetHeight = sheet.getHeight() > 0 ? sheet.getHeight() : overlay.getHeight();
|
||||
final int headerHeight = header != null && header.getHeight() > 0
|
||||
? header.getHeight() : overlay.getResources()
|
||||
.getDimensionPixelSize(R.dimen.home_header_height);
|
||||
final int gap = overlay.getResources().getDimensionPixelSize(R.dimen.home_gallery_grid_gap);
|
||||
final int width = overlay.getWidth() > 0 ? overlay.getWidth()
|
||||
: overlay.getResources().getDisplayMetrics().widthPixels;
|
||||
final int cell = width / SPAN_COUNT;
|
||||
final int peekHeight = headerHeight + PEEK_ROWS * (cell + gap * 2);
|
||||
return Math.max(0f, sheetHeight - peekHeight);
|
||||
}
|
||||
|
||||
private static int resolveSheetTop(@NonNull final Activity activity,
|
||||
@NonNull final View host) {
|
||||
final View conversationHeader = activity.findViewById(R.id.conversation_header);
|
||||
@@ -375,7 +444,9 @@ public final class HomeGallerySheet {
|
||||
final View view = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.home_gallery_item, parent, false);
|
||||
if (mThumbSize <= 0) {
|
||||
mThumbSize = parent.getResources().getDisplayMetrics().widthPixels / SPAN_COUNT;
|
||||
final int width = parent.getWidth() > 0 ? parent.getWidth()
|
||||
: parent.getResources().getDisplayMetrics().widthPixels;
|
||||
mThumbSize = width / SPAN_COUNT;
|
||||
}
|
||||
return new Holder(view);
|
||||
}
|
||||
|
||||
@@ -33,7 +33,6 @@ import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.PopupMenu;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
@@ -59,6 +58,7 @@ import com.android.messaging.ui.conversation.ConversationActivity;
|
||||
import com.android.messaging.ui.conversationsettings.CopyContactDetailDialog;
|
||||
import com.android.messaging.util.AvatarUriUtil;
|
||||
import com.android.messaging.util.ContactUtil;
|
||||
import com.android.messaging.util.ImeUtil;
|
||||
import com.android.messaging.util.NotificationsUtil;
|
||||
import com.android.messaging.util.PhoneUtils;
|
||||
import com.android.messaging.util.UiUtils;
|
||||
@@ -78,8 +78,11 @@ public class HomeProfileActivity extends BugleActionBarActivity
|
||||
private List<ParticipantData> mParticipants = new ArrayList<>();
|
||||
|
||||
private TextView mNameView;
|
||||
private TextView mStatusView;
|
||||
private LinearLayout mParticipantsContainer;
|
||||
private TextView mParticipantsHeader;
|
||||
private TextView mParticipantsSummary;
|
||||
private boolean mParticipantsLoaded;
|
||||
|
||||
@Override
|
||||
protected void onCreate(final Bundle savedInstanceState) {
|
||||
@@ -95,22 +98,21 @@ public class HomeProfileActivity extends BugleActionBarActivity
|
||||
return;
|
||||
}
|
||||
|
||||
final ImageButton back = findViewById(R.id.home_sub_header_back);
|
||||
back.setOnClickListener(v -> finish());
|
||||
final TextView headerTitle = findViewById(R.id.home_sub_header_title);
|
||||
if (headerTitle != null) {
|
||||
headerTitle.setVisibility(View.GONE);
|
||||
}
|
||||
bindHeaderChrome();
|
||||
|
||||
mNameView = findViewById(R.id.home_profile_name);
|
||||
mStatusView = findViewById(R.id.home_profile_status);
|
||||
mParticipantsContainer = findViewById(R.id.home_profile_participants);
|
||||
mParticipantsHeader = findViewById(R.id.home_profile_participants_header);
|
||||
mParticipantsSummary = findViewById(R.id.home_profile_participants_summary);
|
||||
findViewById(R.id.home_profile_rename).setOnClickListener(v -> showRenameDialog());
|
||||
|
||||
bindQuickAction(R.id.home_profile_action_call, R.drawable.ic_phone_small_light,
|
||||
R.string.home_profile_action_call, v -> onPrimaryQuickAction());
|
||||
bindQuickAction(R.id.home_profile_action_add_contact, R.drawable.ic_home_group_add,
|
||||
bindQuickAction(R.id.home_profile_action_add_contact, R.drawable.ic_person_add_light,
|
||||
R.string.home_profile_action_add_contact, v -> onSecondaryQuickAction());
|
||||
bindQuickAction(R.id.home_profile_action_message, R.drawable.ic_home_nav_messages,
|
||||
R.string.home_profile_action_message, v -> openConversation());
|
||||
bindQuickAction(R.id.home_profile_action_search, R.drawable.ic_home_search_line,
|
||||
R.string.home_profile_action_search,
|
||||
v -> UIIntents.get().launchHomeConversationSearchActivity(this, mConversationId));
|
||||
@@ -119,6 +121,19 @@ public class HomeProfileActivity extends BugleActionBarActivity
|
||||
mBinding.getData().init(LoaderManager.getInstance(this), mBinding);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
final View root = findViewById(android.R.id.content);
|
||||
if (root != null) {
|
||||
ImeUtil.get().hideImeKeyboard(this, root);
|
||||
}
|
||||
bindHeaderChrome();
|
||||
if (mConversation != null) {
|
||||
updateViews();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
@@ -138,15 +153,37 @@ public class HomeProfileActivity extends BugleActionBarActivity
|
||||
final List<ParticipantData> participants) {
|
||||
mBinding.ensureBound(data);
|
||||
mParticipants = participants;
|
||||
mParticipantsLoaded = true;
|
||||
updateViews();
|
||||
}
|
||||
|
||||
private void bindHeaderChrome() {
|
||||
final ImageButton back = findViewById(R.id.home_sub_header_back);
|
||||
if (back != null) {
|
||||
back.setOnClickListener(v -> finish());
|
||||
}
|
||||
final TextView headerTitle = findViewById(R.id.home_sub_header_title);
|
||||
if (headerTitle != null) {
|
||||
headerTitle.setText(R.string.home_profile_screen_title);
|
||||
headerTitle.setVisibility(View.VISIBLE);
|
||||
}
|
||||
final ImageButton overflow = findViewById(R.id.home_sub_header_overflow);
|
||||
if (overflow != null) {
|
||||
overflow.setVisibility(View.VISIBLE);
|
||||
overflow.setOnClickListener(this::showOverflowMenu);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateViews() {
|
||||
if (mConversation == null) {
|
||||
return;
|
||||
}
|
||||
updateAvatars();
|
||||
updateName();
|
||||
if (!mParticipantsLoaded) {
|
||||
return;
|
||||
}
|
||||
updateAvatars();
|
||||
updateStatus();
|
||||
updateQuickActions();
|
||||
updateOptions();
|
||||
updateParticipants();
|
||||
@@ -191,6 +228,27 @@ public class HomeProfileActivity extends BugleActionBarActivity
|
||||
mNameView.setText(name);
|
||||
}
|
||||
|
||||
private void updateStatus() {
|
||||
if (mStatusView == null) {
|
||||
return;
|
||||
}
|
||||
if (mParticipants.size() > 1) {
|
||||
mStatusView.setVisibility(View.GONE);
|
||||
return;
|
||||
}
|
||||
final ParticipantData other = getOtherParticipant();
|
||||
final boolean unregistered = other != null
|
||||
&& !ContactUtil.isParticipantInAddressBook(this,
|
||||
other.getContactId(), other.getLookupKey(),
|
||||
other.getNormalizedDestination());
|
||||
if (unregistered) {
|
||||
mStatusView.setText(R.string.home_profile_status_unregistered);
|
||||
mStatusView.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
mStatusView.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateQuickActions() {
|
||||
final boolean isGroup = mParticipants.size() > 1;
|
||||
final View primary = findViewById(R.id.home_profile_action_call);
|
||||
@@ -200,13 +258,17 @@ public class HomeProfileActivity extends BugleActionBarActivity
|
||||
R.string.home_profile_action_video, v -> onPrimaryQuickAction());
|
||||
bindQuickAction(R.id.home_profile_action_add_contact, R.drawable.ic_home_group_add,
|
||||
R.string.home_profile_action_add_people, v -> onSecondaryQuickAction());
|
||||
bindQuickAction(R.id.home_profile_action_message, R.drawable.ic_home_nav_messages,
|
||||
R.string.home_profile_action_message, v -> openConversation());
|
||||
primary.setVisibility(View.VISIBLE);
|
||||
secondary.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
bindQuickAction(R.id.home_profile_action_call, R.drawable.ic_phone_small_light,
|
||||
R.string.home_profile_action_call, v -> onPrimaryQuickAction());
|
||||
bindQuickAction(R.id.home_profile_action_add_contact, R.drawable.ic_home_group_add,
|
||||
bindQuickAction(R.id.home_profile_action_add_contact, R.drawable.ic_person_add_light,
|
||||
R.string.home_profile_action_add_contact, v -> onSecondaryQuickAction());
|
||||
bindQuickAction(R.id.home_profile_action_message, R.drawable.ic_home_nav_messages,
|
||||
R.string.home_profile_action_message, v -> openConversation());
|
||||
primary.setVisibility(getCallablePhoneNumber() == null ? View.GONE : View.VISIBLE);
|
||||
final ParticipantData other = getOtherParticipant();
|
||||
final boolean canAddContact = other != null
|
||||
@@ -219,7 +281,7 @@ public class HomeProfileActivity extends BugleActionBarActivity
|
||||
|
||||
private void onPrimaryQuickAction() {
|
||||
if (mParticipants.size() > 1) {
|
||||
Toast.makeText(this, R.string.home_popover_coming_soon, Toast.LENGTH_SHORT).show();
|
||||
HomeToast.show(this, R.string.home_popover_coming_soon);
|
||||
return;
|
||||
}
|
||||
placeCall();
|
||||
@@ -227,21 +289,26 @@ public class HomeProfileActivity extends BugleActionBarActivity
|
||||
|
||||
private void onSecondaryQuickAction() {
|
||||
if (mParticipants.size() > 1) {
|
||||
Toast.makeText(this, R.string.home_popover_coming_soon, Toast.LENGTH_SHORT).show();
|
||||
HomeToast.show(this, R.string.home_popover_coming_soon);
|
||||
return;
|
||||
}
|
||||
addContact();
|
||||
}
|
||||
|
||||
private void updateOptions() {
|
||||
bindOption(R.id.home_profile_option_notifications, R.drawable.ic_home_bell,
|
||||
R.string.home_profile_option_notifications, false, v -> openNotificationSettings());
|
||||
final View notifications = findViewById(R.id.home_profile_option_notifications);
|
||||
HomeSettingsRow.bind(notifications, R.drawable.ic_home_bell,
|
||||
getString(R.string.home_profile_option_notifications), null,
|
||||
true, false, false, v -> openNotificationSettings(), null);
|
||||
HomeSettingsRow.setValue(notifications,
|
||||
getString(R.string.home_profile_option_notifications_value));
|
||||
|
||||
final boolean archived = mConversation.getIsArchived();
|
||||
bindOption(R.id.home_profile_option_archive, R.drawable.ic_archive_small_dark,
|
||||
archived ? R.string.home_profile_option_unarchive
|
||||
: R.string.home_profile_option_archive,
|
||||
false, v -> toggleArchived(archived));
|
||||
HomeSettingsRow.bind(findViewById(R.id.home_profile_option_archive),
|
||||
R.drawable.ic_home_menu_archive,
|
||||
getString(archived ? R.string.home_profile_option_unarchive
|
||||
: R.string.home_profile_option_move_archive),
|
||||
null, true, false, false, v -> toggleArchived(archived), null);
|
||||
|
||||
final ParticipantData other = getOtherParticipant();
|
||||
final View spamRow = findViewById(R.id.home_profile_option_spam);
|
||||
@@ -250,28 +317,35 @@ public class HomeProfileActivity extends BugleActionBarActivity
|
||||
} else {
|
||||
spamRow.setVisibility(View.VISIBLE);
|
||||
final boolean blocked = other.isBlocked();
|
||||
bindOption(R.id.home_profile_option_spam, R.drawable.ic_home_menu_spam,
|
||||
blocked ? R.string.home_profile_option_unblock
|
||||
: R.string.home_profile_option_spam,
|
||||
!blocked, v -> toggleBlocked(other, blocked));
|
||||
HomeSettingsRow.bind(spamRow, R.drawable.ic_home_menu_spam,
|
||||
getString(blocked ? R.string.home_profile_option_unblock
|
||||
: R.string.home_profile_option_spam),
|
||||
blocked ? null : getString(R.string.home_profile_option_spam_subtitle),
|
||||
true, false, false, v -> toggleBlocked(other, blocked), null);
|
||||
HomeSettingsRow.setDestructive(spamRow, !blocked);
|
||||
}
|
||||
|
||||
bindOption(R.id.home_profile_option_delete, R.drawable.ic_home_menu_trash,
|
||||
R.string.home_profile_option_delete, true, v -> confirmDelete());
|
||||
final View deleteRow = findViewById(R.id.home_profile_option_delete);
|
||||
HomeSettingsRow.bind(deleteRow, R.drawable.ic_home_menu_trash,
|
||||
getString(R.string.home_profile_option_delete), null,
|
||||
true, false, false, v -> confirmDelete(), null);
|
||||
HomeSettingsRow.setDestructive(deleteRow, true);
|
||||
}
|
||||
|
||||
private void updateParticipants() {
|
||||
final int count = mParticipants.size();
|
||||
mParticipantsHeader.setText(count == 1
|
||||
? getString(R.string.home_profile_participants_one)
|
||||
: getString(R.string.home_profile_participants_many, count));
|
||||
mParticipantsHeader.setText(getString(R.string.home_profile_participants_title,
|
||||
Math.max(count + 1, 2)));
|
||||
if (mParticipantsSummary != null) {
|
||||
mParticipantsSummary.setText(count <= 1
|
||||
? getString(R.string.home_profile_participants_summary_one)
|
||||
: getString(R.string.home_profile_participants_summary_many, count));
|
||||
mParticipantsSummary.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
final TextView addPeople = findViewById(R.id.home_profile_participants_add);
|
||||
if (addPeople != null) {
|
||||
final boolean isGroup = count > 1;
|
||||
addPeople.setVisibility(isGroup ? View.VISIBLE : View.GONE);
|
||||
addPeople.setOnClickListener(isGroup ? v -> Toast.makeText(this,
|
||||
R.string.home_popover_coming_soon, Toast.LENGTH_SHORT).show() : null);
|
||||
addPeople.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
mParticipantsContainer.removeAllViews();
|
||||
@@ -286,8 +360,13 @@ public class HomeProfileActivity extends BugleActionBarActivity
|
||||
participant.getNormalizedDestination());
|
||||
((TextView) row.findViewById(R.id.home_profile_participant_name))
|
||||
.setText(participant.getDisplayName(true));
|
||||
((TextView) row.findViewById(R.id.home_profile_participant_details))
|
||||
.setText(participant.getDisplayDestination());
|
||||
final TextView details = row.findViewById(R.id.home_profile_participant_details);
|
||||
final boolean inBook = ContactUtil.isParticipantInAddressBook(this,
|
||||
participant.getContactId(), participant.getLookupKey(),
|
||||
participant.getNormalizedDestination());
|
||||
details.setText(inBook
|
||||
? participant.getDisplayDestination()
|
||||
: getString(R.string.home_profile_status_unregistered));
|
||||
row.setOnClickListener(v -> ContactUtil.openContactDetail(this,
|
||||
participant.getContactId(), participant.getLookupKey(),
|
||||
participant.getNormalizedDestination()));
|
||||
@@ -308,19 +387,19 @@ public class HomeProfileActivity extends BugleActionBarActivity
|
||||
root.findViewById(R.id.home_profile_quick_action_button).setOnClickListener(listener);
|
||||
}
|
||||
|
||||
private void bindOption(@IdRes final int rootId, @DrawableRes final int iconRes,
|
||||
@StringRes final int labelRes, final boolean destructive,
|
||||
final View.OnClickListener listener) {
|
||||
final View root = findViewById(rootId);
|
||||
final ImageView icon = root.findViewById(R.id.home_profile_option_icon);
|
||||
final TextView label = root.findViewById(R.id.home_profile_option_label);
|
||||
icon.setImageResource(iconRes);
|
||||
icon.setImageTintList(getColorStateList(destructive
|
||||
? R.color.home_destructive : R.color.home_text_primary));
|
||||
label.setText(labelRes);
|
||||
label.setTextColor(getColor(destructive
|
||||
? R.color.home_destructive : R.color.home_text_primary));
|
||||
root.setOnClickListener(listener);
|
||||
private void showOverflowMenu(final View anchor) {
|
||||
final PopupMenu menu = new PopupMenu(this, anchor, Gravity.END);
|
||||
menu.getMenu().add(R.string.home_profile_rename)
|
||||
.setOnMenuItemClickListener(item -> {
|
||||
showRenameDialog();
|
||||
return true;
|
||||
});
|
||||
menu.show();
|
||||
}
|
||||
|
||||
private void openConversation() {
|
||||
UIIntents.get().launchConversationActivity(this, mConversationId, null);
|
||||
finish();
|
||||
}
|
||||
|
||||
private ParticipantData getOtherParticipant() {
|
||||
|
||||
@@ -25,7 +25,6 @@ import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.IdRes;
|
||||
@@ -114,9 +113,8 @@ public class HomeSearchActivity extends BugleActionBarActivity {
|
||||
if (label != null) {
|
||||
label.setText(labelRes);
|
||||
}
|
||||
tile.setOnClickListener(v -> Toast.makeText(this,
|
||||
getString(R.string.home_search_coming_soon, getString(labelRes)),
|
||||
Toast.LENGTH_SHORT).show());
|
||||
tile.setOnClickListener(v -> HomeToast.show(this,
|
||||
getString(R.string.home_search_coming_soon, getString(labelRes))));
|
||||
}
|
||||
|
||||
private void updateClearButton() {
|
||||
@@ -132,8 +130,8 @@ public class HomeSearchActivity extends BugleActionBarActivity {
|
||||
return;
|
||||
}
|
||||
ImeUtil.get().hideImeKeyboard(this, mSearchField);
|
||||
Toast.makeText(this, getString(R.string.home_search_coming_soon,
|
||||
mSearchField.getText().toString()), Toast.LENGTH_SHORT).show();
|
||||
HomeToast.show(this, getString(R.string.home_search_coming_soon,
|
||||
mSearchField.getText().toString()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
package com.android.messaging.ui.home;
|
||||
|
||||
import android.content.res.ColorStateList;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.widget.SwitchCompat;
|
||||
|
||||
import com.android.messaging.R;
|
||||
|
||||
/** Binds a home-styled settings row (icon, title, optional subtitle, chevron or switch). */
|
||||
public final class HomeSettingsRow {
|
||||
|
||||
private HomeSettingsRow() {
|
||||
}
|
||||
|
||||
public static void bind(@NonNull final View row, @DrawableRes final int iconRes,
|
||||
@NonNull final CharSequence title, @Nullable final CharSequence subtitle,
|
||||
final boolean showChevron, final boolean showSwitch, final boolean switchOn,
|
||||
@Nullable final View.OnClickListener clickListener,
|
||||
@Nullable final CompoundButton.OnCheckedChangeListener switchListener) {
|
||||
final ImageView icon = row.findViewById(R.id.home_settings_row_icon);
|
||||
final TextView titleView = row.findViewById(R.id.home_settings_row_title);
|
||||
final TextView subtitleView = row.findViewById(R.id.home_settings_row_subtitle);
|
||||
final SwitchCompat toggle = row.findViewById(R.id.home_settings_row_switch);
|
||||
final View chevron = row.findViewById(R.id.home_settings_row_chevron);
|
||||
|
||||
icon.setImageResource(iconRes);
|
||||
icon.setImageTintList(ColorStateList.valueOf(
|
||||
row.getContext().getColor(R.color.home_text_primary)));
|
||||
titleView.setText(title);
|
||||
|
||||
if (subtitleView != null) {
|
||||
if (TextUtils.isEmpty(subtitle)) {
|
||||
subtitleView.setVisibility(View.GONE);
|
||||
} else {
|
||||
subtitleView.setVisibility(View.VISIBLE);
|
||||
subtitleView.setText(subtitle);
|
||||
}
|
||||
}
|
||||
if (chevron != null) {
|
||||
chevron.setVisibility(showChevron ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
if (toggle != null) {
|
||||
toggle.setOnCheckedChangeListener(null);
|
||||
toggle.setVisibility(showSwitch ? View.VISIBLE : View.GONE);
|
||||
toggle.setChecked(switchOn);
|
||||
toggle.setOnCheckedChangeListener(switchListener);
|
||||
}
|
||||
row.setOnClickListener(v -> {
|
||||
if (showSwitch && toggle != null) {
|
||||
toggle.toggle();
|
||||
}
|
||||
if (clickListener != null) {
|
||||
clickListener.onClick(v);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void setValue(@NonNull final View row, @Nullable final CharSequence value) {
|
||||
final TextView valueView = row.findViewById(R.id.home_settings_row_value);
|
||||
if (valueView == null) {
|
||||
return;
|
||||
}
|
||||
if (TextUtils.isEmpty(value)) {
|
||||
valueView.setVisibility(View.GONE);
|
||||
} else {
|
||||
valueView.setVisibility(View.VISIBLE);
|
||||
valueView.setText(value);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setDestructive(@NonNull final View row, final boolean destructive) {
|
||||
final int color = row.getContext().getColor(destructive
|
||||
? R.color.home_destructive : R.color.home_text_primary);
|
||||
final ColorStateList tint = ColorStateList.valueOf(color);
|
||||
final ImageView icon = row.findViewById(R.id.home_settings_row_icon);
|
||||
if (icon != null) {
|
||||
icon.setImageTintList(tint);
|
||||
}
|
||||
final TextView titleView = row.findViewById(R.id.home_settings_row_title);
|
||||
if (titleView != null) {
|
||||
titleView.setTextColor(color);
|
||||
}
|
||||
final TextView subtitleView = row.findViewById(R.id.home_settings_row_subtitle);
|
||||
if (subtitleView != null) {
|
||||
subtitleView.setTextColor(row.getContext().getColor(destructive
|
||||
? R.color.home_destructive : R.color.home_text_secondary));
|
||||
}
|
||||
}
|
||||
|
||||
public static void setEnabled(@NonNull final View row, final boolean enabled) {
|
||||
row.setEnabled(enabled);
|
||||
row.setAlpha(enabled ? 1f : 0.4f);
|
||||
final SwitchCompat toggle = row.findViewById(R.id.home_settings_row_switch);
|
||||
if (toggle != null) {
|
||||
toggle.setEnabled(enabled);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setSubtitle(@NonNull final View row, @Nullable final CharSequence subtitle) {
|
||||
final TextView subtitleView = row.findViewById(R.id.home_settings_row_subtitle);
|
||||
if (subtitleView == null) {
|
||||
return;
|
||||
}
|
||||
if (TextUtils.isEmpty(subtitle)) {
|
||||
subtitleView.setVisibility(View.GONE);
|
||||
} else {
|
||||
subtitleView.setVisibility(View.VISIBLE);
|
||||
subtitleView.setText(subtitle);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,6 @@ import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
import androidx.cursoradapter.widget.CursorAdapter;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.loader.app.LoaderManager;
|
||||
@@ -41,7 +40,7 @@ import com.android.messaging.util.Assert;
|
||||
public class HomeSpamFragment extends Fragment implements BlockedParticipantsDataListener {
|
||||
|
||||
private ListView mListView;
|
||||
private TextView mEmptyView;
|
||||
private HomeEmptyState mEmptyView;
|
||||
private BlockedParticipantListAdapter mAdapter;
|
||||
private final Binding<BlockedParticipantsData> mBinding = BindingBase.createBinding(this);
|
||||
|
||||
@@ -57,8 +56,11 @@ public class HomeSpamFragment extends Fragment implements BlockedParticipantsDat
|
||||
mBinding.getData().init(LoaderManager.getInstance(this), mBinding);
|
||||
|
||||
final View activityRoot = requireActivity().findViewById(R.id.home_spam_empty);
|
||||
if (activityRoot instanceof TextView) {
|
||||
mEmptyView = (TextView) activityRoot;
|
||||
if (activityRoot instanceof HomeEmptyState) {
|
||||
mEmptyView = (HomeEmptyState) activityRoot;
|
||||
mEmptyView.setContent(R.drawable.ic_home_menu_spam,
|
||||
R.string.home_empty_spam_title,
|
||||
R.string.home_empty_spam_body);
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
package com.android.messaging.ui.home;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.StringRes;
|
||||
|
||||
import com.android.messaging.Factory;
|
||||
import com.android.messaging.R;
|
||||
import com.android.messaging.ui.SnackBar;
|
||||
import com.android.messaging.ui.SnackBarManager;
|
||||
import com.android.messaging.util.UiUtils;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
/** Floating rounded toast used for every in-app toast and snackbar. */
|
||||
public final class HomeToast {
|
||||
private static final int SHORT_DURATION_MS = 2500;
|
||||
private static final int LONG_DURATION_MS = 4000;
|
||||
|
||||
private static boolean sTracking;
|
||||
private static WeakReference<Activity> sResumedActivity;
|
||||
|
||||
private HomeToast() {
|
||||
}
|
||||
|
||||
public static void install(final Application application) {
|
||||
if (application == null) {
|
||||
return;
|
||||
}
|
||||
ensureTracking(application);
|
||||
}
|
||||
|
||||
public static void show(final Context context, @StringRes final int messageId) {
|
||||
if (context == null) {
|
||||
return;
|
||||
}
|
||||
show(context, context.getString(messageId), Toast.LENGTH_SHORT);
|
||||
}
|
||||
|
||||
public static void show(final Context context, @StringRes final int messageId,
|
||||
final int duration) {
|
||||
if (context == null) {
|
||||
return;
|
||||
}
|
||||
show(context, context.getString(messageId), duration);
|
||||
}
|
||||
|
||||
public static void show(final Context context, final CharSequence message) {
|
||||
show(context, message, Toast.LENGTH_SHORT);
|
||||
}
|
||||
|
||||
public static void show(final Context context, final CharSequence message, final int duration) {
|
||||
if (context == null || TextUtils.isEmpty(message)) {
|
||||
return;
|
||||
}
|
||||
Activity activity = UiUtils.getActivity(context);
|
||||
if (activity == null) {
|
||||
activity = getResumedActivity();
|
||||
}
|
||||
final View parent = activity != null ? activity.getWindow().getDecorView() : null;
|
||||
if (parent != null && parent.getWindowToken() != null) {
|
||||
final SnackBar.Builder builder = SnackBarManager.get()
|
||||
.newBuilder(parent)
|
||||
.setText(message.toString())
|
||||
.setDuration(duration == Toast.LENGTH_LONG
|
||||
? LONG_DURATION_MS : SHORT_DURATION_MS);
|
||||
final View compose = activity.findViewById(R.id.message_compose_view_container);
|
||||
if (compose != null && compose.isShown()) {
|
||||
builder.withPlacement(SnackBar.Placement.above(compose));
|
||||
}
|
||||
builder.show();
|
||||
return;
|
||||
}
|
||||
Toast.makeText(context.getApplicationContext(), message, duration).show();
|
||||
}
|
||||
|
||||
private static Activity getResumedActivity() {
|
||||
if (!sTracking) {
|
||||
final Context appContext = Factory.get().getApplicationContext();
|
||||
if (appContext instanceof Application) {
|
||||
ensureTracking((Application) appContext);
|
||||
}
|
||||
}
|
||||
return sResumedActivity != null ? sResumedActivity.get() : null;
|
||||
}
|
||||
|
||||
private static void ensureTracking(final Application application) {
|
||||
if (sTracking) {
|
||||
return;
|
||||
}
|
||||
sTracking = true;
|
||||
application.registerActivityLifecycleCallbacks(
|
||||
new Application.ActivityLifecycleCallbacks() {
|
||||
@Override
|
||||
public void onActivityCreated(@NonNull Activity activity,
|
||||
Bundle savedInstanceState) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityStarted(@NonNull Activity activity) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResumed(@NonNull Activity activity) {
|
||||
sResumedActivity = new WeakReference<>(activity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityPaused(@NonNull Activity activity) {
|
||||
if (sResumedActivity != null && sResumedActivity.get() == activity) {
|
||||
sResumedActivity = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityStopped(@NonNull Activity activity) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivitySaveInstanceState(@NonNull Activity activity,
|
||||
@NonNull Bundle outState) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityDestroyed(@NonNull Activity activity) {
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,7 @@ public final class HomeUiMotion {
|
||||
private static final int POPOVER_IN_MS = 280;
|
||||
private static final int POPOVER_OUT_MS = 200;
|
||||
private static final int DOCK_SWAP_MS = 220;
|
||||
private static final int HOME_TAB_MS = 280;
|
||||
private static final int PROFILE_PANEL_MS = 320;
|
||||
private static final int UNKNOWN_SENDER_BANNER_MS = 340;
|
||||
private static final int LIST_PROFILE_POPUP_MS = 360;
|
||||
@@ -108,6 +109,177 @@ public final class HomeUiMotion {
|
||||
.start();
|
||||
}
|
||||
|
||||
/** Slides the home nav active pill to a tab's bounds. */
|
||||
public static void slideNavIndicator(final View indicator, final float x, final float y,
|
||||
final int width, final int height, final boolean animate) {
|
||||
if (indicator == null || width <= 0 || height <= 0) {
|
||||
return;
|
||||
}
|
||||
final Object running = indicator.getTag(R.id.home_nav_indicator);
|
||||
if (running instanceof ValueAnimator) {
|
||||
((ValueAnimator) running).cancel();
|
||||
indicator.setTag(R.id.home_nav_indicator, null);
|
||||
}
|
||||
indicator.animate().cancel();
|
||||
indicator.setVisibility(View.VISIBLE);
|
||||
if (!enabled() || !animate || indicator.getWidth() <= 0) {
|
||||
final ViewGroup.LayoutParams params = indicator.getLayoutParams();
|
||||
if (params != null) {
|
||||
params.width = width;
|
||||
params.height = height;
|
||||
indicator.setLayoutParams(params);
|
||||
}
|
||||
indicator.setX(x);
|
||||
indicator.setY(y);
|
||||
return;
|
||||
}
|
||||
final ViewGroup.LayoutParams params = indicator.getLayoutParams();
|
||||
final int startW = indicator.getWidth();
|
||||
final int startH = indicator.getHeight();
|
||||
final float startX = indicator.getX();
|
||||
final float startY = indicator.getY();
|
||||
final ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
|
||||
animator.setDuration(HOME_TAB_MS);
|
||||
animator.setInterpolator(EASE);
|
||||
animator.addUpdateListener(animation -> {
|
||||
final float t = (Float) animation.getAnimatedValue();
|
||||
if (params != null) {
|
||||
params.width = Math.round(startW + (width - startW) * t);
|
||||
params.height = Math.round(startH + (height - startH) * t);
|
||||
indicator.setLayoutParams(params);
|
||||
}
|
||||
indicator.setX(startX + (x - startX) * t);
|
||||
indicator.setY(startY + (y - startY) * t);
|
||||
});
|
||||
animator.addListener(new android.animation.AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(final android.animation.Animator animation) {
|
||||
if (indicator.getTag(R.id.home_nav_indicator) == animator) {
|
||||
indicator.setTag(R.id.home_nav_indicator, null);
|
||||
}
|
||||
}
|
||||
});
|
||||
indicator.setTag(R.id.home_nav_indicator, animator);
|
||||
animator.start();
|
||||
}
|
||||
|
||||
/** Crossfades two home pages with a short horizontal slide. */
|
||||
public static void crossfadeHomePages(final View outgoing, final View incoming,
|
||||
final boolean incomingFromEnd) {
|
||||
if (incoming != null) {
|
||||
incoming.animate().cancel();
|
||||
incoming.setVisibility(View.VISIBLE);
|
||||
}
|
||||
if (!enabled()) {
|
||||
if (incoming != null) {
|
||||
incoming.setAlpha(1f);
|
||||
incoming.setTranslationX(0f);
|
||||
}
|
||||
if (outgoing != null && outgoing != incoming) {
|
||||
outgoing.animate().cancel();
|
||||
outgoing.setVisibility(View.GONE);
|
||||
outgoing.setAlpha(1f);
|
||||
outgoing.setTranslationX(0f);
|
||||
}
|
||||
return;
|
||||
}
|
||||
final int slide = incoming != null
|
||||
? Math.max(incoming.getWidth() / 10, incoming.getResources()
|
||||
.getDimensionPixelSize(R.dimen.looply_space_xl))
|
||||
: 0;
|
||||
if (incoming != null) {
|
||||
incoming.setAlpha(0f);
|
||||
incoming.setTranslationX(incomingFromEnd ? slide : -slide);
|
||||
beginLayer(incoming);
|
||||
incoming.animate()
|
||||
.alpha(1f)
|
||||
.translationX(0f)
|
||||
.setDuration(HOME_TAB_MS)
|
||||
.setInterpolator(EASE)
|
||||
.withEndAction(() -> endLayer(incoming))
|
||||
.start();
|
||||
}
|
||||
if (outgoing != null && outgoing != incoming) {
|
||||
outgoing.animate().cancel();
|
||||
beginLayer(outgoing);
|
||||
outgoing.animate()
|
||||
.alpha(0f)
|
||||
.translationX(incomingFromEnd ? -slide : slide)
|
||||
.setDuration(HOME_TAB_MS)
|
||||
.setInterpolator(EASE)
|
||||
.withEndAction(() -> {
|
||||
outgoing.setVisibility(View.GONE);
|
||||
outgoing.setAlpha(1f);
|
||||
outgoing.setTranslationX(0f);
|
||||
endLayer(outgoing);
|
||||
})
|
||||
.start();
|
||||
}
|
||||
}
|
||||
|
||||
/** Fades a title, swaps the string, then fades back in. */
|
||||
public static void fadeSwapText(final android.widget.TextView title, final CharSequence text) {
|
||||
if (title == null) {
|
||||
return;
|
||||
}
|
||||
if (!enabled() || title.getVisibility() != View.VISIBLE) {
|
||||
title.setText(text);
|
||||
return;
|
||||
}
|
||||
title.animate().cancel();
|
||||
title.animate()
|
||||
.alpha(0f)
|
||||
.setDuration(HOME_TAB_MS / 2)
|
||||
.setInterpolator(EASE)
|
||||
.withEndAction(() -> {
|
||||
title.setText(text);
|
||||
title.animate()
|
||||
.alpha(1f)
|
||||
.setDuration(HOME_TAB_MS / 2)
|
||||
.setInterpolator(EASE)
|
||||
.start();
|
||||
})
|
||||
.start();
|
||||
}
|
||||
|
||||
public static void fadeSwapViews(final View outgoing, final View incoming) {
|
||||
if (incoming != null) {
|
||||
incoming.animate().cancel();
|
||||
incoming.setVisibility(View.VISIBLE);
|
||||
}
|
||||
if (!enabled()) {
|
||||
if (incoming != null) {
|
||||
incoming.setAlpha(1f);
|
||||
}
|
||||
if (outgoing != null && outgoing != incoming) {
|
||||
outgoing.animate().cancel();
|
||||
outgoing.setVisibility(View.GONE);
|
||||
outgoing.setAlpha(1f);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (incoming != null) {
|
||||
incoming.setAlpha(0f);
|
||||
incoming.animate()
|
||||
.alpha(1f)
|
||||
.setDuration(HOME_TAB_MS)
|
||||
.setInterpolator(EASE)
|
||||
.start();
|
||||
}
|
||||
if (outgoing != null && outgoing != incoming) {
|
||||
outgoing.animate().cancel();
|
||||
outgoing.animate()
|
||||
.alpha(0f)
|
||||
.setDuration(HOME_TAB_MS)
|
||||
.setInterpolator(EASE)
|
||||
.withEndAction(() -> {
|
||||
outgoing.setVisibility(View.GONE);
|
||||
outgoing.setAlpha(1f);
|
||||
})
|
||||
.start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Swaps two headers that share a vertical container, so they are never both laid out.
|
||||
*/
|
||||
@@ -751,6 +923,16 @@ public final class HomeUiMotion {
|
||||
|
||||
/** Slides the in-conversation gallery sheet up over a fading scrim. */
|
||||
public static void gallerySheetOpen(final View scrim, final View sheet) {
|
||||
gallerySheetOpen(scrim, sheet, 0f);
|
||||
}
|
||||
|
||||
public static void gallerySheetOpen(final View scrim, final View sheet,
|
||||
final float targetTranslationY) {
|
||||
gallerySheetOpen(scrim, sheet, targetTranslationY, null);
|
||||
}
|
||||
|
||||
public static void gallerySheetOpen(final View scrim, final View sheet,
|
||||
final float targetTranslationY, final Runnable onUpdate) {
|
||||
if (sheet == null) {
|
||||
return;
|
||||
}
|
||||
@@ -764,29 +946,86 @@ public final class HomeUiMotion {
|
||||
if (scrim != null) {
|
||||
scrim.setAlpha(1f);
|
||||
}
|
||||
sheet.setTranslationY(0f);
|
||||
sheet.setTranslationY(targetTranslationY);
|
||||
if (onUpdate != null) {
|
||||
onUpdate.run();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (scrim != null) {
|
||||
scrim.setAlpha(0f);
|
||||
}
|
||||
sheet.setTranslationY(height);
|
||||
if (onUpdate != null) {
|
||||
onUpdate.run();
|
||||
}
|
||||
if (scrim != null) {
|
||||
scrim.animate().alpha(1f).setDuration(GALLERY_SHEET_MS)
|
||||
.setInterpolator(EASE_MATERIAL).start();
|
||||
}
|
||||
beginLayer(sheet);
|
||||
sheet.animate()
|
||||
.translationY(0f)
|
||||
.translationY(targetTranslationY)
|
||||
.setDuration(GALLERY_SHEET_MS)
|
||||
.setInterpolator(EASE_MATERIAL)
|
||||
.withEndAction(() -> endLayer(sheet))
|
||||
.setUpdateListener(a -> {
|
||||
if (onUpdate != null) {
|
||||
onUpdate.run();
|
||||
}
|
||||
})
|
||||
.withEndAction(() -> {
|
||||
endLayer(sheet);
|
||||
if (onUpdate != null) {
|
||||
onUpdate.run();
|
||||
}
|
||||
})
|
||||
.start();
|
||||
}
|
||||
|
||||
public static void gallerySheetSnap(final View sheet, final float targetTranslationY) {
|
||||
gallerySheetSnap(sheet, targetTranslationY, null);
|
||||
}
|
||||
|
||||
public static void gallerySheetSnap(final View sheet, final float targetTranslationY,
|
||||
final Runnable onUpdate) {
|
||||
if (sheet == null) {
|
||||
return;
|
||||
}
|
||||
sheet.animate().cancel();
|
||||
if (!enabled()) {
|
||||
sheet.setTranslationY(targetTranslationY);
|
||||
if (onUpdate != null) {
|
||||
onUpdate.run();
|
||||
}
|
||||
return;
|
||||
}
|
||||
beginLayer(sheet);
|
||||
sheet.animate()
|
||||
.translationY(targetTranslationY)
|
||||
.setDuration(220)
|
||||
.setInterpolator(EASE_MATERIAL)
|
||||
.setUpdateListener(a -> {
|
||||
if (onUpdate != null) {
|
||||
onUpdate.run();
|
||||
}
|
||||
})
|
||||
.withEndAction(() -> {
|
||||
endLayer(sheet);
|
||||
if (onUpdate != null) {
|
||||
onUpdate.run();
|
||||
}
|
||||
})
|
||||
.start();
|
||||
}
|
||||
|
||||
/** Slides the gallery sheet down and fades the scrim; conversation stays visible. */
|
||||
public static void gallerySheetClose(final View scrim, final View sheet,
|
||||
final Runnable endAction) {
|
||||
gallerySheetClose(scrim, sheet, endAction, null);
|
||||
}
|
||||
|
||||
public static void gallerySheetClose(final View scrim, final View sheet,
|
||||
final Runnable endAction, final Runnable onUpdate) {
|
||||
final Runnable finish = once(endAction);
|
||||
if (sheet == null) {
|
||||
if (scrim != null) {
|
||||
@@ -800,6 +1039,9 @@ public final class HomeUiMotion {
|
||||
}
|
||||
sheet.animate().cancel();
|
||||
if (!enabled()) {
|
||||
if (onUpdate != null) {
|
||||
onUpdate.run();
|
||||
}
|
||||
finish.run();
|
||||
return;
|
||||
}
|
||||
@@ -814,8 +1056,16 @@ public final class HomeUiMotion {
|
||||
.translationY(height)
|
||||
.setDuration(GALLERY_SHEET_MS)
|
||||
.setInterpolator(EASE_MATERIAL)
|
||||
.setUpdateListener(a -> {
|
||||
if (onUpdate != null) {
|
||||
onUpdate.run();
|
||||
}
|
||||
})
|
||||
.withEndAction(() -> {
|
||||
endLayer(sheet);
|
||||
if (onUpdate != null) {
|
||||
onUpdate.run();
|
||||
}
|
||||
finish.run();
|
||||
})
|
||||
.start();
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.android.messaging.ui.home;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/** Persists conversation ids whose alerts are hidden. */
|
||||
public final class MutedConversationStore {
|
||||
private static final String PREFS_NAME = "home_muted_conversations";
|
||||
private static final String KEY_IDS = "muted_ids";
|
||||
|
||||
private MutedConversationStore() {
|
||||
}
|
||||
|
||||
public static boolean isMuted(final Context context, final String conversationId) {
|
||||
if (context == null || TextUtils.isEmpty(conversationId)) {
|
||||
return false;
|
||||
}
|
||||
return ids(context).contains(conversationId);
|
||||
}
|
||||
|
||||
public static boolean toggle(final Context context, final String conversationId) {
|
||||
if (context == null || TextUtils.isEmpty(conversationId)) {
|
||||
return false;
|
||||
}
|
||||
final Set<String> ids = new HashSet<>(ids(context));
|
||||
final boolean muted;
|
||||
if (ids.contains(conversationId)) {
|
||||
ids.remove(conversationId);
|
||||
muted = false;
|
||||
} else {
|
||||
ids.add(conversationId);
|
||||
muted = true;
|
||||
}
|
||||
prefs(context).edit().putStringSet(KEY_IDS, ids).apply();
|
||||
return muted;
|
||||
}
|
||||
|
||||
private static Set<String> ids(final Context context) {
|
||||
final Set<String> stored = prefs(context).getStringSet(KEY_IDS, null);
|
||||
return stored == null ? new HashSet<>() : stored;
|
||||
}
|
||||
|
||||
private static SharedPreferences prefs(final Context context) {
|
||||
return context.getApplicationContext()
|
||||
.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright (C) 2026 The LineageOS Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.messaging.ui.home;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/** Local starred-message flags until a messages-table column exists. */
|
||||
public final class StarredMessagesStore {
|
||||
|
||||
private static final String PREFS_NAME = "home_starred_messages";
|
||||
private static final String KEY_IDS = "message_ids";
|
||||
|
||||
private StarredMessagesStore() {
|
||||
}
|
||||
|
||||
public static boolean isStarred(final Context context, final String messageId) {
|
||||
if (TextUtils.isEmpty(messageId)) {
|
||||
return false;
|
||||
}
|
||||
return ids(context).contains(messageId);
|
||||
}
|
||||
|
||||
public static boolean toggle(final Context context, final String messageId) {
|
||||
if (TextUtils.isEmpty(messageId)) {
|
||||
return false;
|
||||
}
|
||||
final Set<String> ids = new HashSet<>(ids(context));
|
||||
final boolean starred;
|
||||
if (ids.contains(messageId)) {
|
||||
ids.remove(messageId);
|
||||
starred = false;
|
||||
} else {
|
||||
ids.add(messageId);
|
||||
starred = true;
|
||||
}
|
||||
prefs(context).edit().putStringSet(KEY_IDS, ids).apply();
|
||||
return starred;
|
||||
}
|
||||
|
||||
private static Set<String> ids(final Context context) {
|
||||
final Set<String> stored = prefs(context).getStringSet(KEY_IDS, null);
|
||||
return stored == null ? new HashSet<>() : stored;
|
||||
}
|
||||
|
||||
private static SharedPreferences prefs(final Context context) {
|
||||
return context.getApplicationContext().getSharedPreferences(PREFS_NAME,
|
||||
Context.MODE_PRIVATE);
|
||||
}
|
||||
}
|
||||
@@ -51,6 +51,10 @@ class CameraMediaChooser extends MediaChooser {
|
||||
private final ActivityResultLauncher<String> mRequestPermissionLauncher;
|
||||
private final DocumentImagePicker mDocumentImagePicker;
|
||||
private Uri mOutputUri;
|
||||
private static final int PENDING_NONE = 0;
|
||||
private static final int PENDING_PICTURE = 1;
|
||||
private static final int PENDING_VIDEO = 2;
|
||||
private int mPendingLaunch = PENDING_NONE;
|
||||
|
||||
CameraMediaChooser(final MediaPicker mediaPicker) {
|
||||
super(mediaPicker);
|
||||
@@ -61,15 +65,17 @@ class CameraMediaChooser extends MediaChooser {
|
||||
});
|
||||
mPictureLauncher = mediaPicker.registerForActivityResult(
|
||||
new ActivityResultContracts.TakePicture(), result -> {
|
||||
if (result) {
|
||||
final Uri output = mOutputUri;
|
||||
if (result && output != null) {
|
||||
List<Uri> list = new ArrayList<>();
|
||||
list.add(mOutputUri);
|
||||
list.add(output);
|
||||
mDocumentImagePicker.onDocumentsPicked(list);
|
||||
}
|
||||
});
|
||||
mVideoLauncher = mediaPicker.registerForActivityResult(
|
||||
new ActivityResultContracts.CaptureVideo(), result -> {
|
||||
if (result) {
|
||||
final Uri output = mOutputUri;
|
||||
if (result && output != null) {
|
||||
final Rect startRect = new Rect();
|
||||
// It's possible to throw out the chooser while taking the
|
||||
// picture/video. In that case, still use the attachment, just
|
||||
@@ -79,7 +85,7 @@ class CameraMediaChooser extends MediaChooser {
|
||||
}
|
||||
mMediaPicker.dispatchItemsSelected(
|
||||
new MediaPickerMessagePartData(startRect, VIDEO_UNSPECIFIED,
|
||||
mOutputUri, MessagingContentProvider.UNSPECIFIED_SIZE,
|
||||
output, MessagingContentProvider.UNSPECIFIED_SIZE,
|
||||
MessagingContentProvider.UNSPECIFIED_SIZE),
|
||||
true /* dismissMediaPicker */);
|
||||
}
|
||||
@@ -87,6 +93,11 @@ class CameraMediaChooser extends MediaChooser {
|
||||
mRequestPermissionLauncher = mediaPicker.registerForActivityResult(
|
||||
new ActivityResultContracts.RequestPermission(), granted -> {
|
||||
updateForPermissionState(granted);
|
||||
if (granted) {
|
||||
tryLaunchPending();
|
||||
} else {
|
||||
mPendingLaunch = PENDING_NONE;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -114,19 +125,9 @@ class CameraMediaChooser extends MediaChooser {
|
||||
mEnabledView = view.findViewById(R.id.mediapicker_enabled);
|
||||
mMissingPermissionView = view.findViewById(R.id.missing_permission_view);
|
||||
View takePictureView = view.findViewById(R.id.take_picture);
|
||||
takePictureView.setOnClickListener(v -> {
|
||||
mOutputUri = MediaScratchFileProvider.buildMediaScratchSpaceUri(null);
|
||||
mPictureLauncher.launch(mOutputUri);
|
||||
});
|
||||
takePictureView.setOnClickListener(v -> launchTakePicture());
|
||||
View takeVideoView = view.findViewById(R.id.take_video);
|
||||
takeVideoView.setOnClickListener(v -> {
|
||||
if (!OsUtil.hasRecordAudioPermission()) {
|
||||
requestRecordAudioPermission();
|
||||
} else {
|
||||
mOutputUri = MediaScratchFileProvider.buildMediaScratchSpaceUri(null);
|
||||
mVideoLauncher.launch(mOutputUri);
|
||||
}
|
||||
});
|
||||
takeVideoView.setOnClickListener(v -> launchTakeVideo());
|
||||
updateForPermissionState(hasCameraPermission());
|
||||
return view;
|
||||
}
|
||||
@@ -151,6 +152,42 @@ class CameraMediaChooser extends MediaChooser {
|
||||
}
|
||||
}
|
||||
|
||||
void launchTakePicture() {
|
||||
mPendingLaunch = PENDING_PICTURE;
|
||||
tryLaunchPending();
|
||||
}
|
||||
|
||||
void launchTakeVideo() {
|
||||
mPendingLaunch = PENDING_VIDEO;
|
||||
tryLaunchPending();
|
||||
}
|
||||
|
||||
private void tryLaunchPending() {
|
||||
if (mPendingLaunch == PENDING_NONE) {
|
||||
return;
|
||||
}
|
||||
if (!hasCameraPermission()) {
|
||||
requestCameraPermission();
|
||||
return;
|
||||
}
|
||||
if (mPendingLaunch == PENDING_VIDEO && !OsUtil.hasRecordAudioPermission()) {
|
||||
requestRecordAudioPermission();
|
||||
return;
|
||||
}
|
||||
final int pending = mPendingLaunch;
|
||||
mPendingLaunch = PENDING_NONE;
|
||||
mOutputUri = MediaScratchFileProvider.buildMediaScratchSpaceUri(null);
|
||||
try {
|
||||
if (pending == PENDING_PICTURE) {
|
||||
mPictureLauncher.launch(mOutputUri);
|
||||
} else {
|
||||
mVideoLauncher.launch(mOutputUri);
|
||||
}
|
||||
} catch (final RuntimeException e) {
|
||||
mOutputUri = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void requestCameraPermission() {
|
||||
mRequestPermissionLauncher.launch(Manifest.permission.CAMERA);
|
||||
}
|
||||
|
||||
@@ -65,6 +65,7 @@ public class DocumentImagePicker {
|
||||
private final SelectionListener mListener;
|
||||
|
||||
private final ActivityResultLauncher<String[]> mPermissionLauncher;
|
||||
private Runnable mAfterPermission;
|
||||
|
||||
/**
|
||||
* Creates a new instance of DocumentImagePicker.
|
||||
@@ -78,7 +79,17 @@ public class DocumentImagePicker {
|
||||
|
||||
mPermissionLauncher = mFragment.registerForActivityResult(
|
||||
new ActivityResultContracts.RequestMultiplePermissions(),
|
||||
result -> showGallerySheet());
|
||||
result -> {
|
||||
final Runnable after = mAfterPermission;
|
||||
mAfterPermission = null;
|
||||
if (after != null) {
|
||||
after.run();
|
||||
return;
|
||||
}
|
||||
if (OsUtil.hasMediaPermissions()) {
|
||||
showGallerySheet();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -89,6 +100,7 @@ public class DocumentImagePicker {
|
||||
if (activity == null) {
|
||||
return;
|
||||
}
|
||||
mAfterPermission = null;
|
||||
if (!OsUtil.hasMediaPermissions()) {
|
||||
mPermissionLauncher.launch(OsUtil.getMediaPermissions());
|
||||
return;
|
||||
@@ -96,6 +108,17 @@ public class DocumentImagePicker {
|
||||
showGallerySheet();
|
||||
}
|
||||
|
||||
public void requestPermission(final Runnable onGranted) {
|
||||
if (OsUtil.hasMediaPermissions()) {
|
||||
if (onGranted != null) {
|
||||
onGranted.run();
|
||||
}
|
||||
return;
|
||||
}
|
||||
mAfterPermission = onGranted;
|
||||
mPermissionLauncher.launch(OsUtil.getMediaPermissions());
|
||||
}
|
||||
|
||||
public void onDocumentsPicked(List<Uri> uris) {
|
||||
for (Uri uri: uris) {
|
||||
prepareDocumentForAttachment(uri);
|
||||
|
||||
@@ -186,6 +186,8 @@ public class MediaPicker extends Fragment implements DraftMessageSubscriptionDat
|
||||
private static final int OPEN_MODE_GALLERY = 2;
|
||||
private static final int OPEN_MODE_AUDIO = 3;
|
||||
private static final int OPEN_MODE_CONTACT = 4;
|
||||
private static final int OPEN_MODE_TAKE_PICTURE = 5;
|
||||
private static final int OPEN_MODE_TAKE_VIDEO = 6;
|
||||
|
||||
private boolean mIsAttached;
|
||||
private int mStartingMediaTypeOnAttach = MEDA_TYPE_INVALID;
|
||||
@@ -203,6 +205,12 @@ public class MediaPicker extends Fragment implements DraftMessageSubscriptionDat
|
||||
} else if (mDeferredOpenMode == OPEN_MODE_CONTACT) {
|
||||
launchContactPickerWithoutPanel();
|
||||
mDeferredOpenMode = OPEN_MODE_INVALID;
|
||||
} else if (mDeferredOpenMode == OPEN_MODE_TAKE_PICTURE) {
|
||||
launchCameraCaptureWithoutPanel(true);
|
||||
mDeferredOpenMode = OPEN_MODE_INVALID;
|
||||
} else if (mDeferredOpenMode == OPEN_MODE_TAKE_VIDEO) {
|
||||
launchCameraCaptureWithoutPanel(false);
|
||||
mDeferredOpenMode = OPEN_MODE_INVALID;
|
||||
} else if (mDeferredOpenMode != OPEN_MODE_INVALID && mDeferredOpenMode != OPEN_MODE_DEFAULT) {
|
||||
performOpenMode(mDeferredOpenMode, mAnimateOnAttach);
|
||||
mDeferredOpenMode = OPEN_MODE_INVALID;
|
||||
@@ -427,6 +435,38 @@ public class MediaPicker extends Fragment implements DraftMessageSubscriptionDat
|
||||
}
|
||||
}
|
||||
|
||||
/** Opens the system camera to take a photo without showing the in-app chooser panel. */
|
||||
public void openTakePicture() {
|
||||
if (mIsAttached) {
|
||||
launchCameraCaptureWithoutPanel(true);
|
||||
} else {
|
||||
mDeferredOpenMode = OPEN_MODE_TAKE_PICTURE;
|
||||
}
|
||||
}
|
||||
|
||||
/** Opens the system camera to record video without showing the in-app chooser panel. */
|
||||
public void openTakeVideo() {
|
||||
if (mIsAttached) {
|
||||
launchCameraCaptureWithoutPanel(false);
|
||||
} else {
|
||||
mDeferredOpenMode = OPEN_MODE_TAKE_VIDEO;
|
||||
}
|
||||
}
|
||||
|
||||
private void launchCameraCaptureWithoutPanel(final boolean picture) {
|
||||
hideInAppPanel();
|
||||
for (final MediaChooser chooser : mEnabledChoosers) {
|
||||
if (chooser instanceof CameraMediaChooser) {
|
||||
if (picture) {
|
||||
((CameraMediaChooser) chooser).launchTakePicture();
|
||||
} else {
|
||||
((CameraMediaChooser) chooser).launchTakeVideo();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void openWithMode(final int openMode, final boolean animate) {
|
||||
mOpen = true;
|
||||
mDeferredOpenMode = openMode;
|
||||
|
||||
+2
-3
@@ -29,8 +29,6 @@ import android.text.TextUtils;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.ShareActionProvider;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.android.ex.photo.PhotoViewController;
|
||||
import com.android.ex.photo.adapters.PhotoPagerAdapter;
|
||||
import com.android.ex.photo.loaders.PhotoBitmapLoaderInterface.BitmapResult;
|
||||
@@ -38,6 +36,7 @@ import com.android.messaging.R;
|
||||
import com.android.messaging.datamodel.ConversationImagePartsView.PhotoViewQuery;
|
||||
import com.android.messaging.datamodel.MediaScratchFileProvider;
|
||||
import com.android.messaging.ui.conversation.ConversationFragment;
|
||||
import com.android.messaging.ui.home.HomeToast;
|
||||
import com.android.messaging.util.Dates;
|
||||
import com.android.messaging.util.LogUtil;
|
||||
|
||||
@@ -153,7 +152,7 @@ public class BuglePhotoViewController extends PhotoViewController {
|
||||
final Context context = getActivity().getContext();
|
||||
final String error = context.getResources().getQuantityString(
|
||||
R.plurals.attachment_save_error, 1, 1);
|
||||
Toast.makeText(context, error, Toast.LENGTH_SHORT).show();
|
||||
HomeToast.show(context, error);
|
||||
return true;
|
||||
}
|
||||
final String photoUri = adapter.getPhotoUri(cursor);
|
||||
|
||||
@@ -95,10 +95,10 @@ public class ChangeDefaultSmsAppHelper {
|
||||
} else {
|
||||
UiUtils.showSnackBarWithCustomAction(activity,
|
||||
rootView,
|
||||
activity.getString(sending ? R.string.requires_default_sms_app_to_send :
|
||||
R.string.requires_default_sms_app),
|
||||
activity.getString(sending ? R.string.home_requires_default_sms_app_to_send :
|
||||
R.string.home_requires_default_sms_app),
|
||||
SnackBar.Action.createCustomAction(mChangeSmsAppSettingRunnable,
|
||||
activity.getString(R.string.requires_default_sms_change_button)),
|
||||
activity.getString(R.string.home_requires_default_sms_change)),
|
||||
null /* interactions */,
|
||||
SnackBar.Placement.above(composeView));
|
||||
}
|
||||
|
||||
@@ -18,9 +18,12 @@
|
||||
package com.android.messaging.util;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.content.ContentUris;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Looper;
|
||||
import android.provider.ContactsContract;
|
||||
import android.provider.ContactsContract.CommonDataKinds.Email;
|
||||
import android.provider.ContactsContract.CommonDataKinds.Phone;
|
||||
@@ -214,9 +217,12 @@ public class ContactUtil {
|
||||
return false;
|
||||
}
|
||||
Uri lookupUri = null;
|
||||
if (contactId > ParticipantData.PARTICIPANT_CONTACT_ID_NOT_RESOLVED
|
||||
&& !TextUtils.isEmpty(contactLookupKey)) {
|
||||
lookupUri = Contacts.getLookupUri(contactId, contactLookupKey);
|
||||
if (contactId > ParticipantData.PARTICIPANT_CONTACT_ID_NOT_RESOLVED) {
|
||||
if (!TextUtils.isEmpty(contactLookupKey)) {
|
||||
lookupUri = Contacts.getLookupUri(contactId, contactLookupKey);
|
||||
} else {
|
||||
lookupUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
|
||||
}
|
||||
}
|
||||
if (lookupUri == null && !TextUtils.isEmpty(normalizedDestination)) {
|
||||
lookupUri = resolveLookupUriFromDestination(context, normalizedDestination);
|
||||
@@ -243,6 +249,11 @@ public class ContactUtil {
|
||||
|| !hasReadContactsPermission()) {
|
||||
return false;
|
||||
}
|
||||
// Options menus and list binds run on the UI thread. A contacts lookup
|
||||
// would trip Assert.isNotMainThread() on debug and ANR on release.
|
||||
if (Looper.myLooper() == Looper.getMainLooper()) {
|
||||
return false;
|
||||
}
|
||||
Cursor cursor = null;
|
||||
try {
|
||||
cursor = lookupDestination(context, normalizedDestination).performSynchronousQuery();
|
||||
@@ -256,7 +267,7 @@ public class ContactUtil {
|
||||
|
||||
private static Uri resolveLookupUriFromDestination(final Context context,
|
||||
final String normalizedDestination) {
|
||||
if (!hasReadContactsPermission()) {
|
||||
if (!hasReadContactsPermission() || Looper.myLooper() == Looper.getMainLooper()) {
|
||||
return null;
|
||||
}
|
||||
Cursor cursor = null;
|
||||
@@ -280,12 +291,25 @@ public class ContactUtil {
|
||||
}
|
||||
|
||||
private static boolean startContactDetailActivity(final Context context, final Uri lookupUri) {
|
||||
final Intent intent = new Intent(Intent.ACTION_VIEW, lookupUri);
|
||||
final Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
intent.setDataAndType(lookupUri, Contacts.CONTENT_ITEM_TYPE);
|
||||
if (!(context instanceof Activity)) {
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
}
|
||||
try {
|
||||
context.startActivity(intent);
|
||||
return true;
|
||||
} catch (final ActivityNotFoundException e) {
|
||||
return false;
|
||||
final Intent fallback = new Intent(Intent.ACTION_VIEW, lookupUri);
|
||||
if (!(context instanceof Activity)) {
|
||||
fallback.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
}
|
||||
try {
|
||||
context.startActivity(fallback);
|
||||
return true;
|
||||
} catch (final ActivityNotFoundException ignored) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -72,16 +72,7 @@ public class Dates {
|
||||
return getContext().getResources().getText(R.string.posted_just_now);
|
||||
}
|
||||
|
||||
// Workaround for b/5657035. The platform method {@link DateUtils#getRelativeTimeSpan()}
|
||||
// passes a null context to other platform methods. However, on some devices, this
|
||||
// context is dereferenced when it shouldn't be and an NPE is thrown. We catch that
|
||||
// here and use a slightly less precise time.
|
||||
try {
|
||||
return DateUtils.getRelativeTimeSpanString(time, now, DateUtils.MINUTE_IN_MILLIS,
|
||||
DateUtils.FORMAT_ABBREV_RELATIVE).toString();
|
||||
} catch (final NullPointerException npe) {
|
||||
return getShortRelativeTimeSpanString(time);
|
||||
}
|
||||
return getShortRelativeTimeSpanString(time);
|
||||
}
|
||||
|
||||
public static CharSequence getConversationTimeString(final long time) {
|
||||
@@ -108,8 +99,7 @@ public class Dates {
|
||||
} else {
|
||||
flags = FORCE_12_HOUR;
|
||||
}
|
||||
return getOlderThanAYearTimestamp(time,
|
||||
context.getResources().getConfiguration().getLocales().get(0),
|
||||
return getOlderThanAYearTimestamp(time, Locale.US,
|
||||
false /*abbreviated*/, flags);
|
||||
}
|
||||
|
||||
@@ -123,8 +113,7 @@ public class Dates {
|
||||
flags = FORCE_12_HOUR;
|
||||
}
|
||||
return getTimestamp(time, System.currentTimeMillis(), abbreviated,
|
||||
context.getResources().getConfiguration().getLocales().get(0), flags,
|
||||
minPeriodToday);
|
||||
Locale.US, flags, minPeriodToday);
|
||||
}
|
||||
|
||||
public static CharSequence getTimestamp(final long time, final long now,
|
||||
@@ -162,17 +151,16 @@ public class Dates {
|
||||
}
|
||||
|
||||
private static CharSequence getTodayTimeStamp(final long time, final int flags) {
|
||||
return DateUtils.formatDateTime(getContext(), time,
|
||||
DateUtils.FORMAT_SHOW_TIME | flags);
|
||||
return getExplicitFormattedTime(time, flags, "HH:mm", "h:mm a");
|
||||
}
|
||||
|
||||
private static CharSequence getExplicitFormattedTime(final long time, final int flags,
|
||||
final String format24, final String format12) {
|
||||
SimpleDateFormat formatter;
|
||||
if ((flags & FORCE_24_HOUR) == FORCE_24_HOUR) {
|
||||
formatter = new SimpleDateFormat(format24, Locale.getDefault());
|
||||
formatter = new SimpleDateFormat(format24, Locale.US);
|
||||
} else {
|
||||
formatter = new SimpleDateFormat(format12, Locale.getDefault());
|
||||
formatter = new SimpleDateFormat(format12, Locale.US);
|
||||
}
|
||||
return formatter.format(new Date(time));
|
||||
}
|
||||
|
||||
@@ -72,10 +72,21 @@ public class ImeUtil {
|
||||
|
||||
final InputMethodManager inputMethodManager =
|
||||
(InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
if (inputMethodManager != null) {
|
||||
v.requestFocus();
|
||||
inputMethodManager.showSoftInput(v, 0 /* flags */);
|
||||
if (inputMethodManager == null) {
|
||||
return;
|
||||
}
|
||||
v.setFocusable(true);
|
||||
v.setFocusableInTouchMode(true);
|
||||
v.requestFocus();
|
||||
final Runnable showKeyboard = () -> {
|
||||
if (!v.hasFocus()) {
|
||||
v.requestFocus();
|
||||
}
|
||||
inputMethodManager.restartInput(v);
|
||||
inputMethodManager.showSoftInput(v, InputMethodManager.SHOW_FORCED);
|
||||
};
|
||||
v.post(showKeyboard);
|
||||
v.postDelayed(showKeyboard, 120);
|
||||
}
|
||||
|
||||
public static void hideSoftInput(@NonNull final Context context, @NonNull final View v) {
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
package com.android.messaging.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.telephony.SubscriptionInfo;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.android.messaging.R;
|
||||
import com.android.messaging.datamodel.data.ParticipantData;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public final class SimNameUtil {
|
||||
private SimNameUtil() {
|
||||
}
|
||||
|
||||
public static boolean shouldShowSimName(final Context context) {
|
||||
if ((context.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
return PhoneUtils.getDefault().getActiveSubscriptionCount() >= 2;
|
||||
} catch (final Exception ignored) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static String getSimDisplayName(final Context context, final String selfId) {
|
||||
List<SubscriptionInfo> infos;
|
||||
try {
|
||||
infos = PhoneUtils.getDefault().getActiveSubscriptionInfoList();
|
||||
} catch (final Exception ignored) {
|
||||
infos = null;
|
||||
}
|
||||
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)) {
|
||||
for (final SubscriptionInfo info : infos) {
|
||||
final String participantId =
|
||||
ParticipantData.getCachedParticipantId(info.getSubscriptionId());
|
||||
if (TextUtils.equals(participantId, selfId)) {
|
||||
match = info;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return formatName(context, match);
|
||||
}
|
||||
|
||||
private static final int NAME_SOURCE_USER_INPUT = 2;
|
||||
|
||||
private static String formatName(final Context context, final SubscriptionInfo info) {
|
||||
final CharSequence displayName = info.getDisplayName();
|
||||
if (isUserAssignedName(info) && !TextUtils.isEmpty(displayName)) {
|
||||
return displayName.toString();
|
||||
}
|
||||
return context.getString(R.string.sim_slot_identifier,
|
||||
String.valueOf(Math.max(0, info.getSimSlotIndex()) + 1));
|
||||
}
|
||||
|
||||
private static boolean isUserAssignedName(final SubscriptionInfo info) {
|
||||
try {
|
||||
final Object source = SubscriptionInfo.class.getMethod("getNameSource").invoke(info);
|
||||
if (source instanceof Integer) {
|
||||
return ((Integer) source) == NAME_SOURCE_USER_INPUT;
|
||||
}
|
||||
} catch (final Exception ignored) {
|
||||
}
|
||||
final CharSequence displayName = info.getDisplayName();
|
||||
return !TextUtils.isEmpty(displayName)
|
||||
&& !isCarrierLike(displayName, info.getCarrierName())
|
||||
&& !isKnownCarrierLabel(displayName);
|
||||
}
|
||||
|
||||
private static boolean isCarrierLike(final CharSequence displayName,
|
||||
final CharSequence carrierName) {
|
||||
if (TextUtils.isEmpty(displayName) || TextUtils.isEmpty(carrierName)) {
|
||||
return false;
|
||||
}
|
||||
final String display = normalizeLabel(displayName);
|
||||
final String carrier = normalizeLabel(carrierName);
|
||||
return display.equals(carrier) || carrier.contains(display) || display.contains(carrier);
|
||||
}
|
||||
|
||||
private static boolean isKnownCarrierLabel(final CharSequence displayName) {
|
||||
final String display = normalizeLabel(displayName);
|
||||
return display.equals("lgu")
|
||||
|| display.equals("lguplus")
|
||||
|| display.equals("lgupluslte")
|
||||
|| display.equals("kt")
|
||||
|| display.equals("olleh")
|
||||
|| display.equals("skt")
|
||||
|| display.equals("sktelecom")
|
||||
|| display.startsWith("lgu")
|
||||
|| display.startsWith("skt")
|
||||
|| display.startsWith("sktelecom");
|
||||
}
|
||||
|
||||
private static String normalizeLabel(final CharSequence value) {
|
||||
return value.toString().replaceAll("[\\s+\\-_.]", "").toLowerCase();
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,6 @@ import android.graphics.Color;
|
||||
import android.graphics.Rect;
|
||||
import android.text.TextPaint;
|
||||
import android.text.TextUtils;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.View.OnLayoutChangeListener;
|
||||
import android.view.animation.Animation;
|
||||
@@ -40,6 +39,7 @@ import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.messaging.Factory;
|
||||
import com.android.messaging.R;
|
||||
import com.android.messaging.ui.home.HomeToast;
|
||||
import com.android.messaging.ui.SnackBar;
|
||||
import com.android.messaging.ui.SnackBar.Placement;
|
||||
import com.android.messaging.ui.SnackBarInteraction;
|
||||
@@ -73,31 +73,24 @@ public class UiUtils {
|
||||
|
||||
/** Show a simple toast at the bottom */
|
||||
public static void showToastAtBottom(final int messageId) {
|
||||
UiUtils.showToastAtBottom(getApplicationContext().getString(messageId));
|
||||
HomeToast.show(getApplicationContext(), messageId, Toast.LENGTH_LONG);
|
||||
}
|
||||
|
||||
/** Show a simple toast at the bottom */
|
||||
public static void showToastAtBottom(final String message) {
|
||||
final Toast toast = Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG);
|
||||
toast.setGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, 0, 0);
|
||||
toast.show();
|
||||
HomeToast.show(getApplicationContext(), message, Toast.LENGTH_LONG);
|
||||
}
|
||||
|
||||
/** Show a simple toast at the default position */
|
||||
public static void showToast(final int messageId) {
|
||||
final Toast toast = Toast.makeText(getApplicationContext(),
|
||||
getApplicationContext().getString(messageId), Toast.LENGTH_LONG);
|
||||
toast.setGravity(Gravity.CENTER_HORIZONTAL, 0, 0);
|
||||
toast.show();
|
||||
HomeToast.show(getApplicationContext(), messageId, Toast.LENGTH_LONG);
|
||||
}
|
||||
|
||||
/** Show a simple toast at the default position */
|
||||
public static void showToast(final int pluralsMessageId, final int count) {
|
||||
final Toast toast = Toast.makeText(getApplicationContext(),
|
||||
HomeToast.show(getApplicationContext(),
|
||||
getApplicationContext().getResources().getQuantityString(pluralsMessageId, count),
|
||||
Toast.LENGTH_LONG);
|
||||
toast.setGravity(Gravity.CENTER_HORIZONTAL, 0, 0);
|
||||
toast.show();
|
||||
}
|
||||
|
||||
public static void showSnackBar(final Context context, @NonNull final View parentView,
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/home_bg" />
|
||||
<corners android:radius="@dimen/looply_radius_pill" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="@color/home_divider" />
|
||||
</shape>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval">
|
||||
<solid android:color="@color/home_bg" />
|
||||
</shape>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<gradient
|
||||
android:angle="90"
|
||||
android:endColor="@android:color/transparent"
|
||||
android:startColor="@color/home_conversation_canvas"
|
||||
android:type="linear" />
|
||||
</shape>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<gradient
|
||||
android:angle="270"
|
||||
android:endColor="@android:color/transparent"
|
||||
android:startColor="@color/home_conversation_canvas"
|
||||
android:type="linear" />
|
||||
</shape>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/home_conversation_canvas" />
|
||||
<corners android:radius="20dp" />
|
||||
<stroke
|
||||
android:width="0.5dp"
|
||||
android:color="@color/home_button_stroke" />
|
||||
</shape>
|
||||
@@ -1,36 +1,33 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- Shadow spread (may extend past view bounds) -->
|
||||
<item
|
||||
android:bottom="-2dp"
|
||||
android:left="4dp"
|
||||
android:right="4dp"
|
||||
android:top="5dp">
|
||||
android:left="5dp"
|
||||
android:right="5dp"
|
||||
android:top="6dp">
|
||||
<shape android:shape="oval">
|
||||
<gradient
|
||||
android:angle="270"
|
||||
android:centerColor="@color/home_shadow_spread_mid"
|
||||
android:centerY="0.4"
|
||||
android:centerY="0.35"
|
||||
android:endColor="@android:color/transparent"
|
||||
android:startColor="@color/home_shadow_spread_outer"
|
||||
android:type="linear" />
|
||||
</shape>
|
||||
</item>
|
||||
<!-- Inner shadow: square inset so oval renders as a true circle -->
|
||||
<item
|
||||
android:bottom="7dp"
|
||||
android:left="@dimen/home_dock_fab_circle_inset"
|
||||
android:right="@dimen/home_dock_fab_circle_inset"
|
||||
android:top="1dp">
|
||||
android:bottom="1dp"
|
||||
android:left="3dp"
|
||||
android:right="3dp"
|
||||
android:top="3dp">
|
||||
<shape android:shape="oval">
|
||||
<solid android:color="@color/home_shadow_spread_inner" />
|
||||
</shape>
|
||||
</item>
|
||||
<!-- Face -->
|
||||
<item
|
||||
android:bottom="@dimen/home_dock_fab_shadow_depth"
|
||||
android:left="@dimen/home_dock_fab_circle_inset"
|
||||
android:right="@dimen/home_dock_fab_circle_inset"
|
||||
android:bottom="@dimen/home_bottom_dock_shadow_inset"
|
||||
android:left="3dp"
|
||||
android:right="3dp"
|
||||
android:top="0dp">
|
||||
<shape android:shape="oval">
|
||||
<solid android:color="@color/home_surface_translucent" />
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval">
|
||||
<solid android:color="#66FFFFFF" />
|
||||
<stroke
|
||||
android:width="1.5dp"
|
||||
android:color="#FFFFFFFF" />
|
||||
</shape>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval">
|
||||
<solid android:color="@color/home_accent" />
|
||||
</shape>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<corners android:radius="22dp" />
|
||||
<solid android:color="@color/home_bg" />
|
||||
<stroke
|
||||
android:width="0.5dp"
|
||||
android:color="@color/home_button_stroke" />
|
||||
</shape>
|
||||
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<gradient
|
||||
android:angle="270"
|
||||
android:centerColor="#40FFFFFF"
|
||||
android:centerY="0.4"
|
||||
android:endColor="@android:color/transparent"
|
||||
android:startColor="#99FFFFFF"
|
||||
android:type="linear" />
|
||||
</shape>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval">
|
||||
<solid android:color="@color/home_bg" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="@color/home_divider" />
|
||||
</shape>
|
||||
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:bottom="-2dp"
|
||||
android:left="5dp"
|
||||
android:right="5dp"
|
||||
android:top="6dp">
|
||||
<shape android:shape="oval">
|
||||
<gradient
|
||||
android:angle="270"
|
||||
android:centerColor="@color/home_shadow_spread_mid"
|
||||
android:centerY="0.35"
|
||||
android:endColor="@android:color/transparent"
|
||||
android:startColor="@color/home_shadow_spread_outer"
|
||||
android:type="linear" />
|
||||
</shape>
|
||||
</item>
|
||||
<item
|
||||
android:bottom="1dp"
|
||||
android:left="3dp"
|
||||
android:right="3dp"
|
||||
android:top="3dp">
|
||||
<shape android:shape="oval">
|
||||
<solid android:color="@color/home_shadow_spread_inner" />
|
||||
</shape>
|
||||
</item>
|
||||
<item
|
||||
android:bottom="@dimen/home_bottom_dock_shadow_inset"
|
||||
android:left="3dp"
|
||||
android:right="3dp"
|
||||
android:top="0dp">
|
||||
<shape android:shape="oval">
|
||||
<solid android:color="@color/home_surface_translucent" />
|
||||
<stroke
|
||||
android:width="0.5dp"
|
||||
android:color="@color/home_button_stroke" />
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/home_row_bg" />
|
||||
<corners android:radius="24dp" />
|
||||
</shape>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/home_row_bg" />
|
||||
<corners android:radius="16dp" />
|
||||
<stroke
|
||||
android:width="0.5dp"
|
||||
android:color="@color/home_button_stroke" />
|
||||
</shape>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/home_accent_soft" />
|
||||
<corners android:radius="16dp" />
|
||||
</shape>
|
||||
@@ -4,7 +4,7 @@
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<corners android:radius="@dimen/looply_radius_md" />
|
||||
<solid android:color="@color/home_bg" />
|
||||
<solid android:color="@color/home_row_bg" />
|
||||
</shape>
|
||||
</item>
|
||||
</ripple>
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Full-height sheet: no top corner radius when aligned under the status bar. -->
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/home_bg" />
|
||||
</shape>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval">
|
||||
<solid android:color="@color/home_swipe_circle" />
|
||||
</shape>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval">
|
||||
<solid android:color="@color/home_swipe_circle_delete" />
|
||||
</shape>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval">
|
||||
<solid android:color="@color/looply_primary" />
|
||||
</shape>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/home_toast_bg" />
|
||||
<corners android:radius="24dp" />
|
||||
</shape>
|
||||
@@ -1,14 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
SPDX-FileCopyrightText: Material Design Authors / Google LLC
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
android:viewportWidth="48"
|
||||
android:viewportHeight="48">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M3 3H21V7H3V3M4 21V8H20V21H4M14 14V11H10V14H7L12 19L17 14H14Z" />
|
||||
android:pathData="M26,18V29.2l2.6,-2.6a2,2 0,0 1,3.1 0.2,2.2 2.2,0 0,1 -0.4,2.7l-5.9,5.9a1.9,1.9 0,0 1,-2.8 0l-5.9,-5.9a2.2,2.2 0,0 1,-0.4 -2.7,2 2,0 0,1 3.1,-0.2L22,29.2V18H4V40a4,4 0,0 0,4 4H40a4,4 0,0 0,4 -4V18Z" />
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M43.9,14H4.1a3.6,3.6 0,0 1,0.7 -1.7l5,-6.7A3.9,3.9 0,0 1,13 4H35a3.9,3.9 0,0 1,3.2 1.6l5,6.7A3.6,3.6 0,0 1,43.9 14Z" />
|
||||
</vector>
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
SPDX-FileCopyrightText: Material Design Authors / Google LLC
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M20,11V13H8L13.5,18.5L12.08,19.92L4.16,12L12.08,4.08L13.5,5.5L8,11H20Z" />
|
||||
android:viewportWidth="1024"
|
||||
android:viewportHeight="1024">
|
||||
<!-- Path centroid sits right/low of 512; shift so the chevron reads centered. -->
|
||||
<group
|
||||
android:translateX="-24"
|
||||
android:translateY="-33">
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M669.6,849.6c8.8,8 22.4,7.2 30.4,-1.6s7.2,-22.4 -1.6,-30.4l-309.6,-280c-8,-7.2 -8,-17.6 0,-24.8l309.6,-270.4c8.8,-8 9.6,-21.6 2.4,-30.4 -8,-8.8 -21.6,-9.6 -30.4,-2.4L360.8,480.8c-27.2,24 -28,64 -0.8,88.8l309.6,280z"
|
||||
android:strokeWidth="56"
|
||||
android:strokeColor="#FFFFFF" />
|
||||
</group>
|
||||
</vector>
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<group
|
||||
android:pivotX="12"
|
||||
android:pivotY="12"
|
||||
android:scaleX="1.1"
|
||||
android:scaleY="1.1">
|
||||
<path
|
||||
android:fillColor="@android:color/transparent"
|
||||
android:pathData="M4.5,8.5L7,4.5h10l2.5,4"
|
||||
android:strokeWidth="2"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" />
|
||||
<path
|
||||
android:fillColor="@android:color/transparent"
|
||||
android:pathData="M4.5,8.5h15v10a2,2 0,0 1,-2 2h-11a2,2 0,0 1,-2 -2z"
|
||||
android:strokeWidth="2"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" />
|
||||
<path
|
||||
android:fillColor="@android:color/transparent"
|
||||
android:pathData="M12,11.5v6M9.4,15.2L12,17.7L14.6,15.2"
|
||||
android:strokeWidth="2"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" />
|
||||
</group>
|
||||
</vector>
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/transparent"
|
||||
android:pathData="M12,5v14M5,12h14"
|
||||
android:strokeWidth="2.6"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeLineCap="round" />
|
||||
</vector>
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/transparent"
|
||||
android:pathData="M3,3L21,21M9.37747,3.56325C10.1871,3.19604 11.0827,3 12,3C13.5913,3 15.1174,3.59 16.2426,4.6402C17.3679,5.69041 18,7.11479 18,8.6C18,10.3566 18.2892,11.7759 18.712,12.9122M17,17H15M6.45339,6.46451C6.15686,7.13542 6,7.86016 6,8.6C6,11.2862 5.3238,13.1835 4.52745,14.4866C3.75616,15.7486 3.37051,16.3797 3.38485,16.5436C3.40095,16.7277 3.43729,16.7925 3.58603,16.9023C3.71841,17 4.34762,17 5.60605,17H9M9,17V18C9,19.6569 10.3431,21 12,21C13.6569,21 15,19.6569 15,18V17M9,17H15"
|
||||
android:strokeWidth="2"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" />
|
||||
</vector>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M3,6.5A1.5,1.5 0,1 0,3 3.5,1.5 1.5,0 0,0 3,6.5zM7,4h14v2H7V4zM3,13.5A1.5,1.5 0,1 0,3 10.5,1.5 1.5,0 0,0 3,13.5zM7,11h14v2H7V11zM3,20.5A1.5,1.5 0,1 0,3 17.5,1.5 1.5,0 0,0 3,20.5zM7,18h14v2H7V18z" />
|
||||
</vector>
|
||||
@@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<group
|
||||
android:pivotX="12"
|
||||
android:pivotY="12"
|
||||
android:scaleX="1.1"
|
||||
android:scaleY="1.1">
|
||||
<path
|
||||
android:fillColor="@android:color/transparent"
|
||||
android:pathData="M5,7h14"
|
||||
android:strokeWidth="2"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeLineCap="round" />
|
||||
<path
|
||||
android:fillColor="@android:color/transparent"
|
||||
android:pathData="M9.2,4.6h5.6"
|
||||
android:strokeWidth="2"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeLineCap="round" />
|
||||
<path
|
||||
android:fillColor="@android:color/transparent"
|
||||
android:pathData="M7.2,7v10.2a2,2 0,0 0,2 2h5.6a2,2 0,0 0,2 -2V7"
|
||||
android:strokeWidth="2"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" />
|
||||
<path
|
||||
android:fillColor="@android:color/transparent"
|
||||
android:pathData="M10.2,10.4v5M13.8,10.4v5"
|
||||
android:strokeWidth="2"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeLineCap="round" />
|
||||
</group>
|
||||
</vector>
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/transparent"
|
||||
android:pathData="M14,3H7a2,2 0,0 0,-2 2v14a2,2 0,0 0,2 2h10a2,2 0,0 0,2 -2V8z"
|
||||
android:strokeWidth="1.5"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" />
|
||||
<path
|
||||
android:fillColor="@android:color/transparent"
|
||||
android:pathData="M14,3v5h5"
|
||||
android:strokeWidth="1.5"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" />
|
||||
</vector>
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<group
|
||||
android:pivotX="12"
|
||||
android:pivotY="12"
|
||||
android:scaleX="1.06"
|
||||
android:scaleY="1.06">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M15.124 4.88497C15.1368 4.89639 15.1497 4.90785 15.1626 4.91933L19.1765 8.48717C19.9254 9.15286 20.5463 9.7047 20.9726 10.2057C21.4206 10.7322 21.7396 11.299 21.7396 12.0005C21.7396 12.7019 21.4206 13.2687 20.9726 13.7952C20.5463 14.2962 19.9254 14.848 19.1765 15.5137L15.1626 19.0816C15.1497 19.0931 15.1368 19.1045 15.124 19.1159C14.7996 19.4044 14.4968 19.6736 14.2352 19.8464C13.9783 20.016 13.5112 20.2623 12.9879 20.0273C12.4647 19.7923 12.3385 19.2796 12.2946 18.9749C12.2499 18.6646 12.2499 18.2595 12.25 17.8253C12.25 17.8081 12.25 17.7909 12.25 17.7736V16.2051C10.7985 16.3051 9.32763 16.6906 8.04102 17.3175C6.52808 18.0547 5.31979 19.1 4.66405 20.3491C4.50365 20.6546 4.1555 20.8112 3.82047 20.7286C3.48544 20.646 3.25 20.3455 3.25 20.0005C3.25 15.2795 4.63138 12.2055 6.6065 10.3157C8.3281 8.66845 10.4264 7.99252 12.25 7.85106V6.2273C12.25 6.21002 12.25 6.19278 12.25 6.17558C12.2499 5.74145 12.2499 5.33628 12.2946 5.02602C12.3385 4.72135 12.4647 4.20856 12.9879 3.97357C13.5112 3.73859 13.9783 3.98495 14.2352 4.15455C14.4968 4.32726 14.7996 4.59649 15.124 4.88497ZM13.7531 5.67741C13.8661 5.77431 14.0011 5.89382 14.1661 6.04044L18.1384 9.57137C18.9395 10.2835 19.48 10.7662 19.8302 11.1778C20.1665 11.573 20.2396 11.8038 20.2396 12.0005C20.2396 12.1971 20.1665 12.4279 19.8302 12.8231C19.48 13.2347 18.9395 13.7174 18.1384 14.4295L14.1661 17.9605C14.0011 18.1071 13.8661 18.2266 13.7531 18.3235C13.7504 18.1746 13.75 17.9943 13.75 17.7736V15.429C13.75 15.0148 13.4142 14.679 13 14.679C11.0874 14.679 9.09995 15.133 7.38398 15.9691C6.46516 16.4168 5.6098 16.9815 4.88393 17.6572C5.25269 14.6103 6.34242 12.6444 7.6435 11.3995C9.26355 9.84942 11.3104 9.32188 13 9.32188C13.4142 9.32188 13.75 8.9861 13.75 8.57188V6.2273C13.75 6.0066 13.7504 5.8263 13.7531 5.67741Z"
|
||||
android:strokeWidth="0.35"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" />
|
||||
</group>
|
||||
</vector>
|
||||
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<group
|
||||
android:pivotX="12"
|
||||
android:pivotY="12"
|
||||
android:scaleX="1.1"
|
||||
android:scaleY="1.1">
|
||||
<path
|
||||
android:fillColor="@android:color/transparent"
|
||||
android:pathData="M12,4.5a7.5,7.5 0,1 1,0 15a7.5,7.5 0,1 1,0 -15z"
|
||||
android:strokeWidth="2"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" />
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M12,8.1m-1.15,0a1.15,1.15 0,1 1,2.3 0a1.15,1.15 0,1 1,-2.3 0" />
|
||||
<path
|
||||
android:fillColor="@android:color/transparent"
|
||||
android:pathData="M12,11.1v5.4"
|
||||
android:strokeWidth="2"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeLineCap="round" />
|
||||
</group>
|
||||
</vector>
|
||||
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="32"
|
||||
android:viewportHeight="32">
|
||||
<group
|
||||
android:pivotX="16"
|
||||
android:pivotY="16"
|
||||
android:scaleX="0.78"
|
||||
android:scaleY="0.78">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M30 14.75h-17c-0.69 0-1.25 0.56-1.25 1.25s0.56 1.25 1.25 1.25v0h17c0.69 0 1.25-0.56 1.25-1.25s-0.56-1.25-1.25-1.25v0zM13 6.25h17c0.69 0 1.25-0.56 1.25-1.25s-0.56-1.25-1.25-1.25v0h-17c-0.69 0-1.25 0.56-1.25 1.25s0.56 1.25 1.25 1.25v0zM6 22.75h-2c-1.794 0.002-3.248 1.456-3.25 3.25v2c0.002 1.794 1.456 3.248 3.25 3.25h2c1.794-0.002 3.248-1.456 3.25-3.25v-2c-0.002-1.794-1.456-3.248-3.25-3.25h-0zM6.75 28c-0 0.414-0.336 0.75-0.75 0.75h-2c-0.414-0-0.75-0.336-0.75-0.75v-2c0-0.414 0.336-0.75 0.75-0.75h2c0.414 0 0.75 0.336 0.75 0.75v0zM30 25.75h-17c-0.69 0-1.25 0.56-1.25 1.25s0.56 1.25 1.25 1.25v0h17c0.69 0 1.25-0.56 1.25-1.25s-0.56-1.25-1.25-1.25v0zM9.078 11.877l-5.121 5.598-1.076-1.069c-0.226-0.225-0.537-0.363-0.881-0.363-0.69 0-1.25 0.559-1.25 1.25 0 0.346 0.141 0.66 0.369 0.886l2 1.988 0.022 0.015 0.015 0.021c0.073 0.059 0.156 0.112 0.246 0.153l0.007 0.003c0.038 0.028 0.082 0.055 0.128 0.080l0.006 0.003c0.135 0.056 0.292 0.088 0.457 0.088 0.175 0 0.341-0.037 0.491-0.103l-0.008 0.003c0.053-0.031 0.098-0.061 0.14-0.094l-0.003 0.002c0.102-0.050 0.189-0.11 0.268-0.179l-0.001 0.001 0.015-0.023 0.020-0.014 6-6.559c0.205-0.222 0.331-0.52 0.331-0.847 0-0.69-0.56-1.25-1.25-1.25-0.366 0-0.696 0.158-0.924 0.409l-0.001 0.001zM9.078 0.877l-5.121 5.598-1.076-1.069c-0.226-0.223-0.536-0.361-0.878-0.361-0.69 0-1.25 0.56-1.25 1.25 0 0.345 0.14 0.658 0.366 0.884v0l2 1.987 0.022 0.015 0.015 0.021c0.072 0.058 0.153 0.109 0.24 0.15l0.007 0.003c0.040 0.029 0.086 0.058 0.134 0.084l0.007 0.003c0.135 0.056 0.292 0.088 0.456 0.088 0.175 0 0.341-0.037 0.491-0.103l-0.008 0.003c0.053-0.031 0.098-0.061 0.14-0.094l-0.003 0.002c0.102-0.050 0.189-0.11 0.268-0.179l-0.001 0.001 0.015-0.023 0.020-0.014 6-6.559c0.206-0.222 0.332-0.521 0.332-0.848 0-0.69-0.56-1.25-1.25-1.25-0.367 0-0.697 0.158-0.925 0.41l-0.001 0.001z"
|
||||
android:strokeWidth="1.1"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" />
|
||||
</group>
|
||||
</vector>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="48"
|
||||
android:viewportHeight="48">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M43.2,12.3l-5,-6.7A4,4 0,0 0,35 4H13A4,4 0,0 0,9.8 5.6l-5,6.7A4.1,4.1 0,0 0,4 14.7V40a4,4 0,0 0,4 4H40a4,4 0,0 0,4 -4V14.7A4.1,4.1 0,0 0,43.2 12.3ZM13,8H35l4.5,6H8.5ZM40,40H8V18H22V29.2l-2.6,-2.6a1.9,1.9 0,0 0,-3 0.2,2.1 2.1,0 0,0 0.2,2.7l6,5.9a1.9,1.9 0,0 0,2.8 0l6,-5.9a2.1,2.1 0,0 0,0.2 -2.7,1.9 1.9,0 0,0 -3,-0.2L26,29.2V18H40Z" />
|
||||
</vector>
|
||||
@@ -1,6 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||
<path android:fillColor="#000000"
|
||||
android:pathData="M20,2H4c-1.1,0 -2,0.9 -2,2v18l4,-4h14c1.1,0 2,-0.9 2,-2V4c0,-1.1 -0.9,-2 -2,-2zM20,16H5.17L4,17.17V4h16v12z"/>
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/transparent"
|
||||
android:pathData="M8.5,19H8C4,19 2,18 2,13V8C2,4 4,2 8,2H16C20,2 22,4 22,8V13C22,17 20,19 16,19H15.5C15.19,19 14.89,19.15 14.7,19.4L13.2,21.4C12.54,22.28 11.46,22.28 10.8,21.4L9.3,19.4C9.14,19.18 8.77,19 8.5,19Z"
|
||||
android:strokeWidth="1.9"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" />
|
||||
<path
|
||||
android:fillColor="@android:color/transparent"
|
||||
android:pathData="M7,8H17"
|
||||
android:strokeWidth="1.9"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" />
|
||||
<path
|
||||
android:fillColor="@android:color/transparent"
|
||||
android:pathData="M7,13H13"
|
||||
android:strokeWidth="1.9"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" />
|
||||
</vector>
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||
<path android:fillColor="#000000"
|
||||
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z"/>
|
||||
<path android:fillColor="#007AFF"
|
||||
android:pathData="M12,17c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2z"/>
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M10.4606,1.25L13.5,1.25C13.9142,1.25 14.25,1.58579 14.25,2C14.25,2.41421 13.9142,2.75 13.5,2.75H10.5C8.84876,2.75 7.663,2.75079 6.73809,2.83873C5.82434,2.92561 5.24291,3.09223 4.77938,3.37628C4.20752,3.72672 3.72672,4.20752 3.37628,4.77938C3.09223,5.24291 2.92561,5.82434 2.83873,6.73809C2.75079,7.663 2.75,8.84876 2.75,10.5V11.5C2.75,12.6751 2.75041,13.5189 2.79584,14.1847C2.84081,14.8438 2.92737,15.2736 3.07351,15.6264C3.50486,16.6678 4.33223,17.4951 5.3736,17.9265C5.88923,18.1401 6.54706,18.2199 7.8025,18.2416L7.83432,18.2421C8.23232,18.249 8.58109,18.2549 8.87097,18.287C9.18246,18.3215 9.4871,18.3912 9.77986,18.5615C10.0702,18.7304 10.2795,18.9559 10.4621,19.2063C10.6307,19.4378 10.804,19.7306 11.0004,20.0623L11.5587,21.0057C11.7515,21.3313 12.2485,21.3313 12.4412,21.0057L12.9996,20.0623C13.1959,19.7306 13.3692,19.4378 13.5379,19.2063C13.7204,18.9559 13.9298,18.7304 14.2201,18.5615C14.5129,18.3912 14.8175,18.3215 15.129,18.287C15.4189,18.2549 15.7676,18.249 16.1656,18.2421L16.1975,18.2416C17.4529,18.2199 18.1108,18.1401 18.6264,17.9265C19.6678,17.4951 20.4951,16.6678 20.9265,15.6264C21.0726,15.2736 21.1592,14.8438 21.2042,14.1847C21.2496,13.5189 21.25,12.6751 21.25,11.5V10.5C21.25,10.0858 21.5858,9.75 22,9.75C22.4142,9.75 22.75,10.0858 22.75,10.5V11.5278C22.75,12.6691 22.75,13.564 22.7007,14.2868C22.6505,15.0223 22.5468,15.6344 22.3123,16.2004C21.7287,17.6093 20.6093,18.7287 19.2004,19.3123C18.3955,19.6457 17.4786,19.7197 16.2233,19.7413C15.7842,19.7489 15.5061,19.7545 15.2941,19.7779C15.096,19.7999 15.0192,19.832 14.9742,19.8582C14.9268,19.8857 14.8622,19.936 14.7501,20.0898C14.6287,20.2564 14.4916,20.4865 14.2742,20.8539L13.7321,21.7697C12.9585,23.0767 11.0415,23.0767 10.2679,21.7697L9.72579,20.8539C9.50835,20.4865 9.37122,20.2564 9.24985,20.0898C9.13772,19.936 9.07313,19.8857 9.02572,19.8582C8.98078,19.832 8.90399,19.7999 8.70588,19.7779C8.49387,19.7545 8.21575,19.7489 7.77666,19.7413C6.52138,19.7197 5.60454,19.6457 4.79957,19.3123C3.39066,18.7287 2.27128,17.6093 1.68769,16.2004C1.45323,15.6344 1.3495,15.0223 1.29932,14.2868C1.24999,13.564 1.25,12.6691 1.25,11.5278L1.25,10.4606C1.24999,8.85726 1.24999,7.60029 1.34547,6.59611C1.44279,5.57256 1.64457,4.73445 2.09732,3.99563C2.57144,3.22194 3.22194,2.57144 3.99563,2.09732C4.73445,1.64457 5.57256,1.44279 6.59611,1.34547C7.60029,1.24999 8.85726,1.24999 10.4606,1.25ZM19,2.75C17.7574,2.75 16.75,3.75736 16.75,5C16.75,6.24264 17.7574,7.25 19,7.25C20.2426,7.25 21.25,6.24264 21.25,5C21.25,3.75736 20.2426,2.75 19,2.75ZM15.25,5C15.25,2.92893 16.9289,1.25 19,1.25C21.0711,1.25 22.75,2.92893 22.75,5C22.75,7.07107 21.0711,8.75 19,8.75C16.9289,8.75 15.25,7.07107 15.25,5Z"
|
||||
android:strokeWidth="0.4"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeLineJoin="round" />
|
||||
</vector>
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/transparent"
|
||||
android:pathData="M12,4a8,8 0,1 1,0 16a8,8 0,1 1,0 -16z"
|
||||
android:strokeWidth="1.5"
|
||||
android:strokeColor="#000000" />
|
||||
<path
|
||||
android:fillColor="@android:color/transparent"
|
||||
android:pathData="M12,8v5l3.5,2"
|
||||
android:strokeWidth="1.5"
|
||||
android:strokeColor="#000000"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round" />
|
||||
</vector>
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="14dp"
|
||||
android:height="18dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#8E8E93"
|
||||
android:pathData="M12,5l5,5H7z" />
|
||||
<path
|
||||
android:fillColor="#8E8E93"
|
||||
android:pathData="M12,19l-5,-5h10z" />
|
||||
</vector>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user